/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │ vi: set et 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 │ │ - Support unlimited lines │ │ - React to terminal resizing │ │ - 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 use ephemeral handlers │ │ - 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-RIGHT FORWARD WORD │ │ CTRL-LEFT BACKWARD WORD │ │ CTRL-ALT-F FORWARD EXPR │ │ CTRL-ALT-B BACKWARD EXPR │ │ ALT-RIGHT FORWARD EXPR │ │ ALT-LEFT BACKWARD EXPR │ │ ALT-SHIFT-B BARF EXPR │ │ ALT-SHIFT-S SLURP EXPR │ │ 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 │ │ ALT-\ SQUEEZE ADJACENT WHITESPACE │ │ CTRL-T TRANSPOSE │ │ ALT-T TRANSPOSE WORD │ │ ALT-U UPPERCASE WORD │ │ ALT-L LOWERCASE WORD │ │ ALT-C CAPITALIZE WORD │ │ CTRL-C CTRL-C INTERRUPT PROCESS │ │ CTRL-Z SUSPEND PROCESS │ │ CTRL-\ QUIT PROCESS │ │ CTRL-S PAUSE OUTPUT │ │ CTRL-Q UNPAUSE OUTPUT (IF PAUSED) │ │ CTRL-Q ESCAPED INSERT │ │ CTRL-SPACE SET MARK │ │ CTRL-X CTRL-X GOTO MARK │ │ PROTIP REMAP CAPS LOCK TO CTRL │ │ │ │ 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 │ │ Copyright 2010-2016 Salvatore Sanfilippo │ │ Copyright 2010-2013 Pieter Noordhuis │ │ │ │ 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 "third_party/linenoise/linenoise.h" #include "libc/assert.h" #include "libc/calls/calls.h" #include "libc/calls/sig.internal.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/dce.h" #include "libc/errno.h" #include "libc/fmt/conv.h" #include "libc/serialize.h" #include "libc/intrin/bsr.h" #include "libc/intrin/nomultics.h" #include "libc/intrin/strace.h" #include "libc/log/check.h" #include "libc/log/log.h" #include "libc/macros.h" #include "libc/mem/alg.h" #include "libc/mem/mem.h" #include "libc/nexgen32e/rdtsc.h" #include "libc/runtime/runtime.h" #include "libc/sock/sock.h" #include "libc/sock/struct/pollfd.h" #include "libc/stdckdint.h" #include "libc/stdio/append.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/str/tab.h" #include "libc/str/unicode.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/s.h" #include "libc/sysv/consts/sa.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/termios.h" #include "libc/sysv/errfuns.h" #include "net/http/escape.h" #include "libc/wctype.h" #include "tool/build/lib/case.h" __notice(linenoise_notice, "\ Cosmopolitan Linenoise (BSD-2)\n\ Copyright 2018-2020 Justine Tunney \n\ Copyright 2010-2016 Salvatore Sanfilippo \n\ Copyright 2010-2013 Pieter Noordhuis "); #define LINENOISE_POLL_MS 50 #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 #define DUFF_ROUTINE_LOOP 0 #define DUFF_ROUTINE_SEARCH 1 #define DUFF_ROUTINE_START 5 #define DUFF_ROUTINE_LABEL(STATE) \ case STATE: \ linenoiseRefreshLineForce(l); \ l->state = STATE #define DUFF_ROUTINE_READ(STATE) \ DUFF_ROUTINE_LABEL(STATE); \ rc = linenoiseRead(l->ifd, seq, sizeof(seq), l, block); \ if (rc == -1 && errno == EAGAIN) { \ l->state = STATE; \ return -1; \ } #define BLOCKING_READ() rc = linenoiseRead(l->ifd, seq, sizeof(seq), l, false) 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 state; /* state machine */ 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 */ 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 final; /* set to true on last update */ char dirty; /* if an update was squashed */ linenoiseCompletions lc; struct abuf ab; int i, j, perline, itemlen; // for reverse search int fail, matlen, oldindex, olderpos; char *oldprompt; }; static const unsigned short kMirrorLeft[][2] = { {L'(', L')'}, {L'[', L']'}, {L'{', L'}'}, {L'⁅', L'⁆'}, {L'⁽', L'⁾'}, {L'₍', L'₎'}, {L'⌈', L'⌉'}, {L'⌊', L'⌋'}, {L'〈', L'〉'}, {L'❨', L'❩'}, {L'❪', L'❫'}, {L'❬', L'❭'}, {L'❮', L'❯'}, {L'❰', L'❱'}, {L'❲', L'❳'}, {L'❴', L'❵'}, {L'⟅', L'⟆'}, {L'⟦', L'⟧'}, {L'⟨', L'⟩'}, {L'⟪', L'⟫'}, {L'⟬', L'⟭'}, {L'⟮', L'⟯'}, {L'⦃', L'⦄'}, {L'⦅', L'⦆'}, {L'⦇', L'⦈'}, {L'⦉', L'⦊'}, {L'⦋', L'⦌'}, {L'⦍', L'⦐'}, {L'⦏', L'⦎'}, {L'⦑', L'⦒'}, {L'⦓', L'⦔'}, {L'⦗', L'⦘'}, {L'⧘', L'⧙'}, {L'⧚', L'⧛'}, {L'⧼', L'⧽'}, {L'﹙', L'﹚'}, {L'﹛', L'﹜'}, {L'﹝', L'﹞'}, {L'(', L')'}, {L'[', L']'}, {L'{', L'}'}, {L'「', L'」'}, }; static const unsigned short kMirrorRight[][2] = { {L')', L'('}, {L']', L'['}, {L'}', L'{'}, {L'⁆', L'⁅'}, {L'⁾', L'⁽'}, {L'₎', L'₍'}, {L'⌉', L'⌈'}, {L'⌋', L'⌊'}, {L'〉', L'〈'}, {L'❩', L'❨'}, {L'❫', L'❪'}, {L'❭', L'❬'}, {L'❯', L'❮'}, {L'❱', L'❰'}, {L'❳', L'❲'}, {L'❵', L'❴'}, {L'⟆', L'⟅'}, {L'⟧', L'⟦'}, {L'⟩', L'⟨'}, {L'⟫', L'⟪'}, {L'⟭', L'⟬'}, {L'⟯', L'⟮'}, {L'⦄', L'⦃'}, {L'⦆', L'⦅'}, {L'⦈', L'⦇'}, {L'⦊', L'⦉'}, {L'⦌', L'⦋'}, {L'⦎', L'⦏'}, {L'⦐', L'⦍'}, {L'⦒', L'⦑'}, {L'⦔', L'⦓'}, {L'⦘', L'⦗'}, {L'⧙', L'⧘'}, {L'⧛', L'⧚'}, {L'⧽', L'⧼'}, {L'﹚', L'﹙'}, {L'﹜', L'﹛'}, {L'﹞', L'﹝'}, {L')', L'('}, {L']', L'['}, {L'}', L'{'}, {L'」', L'「'}, }; static const char *const kUnsupported[] = {"dumb", "cons25", "emacs"}; static int gotint; static int gotcont; static int gotwinch; static char maskmode; static char ispaused; static char iscapital; static int historylen; static signed char rawmode = -1; static struct linenoiseRing ring; static struct sigaction orig_cont; static struct sigaction orig_winch; static struct termios orig_termios; static char *history[LINENOISE_MAX_HISTORY]; static linenoiseXlatCallback *xlatCallback; static linenoiseHintsCallback *hintsCallback; static linenoiseFreeHintsCallback *freeHintsCallback; static linenoiseCompletionCallback *completionCallback; static unsigned GetMirror(const unsigned short A[][2], size_t n, unsigned c) { int l, m, r; l = 0; r = n - 1; while (l <= r) { m = (l & r) + ((l ^ r) >> 1); // floor((a+b)/2) if (A[m][0] < c) { l = m + 1; } else if (A[m][0] > c) { r = m - 1; } else { return A[m][1]; } } return 0; } static unsigned GetMirrorLeft(unsigned c) { return GetMirror(kMirrorRight, ARRAYLEN(kMirrorRight), c); } static unsigned GetMirrorRight(unsigned c) { return GetMirror(kMirrorLeft, ARRAYLEN(kMirrorLeft), c); } static int isxseparator(wint_t c) { return iswseparator(c) && !GetMirrorLeft(c) && !GetMirrorRight(c); } static int notwseparator(wint_t c) { return !iswseparator(c); } static int iswname(wint_t c) { return !iswseparator(c) || c == '_' || c == '-' || c == '.' || c == ':'; } static int notwname(wint_t c) { return !iswname(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 IsCharDev(int fd) { struct stat st; st.st_mode = 0; fstat(fd, &st); return (st.st_mode & S_IFMT) == S_IFCHR; } 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; } int linenoiseIsTerminal(void) { static int once, res; if (!once) { res = isatty(fileno(stdin)) && isatty(fileno(stdout)) && !linenoiseIsUnsupportedTerm(); once = 1; } return res; } int linenoiseIsTeletype(void) { static int once, res; if (!once) { res = linenoiseIsTerminal() || (IsCharDev(fileno(stdin)) && IsCharDev(fileno(stdout))); once = 1; } return res; } char *linenoiseGetLine(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 (kToLower[p[n + i] & 255] != kToLower[q[i] & 255]) { break; } } if (kToLower[i & 255] == kToLower[m & 255]) { 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; } /** * Returns UNICODE CJK Monospace Width of string. * * Control codes and ANSI sequences have a width of zero. We only parse * a limited subset of ANSI here since we don't store ANSI codes in the * linenoiseState::buf, but we do encourage CSI color codes in prompts. */ 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 } 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) { d = wcwidth(r.c); d = MAX(0, d); w += d; haswides |= d > 1; t = kAscii; } } else { goto Whoopsie; } break; case kEsc: if (c == '[') { t = kCsi1; } else { t = kAscii; } break; case kCsi1: if (0x20 <= c && c <= 0x2f) { t = kCsi2; } 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; default: __builtin_unreachable(); } } 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 void linenoiseUnpause(int fd) { if (ispaused) { tcflow(fd, TCOON); ispaused = 0; } } int linenoiseEnableRawMode(int fd) { struct termios raw; struct sigaction sa; if (rawmode == -1) { if (tcgetattr(fd, &orig_termios) != -1) { raw = orig_termios; raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); raw.c_oflag |= OPOST | ONLCR; 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; } } return enotty(); } else { return 0; } } void linenoiseDisableRawMode(void) { if (rawmode != -1) { linenoiseUnpause(rawmode); sigaction(SIGCONT, &orig_cont, 0); sigaction(SIGWINCH, &orig_winch, 0); tcsetattr(rawmode, TCSANOW, &orig_termios); rawmode = -1; } } static int linenoiseWrite(int fd, const void *p, size_t n) { ssize_t rc; size_t wrote; do { for (;;) { if (ispaused) { return 0; } 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, 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 int linenoisePoll(struct linenoiseState *l, int fd) { int rc; if ((rc = poll((struct pollfd[]){{fd, POLLIN}}, 1, 0))) { return rc; } else { l->dirty = true; return eagain(); } } static ssize_t linenoiseRead(int fd, char *buf, size_t size, struct linenoiseState *l, int block) { ssize_t rc; int refreshme; for (;;) { refreshme = 0; if (gotint) { errno = EINTR; return -1; } if (gotcont && rawmode != -1) { rawmode = -1; strace_enabled(-1); linenoiseEnableRawMode(0); strace_enabled(+1); if (l) refreshme = 1; } if (l && gotwinch) refreshme = 1; if (refreshme) linenoiseRefreshLine(l); if (!block && linenoisePoll(l, fd) == -1) return -1; strace_enabled(-1); rc = readansi(fd, buf, size); strace_enabled(+1); if (rc == -1 && errno == EINTR) { if (!block) break; } else { break; } } 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 */ struct winsize linenoiseGetTerminalSize(struct winsize ws, int ifd, int ofd) { int x; ssize_t n; char *p, *s, b[16]; tcgetwinsize(ofd, &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) != -1 && linenoiseWriteStr( ofd, "\e7" /* save position */ "\e[9979;9979H" /* move cursor to bottom right corner */ "\e[6n" /* report position */ "\e8") != -1 && /* restore position */ (n = linenoiseRead(ifd, b, sizeof(b), 0, 1)) != -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, "\e[H" /* move cursor to top left corner */ "\e[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; } static wint_t ScrubCompletionCharacter(wint_t c) { if ((0x00 <= c && c <= 0x1F) || c == 0x7F) { return kCp437[c]; } else { return c; } } static size_t linenoiseMaxCompletionWidth(linenoiseCompletions *lc) { size_t i, n, m; for (m = i = 0; i < lc->len; ++i) { n = GetMonospaceWidth(lc->cvec[i], strlen(lc->cvec[i]), 0); m = MAX(n, m); } return m; } 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 GetCommonPrefixLength(struct linenoiseCompletions *lc) { struct rune r; int i, j, n, c; i = 0; for (n = -1, i = 0; i < lc->len; ++i) { if (n != -1) { n = strnlen(lc->cvec[i], n); } else { n = strlen(lc->cvec[i]); } } for (i = 0, r.n = 0; i < n; i += r.n) { for (c = -1, j = 0; j < lc->len; ++j) { r = GetUtf8(lc->cvec[j] + i, n - i); if (c != -1) { if (r.c != c) { goto Finished; } } else { c = r.c; } } } Finished: return i; } 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(int fail, const char *s, int n) { struct abuf ab; abInit(&ab); abAppendw(&ab, '('); if (fail) abAppends(&ab, "failed "); abAppends(&ab, "reverse-i-search `\e[4m"); abAppend(&ab, s, n); abAppends(&ab, "\e[24m"); abAppends(&ab, s + n); abAppendw(&ab, READ32LE("') ")); return ab.b; } 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 = "\e[90m"; ansi2 = "\e[39m"; if (ansi1) abAppends(&ab, ansi1); abAppends(&ab, hint); if (ansi2) abAppends(&ab, ansi2); if (freeHintsCallback) freeHintsCallback(hint); return ab.b; } static int linenoiseMirrorLeft(struct linenoiseState *l, unsigned res[2]) { unsigned c, pos, left, right, depth, index; if ((pos = Backward(l, l->pos))) { right = GetUtf8(l->buf + pos, l->len - pos).c; if ((left = GetMirrorLeft(right))) { depth = 0; index = pos; do { pos = Backward(l, pos); c = GetUtf8(l->buf + pos, l->len - pos).c; if (c == right) { ++depth; } else if (c == left) { if (depth) { --depth; } else { res[0] = pos; res[1] = index; return 0; } } } while (pos); } } return -1; } static int linenoiseMirrorRight(struct linenoiseState *l, unsigned res[2]) { struct rune rune; unsigned pos, left, right, depth, index; pos = l->pos; rune = GetUtf8(l->buf + pos, l->len - pos); left = rune.c; if ((right = GetMirrorRight(left))) { depth = 0; index = pos; do { pos += rune.n; rune = GetUtf8(l->buf + pos, l->len - pos); if (rune.c == left) { ++depth; } else if (rune.c == right) { if (depth) { --depth; } else { res[0] = index; res[1] = pos; return 0; } } } while (pos + rune.n < l->len); } return -1; } static int linenoiseMirror(struct linenoiseState *l, unsigned res[2]) { int rc; rc = linenoiseMirrorLeft(l, res); if (rc == -1) rc = linenoiseMirrorRight(l, res); return rc; } static void linenoiseRefreshLineImpl(struct linenoiseState *l, int force, const char *prefix) { char *hint; char flipit; char hasflip; char haswides; struct abuf ab; struct rune rune; unsigned flip[2]; const char *buf; struct winsize oldsize; int i, x, t, xn, yn, cx, cy, tn, resized; int fd, plen, width, pwidth, rows, len, pos; /* * synchonize the i/o state */ if (ispaused) { if (force) { linenoiseUnpause(l->ofd); } else { return; } } if (!force && HasPendingInput(l->ifd)) { l->dirty = 1; return; } oldsize = l->ws; if ((resized = gotwinch) && rawmode != -1) { gotwinch = 0; l->ws = linenoiseGetTerminalSize(l->ws, l->ifd, l->ofd); } hasflip = !l->final && !linenoiseMirror(l, flip); 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); if (prefix) { // to prevent flicker with ctrl+l abAppends(&ab, prefix); } abAppendw(&ab, '\r'); /* start of line */ if (l->rows - l->oldpos - 1 > 0) { abAppends(&ab, "\e["); 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; if (x < xn) { abAppends(&ab, "\e[K"); /* clear line forward */ } abAppends(&ab, "\r" /* start of line */ "\n"); /* cursor down unclamped */ ++rows; x = 0; } if (i == pos) { cy = 0; cx = x; } if (maskmode) { abAppendw(&ab, '*'); } else { flipit = hasflip && (i == flip[0] || i == flip[1]); if (flipit) abAppendw(&ab, READ32LE("\e[1m")); abAppendw(&ab, tpenc(rune.c)); if (flipit) abAppendw(&ab, READ64LE("\e[22m\0\0")); } t = wcwidth(rune.c); t = MAX(0, t); x += t; } if (!l->final && (hint = linenoiseRefreshHints(l))) { if (GetMonospaceWidth(hint, strlen(hint), 0) < xn - x) { if (cx < 0) { cx = x; } abAppends(&ab, hint); } free(hint); } abAppendw(&ab, READ32LE("\e[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("\e[\0")); abAppendu(&ab, cy); abAppendw(&ab, 'A'); /* cursor up */ } if (cx > 0) { abAppendw(&ab, READ32LE("\r\e[")); 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); } void linenoiseRefreshLine(struct linenoiseState *l) { strace_enabled(-1); linenoiseRefreshLineImpl(l, 0, 0); strace_enabled(+1); } static void linenoiseRefreshLineForce(struct linenoiseState *l) { strace_enabled(-1); linenoiseRefreshLineImpl(l, 1, 0); strace_enabled(+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) { strace_enabled(-1); linenoiseRefreshLineImpl(l, 1, "\e[H" // move cursor to top left corner "\e[2J"); // erase display strace_enabled(+1); } 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 linenoiseEditLeftExpr(struct linenoiseState *l) { unsigned mark[2]; l->pos = Backwards(l, l->pos, isxseparator); if (!linenoiseMirrorLeft(l, mark)) { l->pos = mark[0]; } else { l->pos = Backwards(l, l->pos, notwseparator); } linenoiseRefreshLine(l); } static void linenoiseEditRightExpr(struct linenoiseState *l) { unsigned mark[2]; l->pos = Forwards(l, l->pos, isxseparator); if (!linenoiseMirrorRight(l, mark)) { l->pos = Forward(l, mark[1]); } else { l->pos = Forwards(l, l->pos, notwseparator); } 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; size_t i, j; struct rune r; struct abuf ab; 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] == '\e' && 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); unassert(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; 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); unassert(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); } static size_t linenoiseEscape(char *d, const char *s, size_t n) { char *p; size_t i; unsigned c, w, l; for (p = d, l = i = 0; i < n; ++i) { switch ((c = s[i] & 255)) { CASE('\e', w = READ16LE("\\e")); CASE('\a', w = READ16LE("\\a")); CASE('\b', w = READ16LE("\\b")); CASE('\t', w = READ16LE("\\t")); CASE('\n', w = READ16LE("\\n")); CASE('\v', w = READ16LE("\\v")); CASE('\f', w = READ16LE("\\f")); CASE('\r', w = READ16LE("\\r")); CASE('"', w = READ16LE("\\\"")); CASE('\'', w = READ16LE("\\\'")); CASE('\\', w = READ16LE("\\\\")); default: if ((0x00 <= c && c <= 0x1F) || c == 0x7F || (c == '?' && l == '?')) { w = READ16LE("\\x"); w |= "0123456789abcdef"[(c & 0xF0) >> 4] << 020; w |= "0123456789abcdef"[(c & 0x0F) >> 0] << 030; } else { w = c; } break; } WRITE32LE(p, w); p += (bsr(w) >> 3) + 1; l = w; } return p - d; } static void linenoiseEditInterrupt(struct linenoiseState *l) { gotint = SIGINT; } static void linenoiseEditQuit(struct linenoiseState *l) { gotint = SIGQUIT; } static void linenoiseEditSuspend(struct linenoiseState *l) { raise(SIGSTOP); } static void linenoiseEditPause(struct linenoiseState *l) { tcflow(l->ofd, TCOOFF); ispaused = 1; } static void linenoiseEditCtrlq(struct linenoiseState *l) { } /** * Moves last item inside current s-expression to outside, e.g. * * (a| b c) * (a| b) c * * The cursor position changes only if a paren is moved before it: * * (a b c |) * (a b) c | * * To accommodate non-LISP languages we connect unspaced outer symbols: * * f(a,| b, g()) * f(a,| b), g() * * Our standard keybinding is ALT-SHIFT-B. */ static void linenoiseEditBarf(struct linenoiseState *l) { struct rune r; unsigned long w; size_t i, pos, depth = 0; unsigned lhs, rhs, end, *stack = 0; /* go as far right within current s-expr as possible */ for (pos = l->pos;; pos += r.n) { if (pos == l->len) goto Finish; r = GetUtf8(l->buf + pos, l->len - pos); if (depth) { if (r.c == stack[depth - 1]) { --depth; } } else { if ((rhs = GetMirrorRight(r.c))) { stack = (unsigned *)realloc(stack, ++depth * sizeof(*stack)); stack[depth - 1] = rhs; } else if (GetMirrorLeft(r.c)) { end = pos; break; } } } /* go back one item */ pos = Backwards(l, pos, isxseparator); for (;; pos = i) { if (!pos) goto Finish; i = Backward(l, pos); r = GetUtf8(l->buf + i, l->len - i); if (depth) { if (r.c == stack[depth - 1]) { --depth; } } else { if ((lhs = GetMirrorLeft(r.c))) { stack = (unsigned *)realloc(stack, ++depth * sizeof(*stack)); stack[depth - 1] = lhs; } else if (iswseparator(r.c)) { break; } } } pos = Backwards(l, pos, isxseparator); /* now move the text */ r = GetUtf8(l->buf + end, l->len - end); memmove(l->buf + pos + r.n, l->buf + pos, end - pos); w = tpenc(r.c); for (i = 0; i < r.n; ++i) { l->buf[pos + i] = w; w >>= 8; } if (l->pos > pos) { l->pos += r.n; } linenoiseRefreshLine(l); Finish: free(stack); } /** * Moves first item outside current s-expression to inside, e.g. * * (a| b) c d * (a| b c) d * * To accommodate non-LISP languages we connect unspaced outer symbols: * * f(a,| b), g() * f(a,| b, g()) * * Our standard keybinding is ALT-SHIFT-S. */ static void linenoiseEditSlurp(struct linenoiseState *l) { char rp[6]; struct rune r; size_t pos, depth = 0; unsigned rhs, point = 0, start = 0, *stack = 0; /* go to outside edge of current s-expr */ for (pos = l->pos; pos < l->len; pos += r.n) { r = GetUtf8(l->buf + pos, l->len - pos); if (depth) { if (r.c == stack[depth - 1]) { --depth; } } else { if ((rhs = GetMirrorRight(r.c))) { stack = (unsigned *)realloc(stack, ++depth * sizeof(*stack)); stack[depth - 1] = rhs; } else if (GetMirrorLeft(r.c)) { point = pos; pos += r.n; start = pos; break; } } } /* go forward one item */ pos = Forwards(l, pos, isxseparator); for (; pos < l->len; pos += r.n) { r = GetUtf8(l->buf + pos, l->len - pos); if (depth) { if (r.c == stack[depth - 1]) { --depth; } } else { if ((rhs = GetMirrorRight(r.c))) { stack = (unsigned *)realloc(stack, ++depth * sizeof(*stack)); stack[depth - 1] = rhs; } else if (iswseparator(r.c)) { break; } } } /* now move the text */ memcpy(rp, l->buf + point, start - point); memmove(l->buf + point, l->buf + start, pos - start); memcpy(l->buf + pos - (start - point), rp, start - point); linenoiseRefreshLine(l); free(stack); } struct linenoiseState *linenoiseBegin(const char *prompt, int ifd, int ofd) { struct linenoiseState *l; if (!(l = calloc(1, sizeof(*l)))) { return 0; } if (!(l->buf = malloc((l->buflen = 32)))) { free(l); return 0; } l->state = DUFF_ROUTINE_START; l->buf[0] = 0; l->ifd = ifd; l->ofd = ofd; l->prompt = strdup(prompt ? prompt : ""); l->ws = linenoiseGetTerminalSize(l->ws, l->ifd, l->ofd); linenoiseWriteStr(l->ofd, l->prompt); abInit(&l->ab); return l; } void linenoiseReset(struct linenoiseState *l) { l->buf[0] = 0; l->dirty = true; l->final = 0; l->hindex = 0; l->len = 0; l->mark = 0; l->oldpos = 0; l->pos = 0; l->yi = 0; l->yj = 0; } void linenoiseEnd(struct linenoiseState *l) { if (l) { linenoiseFreeCompletions(&l->lc); abFree(&l->ab); free(l->oldprompt); free(l->prompt); free(l->buf); free(l); } } static int CompareStrings(const void *a, const void *b) { return strcmp(*(const char **)a, *(const char **)b); } /** * 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 exit conditions are: * * 1. ret > 0 / buf ≠ 0 / errno = ? -- means we got some * 2. ret = 0 / buf ≠ 0 / errno = ? -- means empty line * 3. ret = 0 / buf = 0 / errno = ? -- means eof * 4. ret = -1 / buf = ? / errno ≠ 0 -- means error * * @param l is linenoise reader object created by linenoiseBegin() * @param prompt if non-null is copied and replaces current prompt * @param block if false will cause -1 / EAGAIN if there's no data * @return chomped character count in buf >=0 or -1 on eof / error */ ssize_t linenoiseEdit(struct linenoiseState *l, const char *prompt, char **obuf, bool block) { ssize_t rc; char seq[16]; gotint = 0; if (prompt && l->state != DUFF_ROUTINE_SEARCH && (!l->prompt || strcmp(prompt, l->prompt))) { free(l->prompt); l->prompt = strdup(prompt); } switch (l->state) { for (;;) { DUFF_ROUTINE_READ(DUFF_ROUTINE_LOOP); HandleRead: if (!rc && l->len) { rc = 1; seq[0] = '\r'; seq[1] = 0; } else if (!rc || rc == -1) { free(history[--historylen]); history[historylen] = 0; linenoiseReset(l); if (!rc) *obuf = 0; return rc; } // handle reverse history search if (seq[0] == CTRL('R')) { int fail, added; if (historylen <= 1) continue; l->ab.len = 0; l->olderpos = l->pos; l->oldprompt = l->prompt; l->oldindex = l->hindex; l->prompt = 0; for (fail = l->matlen = 0;;) { free(l->prompt); l->prompt = linenoiseMakeSearchPrompt(fail, l->ab.b, l->matlen); DUFF_ROUTINE_READ(DUFF_ROUTINE_SEARCH); fail = 1; added = 0; l->j = l->pos; l->i = l->hindex; if (rc > 0) { if (seq[0] == CTRL('?') || seq[0] == CTRL('H')) { if (l->ab.len) { --l->ab.len; l->matlen = MIN(l->matlen, l->ab.len); } } else if (seq[0] == CTRL('R')) { if (l->j) { --l->j; } else if (l->i + 1 < historylen) { ++l->i; l->j = strlen(history[historylen - 1 - l->i]); } } else if (seq[0] == CTRL('G')) { linenoiseEditHistoryGoto(l, l->oldindex); l->pos = l->olderpos; break; } else if (iswcntrl(seq[0])) { // only sees canonical c0 break; } else { abAppend(&l->ab, seq, rc); added = rc; } } else { break; } while (l->i < historylen) { int k; char *p; const char *q; p = history[historylen - 1 - l->i]; k = strlen(p); l->j = l->j >= 0 ? MIN(k, l->j + l->ab.len) : k; if ((q = FindSubstringReverse(p, l->j, l->ab.b, l->ab.len))) { linenoiseEditHistoryGoto(l, l->i); l->pos = q - p; fail = 0; if (added) { l->matlen += added; added = 0; } break; } else { l->i = l->i + 1; l->j = -1; } } } free(l->prompt); l->prompt = l->oldprompt; l->oldprompt = 0; linenoiseRefreshLine(l); goto HandleRead; } // handle tab and tab-tab completion if (seq[0] == '\t' && completionCallback) { size_t i, n, m; // we know that the user pressed tab once rc = 0; linenoiseFreeCompletions(&l->lc); i = Backwards(l, l->pos, iswname); { char *s = strndup(l->buf + i, l->pos - i); completionCallback(s, &l->lc); free(s); } m = GetCommonPrefixLength(&l->lc); if (m > l->pos - i || (m == l->pos - i && l->lc.len == 1)) { // on common prefix (or single completion) we complete and return n = i + m + (l->len - l->pos); if (linenoiseGrow(l, n + 1)) { memmove(l->buf + i + m, l->buf + l->pos, l->len - l->pos + 1); memcpy(l->buf + i, l->lc.cvec[0], m); l->pos = i + m; l->len = n; } continue; } if (l->lc.len > 1) { qsort(l->lc.cvec, l->lc.len, sizeof(*l->lc.cvec), CompareStrings); // if there's a multiline completions, then do nothing and wait and // see if the user presses tab again. if the user does this we then // print ALL the completions, to above the editing line for (i = 0; i < l->lc.len; ++i) { char *s = l->lc.cvec[i]; l->lc.cvec[i] = VisualizeControlCodes(s, -1, 0); free(s); } for (;;) { DUFF_ROUTINE_READ(2); if (rc == 1 && seq[0] == '\t') { const char **p; struct abuf ab; int i, k, x, y, xn, yn, xy, itemlen; itemlen = linenoiseMaxCompletionWidth(&l->lc) + 4; xn = MAX(1, (l->ws.ws_col - 1) / itemlen); yn = (l->lc.len + (xn - 1)) / xn; if (!ckd_mul(&xy, xn, yn) && (p = calloc(xy, sizeof(char *)))) { // arrange in column major order for (i = x = 0; x < xn; ++x) { for (y = 0; y < yn; ++y) { p[y * xn + x] = i < l->lc.len ? l->lc.cvec[i++] : ""; } } abInit(&ab); abAppends(&ab, "\r\n\e[K"); for (x = i = 0; i < xy; ++i) { n = GetMonospaceWidth(p[i], strlen(p[i]), 0); abAppends(&ab, p[i]); for (k = n; k < itemlen; ++k) { abAppendw(&ab, ' '); } if (++x == xn) { abAppendw(&ab, READ16LE("\r\n")); x = 0; } } ab.len -= 2; abAppends(&ab, "\n"); linenoiseWriteStr(l->ofd, ab.b); linenoiseRefreshLine(l); abFree(&ab); free(p); } } else { goto HandleRead; } } } } // handle (1) emacs keyboard combos // (2) otherwise sigint exit if (seq[0] == CTRL('C')) { DUFF_ROUTINE_READ(3); if (rc == 1) { switch (seq[0]) { CASE(CTRL('C'), linenoiseEditInterrupt(l)); CASE(CTRL('B'), linenoiseEditBarf(l)); CASE(CTRL('S'), linenoiseEditSlurp(l)); default: goto HandleRead; } continue; } else { goto HandleRead; } } // handle (1) unpausing terminal after ctrl-s // (2) otherwise raw keystroke inserts if (seq[0] == CTRL('Q')) { if (ispaused) { linenoiseUnpause(l->ofd); } else { DUFF_ROUTINE_READ(4); if (rc > 0) { char esc[sizeof(seq) * 4]; size_t m = linenoiseEscape(esc, seq, rc); linenoiseEditInsert(l, esc, m); } else { goto HandleRead; } } continue; } // handle enter key if (seq[0] == '\r') { char *p; l->final = 1; free(history[--historylen]); history[historylen] = 0; linenoiseEditEnd(l); linenoiseRefreshLineForce(l); p = strdup(l->buf); linenoiseReset(l); if (p) { *obuf = p; l->state = DUFF_ROUTINE_START; return l->len; } else { return -1; } DUFF_ROUTINE_LABEL(DUFF_ROUTINE_START); linenoiseHistoryAdd(""); continue; } // handle keystrokes that don't need read() 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('\\'), linenoiseEditQuit(l)); CASE(CTRL('S'), linenoiseEditPause(l)); CASE(CTRL('?'), linenoiseEditRubout(l)); CASE(CTRL('H'), linenoiseEditRubout(l)); CASE(CTRL('L'), linenoiseEditRefresh(l)); CASE(CTRL('Z'), linenoiseEditSuspend(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; linenoiseReset(l); *obuf = 0; return 0; } break; case '\e': // handle ansi escape if (rc < 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('B'), linenoiseEditLeftExpr(l)); CASE(CTRL('F'), linenoiseEditRightExpr(l)); CASE(CTRL('H'), linenoiseEditRuboutWord(l)); case '[': // handle ansi csi sequences if (rc < 3) break; if (seq[2] >= '0' && seq[2] <= '9') { if (rc < 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 if (rc == 6 && seq[2] == '1' && seq[3] == ';' && seq[4] == '5') { switch (seq[5]) { CASE('C', linenoiseEditRightWord(l)); // \e[1;5C ctrl-right CASE('D', linenoiseEditLeftWord(l)); // \e[1;5D ctrl-left 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 (rc < 3) break; 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 '\e': if (rc < 3) break; switch (seq[2]) { case '[': if (rc < 4) break; switch (seq[3]) { CASE('C', linenoiseEditRightExpr(l)); // \e\e[C alt-right CASE('D', linenoiseEditLeftExpr(l)); // \e\e[D alt-left default: break; } break; case 'O': if (rc < 4) break; switch (seq[3]) { CASE('C', linenoiseEditRightExpr(l)); // \e\eOC alt-right CASE('D', linenoiseEditLeftExpr(l)); // \e\eOD alt-left default: break; } break; default: break; } break; default: break; } break; default: // handle normal keystrokes if (!iswcntrl(seq[0])) { // only sees canonical c0 if (xlatCallback) { uint64_t w; struct rune rune; rune = GetUtf8(seq, rc); w = tpenc(xlatCallback(rune.c)); rc = 0; do { seq[rc++] = w; } while ((w >>= 8)); } linenoiseEditInsert(l, seq, rc); } break; } } default: __builtin_unreachable(); } } 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; } 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; if (filename) { 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, t; 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((t = h[k * 2 + 1] - h[k * 2]) + 1))) { memcpy(s, h[k * 2], t), s[t] = 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; } /** * Returns appropriate system config location. * @return path needing free or null if prog is null */ char *linenoiseGetHistoryPath(const char *prog) { struct abuf path; const char *a, *b; if (!prog) return 0; if (strchr(prog, '/') || strchr(prog, '.')) return strdup(prog); abInit(&path); b = ""; if (!(a = getenv("HOME"))) { if (!(a = getenv("HOMEDRIVE")) || !(b = getenv("HOMEPATH"))) { a = ""; } } if (*a) { abAppends(&path, a); abAppends(&path, b); if (!endswith(path.b, "/") && !endswith(path.b, "\\")) { abAppendw(&path, '/'); } } abAppendw(&path, '.'); abAppends(&path, prog); abAppends(&path, "_history"); return path.b; } /** * 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; struct sigaction sa[3]; struct linenoiseState *l; if (linenoiseEnableRawMode(infd) == -1) return 0; sigemptyset(&sa->sa_mask); sa->sa_flags = SA_NODEFER; sa->sa_handler = linenoiseOnInt; sigaction(SIGINT, sa, sa + 1); sigaction(SIGQUIT, sa, sa + 2); l = linenoiseBegin(prompt, infd, outfd); rc = linenoiseEdit(l, 0, &buf, true); linenoiseEnd(l); linenoiseDisableRawMode(); sigaction(SIGQUIT, sa + 2, 0); sigaction(SIGINT, sa + 1, 0); if (gotint) { if (rc != -1) { free(buf); } raise(gotint); errno = EINTR; gotint = 0; rc = -1; } if (rc != -1) { if (buf) { linenoiseWriteStr(outfd, "\n"); } return buf; } else { return 0; } } static int linenoiseFallback(const char *prompt, char **res) { if (prompt && *prompt && (strchr(prompt, '\n') || strchr(prompt, '\t') || strchr(prompt + 1, '\r'))) { errno = EINVAL; *res = 0; return 1; } if (!linenoiseIsTerminal()) { if (prompt && *prompt && linenoiseIsTeletype()) { fputs(prompt, stdout); fflush(stdout); } *res = linenoiseGetLine(stdin); return 1; } else { 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) { char *res; bool rm, rs; if (linenoiseFallback(prompt, &res)) return res; fflush(stdout); fflush(stdout); rm = __ttyconf.replmode; rs = __ttyconf.replstderr; __ttyconf.replmode = true; if (isatty(2)) __ttyconf.replstderr = true; res = linenoiseRaw(prompt, fileno(stdin), fileno(stdout)); __ttyconf.replstderr = rs; __ttyconf.replmode = rm; return res; } /** * 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 * noting that on eof your errno is not changed */ char *linenoiseWithHistory(const char *prompt, const char *prog) { char *path, *line, *res; if (linenoiseFallback(prompt, &res)) return res; fflush(stdout); if ((path = linenoiseGetHistoryPath(prog))) { if (linenoiseHistoryLoad(path) == -1) { fprintf(stderr, "%s: failed to load history: %m\n", path); free(path); path = 0; } } line = linenoise(prompt); if (path && 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); linenoiseHistoryAdd(line); linenoiseHistorySave(path); } free(path); return line; } /** * Returns 0 otherwise SIGINT or SIGQUIT if interrupt was received. */ int linenoiseGetInterrupt(void) { return gotint; } /** * 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; } /** * Sets character translation callback. */ void linenoiseSetXlatCallback(linenoiseXlatCallback *fn) { xlatCallback = 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 . 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; if (lc->cvec) { for (i = 0; i < lc->len; i++) { free(lc->cvec[i]); } free(lc->cvec); } lc->cvec = 0; lc->len = 0; } /** * 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; } static void linenoiseAtExit(void) { linenoiseDisableRawMode(); linenoiseHistoryFree(); linenoiseRingFree(); } __attribute__((__constructor__(99))) static textstartup void linenoiseInit() { atexit(linenoiseAtExit); }