cosmopolitan/third_party/linenoise/linenoise.c
Justine Tunney 39bf41f4eb Make numerous improvements
- Python static hello world now 1.8mb
- Python static fully loaded now 10mb
- Python HTTPS client now uses MbedTLS
- Python REPL now completes import stmts
- Increase stack size for Python for now
- Begin synthesizing posixpath and ntpath
- Restore Python \N{UNICODE NAME} support
- Restore Python NFKD symbol normalization
- Add optimized code path for Intel SHA-NI
- Get more Python unit tests passing faster
- Get Python help() pagination working on NT
- Python hashlib now supports MbedTLS PBKDF2
- Make memcpy/memmove/memcmp/bcmp/etc. faster
- Add Mersenne Twister and Vigna to LIBC_RAND
- Provide privileged __printf() for error code
- Fix zipos opendir() so that it reports ENOTDIR
- Add basic chmod() implementation for Windows NT
- Add Cosmo's best functions to Python cosmo module
- Pin function trace indent depth to that of caller
- Show memory diagram on invalid access in MODE=dbg
- Differentiate stack overflow on crash in MODE=dbg
- Add stb_truetype and tools for analyzing font files
- Upgrade to UNICODE 13 and reduce its binary footprint
- COMPILE.COM now logs resource usage of build commands
- Start implementing basic poll() support on bare metal
- Set getauxval(AT_EXECFN) to GetModuleFileName() on NT
- Add descriptions to strerror() in non-TINY build modes
- Add COUNTBRANCH() macro to help with micro-optimizations
- Make error / backtrace / asan / memory code more unbreakable
- Add fast perfect C implementation of μ-Law and a-Law audio codecs
- Make strtol() functions consistent with other libc implementations
- Improve Linenoise implementation (see also github.com/jart/bestline)
- COMPILE.COM now suppresses stdout/stderr of successful build commands
2021-09-28 01:52:34 -07:00

