cosmopolitan/third_party/linenoise/linenoise.c
Justine Tunney 47a53e143b Productionize new APE loader and more
The APE_NO_MODIFY_SELF loader payload has been moved out of the examples
folder and improved so that it works on BSD systems, and permits general
elf program headers. This brings its quality up enough that it should be
acceptable to use by default for many programs, e.g. Python, Lua, SQLite
and Python. It's the responsibility of the user to define an appropriate
TMPDIR if /tmp is considered an adversarial environment. Mac OS shall be
supported by APE_NO_MODIFY_SELF soon.

Fixes and improvements have been made to program_executable_name as it's
now the one true way to get the absolute path of the executing image.

This change fixes a memory leak in linenoise history loading, introduced
by performance optimizations in 51904e2687
This change fixes a longstanding regression with Mach system calls, that
23ae9dfceb back in February which impacted
our sched_yield() implementation, which is why no one noticed until now.

The Blinkenlights PC emulator has been improved. We now fix rendering on
XNU and BSD by not making the assumption that the kernel terminal driver
understands UTF8 since that seems to break its internal modeling of \r\n
which is now being addressed by using \e[𝑦H instead. The paneling is now
more compact in real mode so you won't need to make your font as tiny if
you're only emulating an 8086 program. The CLMUL ISA is now emulated too

This change also makes improvement to time. CLOCK_MONOTONIC now does the
right thing on Windows NT. The nanosecond time module functions added in
Python 3.7 have been backported.

This change doubles the performance of Argon2 password stretching simply
by not using its copy_block and xor_block helper functions, as they were
trivial to inline thus resulting in us needing to iterate over each 1024
byte block four fewer times.

This change makes code size improvements. _PyUnicode_ToNumeric() was 64k
in size and now it's 10k. The CJK codec lookup tables now use lazy delta
zigzag deflate (δzd) encoding which reduces their size from 600k to 200k
plus the code bloat caused by macro abuse in _decimal.c is now addressed
so our fully-loaded statically-linked hermetically-sealed Python virtual
interpreter container is now 9.4 megs in the default build mode and 5.5m
in MODE=tiny which leaves plenty of room for chibicc.

The pydoc web server now accommodates the use case of people who work by
SSH'ing into a different machine w/ python.com -m pydoc -p8080 -h0.0.0.0

Finally Python Capsulae delenda est and won't be supported in the future
2021-10-02 08:27:03 -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, 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;
}
/**
* 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;
}