mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-07 15:03:34 +00:00
This change reinvents all the GNU Readline features I discovered that I couldn't live without, e.g. UTF-8, CTRL-R search and CTRL-Y yanking. It now feels just as good in terms of user interface from the subconscious workflow perspective. It's real nice to finally have an embeddable line reader that's actually good with a 30 kb footprint and a bsd-2 license. This change adds a directory to the examples folder, explaining how the new Python compiler may be used. Some of the bugs with Python binaries have been addressed but overall it's still a work in progress.
1792 lines
54 KiB
C
1792 lines
54 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 │
|
|
│ - 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 │
|
|
│ - 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 = ezlinenoise("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/mem/mem.h"
|
|
#include "libc/nexgen32e/bsr.h"
|
|
#include "libc/nexgen32e/rdtsc.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 "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_MAX_LINE 4096
|
|
|
|
#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;
|
|
};
|
|
|
|
struct rune {
|
|
unsigned c;
|
|
unsigned n;
|
|
};
|
|
|
|
struct linenoiseRing {
|
|
unsigned i;
|
|
char *p[LINENOISE_MAX_RING];
|
|
};
|
|
|
|
/* The linenoiseState structure represents the state during line editing.
|
|
* We pass this state to functions implementing specific editing
|
|
* functionalities. */
|
|
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 */
|
|
unsigned buflen; /* Edited line buffer size */
|
|
unsigned pos; /* Current cursor position */
|
|
unsigned oldpos; /* Previous refresh cursor position */
|
|
unsigned len; /* Current edited line length */
|
|
unsigned maxrows; /* Maximum num of rows used so far */
|
|
unsigned mark; /* Saved cursor position */
|
|
unsigned yi, yj; /* Boundaries of last yank */
|
|
unsigned debugrow; /* Debug log row offset */
|
|
char seq[2][16]; /* Keystroke history for yanking code */
|
|
};
|
|
|
|
static const char *const kUnsupported[] = {"dumb", "cons25", "emacs"};
|
|
|
|
static jmp_buf jraw;
|
|
static char rawmode;
|
|
static char gotcont;
|
|
static char gotwinch;
|
|
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 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 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) {
|
|
for (n -= m; n; --n) {
|
|
for (i = 0; i < m; ++i) {
|
|
if (p[n + i] != q[i]) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == m) {
|
|
return p + 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 size_t GetMonospaceWidth(const char *p, size_t n) {
|
|
int c;
|
|
size_t i, w;
|
|
struct rune r;
|
|
enum { kAscii, kUtf8, kEsc, kCsi1, kCsi2, kSs, kNf, kStr, kStr2 } t;
|
|
for (r.c = 0, r.n = 0, t = kAscii, 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:
|
|
w += wcwidth(r.c);
|
|
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);
|
|
}
|
|
}
|
|
return w;
|
|
}
|
|
|
|
static void abInit(struct abuf *ab) {
|
|
ab->b = malloc(1);
|
|
ab->len = 0;
|
|
ab->b[0] = 0;
|
|
}
|
|
|
|
static void abAppend(struct abuf *ab, const char *s, int len) {
|
|
char *p;
|
|
if (!(p = realloc(ab->b, ab->len + len + 1))) return;
|
|
memcpy(p + ab->len, s, len);
|
|
p[ab->len + len] = 0;
|
|
ab->b = p;
|
|
ab->len += len;
|
|
}
|
|
|
|
static void abAppends(struct abuf *ab, const char *s) {
|
|
abAppend(ab, s, strlen(s));
|
|
}
|
|
|
|
static void abAppendu(struct abuf *ab, unsigned u) {
|
|
char b[11];
|
|
abAppend(ab, b, FormatUnsigned(b, u) - b);
|
|
}
|
|
|
|
static void abAppendw(struct abuf *ab, unsigned long long w) {
|
|
char b[8];
|
|
unsigned n = 0;
|
|
do b[n++] = w;
|
|
while ((w >>= 8));
|
|
abAppend(ab, b, n);
|
|
}
|
|
|
|
static void abFree(struct abuf *ab) {
|
|
free(ab->b);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
void linenoiseMaskModeEnable(void) {
|
|
maskmode = 1;
|
|
}
|
|
|
|
void linenoiseMaskModeDisable(void) {
|
|
maskmode = 0;
|
|
}
|
|
|
|
static void linenoiseOnCont(int sig) {
|
|
gotcont = 1;
|
|
}
|
|
|
|
static void linenoiseOnWinch(int sig) {
|
|
gotwinch = 1;
|
|
}
|
|
|
|
static void linenoiseOnInt(int sig) {
|
|
longjmp(jraw, sig);
|
|
}
|
|
|
|
static void linenoiseOnQuit(int sig) {
|
|
longjmp(jraw, sig);
|
|
}
|
|
|
|
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 (;;) {
|
|
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));
|
|
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 (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 */
|
|
}
|
|
|
|
/* Free a 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);
|
|
}
|
|
|
|
/* 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 int linenoiseCompleteLine(struct linenoiseState *ls, char *seq,
|
|
int size) {
|
|
size_t i, n, stop;
|
|
int nwritten, nread;
|
|
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);
|
|
}
|
|
nread = linenoiseRead(ls->ifd, seq, size, ls);
|
|
if (nread <= 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]);
|
|
nwritten = MIN(n, ls->buflen);
|
|
memcpy(ls->buf, lc.cvec[i], nwritten + 1);
|
|
ls->len = ls->pos = nwritten;
|
|
}
|
|
stop = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
linenoiseFreeCompletions(&lc);
|
|
return nread;
|
|
}
|
|
|
|
static void linenoiseEditHistoryGoto(struct linenoiseState *l, int i) {
|
|
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;
|
|
if (!memccpy(l->buf, history[historylen - 1 - l->hindex], 0, l->buflen)) {
|
|
l->buf[l->buflen - 1] = 0;
|
|
}
|
|
l->len = l->pos = strlen(l->buf);
|
|
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 = MIN(k, j + ab.len);
|
|
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 = LINENOISE_MAX_LINE;
|
|
}
|
|
}
|
|
}
|
|
l->prompt = oldprompt;
|
|
linenoiseRefreshLine(l);
|
|
abFree(&prompt);
|
|
abFree(&ab);
|
|
linenoiseRefreshLine(l);
|
|
return rc;
|
|
}
|
|
|
|
/* Register a callback function to be called for tab-completion. */
|
|
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
|
|
completionCallback = fn;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Register a function to free the hints returned by the hints callback
|
|
* registered with linenoiseSetHintsCallback(). */
|
|
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) {
|
|
freeHintsCallback = fn;
|
|
}
|
|
|
|
/* 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 =
|
|
(char **)realloc(lc->cvec, (lc->len + 1) * sizeof(*lc->cvec)))) {
|
|
lc->cvec = cvec;
|
|
lc->cvec[lc->len++] = copy;
|
|
} else {
|
|
free(copy);
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
if (LINENOISE_MAX_RING && n) {
|
|
ring.i = (ring.i + 1) % LINENOISE_MAX_RING;
|
|
free(ring.p[ring.i]);
|
|
ring.p[ring.i] = strndup(p, n);
|
|
}
|
|
}
|
|
|
|
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 void linenoiseRefreshHints(struct abuf *ab, struct linenoiseState *l) {
|
|
char *hint;
|
|
const char *ansi1, *ansi2;
|
|
if (!hintsCallback) return;
|
|
ansi1 = "\033[90m";
|
|
ansi2 = "\033[39m";
|
|
if (!(hint = hintsCallback(l->buf, &ansi1, &ansi2))) return;
|
|
if (ansi1) abAppends(ab, ansi1);
|
|
abAppends(ab, hint);
|
|
if (ansi2) abAppends(ab, ansi2);
|
|
if (freeHintsCallback) freeHintsCallback(hint);
|
|
}
|
|
|
|
static void linenoiseRefreshLine(struct linenoiseState *l) {
|
|
struct abuf ab;
|
|
int i, j, fd, plen, pwidth, rows, rpos, rpos2, col, old_rows;
|
|
if (gotwinch && rawmode != -1) {
|
|
l->ws = GetTerminalSize(l->ws, l->ifd, l->ofd);
|
|
gotwinch = 0;
|
|
}
|
|
fd = l->ofd;
|
|
old_rows = l->maxrows;
|
|
plen = strlen(l->prompt);
|
|
pwidth = GetMonospaceWidth(l->prompt, plen);
|
|
/* cursor relative row */
|
|
rpos = (pwidth + l->oldpos + l->ws.ws_col) / l->ws.ws_col;
|
|
/* rows used by current buf */
|
|
rows = (pwidth + GetMonospaceWidth(l->buf, l->len) + l->ws.ws_col - 1) /
|
|
l->ws.ws_col;
|
|
if (rows > (int)l->maxrows) l->maxrows = rows;
|
|
/* First step: clear all the lines used before.
|
|
* To do so start by going to the last row. */
|
|
abInit(&ab);
|
|
if (old_rows - rpos > 0) {
|
|
abAppendw(&ab, READ32LE("\033[\0"));
|
|
abAppendu(&ab, old_rows - rpos);
|
|
abAppendw(&ab, 'B');
|
|
}
|
|
/* Now for every row clear it, go up. */
|
|
for (j = 0; j < old_rows - 1; j++) {
|
|
abAppends(&ab, "\r\033[K\033[A");
|
|
}
|
|
abAppendw(&ab, READ32LE("\r\033[K"));
|
|
abAppends(&ab, l->prompt);
|
|
if (maskmode) {
|
|
for (i = 0; i < l->len; i++) {
|
|
abAppendw(&ab, '*');
|
|
}
|
|
} else {
|
|
abAppend(&ab, l->buf, l->len);
|
|
}
|
|
linenoiseRefreshHints(&ab, l);
|
|
/* 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 ((l->pos && l->pos == l->len &&
|
|
!((pwidth + GetMonospaceWidth(l->buf, l->pos)) % l->ws.ws_col))) {
|
|
abAppendw(&ab, READ32LE("\n\r\0"));
|
|
if (++rows > (int)l->maxrows) {
|
|
l->maxrows = rows;
|
|
}
|
|
}
|
|
/* move cursor to right position */
|
|
/* get current cursor relative row */
|
|
rpos2 = (pwidth + GetMonospaceWidth(l->buf, l->pos) + l->ws.ws_col) /
|
|
l->ws.ws_col;
|
|
/* go up till we reach expected positon */
|
|
if (rows - rpos2 > 0) {
|
|
abAppendw(&ab, READ32LE("\033[\0"));
|
|
abAppendu(&ab, rows - rpos2);
|
|
abAppendw(&ab, 'A');
|
|
}
|
|
/* set column */
|
|
col = (pwidth + (int)GetMonospaceWidth(l->buf, l->pos)) % (int)l->ws.ws_col;
|
|
if (col) {
|
|
abAppendw(&ab, READ32LE("\r\033["));
|
|
abAppendu(&ab, col);
|
|
abAppendw(&ab, 'C');
|
|
} else {
|
|
abAppendw(&ab, '\r');
|
|
}
|
|
l->oldpos = l->pos;
|
|
linenoiseWrite(fd, ab.b, ab.len);
|
|
abFree(&ab);
|
|
}
|
|
|
|
static int linenoiseEditInsert(struct linenoiseState *l, const char *p,
|
|
size_t n) {
|
|
char d;
|
|
if (l->len + n < l->buflen) {
|
|
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);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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 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 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->len);
|
|
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 && i + ab.len + l->len - j < l->buflen) {
|
|
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]);
|
|
if (l->len + n < l->buflen) {
|
|
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);
|
|
l->yi = l->pos;
|
|
l->yj = l->pos + n;
|
|
l->pos += n;
|
|
l->len += n;
|
|
free(p);
|
|
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, char *buf,
|
|
size_t buflen, const char *prompt) {
|
|
ssize_t rc;
|
|
size_t nread;
|
|
char seq[16];
|
|
struct linenoiseState l;
|
|
linenoiseHintsCallback *hc;
|
|
memset(&l, 0, sizeof(l));
|
|
buf[0] = 0;
|
|
l.buf = buf;
|
|
l.ifd = stdin_fd;
|
|
l.ofd = stdout_fd;
|
|
l.buflen = buflen - 1;
|
|
l.prompt = prompt ? prompt : "";
|
|
l.ws = GetTerminalSize(l.ws, l.ifd, l.ofd);
|
|
linenoiseHistoryAdd("");
|
|
linenoiseWriteStr(l.ofd, l.prompt);
|
|
while (1) {
|
|
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;
|
|
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;
|
|
return -1;
|
|
}
|
|
break;
|
|
case '\r':
|
|
free(history[--historylen]);
|
|
history[historylen] = 0;
|
|
linenoiseEditEnd(&l);
|
|
if (hintsCallback) {
|
|
/* Force a refresh without hints to leave the previous
|
|
* line as the user typed it after a newline. */
|
|
hc = hintsCallback;
|
|
hintsCallback = 0;
|
|
linenoiseRefreshLine(&l);
|
|
hintsCallback = hc;
|
|
}
|
|
DEBUG(&l, "%`'s", buf);
|
|
return l.len;
|
|
case 033:
|
|
if (nread < 2) break;
|
|
switch (seq[1]) {
|
|
CASE('<', linenoiseEditBof(&l));
|
|
CASE('>', linenoiseEditEof(&l));
|
|
CASE('y', linenoiseEditRotate(&l));
|
|
CASE('\\', linenoiseEditSqueeze(&l));
|
|
CASE('b', linenoiseEditLeftWord(&l));
|
|
CASE('f', linenoiseEditRightWord(&l));
|
|
CASE('h', linenoiseEditRuboutWord(&l));
|
|
CASE('d', linenoiseEditDeleteWord(&l));
|
|
CASE('l', linenoiseEditLowercaseWord(&l));
|
|
CASE('u', linenoiseEditUppercaseWord(&l));
|
|
CASE('c', linenoiseEditCapitalizeWord(&l));
|
|
CASE('t', linenoiseEditTransposeWords(&l));
|
|
CASE(CTRL('H'), linenoiseEditRuboutWord(&l));
|
|
case '[':
|
|
if (nread < 3) break;
|
|
if (seq[2] >= '0' && seq[2] <= '9') {
|
|
if (nread < 4) break;
|
|
if (seq[3] == '~') {
|
|
switch (seq[2]) {
|
|
CASE('1', linenoiseEditHome(&l)); /* \e[1~ */
|
|
CASE('3', linenoiseEditDelete(&l)); /* \e[3~ */
|
|
CASE('4', linenoiseEditEnd(&l)); /* \e[4~ */
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
switch (seq[2]) {
|
|
CASE('A', linenoiseEditUp(&l));
|
|
CASE('B', linenoiseEditDown(&l));
|
|
CASE('C', linenoiseEditRight(&l));
|
|
CASE('D', linenoiseEditLeft(&l));
|
|
CASE('H', linenoiseEditHome(&l));
|
|
CASE('F', linenoiseEditEnd(&l));
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'O':
|
|
if (nread < 3) break;
|
|
switch (seq[2]) {
|
|
CASE('H', linenoiseEditHome(&l));
|
|
CASE('F', linenoiseEditEnd(&l));
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
if (!iswcntrl(seq[0])) { /* only sees canonical c0 */
|
|
linenoiseEditInsert(&l, seq, nread);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void linenoiseFree(void *ptr) {
|
|
free(ptr);
|
|
}
|
|
|
|
void linenoiseHistoryFree(void) {
|
|
size_t i;
|
|
for (i = 0; i < LINENOISE_MAX_HISTORY; i++) {
|
|
if (history[i]) {
|
|
free(history[i]);
|
|
history[i] = 0;
|
|
}
|
|
}
|
|
historylen = 0;
|
|
}
|
|
|
|
static void linenoiseAtExit(void) {
|
|
linenoiseDisableRawMode();
|
|
linenoiseHistoryFree();
|
|
linenoiseRingFree();
|
|
}
|
|
|
|
int linenoiseHistoryAdd(const char *line) {
|
|
char *linecopy;
|
|
if (!LINENOISE_MAX_HISTORY) return 0;
|
|
if (historylen && !strcmp(history[historylen - 1], line)) return 0;
|
|
if (!(linecopy = strdup(line))) return 0;
|
|
if (historylen == LINENOISE_MAX_HISTORY) {
|
|
free(history[0]);
|
|
memmove(history, history + 1, sizeof(char *) * (LINENOISE_MAX_HISTORY - 1));
|
|
historylen--;
|
|
}
|
|
history[historylen++] = linecopy;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Saves line editing history to file.
|
|
*
|
|
* @return 0 on success, or -1 w/ errno
|
|
*/
|
|
int linenoiseHistorySave(const char *filename) {
|
|
int j;
|
|
FILE *fp;
|
|
mode_t old_umask;
|
|
old_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
|
|
fp = fopen(filename, "w");
|
|
umask(old_umask);
|
|
if (!fp) return -1;
|
|
chmod(filename, S_IRUSR | S_IWUSR);
|
|
for (j = 0; j < historylen; j++) {
|
|
fputs(history[j], fp);
|
|
fputc('\n', fp);
|
|
}
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Loads history from the specified file.
|
|
*
|
|
* If the file doesn't exist, zero is returned and this will do nothing.
|
|
* If the file does exists and the operation succeeded zero is returned
|
|
* otherwise on error -1 is returned.
|
|
*
|
|
* @return 0 on success, or -1 w/ errno
|
|
*/
|
|
int linenoiseHistoryLoad(const char *filename) {
|
|
char **h;
|
|
int rc, fd, err;
|
|
size_t i, j, k, n;
|
|
char *m, *e, *p, *q, *f, *s;
|
|
err = errno, rc = 0;
|
|
if (!LINENOISE_MAX_HISTORY) return 0;
|
|
if (!(h = (char **)calloc(2 * LINENOISE_MAX_HISTORY, sizeof(char *))))
|
|
return -1;
|
|
if ((fd = open(filename, O_RDONLY)) != -1) {
|
|
if ((n = GetFdSize(fd))) {
|
|
if ((m = (char *)mmap(0, n, PROT_READ, MAP_SHARED, fd, 0)) !=
|
|
MAP_FAILED) {
|
|
for (i = 0, e = (p = m) + n; p < e; p = f + 1) {
|
|
if (!(q = (char *)memchr(p, '\n', e - p))) q = e;
|
|
for (f = q; q > p; --q) {
|
|
if (q[-1] != '\n' && q[-1] != '\r') break;
|
|
}
|
|
if (q > p) {
|
|
h[i * 2 + 0] = p;
|
|
h[i * 2 + 1] = q;
|
|
i = (i + 1) % LINENOISE_MAX_HISTORY;
|
|
}
|
|
}
|
|
linenoiseHistoryFree();
|
|
for (j = 0; j < LINENOISE_MAX_HISTORY; ++j) {
|
|
if (h[(k = (i + j) % LINENOISE_MAX_HISTORY) * 2]) {
|
|
if ((s = malloc((n = h[k * 2 + 1] - h[k * 2]) + 1))) {
|
|
memcpy(s, h[k * 2], n), s[n] = 0;
|
|
history[historylen++] = s;
|
|
}
|
|
}
|
|
}
|
|
munmap(m, n);
|
|
} else {
|
|
rc = -1;
|
|
}
|
|
}
|
|
close(fd);
|
|
} else if (errno == ENOENT) {
|
|
errno = err;
|
|
} else {
|
|
rc = -1;
|
|
}
|
|
free(h);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Reads line interactively.
|
|
*
|
|
* This function can be used instead of linenoise() in cases where we
|
|
* know for certain we're dealing with a terminal, which means we can
|
|
* avoid linking any stdio code.
|
|
*
|
|
* @return chomped allocated string of read line or null on eof/error
|
|
*/
|
|
char *linenoiseRaw(const char *prompt, int infd, int outfd) {
|
|
int sig;
|
|
ssize_t rc;
|
|
size_t nread;
|
|
char *buf, *p;
|
|
sigset_t omask;
|
|
static char once;
|
|
struct sigaction sa;
|
|
if (!once) atexit(linenoiseAtExit), once = 1;
|
|
if (!(buf = malloc(LINENOISE_MAX_LINE))) return 0;
|
|
if (enableRawMode(infd) == -1) return 0;
|
|
sigemptyset(&sa.sa_mask);
|
|
sigaddset(&sa.sa_mask, SIGINT);
|
|
sigaddset(&sa.sa_mask, SIGQUIT);
|
|
sigprocmask(SIG_BLOCK, &sa.sa_mask, &omask);
|
|
sa.sa_flags = SA_NODEFER;
|
|
sa.sa_handler = linenoiseOnInt;
|
|
sigaction(SIGINT, &sa, &orig_int);
|
|
sa.sa_handler = linenoiseOnQuit;
|
|
sigaction(SIGQUIT, &sa, &orig_quit);
|
|
if (!(sig = setjmp(jraw))) {
|
|
sigprocmask(SIG_UNBLOCK, &sa.sa_mask, 0);
|
|
rc = linenoiseEdit(infd, outfd, buf, LINENOISE_MAX_LINE, prompt);
|
|
} else {
|
|
rc = -1;
|
|
}
|
|
linenoiseDisableRawMode();
|
|
sigaction(SIGINT, &orig_int, 0);
|
|
sigaction(SIGQUIT, &orig_quit, 0);
|
|
sigprocmask(SIG_SETMASK, &omask, 0);
|
|
if (sig) raise(sig);
|
|
if (rc != -1) {
|
|
nread = rc;
|
|
linenoiseWriteStr(outfd, "\n");
|
|
if ((p = realloc(buf, nread + 1))) buf = p;
|
|
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 ((!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 = ezlinenoise("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 *ezlinenoise(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;
|
|
}
|