1975 lines
58 KiB
C

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ │
│ Cosmopolitan Linenoise ── guerrilla line editing library against the │
│ notion that a library for user-friendly pseudoteletypewriter command │
│ sessions using ANSI Standard X3.64 control sequences must have 100k+ │
│ lines of GPL workarounds to 300 baud and bygone commercial rivalries │
│ │
│ CHANGES │
│ │
│ - Remove bell │
│ - Add kill ring │
│ - Fix flickering │
│ - Add UTF-8 editing │
│ - Add CTRL-R search │
│ - React to terminal resizing │
│ - Don't generate .data section │
│ - Support terminal flow control │
│ - Make history loading 10x faster │
│ - Make multiline mode the only mode │
│ - Support unlimited input line length │
│ - Restore raw mode on process foregrounding │
│ - Make source code compatible with C++ compilers │
│ - Fix corruption issues by using generalized parsing │
│ - Implement nearly all GNU readline editing shortcuts │
│ - Remove heavyweight dependencies like printf/sprintf │
│ - Remove ISIG→^C→EAGAIN hack and catch signals properly │
│ - Support running on Windows in MinTTY or CMD.EXE on Win10+ │
│ - Support diacratics, русский, Ελληνικά, 中国人, 한국인, 日本 │
│ │
│ SHORTCUTS │
│ │
│ CTRL-E END │
│ CTRL-A START │
│ CTRL-B BACK │
│ CTRL-F FORWARD │
│ CTRL-L CLEAR │
│ CTRL-H BACKSPACE │
│ CTRL-D DELETE │
│ CTRL-D EOF (IF EMPTY) │
│ CTRL-N NEXT HISTORY │
│ CTRL-P PREVIOUS HISTORY │
│ CTRL-R SEARCH HISTORY │
│ CTRL-G CANCEL SEARCH │
│ ALT-< BEGINNING OF HISTORY │
│ ALT-> END OF HISTORY │
│ ALT-F FORWARD WORD │
│ ALT-B BACKWARD WORD │
│ CTRL-K KILL LINE FORWARDS │
│ CTRL-U KILL LINE BACKWARDS │
│ ALT-H KILL WORD BACKWARDS │
│ CTRL-W KILL WORD BACKWARDS │
│ CTRL-ALT-H KILL WORD BACKWARDS │
│ ALT-D KILL WORD FORWARDS │
│ CTRL-Y YANK │
│ ALT-Y ROTATE KILL RING AND YANK AGAIN │
│ CTRL-T TRANSPOSE │
│ ALT-T TRANSPOSE WORD │
│ ALT-U UPPERCASE WORD │
│ ALT-L LOWERCASE WORD │
│ ALT-C CAPITALIZE WORD │
│ CTRL-SPACE SET MARK │
│ CTRL-X CTRL-X GOTO MARK │
│ CTRL-S PAUSE OUTPUT │
│ CTRL-Q RESUME OUTPUT │
│ CTRL-Z SUSPEND PROCESS │
│ │
│ EXAMPLE │
│ │
│ // should be ~80kb statically linked │
│ // will save history to ~/.foo_history │
│ // cc -fno-jump-tables -Os -o foo foo.c linenoise.c │
│ main() { │
│ char *line; │
│ while ((line = linenoiseWithHistory("IN> ", "foo"))) { │
│ fputs("OUT> ", stdout); │
│ fputs(line, stdout); │
│ fputs("\n", stdout); │
│ free(line); │
│ } │
│ } │
│ │
╞══════════════════════════════════════════════════════════════════════════════╡
│ │
│ Copyright 2018-2021 Justine Tunney <jtunney@gmail.com> │
│ Copyright 2010-2016 Salvatore Sanfilippo <antirez@gmail.com> │
│ Copyright 2010-2013 Pieter Noordhuis <pcnoordhuis@gmail.com> │
│ │
│ All rights reserved. │
│ │
│ Redistribution and use in source and binary forms, with or without │
│ modification, are permitted provided that the following conditions are │
│ met: │
│ │
│ * Redistributions of source code must retain the above copyright │
│ notice, this list of conditions and the following disclaimer. │
│ │
│ * Redistributions in binary form must reproduce the above copyright │
│ notice, this list of conditions and the following disclaimer in the │
│ documentation and/or other materials provided with the distribution. │
│ │
│ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS │
│ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT │
│ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR │
│ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT │
│ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, │
│ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT │
│ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, │
│ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY │
│ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT │
│ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE │
│ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. │
│ │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
#include "libc/bits/bits.h"
#include "libc/calls/calls.h"
#include "libc/calls/sigbits.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/termios.h"
#include "libc/calls/ttydefaults.h"
#include "libc/calls/weirdtypes.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/intrin/asan.internal.h"
#include "libc/log/log.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/bsr.h"
#include "libc/nexgen32e/rdtsc.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/sock.h"
#include "libc/stdio/append.internal.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/str/tpenc.h"
#include "libc/sysv/consts/fileno.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/prot.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h"
#include "libc/unicode/unicode.h"
#include "third_party/linenoise/linenoise.h"
#include "tool/build/lib/case.h"
asm(".ident\t\"\\n\\n\
Cosmopolitan Linenoise (BSD-2)\\n\
Copyright 2018-2020 Justine Tunney <jtunney@gmail.com>\\n\
Copyright 2010-2016 Salvatore Sanfilippo <antirez@gmail.com>\\n\
Copyright 2010-2013 Pieter Noordhuis <pcnoordhuis@gmail.com>\"");
#define LINENOISE_MAX_RING 8
#define LINENOISE_MAX_DEBUG 16
#define LINENOISE_MAX_HISTORY 1024
#define LINENOISE_HISTORY_FIRST +LINENOISE_MAX_HISTORY
#define LINENOISE_HISTORY_PREV +1
#define LINENOISE_HISTORY_NEXT -1
#define LINENOISE_HISTORY_LAST -LINENOISE_MAX_HISTORY
#if 0
#define DEBUG(L, ...) linenoiseDebug(L, __VA_ARGS__)
#else
#define DEBUG(L, ...) (void)0
#endif
struct abuf {
char *b;
int len;
int cap;
};
struct rune {
unsigned c;
unsigned n;
};
struct linenoiseRing {
unsigned i;
char *p[LINENOISE_MAX_RING];
};
struct linenoiseState {
int ifd; /* terminal stdin file descriptor */
int ofd; /* terminal stdout file descriptor */
struct winsize ws; /* rows and columns in terminal */
char *buf; /* edited line buffer */
const char *prompt; /* prompt to display */
int hindex; /* history index */
int rows; /* rows being used */
int oldpos; /* previous refresh cursor position */
unsigned debugrow; /* row for debug display */
unsigned buflen; /* edited line buffer size */
unsigned pos; /* current buffer index */
unsigned len; /* current edited line length */
unsigned mark; /* saved cursor position */
unsigned yi, yj; /* boundaries of last yank */
char seq[2][16]; /* keystroke history for yanking code */
char dirty; /* if an update was squashed */
};
static const char *const kUnsupported[] = {"dumb", "cons25", "emacs"};
static int gotint;
static int gotcont;
static int gotwinch;
static char rawmode;
static char maskmode;
static char iscapital;
static int historylen;
static struct linenoiseRing ring;
static struct sigaction orig_int;
static struct sigaction orig_quit;
static struct sigaction orig_cont;
static struct sigaction orig_winch;
static struct termios orig_termios;
static char *history[LINENOISE_MAX_HISTORY];
static linenoiseHintsCallback *hintsCallback;
static linenoiseFreeHintsCallback *freeHintsCallback;
static linenoiseCompletionCallback *completionCallback;
static void linenoiseAtExit(void);
static void linenoiseRefreshLine(struct linenoiseState *);
static int notwseparator(wint_t c) {
return !iswseparator(c);
}
static void linenoiseOnInt(int sig) {
gotint = sig;
}
static void linenoiseOnCont(int sig) {
gotcont = sig;
}
static void linenoiseOnWinch(int sig) {
gotwinch = sig;
}
static wint_t Capitalize(wint_t c) {
if (!iscapital) {
c = towupper(c);
iscapital = 1;
}
return c;
}
static struct rune DecodeUtf8(int c) {
struct rune r;
if (c < 252) {
r.n = bsr(255 & ~c);
r.c = c & (((1 << r.n) - 1) | 3);
r.n = 6 - r.n;
} else {
r.c = c & 3;
r.n = 5;
}
return r;
}
static struct rune GetUtf8(const char *p, size_t n) {
struct rune r;
if ((r.n = r.c = 0) < n && (r.c = p[r.n++] & 255) >= 0300) {
r.c = DecodeUtf8(r.c).c;
while (r.n < n && (p[r.n] & 0300) == 0200) {
r.c = r.c << 6 | p[r.n++] & 077;
}
}
return r;
}
static size_t GetFdSize(int fd) {
struct stat st;
st.st_size = 0;
fstat(fd, &st);
return st.st_size;
}
static char *GetLine(FILE *f) {
ssize_t rc;
char *p = 0;
size_t n, c = 0;
if ((rc = getdelim(&p, &c, '\n', f)) != EOF) {
for (n = rc; n; --n) {
if (p[n - 1] == '\r' || p[n - 1] == '\n') {
p[n - 1] = 0;
} else {
break;
}
}
return p;
} else {
free(p);
return 0;
}
}
static const char *FindSubstringReverse(const char *p, size_t n, const char *q,
size_t m) {
size_t i;
if (m <= n) {
n -= m;
do {
for (i = 0; i < m; ++i) {
if (p[n + i] != q[i]) {
break;
}
}
if (i == m) {
return p + n;
}
} while (n--);
}
return 0;
}
static int ParseUnsigned(const char *s, void *e) {
int c, x;
for (x = 0; (c = *s++);) {
if ('0' <= c && c <= '9') {
x = MIN(c - '0' + x * 10, 32767);
} else {
break;
}
}
if (e) *(const char **)e = s;
return x;
}
static char *FormatUnsigned(char *p, unsigned x) {
char t;
size_t i, a, b;
i = 0;
do {
p[i++] = x % 10 + '0';
x = x / 10;
} while (x > 0);
p[i] = '\0';
if (i) {
for (a = 0, b = i - 1; a < b; ++a, --b) {
t = p[a];
p[a] = p[b];
p[b] = t;
}
}
return p + i;
}
static char HasPendingInput(int fd) {
return poll((struct pollfd[]){{fd, POLLIN}}, 1, 0) == 1;
}
static size_t GetMonospaceWidth(const char *p, size_t n, char *out_haswides) {
int c, d;
size_t i, w;
struct rune r;
char haswides;
enum { kAscii, kUtf8, kEsc, kCsi1, kCsi2, kSs, kNf, kStr, kStr2 } t;
for (haswides = r.c = r.n = t = w = i = 0; i < n; ++i) {
c = p[i] & 255;
switch (t) {
Whoopsie:
t = kAscii;
/* fallthrough */
case kAscii:
if (c < 0200) {
if (c == 033) {
t = kEsc;
} else {
++w;
}
} else if (c >= 0300) {
t = kUtf8;
r = DecodeUtf8(c);
}
break;
case kUtf8:
if ((c & 0300) == 0200) {
r.c <<= 6;
r.c |= c & 077;
if (!--r.n) {
switch (r.c) {
case 033:
t = kEsc;
break;
case 0x9b:
t = kCsi1;
break;
case 0x8e:
case 0x8f:
t = kSs;
break;
case 0x90:
case 0x98:
case 0x9d:
case 0x9e:
case 0x9f:
t = kStr;
break;
default:
d = wcwidth(r.c);
d = MAX(0, d);
w += d;
haswides |= d > 1;
t = kAscii;
break;
}
}
} else {
goto Whoopsie;
}
break;
case kEsc:
if (0x20 <= c && c <= 0x2f) {
t = kNf;
} else if (0x30 <= c && c <= 0x3f) {
t = kAscii;
} else if (0x20 <= c && c <= 0x5F) {
switch (c) {
case '[':
t = kCsi1;
break;
case 'N':
case 'O':
t = kSs;
break;
case 'P':
case 'X':
case ']':
case '^':
case '_':
t = kStr;
break;
case '\\':
goto Whoopsie;
default:
t = kAscii;
break;
}
} else if (0x60 <= c && c <= 0x7e) {
t = kAscii;
} else if (c == 033) {
if (i == 3) t = kAscii;
} else {
t = kAscii;
}
break;
case kSs:
t = kAscii;
break;
case kNf:
if (0x30 <= c && c <= 0x7e) {
t = kAscii;
} else if (!(0x20 <= c && c <= 0x2f)) {
goto Whoopsie;
}
break;
case kCsi1:
if (0x20 <= c && c <= 0x2f) {
t = kCsi2;
} else if (c == '[' && i == 3) {
/* linux function keys */
} else if (0x40 <= c && c <= 0x7e) {
t = kAscii;
} else if (!(0x30 <= c && c <= 0x3f)) {
goto Whoopsie;
}
break;
case kCsi2:
if (0x40 <= c && c <= 0x7e) {
t = kAscii;
} else if (!(0x20 <= c && c <= 0x2f)) {
goto Whoopsie;
}
break;
case kStr:
switch (c) {
case '\a':
t = kAscii;
break;
case 0033:
case 0302:
t = kStr2;
break;
default:
break;
}
break;
case kStr2:
switch (c) {
case '\a':
case '\\':
case 0234:
t = kAscii;
break;
default:
t = kStr;
break;
}
break;
default:
assert(0);
}
}
if (out_haswides) {
*out_haswides = haswides;
}
return w;
}
static void abInit(struct abuf *a) {
a->len = 0;
a->cap = 16;
a->b = malloc(a->cap);
a->b[0] = 0;
}
static char abGrow(struct abuf *a, int need) {
int cap;
char *b;
cap = a->cap;
do cap += cap / 2;
while (cap < need);
if (!(b = realloc(a->b, cap * sizeof(*a->b)))) return 0;
a->cap = cap;
a->b = b;
return 1;
}
static void abAppend(struct abuf *a, const char *s, int len) {
if (a->len + len + 1 > a->cap && !abGrow(a, a->len + len + 1)) return;
memcpy(a->b + a->len, s, len);
a->b[a->len + len] = 0;
a->len += len;
}
static void abAppends(struct abuf *a, const char *s) {
abAppend(a, s, strlen(s));
}
static void abAppendu(struct abuf *a, unsigned u) {
char b[11];
abAppend(a, b, FormatUnsigned(b, u) - b);
}
static void abAppendw(struct abuf *a, unsigned long long w) {
char *p;
if (a->len + 8 + 1 > a->cap && !abGrow(a, a->len + 8 + 1)) return;
p = a->b + a->len;
p[0] = (0x00000000000000ff & w) >> 000;
p[1] = (0x000000000000ff00 & w) >> 010;
p[2] = (0x0000000000ff0000 & w) >> 020;
p[3] = (0x00000000ff000000 & w) >> 030;
p[4] = (0x000000ff00000000 & w) >> 040;
p[5] = (0x0000ff0000000000 & w) >> 050;
p[6] = (0x00ff000000000000 & w) >> 060;
p[7] = (0xff00000000000000 & w) >> 070;
a->len += w ? (bsrll(w) >> 3) + 1 : 1;
p[8] = 0;
}
static void abFree(struct abuf *a) {
free(a->b);
}
static int linenoiseIsUnsupportedTerm(void) {
int i;
char *term;
static char once, res;
if (!once) {
if ((term = getenv("TERM"))) {
for (i = 0; i < sizeof(kUnsupported) / sizeof(*kUnsupported); i++) {
if (!strcasecmp(term, kUnsupported[i])) {
res = 1;
break;
}
}
}
once = 1;
}
return res;
}
static int enableRawMode(int fd) {
struct termios raw;
struct sigaction sa;
if (tcgetattr(fd, &orig_termios) != -1) {
raw = orig_termios;
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN);
raw.c_oflag &= ~OPOST;
raw.c_iflag |= IUTF8;
raw.c_cflag |= CS8;
raw.c_cc[VMIN] = 1;
raw.c_cc[VTIME] = 0;
if (tcsetattr(fd, TCSANOW, &raw) != -1) {
sa.sa_flags = 0;
sa.sa_handler = linenoiseOnCont;
sigemptyset(&sa.sa_mask);
sigaction(SIGCONT, &sa, &orig_cont);
sa.sa_handler = linenoiseOnWinch;
sigaction(SIGWINCH, &sa, &orig_winch);
rawmode = fd;
gotwinch = 0;
gotcont = 0;
return 0;
}
}
errno = ENOTTY;
return -1;
}
void linenoiseDisableRawMode(void) {
if (rawmode != -1) {
sigaction(SIGCONT, &orig_cont, 0);
sigaction(SIGWINCH, &orig_winch, 0);
tcsetattr(rawmode, TCSAFLUSH, &orig_termios);
rawmode = -1;
}
}
static int linenoiseWrite(int fd, const void *p, size_t n) {
ssize_t rc;
size_t wrote;
do {
for (;;) {
if (gotint) {
errno = EINTR;
return -1;
}
rc = write(fd, p, n);
if (rc == -1 && errno == EINTR) {
continue;
} else {
break;
}
}
if (rc != -1) {
wrote = rc;
n -= wrote;
p = (char *)p + wrote;
} else {
return -1;
}
} while (n);
return 0;
}
static int linenoiseWriteStr(int fd, const char *p) {
return linenoiseWrite(fd, p, strlen(p));
}
static void linenoiseDebug(struct linenoiseState *l, const char *fmt, ...) {
va_list va;
char *msg = 0;
char *ansi = 0;
int x, y, n, dy, xn;
va_start(va, fmt);
(vappendf)(&msg, fmt, va);
va_end(va);
xn = l->ws.ws_col;
xn = MAX(xn, 80);
y = l->debugrow;
n = GetMonospaceWidth(msg, strlen(msg), 0);
x = MAX(xn - n, 0);
(appendf)(&ansi, "\e7\e[%u;%uH\e[1K%s\e8", y + 1, x + 1, msg);
linenoiseWrite(l->ofd, ansi, appendz(ansi).i);
y = (y + (n + (xn - 1)) / xn) % LINENOISE_MAX_DEBUG;
l->debugrow = y;
free(ansi);
free(msg);
}
static ssize_t linenoiseRead(int fd, char *buf, size_t size,
struct linenoiseState *l) {
ssize_t rc;
int refreshme;
do {
refreshme = 0;
if (gotint) {
errno = EINTR;
return -1;
}
if (gotcont && rawmode != -1) {
enableRawMode(rawmode);
if (l) refreshme = 1;
}
if (l && gotwinch) refreshme = 1;
if (refreshme) linenoiseRefreshLine(l);
rc = readansi(fd, buf, size);
} while (rc == -1 && errno == EINTR);
if (l && rc > 0) {
memcpy(l->seq[1], l->seq[0], sizeof(l->seq[0]));
memset(l->seq[0], 0, sizeof(l->seq[0]));
memcpy(l->seq[0], buf, MIN(MIN(size, rc), sizeof(l->seq[0]) - 1));
}
return rc;
}
/**
* Returns number of columns in current terminal.
*
* 1. Checks COLUMNS environment variable (set by Emacs)
* 2. Tries asking termios (works for pseudoteletypewriters)
* 3. Falls back to inband signalling (works w/ pipe or serial)
* 4. Otherwise we conservatively assume 80 columns
*
* @param ws should be initialized by caller to zero before first call
* @param ifd is input file descriptor
* @param ofd is output file descriptor
* @return window size
*/
static struct winsize GetTerminalSize(struct winsize ws, int ifd, int ofd) {
int x;
ssize_t n;
char *p, *s, b[16];
ioctl(ofd, TIOCGWINSZ, &ws);
if ((!ws.ws_row && (s = getenv("ROWS")) && (x = ParseUnsigned(s, 0)))) {
ws.ws_row = x;
}
if ((!ws.ws_col && (s = getenv("COLUMNS")) && (x = ParseUnsigned(s, 0)))) {
ws.ws_col = x;
}
if (((!ws.ws_col || !ws.ws_row) && linenoiseRead(ifd, 0, 0, 0) != -1 &&
linenoiseWriteStr(
ofd, "\0337" /* save position */
"\033[9979;9979H" /* move cursor to bottom right corner */
"\033[6n" /* report position */
"\0338") != -1 && /* restore position */
(n = linenoiseRead(ifd, b, sizeof(b), 0)) != -1 &&
n && b[0] == 033 && b[1] == '[' && b[n - 1] == 'R')) {
p = b + 2;
if ((x = ParseUnsigned(p, &p))) ws.ws_row = x;
if (*p++ == ';' && (x = ParseUnsigned(p, 0))) ws.ws_col = x;
}
if (!ws.ws_col) ws.ws_col = 80;
if (!ws.ws_row) ws.ws_row = 24;
return ws;
}
/* Clear the screen. Used to handle ctrl+l */
void linenoiseClearScreen(int fd) {
linenoiseWriteStr(fd, "\033[H" /* move cursor to top left corner */
"\033[2J"); /* erase display */
}
static void linenoiseBeep(void) {
/* THE TERMINAL BELL IS DEAD - HISTORY HAS KILLED IT */
}
static char linenoiseGrow(struct linenoiseState *ls, size_t n) {
char *p;
size_t m;
m = ls->buflen;
if (m >= n) return 1;
do m += m >> 1;
while (m < n);
if (!(p = realloc(ls->buf, m * sizeof(*ls->buf)))) return 0;
ls->buf = p;
ls->buflen = m;
return 1;
}
/* This is an helper function for linenoiseEdit() and is called when the
* user types the <tab> key in order to complete the string currently in the
* input.
*
* The state of the editing is encapsulated into the pointed linenoiseState
* structure as described in the structure definition. */
static ssize_t linenoiseCompleteLine(struct linenoiseState *ls, char *seq,
int size) {
ssize_t nread;
size_t i, n, stop;
linenoiseCompletions lc;
struct linenoiseState saved;
nread = 0;
memset(&lc, 0, sizeof(lc));
completionCallback(ls->buf, &lc);
if (!lc.len) {
linenoiseBeep();
} else {
i = 0;
stop = 0;
while (!stop) {
/* Show completion or original buffer */
if (i < lc.len) {
saved = *ls;
ls->len = ls->pos = strlen(lc.cvec[i]);
ls->buf = lc.cvec[i];
linenoiseRefreshLine(ls);
ls->len = saved.len;
ls->pos = saved.pos;
ls->buf = saved.buf;
} else {
linenoiseRefreshLine(ls);
}
if ((nread = linenoiseRead(ls->ifd, seq, size, ls)) <= 0) {
linenoiseFreeCompletions(&lc);
return -1;
}
switch (seq[0]) {
case '\t':
i = (i + 1) % (lc.len + 1);
if (i == lc.len) {
linenoiseBeep();
}
break;
default:
if (i < lc.len) {
n = strlen(lc.cvec[i]);
if (linenoiseGrow(ls, n + 1)) {
memcpy(ls->buf, lc.cvec[i], n + 1);
ls->len = ls->pos = n;
}
}
stop = 1;
break;
}
}
}
linenoiseFreeCompletions(&lc);
return nread;
}
static void linenoiseEditHistoryGoto(struct linenoiseState *l, int i) {
size_t n;
if (historylen <= 1) return;
i = MAX(MIN(i, historylen - 1), 0);
free(history[historylen - 1 - l->hindex]);
history[historylen - 1 - l->hindex] = strdup(l->buf);
l->hindex = i;
n = strlen(history[historylen - 1 - l->hindex]);
linenoiseGrow(l, n + 1);
n = MIN(n, l->buflen - 1);
memcpy(l->buf, history[historylen - 1 - l->hindex], n);
l->buf[n] = 0;
l->len = l->pos = n;
linenoiseRefreshLine(l);
}
static void linenoiseEditHistoryMove(struct linenoiseState *l, int dx) {
linenoiseEditHistoryGoto(l, l->hindex + dx);
}
static char *linenoiseMakeSearchPrompt(struct abuf *ab, int fail, const char *s,
int n) {
ab->len = 0;
abAppendw(ab, '(');
if (fail) abAppends(ab, "failed ");
abAppends(ab, "reverse-i-search `\e[4m");
abAppend(ab, s, n);
abAppends(ab, "\033[24m");
abAppends(ab, s + n);
abAppendw(ab, READ32LE("') "));
return ab->b;
}
static int linenoiseSearch(struct linenoiseState *l, char *seq, int size) {
char *p;
struct abuf ab;
struct abuf prompt;
const char *oldprompt, *q;
int i, j, k, rc, fail, added, oldpos, matlen, oldindex;
if (historylen <= 1) return 0;
abInit(&ab);
abInit(&prompt);
oldpos = l->pos;
oldprompt = l->prompt;
oldindex = l->hindex;
for (fail = matlen = 0;;) {
l->prompt = linenoiseMakeSearchPrompt(&prompt, fail, ab.b, matlen);
linenoiseRefreshLine(l);
fail = 1;
added = 0;
j = l->pos;
i = l->hindex;
rc = linenoiseRead(l->ifd, seq, size, l);
if (rc > 0) {
if (seq[0] == CTRL('?') || seq[0] == CTRL('H')) {
if (ab.len) {
--ab.len;
matlen = MIN(matlen, ab.len);
}
} else if (seq[0] == CTRL('R')) {
if (j) {
--j;
} else if (i + 1 < historylen) {
++i;
j = strlen(history[historylen - 1 - i]);
}
} else if (seq[0] == CTRL('G')) {
linenoiseEditHistoryGoto(l, oldindex);
l->pos = oldpos;
rc = 0;
break;
} else if (iswcntrl(seq[0])) { /* only sees canonical c0 */
break;
} else {
abAppend(&ab, seq, rc);
added = rc;
}
} else {
break;
}
while (i < historylen) {
p = history[historylen - 1 - i];
k = strlen(p);
j = j >= 0 ? MIN(k, j + ab.len) : k;
if ((q = FindSubstringReverse(p, j, ab.b, ab.len))) {
linenoiseEditHistoryGoto(l, i);
l->pos = q - p;
fail = 0;
if (added) {
matlen += added;
added = 0;
}
break;
} else {
i = i + 1;
j = -1;
}
}
}
l->prompt = oldprompt;
linenoiseRefreshLine(l);
abFree(&prompt);
abFree(&ab);
linenoiseRefreshLine(l);
return rc;
}
static void linenoiseRingFree(void) {
size_t i;
for (i = 0; i < LINENOISE_MAX_RING; ++i) {
if (ring.p[i]) {
free(ring.p[i]);
ring.p[i] = 0;
}
}
}
static void linenoiseRingPush(const char *p, size_t n) {
char *q;
if (LINENOISE_MAX_RING && n) {
if ((q = malloc(n + 1))) {
ring.i = (ring.i + 1) % LINENOISE_MAX_RING;
free(ring.p[ring.i]);
ring.p[ring.i] = memcpy(q, p, n);
ring.p[ring.i][n] = 0;
}
}
}
static void linenoiseRingRotate(void) {
size_t i;
for (i = 0; i < LINENOISE_MAX_RING; ++i) {
ring.i = (ring.i - 1) % LINENOISE_MAX_RING;
if (ring.p[ring.i]) {
break;
}
}
}
static char *linenoiseRefreshHints(struct linenoiseState *l) {
char *hint;
struct abuf ab;
const char *ansi1, *ansi2;
if (!hintsCallback) return 0;
if (!(hint = hintsCallback(l->buf, &ansi1, &ansi2))) return 0;
abInit(&ab);
ansi1 = "\033[90m";
ansi2 = "\033[39m";
if (ansi1) abAppends(&ab, ansi1);
abAppends(&ab, hint);
if (ansi2) abAppends(&ab, ansi2);
if (freeHintsCallback) freeHintsCallback(hint);
return ab.b;
}
static void linenoiseRefreshLineImpl(struct linenoiseState *l, int force) {
char *hint;
char haswides;
struct abuf ab;
struct rune rune;
const char *p, *buf;
struct winsize oldsize;
int i, x, y, t, xn, yn, cx, cy, tn, resized;
int fd, plen, width, pwidth, rows, len, pos;
/*
* synchonize the i/o state
*/
if (!force && HasPendingInput(l->ifd)) {
l->dirty = 1;
return;
}
oldsize = l->ws;
if ((resized = gotwinch) && rawmode != -1) {
gotwinch = 0;
l->ws = GetTerminalSize(l->ws, l->ifd, l->ofd);
}
StartOver:
fd = l->ofd;
buf = l->buf;
pos = l->pos;
len = l->len;
xn = l->ws.ws_col;
yn = l->ws.ws_row;
plen = strlen(l->prompt);
pwidth = GetMonospaceWidth(l->prompt, plen, 0);
width = GetMonospaceWidth(buf, len, &haswides);
/*
* handle the case where the line is larger than the whole display
* gnu readline actually isn't able to deal with this situation!!!
* we kludge xn to address the edge case of wide chars on the edge
*/
for (tn = xn - haswides;;) {
if (pwidth + width + 1 < tn * yn) break; /* we're fine */
if (!len || width < 2) break; /* we can't do anything */
if (pwidth + 2 > tn * yn) break; /* we can't do anything */
if (pos > len / 2) {
/* hide content on the left if we're editing on the right */
rune = GetUtf8(buf, len);
buf += rune.n;
len -= rune.n;
pos -= rune.n;
} else {
/* hide content on the right if we're editing on left */
t = len;
while (len && (buf[len - 1] & 0300) == 0200) --len;
if (len) --len;
rune = GetUtf8(buf + len, t - len);
}
if ((t = wcwidth(rune.c)) > 0) {
width -= t;
}
}
pos = MAX(0, MIN(pos, len));
/*
* now generate the terminal codes to update the line
*
* since we support unlimited lines it's important that we don't
* clear the screen before we draw the screen. doing that causes
* flickering. the key with terminals is to overwrite cells, and
* then use \e[K and \e[J to clear everything else.
*
* we make the assumption that prompts and hints may contain ansi
* sequences, but the buffer does not.
*
* we need to handle the edge case where a wide character like 度
* might be at the edge of the window, when there's one cell left.
* so we can't use division based on string width to compute the
* coordinates and have to track it as we go.
*/
cy = -1;
cx = -1;
rows = 1;
abInit(&ab);
abAppendw(&ab, '\r'); /* start of line */
if (l->rows - l->oldpos - 1 > 0) {
abAppends(&ab, "\033[");
abAppendu(&ab, l->rows - l->oldpos - 1);
abAppendw(&ab, 'A'); /* cursor up clamped */
}
abAppends(&ab, l->prompt);
x = pwidth;
for (i = 0; i < len; i += rune.n) {
rune = GetUtf8(buf + i, len - i);
if (x && x + rune.n > xn) {
if (cy >= 0) ++cy;
abAppends(&ab, "\033[K" /* clear line forward */
"\r" /* start of line */
"\n"); /* cursor down unclamped */
++rows;
x = 0;
}
if (i == pos) {
cy = 0;
cx = x;
}
if (maskmode) {
abAppendw(&ab, '*');
} else {
abAppendw(&ab, tpenc(rune.c));
}
t = wcwidth(rune.c);
t = MAX(0, t);
x += t;
}
if ((hint = linenoiseRefreshHints(l))) {
if (GetMonospaceWidth(hint, strlen(hint), 0) < xn - x) {
if (cx < 0) {
cx = x;
}
abAppends(&ab, hint);
}
free(hint);
}
abAppendw(&ab, READ32LE("\033[J")); /* erase display forwards */
/*
* if we are at the very end of the screen with our prompt, we need to
* emit a newline and move the prompt to the first column.
*/
if (pos && pos == len && x >= xn) {
abAppendw(&ab, READ32LE("\n\r\0"));
++rows;
}
/*
* move cursor to right position
*/
if (cy > 0) {
abAppendw(&ab, READ32LE("\033[\0"));
abAppendu(&ab, cy);
abAppendw(&ab, 'A'); /* cursor up */
}
if (cx > 0) {
abAppendw(&ab, READ32LE("\r\033["));
abAppendu(&ab, cx);
abAppendw(&ab, 'C'); /* cursor right */
} else if (!cx) {
abAppendw(&ab, '\r'); /* start */
}
/*
* now get ready to progress state
* we use a mostly correct kludge when the tty resizes
*/
l->rows = rows;
if (resized && oldsize.ws_col > l->ws.ws_col) {
resized = 0;
abFree(&ab);
goto StartOver;
}
l->oldpos = MAX(0, cy);
l->dirty = 0;
/*
* send codes to terminal
*/
linenoiseWrite(fd, ab.b, ab.len);
abFree(&ab);
}
static void linenoiseRefreshLine(struct linenoiseState *l) {
linenoiseRefreshLineImpl(l, 0);
}
static void linenoiseRefreshLineForce(struct linenoiseState *l) {
linenoiseRefreshLineImpl(l, 1);
}
static void linenoiseEditInsert(struct linenoiseState *l, const char *p,
size_t n) {
if (linenoiseGrow(l, l->len + n + 1)) {
memmove(l->buf + l->pos + n, l->buf + l->pos, l->len - l->pos);
memcpy(l->buf + l->pos, p, n);
l->pos += n;
l->len += n;
l->buf[l->len] = 0;
linenoiseRefreshLine(l);
}
}
static void linenoiseEditHome(struct linenoiseState *l) {
l->pos = 0;
linenoiseRefreshLine(l);
}
static void linenoiseEditEnd(struct linenoiseState *l) {
l->pos = l->len;
linenoiseRefreshLine(l);
}
static void linenoiseEditUp(struct linenoiseState *l) {
linenoiseEditHistoryMove(l, LINENOISE_HISTORY_PREV);
}
static void linenoiseEditDown(struct linenoiseState *l) {
linenoiseEditHistoryMove(l, LINENOISE_HISTORY_NEXT);
}
static void linenoiseEditBof(struct linenoiseState *l) {
linenoiseEditHistoryMove(l, LINENOISE_HISTORY_FIRST);
}
static void linenoiseEditEof(struct linenoiseState *l) {
linenoiseEditHistoryMove(l, LINENOISE_HISTORY_LAST);
}
static void linenoiseEditRefresh(struct linenoiseState *l) {
linenoiseClearScreen(l->ofd);
linenoiseRefreshLine(l);
}
static size_t Forward(struct linenoiseState *l, size_t pos) {
return pos + GetUtf8(l->buf + pos, l->len - pos).n;
}
static size_t Backward(struct linenoiseState *l, size_t pos) {
if (pos) {
do --pos;
while (pos && (l->buf[pos] & 0300) == 0200);
}
return pos;
}
static size_t Backwards(struct linenoiseState *l, size_t pos,
int pred(wint_t)) {
size_t i;
struct rune r;
while (pos) {
i = Backward(l, pos);
r = GetUtf8(l->buf + i, l->len - i);
if (pred(r.c)) {
pos = i;
} else {
break;
}
}
return pos;
}
static size_t Forwards(struct linenoiseState *l, size_t pos, int pred(wint_t)) {
struct rune r;
while (pos < l->len) {
r = GetUtf8(l->buf + pos, l->len - pos);
if (pred(r.c)) {
pos += r.n;
} else {
break;
}
}
return pos;
}
static size_t ForwardWord(struct linenoiseState *l, size_t pos) {
pos = Forwards(l, pos, iswseparator);
pos = Forwards(l, pos, notwseparator);
return pos;
}
static size_t BackwardWord(struct linenoiseState *l, size_t pos) {
pos = Backwards(l, pos, iswseparator);
pos = Backwards(l, pos, notwseparator);
return pos;
}
static size_t EscapeWord(struct linenoiseState *l) {
size_t i, j;
struct rune r;
for (i = l->pos; i && i < l->len; i += r.n) {
if (i < l->len) {
r = GetUtf8(l->buf + i, l->len - i);
if (iswseparator(r.c)) break;
}
if ((j = i)) {
do --j;
while (j && (l->buf[j] & 0300) == 0200);
r = GetUtf8(l->buf + j, l->len - j);
if (iswseparator(r.c)) break;
}
}
return i;
}
static void linenoiseEditLeft(struct linenoiseState *l) {
l->pos = Backward(l, l->pos);
linenoiseRefreshLine(l);
}
static void linenoiseEditRight(struct linenoiseState *l) {
if (l->pos == l->len) return;
do l->pos++;
while (l->pos < l->len && (l->buf[l->pos] & 0300) == 0200);
linenoiseRefreshLine(l);
}
static void linenoiseEditLeftWord(struct linenoiseState *l) {
l->pos = BackwardWord(l, l->pos);
linenoiseRefreshLine(l);
}
static void linenoiseEditRightWord(struct linenoiseState *l) {
l->pos = ForwardWord(l, l->pos);
linenoiseRefreshLine(l);
}
static void linenoiseEditDelete(struct linenoiseState *l) {
size_t i;
if (l->pos == l->len) return;
i = Forward(l, l->pos);
memmove(l->buf + l->pos, l->buf + i, l->len - i + 1);
l->len -= i - l->pos;
linenoiseRefreshLine(l);
}
static void linenoiseEditRubout(struct linenoiseState *l) {
size_t i;
if (!l->pos) return;
i = Backward(l, l->pos);
memmove(l->buf + i, l->buf + l->pos, l->len - l->pos + 1);
l->len -= l->pos - i;
l->pos = i;
linenoiseRefreshLine(l);
}
static void linenoiseEditDeleteWord(struct linenoiseState *l) {
size_t i;
if (l->pos == l->len) return;
i = ForwardWord(l, l->pos);
linenoiseRingPush(l->buf + l->pos, i - l->pos);
memmove(l->buf + l->pos, l->buf + i, l->len - i + 1);
l->len -= i - l->pos;
linenoiseRefreshLine(l);
}
static void linenoiseEditRuboutWord(struct linenoiseState *l) {
size_t i;
if (!l->pos) return;
i = BackwardWord(l, l->pos);
linenoiseRingPush(l->buf + i, l->pos - i);
memmove(l->buf + i, l->buf + l->pos, l->len - l->pos + 1);
l->len -= l->pos - i;
l->pos = i;
linenoiseRefreshLine(l);
}
static void linenoiseEditXlatWord(struct linenoiseState *l,
wint_t xlat(wint_t)) {
int c;
struct rune r;
struct abuf ab;
size_t i, j, p;
abInit(&ab);
i = Forwards(l, l->pos, iswseparator);
for (j = i; j < l->len; j += r.n) {
r = GetUtf8(l->buf + j, l->len - j);
if (iswseparator(r.c)) break;
if ((c = xlat(r.c)) != r.c) {
abAppendw(&ab, tpenc(c));
} else { /* avoid canonicalization */
abAppend(&ab, l->buf + j, r.n);
}
}
if (ab.len && linenoiseGrow(l, i + ab.len + l->len - j + 1)) {
l->pos = i + ab.len;
abAppend(&ab, l->buf + j, l->len - j);
l->len = i + ab.len;
memcpy(l->buf + i, ab.b, ab.len + 1);
linenoiseRefreshLine(l);
}
abFree(&ab);
}
static void linenoiseEditLowercaseWord(struct linenoiseState *l) {
linenoiseEditXlatWord(l, towlower);
}
static void linenoiseEditUppercaseWord(struct linenoiseState *l) {
linenoiseEditXlatWord(l, towupper);
}
static void linenoiseEditCapitalizeWord(struct linenoiseState *l) {
iscapital = 0;
linenoiseEditXlatWord(l, Capitalize);
}
static void linenoiseEditKillLeft(struct linenoiseState *l) {
size_t diff, old_pos;
linenoiseRingPush(l->buf, l->pos);
old_pos = l->pos;
l->pos = 0;
diff = old_pos - l->pos;
memmove(l->buf + l->pos, l->buf + old_pos, l->len - old_pos + 1);
l->len -= diff;
linenoiseRefreshLine(l);
}
static void linenoiseEditKillRight(struct linenoiseState *l) {
linenoiseRingPush(l->buf + l->pos, l->len - l->pos);
l->buf[l->pos] = '\0';
l->len = l->pos;
linenoiseRefreshLine(l);
}
static void linenoiseEditYank(struct linenoiseState *l) {
char *p;
size_t n;
if (!ring.p[ring.i]) return;
n = strlen(ring.p[ring.i]);
linenoiseGrow(l, l->len + n + 1);
p = malloc(l->len - l->pos + 1);
memcpy(p, l->buf + l->pos, l->len - l->pos + 1);
memcpy(l->buf + l->pos, ring.p[ring.i], n);
memcpy(l->buf + l->pos + n, p, l->len - l->pos + 1);
free(p);
l->yi = l->pos;
l->yj = l->pos + n;
l->pos += n;
l->len += n;
linenoiseRefreshLine(l);
}
static void linenoiseEditRotate(struct linenoiseState *l) {
if ((l->seq[1][0] == CTRL('Y') ||
(l->seq[1][0] == 033 && l->seq[1][1] == 'y'))) {
if (l->yi < l->len && l->yj <= l->len) {
memmove(l->buf + l->yi, l->buf + l->yj, l->len - l->yj + 1);
l->len -= l->yj - l->yi;
l->pos -= l->yj - l->yi;
}
linenoiseRingRotate();
linenoiseEditYank(l);
}
}
static void linenoiseEditTranspose(struct linenoiseState *l) {
char *q, *p;
size_t a, b, c;
b = l->pos;
a = Backward(l, b);
c = Forward(l, b);
if (!(a < b && b < c)) return;
p = q = malloc(c - a);
p = mempcpy(p, l->buf + b, c - b);
p = mempcpy(p, l->buf + a, b - a);
assert(p - q == c - a);
memcpy(l->buf + a, q, p - q);
l->pos = c;
free(q);
linenoiseRefreshLine(l);
}
static void linenoiseEditTransposeWords(struct linenoiseState *l) {
char *q, *p;
struct rune r;
size_t pi, xi, xj, yi, yj;
pi = EscapeWord(l);
xj = Backwards(l, pi, iswseparator);
xi = Backwards(l, xj, notwseparator);
yi = Forwards(l, pi, iswseparator);
yj = Forwards(l, yi, notwseparator);
if (!(xi < xj && xj < yi && yi < yj)) return;
p = q = malloc(yj - xi);
p = mempcpy(p, l->buf + yi, yj - yi);
p = mempcpy(p, l->buf + xj, yi - xj);
p = mempcpy(p, l->buf + xi, xj - xi);
assert(p - q == yj - xi);
memcpy(l->buf + xi, q, p - q);
l->pos = yj;
free(q);
linenoiseRefreshLine(l);
}
static void linenoiseEditSqueeze(struct linenoiseState *l) {
size_t i, j;
i = Backwards(l, l->pos, iswseparator);
j = Forwards(l, l->pos, iswseparator);
if (!(i < j)) return;
memmove(l->buf + i, l->buf + j, l->len - j + 1);
l->len -= j - i;
l->pos = i;
linenoiseRefreshLine(l);
}
static void linenoiseEditMark(struct linenoiseState *l) {
l->mark = l->pos;
}
static void linenoiseEditGoto(struct linenoiseState *l) {
if (l->mark > l->len) return;
l->pos = MIN(l->mark, l->len);
linenoiseRefreshLine(l);
}
/**
* Runs linenoise engine.
*
* This function is the core of the line editing capability of linenoise.
* It expects 'fd' to be already in "raw mode" so that every key pressed
* will be returned ASAP to read().
*
* The resulting string is put into 'buf' when the user type enter, or
* when ctrl+d is typed.
*
* Returns chomped character count in buf >=0 or -1 on eof / error
*/
static ssize_t linenoiseEdit(int stdin_fd, int stdout_fd, const char *prompt,
char **obuf) {
ssize_t rc;
size_t nread;
char *p, seq[16];
struct linenoiseState l;
linenoiseHintsCallback *hc;
bzero(&l, sizeof(l));
if (!(l.buf = malloc((l.buflen = 32)))) return -1;
l.buf[0] = 0;
l.ifd = stdin_fd;
l.ofd = stdout_fd;
l.prompt = prompt ? prompt : "";
l.ws = GetTerminalSize(l.ws, l.ifd, l.ofd);
linenoiseHistoryAdd("");
linenoiseWriteStr(l.ofd, l.prompt);
while (1) {
if (l.dirty) linenoiseRefreshLineForce(&l);
rc = linenoiseRead(l.ifd, seq, sizeof(seq), &l);
if (rc > 0) {
if (seq[0] == CTRL('R')) {
rc = linenoiseSearch(&l, seq, sizeof(seq));
if (!rc) continue;
} else if (seq[0] == '\t' && completionCallback) {
rc = linenoiseCompleteLine(&l, seq, sizeof(seq));
if (!rc) continue;
}
}
if (rc > 0) {
nread = rc;
} else if (!rc && l.len) {
nread = 1;
seq[0] = '\r';
seq[1] = 0;
} else {
historylen--;
free(history[historylen]);
history[historylen] = 0;
free(l.buf);
return -1;
}
switch (seq[0]) {
CASE(CTRL('P'), linenoiseEditUp(&l));
CASE(CTRL('E'), linenoiseEditEnd(&l));
CASE(CTRL('N'), linenoiseEditDown(&l));
CASE(CTRL('A'), linenoiseEditHome(&l));
CASE(CTRL('B'), linenoiseEditLeft(&l));
CASE(CTRL('@'), linenoiseEditMark(&l));
CASE(CTRL('Y'), linenoiseEditYank(&l));
CASE(CTRL('F'), linenoiseEditRight(&l));
CASE(CTRL('?'), linenoiseEditRubout(&l));
CASE(CTRL('H'), linenoiseEditRubout(&l));
CASE(CTRL('L'), linenoiseEditRefresh(&l));
CASE(CTRL('U'), linenoiseEditKillLeft(&l));
CASE(CTRL('T'), linenoiseEditTranspose(&l));
CASE(CTRL('K'), linenoiseEditKillRight(&l));
CASE(CTRL('W'), linenoiseEditRuboutWord(&l));
case CTRL('X'):
if (l.seq[1][0] == CTRL('X')) {
linenoiseEditGoto(&l);
}
break;
case CTRL('D'):
if (l.len) {
linenoiseEditDelete(&l);
} else {
free(history[--historylen]);
history[historylen] = 0;
free(l.buf);
return -1;
}
break;
case '\r':
free(history[--historylen]);
history[historylen] = 0;
linenoiseEditEnd(&l);
hc = hintsCallback;
hintsCallback = 0;
linenoiseRefreshLineForce(&l);
hintsCallback = hc;
if ((p = realloc(l.buf, l.len + 1))) l.buf = p;
*obuf = l.buf;
return l.len;
case 033:
if (nread < 2) break;
switch (seq[1]) {
CASE('<', linenoiseEditBof(&l));
CASE('>', linenoiseEditEof(&l));
CASE('y', linenoiseEditRotate(&l));
CASE('\\', linenoiseEditSqueeze(&l));
CASE('b', linenoiseEditLeftWord(&l));
CASE('f', linenoiseEditRightWord(&l));
CASE('h', linenoiseEditRuboutWord(&l));
CASE('d', linenoiseEditDeleteWord(&l));
CASE('l', linenoiseEditLowercaseWord(&l));
CASE('u', linenoiseEditUppercaseWord(&l));
CASE('c', linenoiseEditCapitalizeWord(&l));
CASE('t', linenoiseEditTransposeWords(&l));
CASE(CTRL('H'), linenoiseEditRuboutWord(&l));
case '[':
if (nread < 3) break;
if (seq[2] >= '0' && seq[2] <= '9') {
if (nread < 4) break;
if (seq[3] == '~') {
switch (seq[2]) {
CASE('1', linenoiseEditHome(&l)); /* \e[1~ */
CASE('3', linenoiseEditDelete(&l)); /* \e[3~ */
CASE('4', linenoiseEditEnd(&l)); /* \e[4~ */
default:
break;
}
}
} else {
switch (seq[2]) {
CASE('A', linenoiseEditUp(&l));
CASE('B', linenoiseEditDown(&l));
CASE('C', linenoiseEditRight(&l));
CASE('D', linenoiseEditLeft(&l));
CASE('H', linenoiseEditHome(&l));
CASE('F', linenoiseEditEnd(&l));
default:
break;
}
}
break;
case 'O':
if (nread < 3) break;
switch (seq[2]) {
CASE('H', linenoiseEditHome(&l));
CASE('F', linenoiseEditEnd(&l));
default:
break;
}
break;
default:
break;
}
break;
default:
if (!iswcntrl(seq[0])) { /* only sees canonical c0 */
linenoiseEditInsert(&l, seq, nread);
}
break;
}
}
}
void linenoiseFree(void *ptr) {
free(ptr);
}
void linenoiseHistoryFree(void) {
size_t i;
for (i = 0; i < LINENOISE_MAX_HISTORY; i++) {
if (history[i]) {
free(history[i]);
history[i] = 0;
}
}
historylen = 0;
}
static void linenoiseAtExit(void) {
linenoiseDisableRawMode();
linenoiseHistoryFree();
linenoiseRingFree();
}
int linenoiseHistoryAdd(const char *line) {
char *linecopy;
if (!LINENOISE_MAX_HISTORY) return 0;
if (historylen && !strcmp(history[historylen - 1], line)) return 0;
if (!(linecopy = strdup(line))) return 0;
if (historylen == LINENOISE_MAX_HISTORY) {
free(history[0]);
memmove(history, history + 1, sizeof(char *) * (LINENOISE_MAX_HISTORY - 1));
historylen--;
}
history[historylen++] = linecopy;
return 1;
}
/**
* Saves line editing history to file.
*
* @return 0 on success, or -1 w/ errno
*/
int linenoiseHistorySave(const char *filename) {
int j;
FILE *fp;
mode_t old_umask;
old_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
fp = fopen(filename, "w");
umask(old_umask);
if (!fp) return -1;
chmod(filename, S_IRUSR | S_IWUSR);
for (j = 0; j < historylen; j++) {
fputs(history[j], fp);
fputc('\n', fp);
}
fclose(fp);
return 0;
}
/**
* Loads history from the specified file.
*
* If the file doesn't exist, zero is returned and this will do nothing.
* If the file does exists and the operation succeeded zero is returned
* otherwise on error -1 is returned.
*
* @return 0 on success, or -1 w/ errno
*/
int linenoiseHistoryLoad(const char *filename) {
char **h;
int rc, fd, err;
size_t i, j, k, n;
char *m, *e, *p, *q, *f, *s;
err = errno, rc = 0;
if (!LINENOISE_MAX_HISTORY) return 0;
if (!(h = (char **)calloc(2 * LINENOISE_MAX_HISTORY, sizeof(char *))))
return -1;
if ((fd = open(filename, O_RDONLY)) != -1) {
if ((n = GetFdSize(fd))) {
if ((m = (char *)mmap(0, n, PROT_READ, MAP_SHARED, fd, 0)) !=
MAP_FAILED) {
for (i = 0, e = (p = m) + n; p < e; p = f + 1) {
if (!(q = (char *)memchr(p, '\n', e - p))) q = e;
for (f = q; q > p; --q) {
if (q[-1] != '\n' && q[-1] != '\r') break;
}
if (q > p) {
h[i * 2 + 0] = p;
h[i * 2 + 1] = q;
i = (i + 1) % LINENOISE_MAX_HISTORY;
}
}
linenoiseHistoryFree();
for (j = 0; j < LINENOISE_MAX_HISTORY; ++j) {
if (h[(k = (i + j) % LINENOISE_MAX_HISTORY) * 2]) {
if ((s = malloc((n = h[k * 2 + 1] - h[k * 2]) + 1))) {
memcpy(s, h[k * 2], n), s[n] = 0;
history[historylen++] = s;
}
}
}
munmap(m, n);
} else {
rc = -1;
}
}
close(fd);
} else if (errno == ENOENT) {
errno = err;
} else {
rc = -1;
}
free(h);
return rc;
}
/**
* Reads line interactively.
*
* This function can be used instead of linenoise() in cases where we
* know for certain we're dealing with a terminal, which means we can
* avoid linking any stdio code.
*
* @return chomped allocated string of read line or null on eof/error
*/
char *linenoiseRaw(const char *prompt, int infd, int outfd) {
char *buf;
ssize_t rc;
static char once;
struct sigaction sa[3];
if (!once) atexit(linenoiseAtExit), once = 1;
if (enableRawMode(infd) == -1) return 0;
buf = 0;
gotint = 0;
sigemptyset(&sa->sa_mask);
sa->sa_flags = SA_NODEFER;
sa->sa_handler = linenoiseOnInt;
sigaction(SIGINT, sa, sa + 1);
sigaction(SIGQUIT, sa, sa + 2);
rc = linenoiseEdit(infd, outfd, prompt, &buf);
linenoiseDisableRawMode();
sigaction(SIGQUIT, sa + 2, 0);
sigaction(SIGINT, sa + 1, 0);
if (gotint) {
free(buf);
buf = 0;
raise(gotint);
errno = EINTR;
rc = -1;
}
if (rc != -1) {
linenoiseWriteStr(outfd, "\n");
return buf;
} else {
free(buf);
return 0;
}
}
/**
* Reads line intelligently.
*
* The high level function that is the main API of the linenoise library.
* This function checks if the terminal has basic capabilities, just checking
* for a blacklist of inarticulate terminals, and later either calls the line
* editing function or uses dummy fgets() so that you will be able to type
* something even in the most desperate of the conditions.
*
* @param prompt is printed before asking for input if we have a term
* and this may be set to empty or null to disable and prompt may
* contain ansi escape sequences, color, utf8, etc.
* @return chomped allocated string of read line or null on eof/error
*/
char *linenoise(const char *prompt) {
if (prompt && *prompt &&
(strchr(prompt, '\n') || strchr(prompt, '\t') ||
strchr(prompt + 1, '\r'))) {
errno = EINVAL;
return 0;
}
if ((!isatty(fileno(stdin)) || !isatty(fileno(stdout)))) {
return GetLine(stdin);
} else if (linenoiseIsUnsupportedTerm()) {
if (prompt && *prompt) {
fputs(prompt, stdout);
fflush(stdout);
}
return GetLine(stdin);
} else {
fflush(stdout);
return linenoiseRaw(prompt, fileno(stdin), fileno(stdout));
}
}
/**
* Reads line intelligently w/ history, e.g.
*
* // see ~/.foo_history
* main() {
* char *line;
* while ((line = linenoiseWithHistory("IN> ", "foo"))) {
* printf("OUT> %s\n", line);
* free(line);
* }
* }
*
* @param prompt is printed before asking for input if we have a term
* and this may be set to empty or null to disable and prompt may
* contain ansi escape sequences, color, utf8, etc.
* @param prog is name of your app, used to generate history filename
* however if it contains a slash / dot then we'll assume prog is
* the history filename which as determined by the caller
* @return chomped allocated string of read line or null on eof/error
*/
char *linenoiseWithHistory(const char *prompt, const char *prog) {
char *line;
struct abuf path;
const char *a, *b;
abInit(&path);
if (prog) {
if (strchr(prog, '/') || strchr(prog, '.')) {
abAppends(&path, prog);
} else {
b = "";
if (!(a = getenv("HOME"))) {
if (!(a = getenv("HOMEDRIVE")) || !(b = getenv("HOMEPATH"))) {
a = "";
}
}
if (*a) {
abAppends(&path, a);
abAppends(&path, b);
abAppendw(&path, '/');
}
abAppendw(&path, '.');
abAppends(&path, prog);
abAppends(&path, "_history");
}
}
if (path.len) {
linenoiseHistoryLoad(path.b);
}
line = linenoise(prompt);
if (path.len && line && *line) {
/* history here is inefficient but helpful when the user has multiple
* repls open at the same time, so history propagates between them */
linenoiseHistoryLoad(path.b);
linenoiseHistoryAdd(line);
linenoiseHistorySave(path.b);
}
abFree(&path);
return line;
}
/**
* Registers tab completion callback.
*/
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
completionCallback = fn;
}
/**
* Registers hints callback.
*
* Register a hits function to be called to show hits to the user at the
* right of the prompt.
*/
void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) {
hintsCallback = fn;
}
/**
* Sets free hints callback.
*
* This registers a function to free the hints returned by the hints
* callback registered with linenoiseSetHintsCallback().
*/
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) {
freeHintsCallback = fn;
}
/**
* Adds completion.
*
* This function is used by the callback function registered by the user
* in order to add completion options given the input string when the
* user typed <tab>. See the example.c source code for a very easy to
* understand example.
*/
void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
size_t len;
char *copy, **cvec;
if ((copy = malloc((len = strlen(str)) + 1))) {
memcpy(copy, str, len + 1);
if ((cvec = realloc(lc->cvec, (lc->len + 1) * sizeof(*lc->cvec)))) {
lc->cvec = cvec;
lc->cvec[lc->len++] = copy;
} else {
free(copy);
}
}
}
/**
* Frees list of completion option populated by linenoiseAddCompletion().
*/
void linenoiseFreeCompletions(linenoiseCompletions *lc) {
size_t i;
for (i = 0; i < lc->len; i++) free(lc->cvec[i]);
if (lc->cvec) free(lc->cvec);
}
/**
* Enables "mask mode".
*
* When it is enabled, instead of the input that the user is typing, the
* terminal will just display a corresponding number of asterisks, like
* "****". This is useful for passwords and other secrets that should
* not be displayed.
*
* @see linenoiseMaskModeDisable()
*/
void linenoiseMaskModeEnable(void) {
maskmode = 1;
}
/**
* Disables "mask mode".
*/
void linenoiseMaskModeDisable(void) {
maskmode = 0;
}