From b32a3f2496ce796db262f819cffbdf82808abc31 Mon Sep 17 00:00:00 2001 From: Simon Forman Date: Mon, 20 Feb 2023 10:20:40 -0800 Subject: [PATCH] Line editing with linenoise. https://github.com/antirez/linenoise https://todo.sr.ht/~sforman/thun-der/33 --- archive/linenoise-master.zip | Bin 0 -> 20296 bytes implementations/C/Makefile | 5 +- implementations/C/joy.c | 25 +- implementations/C/linenoise.c | 1225 +++++++++++++++++++++++++++++++++ implementations/C/linenoise.h | 75 ++ 5 files changed, 1309 insertions(+), 21 deletions(-) create mode 100644 archive/linenoise-master.zip create mode 100644 implementations/C/linenoise.c create mode 100644 implementations/C/linenoise.h diff --git a/archive/linenoise-master.zip b/archive/linenoise-master.zip new file mode 100644 index 0000000000000000000000000000000000000000..6bfbd2574613af2ef9424a45b83f21cf4b4f9815 GIT binary patch literal 20296 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. #include "joy.h" #include "definitions.h" #include "uthash.h" +#include "linenoise.h" static jmp_buf jbuf; @@ -924,7 +925,6 @@ int main(int argc, char *argv[]) { char *line; - char *status; JoyList stack = EMPTY_LIST; JoyList expression = EMPTY_LIST; JoyList s; @@ -939,29 +939,13 @@ main(int argc, char *argv[]) quiet = ((2 == argc) && (!strcmp("-q", argv[1]))); - line = (char *)GC_malloc(1025); - while (1) { - SHH("joy? ") - status = gets_s(line, 1025); - if (NULL == status) { - /* - From the man page: - - > Upon successful completion, fgets(), gets_s(), and gets() return a - pointer to the string. If end-of-file occurs before any characters are - read, they return NULL and the buffer contents remain unchanged. If an - error occurs, they return NULL and the buffer contents are indeterminate. - The fgets(), gets_s(), and gets() functions do not distinguish between - end-of-file and error, and callers must use feof(3) and ferror(3) to - determine which occurred. - - TODO: "use feof(3) and ferror(3)"... - - */ + line = linenoise(quiet? "" : "joy? "); + if (NULL == line) { SHH("\n") break; } + linenoiseHistoryAdd(line); s = stack; if (!setjmp(jbuf)) { expression = text_to_expression(line); @@ -970,6 +954,7 @@ main(int argc, char *argv[]) /* err */ stack = s; } + free(line); print_stack(stack); printf("\n"); } diff --git a/implementations/C/linenoise.c b/implementations/C/linenoise.c new file mode 100644 index 0000000..cfe51e7 --- /dev/null +++ b/implementations/C/linenoise.c @@ -0,0 +1,1225 @@ +/* linenoise.c -- guerrilla line editing library against the idea that a + * line editing lib needs to be 20,000 lines of C code. + * + * You can find the latest source code at: + * + * http://github.com/antirez/linenoise + * + * Does a number of crazy assumptions that happen to be true in 99.9999% of + * the 2010 UNIX computers around. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010-2016, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ------------------------------------------------------------------------ + * + * References: + * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html + * + * Todo list: + * - Filter bogus Ctrl+ combinations. + * - Win32 support + * + * Bloat: + * - History search like Ctrl+r in readline? + * + * List of escape sequences used by this program, we do everything just + * with three sequences. In order to be so cheap we may have some + * flickering effect with some slow terminal, but the lesser sequences + * the more compatible. + * + * EL (Erase Line) + * Sequence: ESC [ n K + * Effect: if n is 0 or missing, clear from cursor to end of line + * Effect: if n is 1, clear from beginning of line to cursor + * Effect: if n is 2, clear entire line + * + * CUF (CUrsor Forward) + * Sequence: ESC [ n C + * Effect: moves cursor forward n chars + * + * CUB (CUrsor Backward) + * Sequence: ESC [ n D + * Effect: moves cursor backward n chars + * + * The following is used to get the terminal width if getting + * the width with the TIOCGWINSZ ioctl fails + * + * DSR (Device Status Report) + * Sequence: ESC [ 6 n + * Effect: reports the current cusor position as ESC [ n ; m R + * where n is the row and m is the column + * + * When multi line mode is enabled, we also use an additional escape + * sequence. However multi line editing is disabled by default. + * + * CUU (Cursor Up) + * Sequence: ESC [ n A + * Effect: moves cursor up of n chars. + * + * CUD (Cursor Down) + * Sequence: ESC [ n B + * Effect: moves cursor down of n chars. + * + * When linenoiseClearScreen() is called, two additional escape sequences + * are used in order to clear the screen and position the cursor at home + * position. + * + * CUP (Cursor position) + * Sequence: ESC [ H + * Effect: moves the cursor to upper left corner + * + * ED (Erase display) + * Sequence: ESC [ 2 J + * Effect: clear the whole screen + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "linenoise.h" + +#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 +#define LINENOISE_MAX_LINE 4096 +static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; +static linenoiseCompletionCallback *completionCallback = NULL; +static linenoiseHintsCallback *hintsCallback = NULL; +static linenoiseFreeHintsCallback *freeHintsCallback = NULL; + +static struct termios orig_termios; /* In order to restore at exit.*/ +static int maskmode = 0; /* Show "***" instead of input. For passwords. */ +static int rawmode = 0; /* For atexit() function to check if restore is needed*/ +static int mlmode = 0; /* Multi line mode. Default is single line. */ +static int atexit_registered = 0; /* Register atexit just 1 time. */ +static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; +static int history_len = 0; +static char **history = NULL; + +/* 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. */ + char *buf; /* Edited line buffer. */ + size_t buflen; /* Edited line buffer size. */ + const char *prompt; /* Prompt to display. */ + size_t plen; /* Prompt length. */ + size_t pos; /* Current cursor position. */ + size_t oldpos; /* Previous refresh cursor position. */ + size_t len; /* Current edited line length. */ + size_t cols; /* Number of columns in terminal. */ + size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ + int history_index; /* The history index we are currently editing. */ +}; + +enum KEY_ACTION{ + KEY_NULL = 0, /* NULL */ + CTRL_A = 1, /* Ctrl+a */ + CTRL_B = 2, /* Ctrl-b */ + CTRL_C = 3, /* Ctrl-c */ + CTRL_D = 4, /* Ctrl-d */ + CTRL_E = 5, /* Ctrl-e */ + CTRL_F = 6, /* Ctrl-f */ + CTRL_H = 8, /* Ctrl-h */ + TAB = 9, /* Tab */ + CTRL_K = 11, /* Ctrl+k */ + CTRL_L = 12, /* Ctrl+l */ + ENTER = 13, /* Enter */ + CTRL_N = 14, /* Ctrl-n */ + CTRL_P = 16, /* Ctrl-p */ + CTRL_T = 20, /* Ctrl-t */ + CTRL_U = 21, /* Ctrl+u */ + CTRL_W = 23, /* Ctrl+w */ + ESC = 27, /* Escape */ + BACKSPACE = 127 /* Backspace */ +}; + +static void linenoiseAtExit(void); +int linenoiseHistoryAdd(const char *line); +static void refreshLine(struct linenoiseState *l); + +/* Debugging macro. */ +#if 0 +FILE *lndebug_fp = NULL; +#define lndebug(...) \ + do { \ + if (lndebug_fp == NULL) { \ + lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ + fprintf(lndebug_fp, \ + "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ + (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ + (int)l->maxrows,old_rows); \ + } \ + fprintf(lndebug_fp, ", " __VA_ARGS__); \ + fflush(lndebug_fp); \ + } while (0) +#else +#define lndebug(fmt, ...) +#endif + +/* ======================= Low level terminal handling ====================== */ + +/* Enable "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; +} + +/* Disable mask mode. */ +void linenoiseMaskModeDisable(void) { + maskmode = 0; +} + +/* Set if to use or not the multi line mode. */ +void linenoiseSetMultiLine(int ml) { + mlmode = ml; +} + +/* Return true if the terminal name is in the list of terminals we know are + * not able to understand basic escape sequences. */ +static int isUnsupportedTerm(void) { + char *term = getenv("TERM"); + int j; + + if (term == NULL) return 0; + for (j = 0; unsupported_term[j]; j++) + if (!strcasecmp(term,unsupported_term[j])) return 1; + return 0; +} + +/* Raw mode: 1960 magic shit. */ +static int enableRawMode(int fd) { + struct termios raw; + + if (!isatty(STDIN_FILENO)) goto fatal; + if (!atexit_registered) { + atexit(linenoiseAtExit); + atexit_registered = 1; + } + if (tcgetattr(fd,&orig_termios) == -1) goto fatal; + + raw = orig_termios; /* modify the original mode */ + /* input modes: no break, no CR to NL, no parity check, no strip char, + * no start/stop output control. */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + /* output modes - disable post processing */ + raw.c_oflag &= ~(OPOST); + /* control modes - set 8 bit chars */ + raw.c_cflag |= (CS8); + /* local modes - choing off, canonical off, no extended functions, + * no signal chars (^Z,^C) */ + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + /* control chars - set return condition: min number of bytes and timer. + * We want read to return every single byte, without timeout. */ + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + + /* put terminal in raw mode after flushing */ + if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; + rawmode = 1; + return 0; + +fatal: + errno = ENOTTY; + return -1; +} + +static void disableRawMode(int fd) { + /* Don't even check the return value as it's too late. */ + if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) + rawmode = 0; +} + +/* Use the ESC [6n escape sequence to query the horizontal cursor position + * and return it. On error -1 is returned, on success the position of the + * cursor. */ +static int getCursorPosition(int ifd, int ofd) { + char buf[32]; + int cols, rows; + unsigned int i = 0; + + /* Report cursor location */ + if (write(ofd, "\x1b[6n", 4) != 4) return -1; + + /* Read the response: ESC [ rows ; cols R */ + while (i < sizeof(buf)-1) { + if (read(ifd,buf+i,1) != 1) break; + if (buf[i] == 'R') break; + i++; + } + buf[i] = '\0'; + + /* Parse it. */ + if (buf[0] != ESC || buf[1] != '[') return -1; + if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; + return cols; +} + +/* Try to get the number of columns in the current terminal, or assume 80 + * if it fails. */ +static int getColumns(int ifd, int ofd) { + struct winsize ws; + + if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { + /* ioctl() failed. Try to query the terminal itself. */ + int start, cols; + + /* Get the initial position so we can restore it later. */ + start = getCursorPosition(ifd,ofd); + if (start == -1) goto failed; + + /* Go to right margin and get position. */ + if (write(ofd,"\x1b[999C",6) != 6) goto failed; + cols = getCursorPosition(ifd,ofd); + if (cols == -1) goto failed; + + /* Restore position. */ + if (cols > start) { + char seq[32]; + snprintf(seq,32,"\x1b[%dD",cols-start); + if (write(ofd,seq,strlen(seq)) == -1) { + /* Can't recover... */ + } + } + return cols; + } else { + return ws.ws_col; + } + +failed: + return 80; +} + +/* Clear the screen. Used to handle ctrl+l */ +void linenoiseClearScreen(void) { + if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { + /* nothing to do, just to avoid warning. */ + } +} + +/* Beep, used for completion when there is nothing to complete or when all + * the choices were already shown. */ +static void linenoiseBeep(void) { + fprintf(stderr, "\x7"); + fflush(stderr); +} + +/* ============================== Completion ================================ */ + +/* Free a list of completion option populated by linenoiseAddCompletion(). */ +static void freeCompletions(linenoiseCompletions *lc) { + size_t i; + for (i = 0; i < lc->len; i++) + free(lc->cvec[i]); + if (lc->cvec != NULL) + free(lc->cvec); +} + +/* This is an helper function for linenoiseEdit() and is called when the + * user types the 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 completeLine(struct linenoiseState *ls) { + linenoiseCompletions lc = { 0, NULL }; + int nread, nwritten; + char c = 0; + + completionCallback(ls->buf,&lc); + if (lc.len == 0) { + linenoiseBeep(); + } else { + size_t stop = 0, i = 0; + + while(!stop) { + /* Show completion or original buffer */ + if (i < lc.len) { + struct linenoiseState saved = *ls; + + ls->len = ls->pos = strlen(lc.cvec[i]); + ls->buf = lc.cvec[i]; + refreshLine(ls); + ls->len = saved.len; + ls->pos = saved.pos; + ls->buf = saved.buf; + } else { + refreshLine(ls); + } + + nread = read(ls->ifd,&c,1); + if (nread <= 0) { + freeCompletions(&lc); + return -1; + } + + switch(c) { + case 9: /* tab */ + i = (i+1) % (lc.len+1); + if (i == lc.len) linenoiseBeep(); + break; + case 27: /* escape */ + /* Re-show original buffer */ + if (i < lc.len) refreshLine(ls); + stop = 1; + break; + default: + /* Update buffer and return */ + if (i < lc.len) { + nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); + ls->len = ls->pos = nwritten; + } + stop = 1; + break; + } + } + } + + freeCompletions(&lc); + return c; /* Return last read character */ +} + +/* 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 . See the example.c source code for a very easy to + * understand example. */ +void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { + size_t len = strlen(str); + char *copy, **cvec; + + copy = malloc(len+1); + if (copy == NULL) return; + memcpy(copy,str,len+1); + cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); + if (cvec == NULL) { + free(copy); + return; + } + lc->cvec = cvec; + lc->cvec[lc->len++] = copy; +} + +/* =========================== Line editing ================================= */ + +/* We define a very simple "append buffer" structure, that is an heap + * allocated string where we can append to. This is useful in order to + * write all the escape sequences in a buffer and flush them to the standard + * output in a single call, to avoid flickering effects. */ +struct abuf { + char *b; + int len; +}; + +static void abInit(struct abuf *ab) { + ab->b = NULL; + ab->len = 0; +} + +static void abAppend(struct abuf *ab, const char *s, int len) { + char *new = realloc(ab->b,ab->len+len); + + if (new == NULL) return; + memcpy(new+ab->len,s,len); + ab->b = new; + ab->len += len; +} + +static void abFree(struct abuf *ab) { + free(ab->b); +} + +/* Helper of refreshSingleLine() and refreshMultiLine() to show hints + * to the right of the prompt. */ +void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { + char seq[64]; + if (hintsCallback && plen+l->len < l->cols) { + int color = -1, bold = 0; + char *hint = hintsCallback(l->buf,&color,&bold); + if (hint) { + int hintlen = strlen(hint); + int hintmaxlen = l->cols-(plen+l->len); + if (hintlen > hintmaxlen) hintlen = hintmaxlen; + if (bold == 1 && color == -1) color = 37; + if (color != -1 || bold != 0) + snprintf(seq,64,"\033[%d;%d;49m",bold,color); + else + seq[0] = '\0'; + abAppend(ab,seq,strlen(seq)); + abAppend(ab,hint,hintlen); + if (color != -1 || bold != 0) + abAppend(ab,"\033[0m",4); + /* Call the function to free the hint returned. */ + if (freeHintsCallback) freeHintsCallback(hint); + } + } +} + +/* Single line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static void refreshSingleLine(struct linenoiseState *l) { + char seq[64]; + size_t plen = strlen(l->prompt); + int fd = l->ofd; + char *buf = l->buf; + size_t len = l->len; + size_t pos = l->pos; + struct abuf ab; + + while((plen+pos) >= l->cols) { + buf++; + len--; + pos--; + } + while (plen+len > l->cols) { + len--; + } + + abInit(&ab); + /* Cursor to left edge */ + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + if (maskmode == 1) { + while (len--) abAppend(&ab,"*",1); + } else { + abAppend(&ab,buf,len); + } + /* Show hits if any. */ + refreshShowHints(&ab,l,plen); + /* Erase to right */ + snprintf(seq,64,"\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); + /* Move cursor to original position. */ + snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); + abAppend(&ab,seq,strlen(seq)); + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ + abFree(&ab); +} + +/* Multi line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static void refreshMultiLine(struct linenoiseState *l) { + char seq[64]; + int plen = strlen(l->prompt); + int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ + int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ + int rpos2; /* rpos after refresh. */ + int col; /* colum position, zero-based. */ + int old_rows = l->maxrows; + int fd = l->ofd, j; + struct abuf ab; + + /* Update maxrows if needed. */ + 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) { + lndebug("go down %d", old_rows-rpos); + snprintf(seq,64,"\x1b[%dB", old_rows-rpos); + abAppend(&ab,seq,strlen(seq)); + } + + /* Now for every row clear it, go up. */ + for (j = 0; j < old_rows-1; j++) { + lndebug("clear+up"); + snprintf(seq,64,"\r\x1b[0K\x1b[1A"); + abAppend(&ab,seq,strlen(seq)); + } + + /* Clean the top line. */ + lndebug("clear"); + snprintf(seq,64,"\r\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); + + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + if (maskmode == 1) { + unsigned int i; + for (i = 0; i < l->len; i++) abAppend(&ab,"*",1); + } else { + abAppend(&ab,l->buf,l->len); + } + + /* Show hits if any. */ + refreshShowHints(&ab,l,plen); + + /* 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 && + (l->pos+plen) % l->cols == 0) + { + lndebug(""); + abAppend(&ab,"\n",1); + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + rows++; + if (rows > (int)l->maxrows) l->maxrows = rows; + } + + /* Move cursor to right position. */ + rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ + lndebug("rpos2 %d", rpos2); + + /* Go up till we reach the expected positon. */ + if (rows-rpos2 > 0) { + lndebug("go-up %d", rows-rpos2); + snprintf(seq,64,"\x1b[%dA", rows-rpos2); + abAppend(&ab,seq,strlen(seq)); + } + + /* Set column. */ + col = (plen+(int)l->pos) % (int)l->cols; + lndebug("set col %d", 1+col); + if (col) + snprintf(seq,64,"\r\x1b[%dC", col); + else + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + + lndebug("\n"); + l->oldpos = l->pos; + + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ + abFree(&ab); +} + +/* Calls the two low level functions refreshSingleLine() or + * refreshMultiLine() according to the selected mode. */ +static void refreshLine(struct linenoiseState *l) { + if (mlmode) + refreshMultiLine(l); + else + refreshSingleLine(l); +} + +/* Insert the character 'c' at cursor current position. + * + * On error writing to the terminal -1 is returned, otherwise 0. */ +int linenoiseEditInsert(struct linenoiseState *l, char c) { + if (l->len < l->buflen) { + if (l->len == l->pos) { + l->buf[l->pos] = c; + l->pos++; + l->len++; + l->buf[l->len] = '\0'; + if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { + /* Avoid a full update of the line in the + * trivial case. */ + char d = (maskmode==1) ? '*' : c; + if (write(l->ofd,&d,1) == -1) return -1; + } else { + refreshLine(l); + } + } else { + memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); + l->buf[l->pos] = c; + l->len++; + l->pos++; + l->buf[l->len] = '\0'; + refreshLine(l); + } + } + return 0; +} + +/* Move cursor on the left. */ +void linenoiseEditMoveLeft(struct linenoiseState *l) { + if (l->pos > 0) { + l->pos--; + refreshLine(l); + } +} + +/* Move cursor on the right. */ +void linenoiseEditMoveRight(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos++; + refreshLine(l); + } +} + +/* Move cursor to the start of the line. */ +void linenoiseEditMoveHome(struct linenoiseState *l) { + if (l->pos != 0) { + l->pos = 0; + refreshLine(l); + } +} + +/* Move cursor to the end of the line. */ +void linenoiseEditMoveEnd(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos = l->len; + refreshLine(l); + } +} + +/* Substitute the currently edited line with the next or previous history + * entry as specified by 'dir'. */ +#define LINENOISE_HISTORY_NEXT 0 +#define LINENOISE_HISTORY_PREV 1 +void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { + if (history_len > 1) { + /* Update the current history entry before to + * overwrite it with the next one. */ + free(history[history_len - 1 - l->history_index]); + history[history_len - 1 - l->history_index] = strdup(l->buf); + /* Show the new entry */ + l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; + if (l->history_index < 0) { + l->history_index = 0; + return; + } else if (l->history_index >= history_len) { + l->history_index = history_len-1; + return; + } + strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); + l->buf[l->buflen-1] = '\0'; + l->len = l->pos = strlen(l->buf); + refreshLine(l); + } +} + +/* Delete the character at the right of the cursor without altering the cursor + * position. Basically this is what happens with the "Delete" keyboard key. */ +void linenoiseEditDelete(struct linenoiseState *l) { + if (l->len > 0 && l->pos < l->len) { + memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Backspace implementation. */ +void linenoiseEditBackspace(struct linenoiseState *l) { + if (l->pos > 0 && l->len > 0) { + memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); + l->pos--; + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Delete the previosu word, maintaining the cursor at the start of the + * current word. */ +void linenoiseEditDeletePrevWord(struct linenoiseState *l) { + size_t old_pos = l->pos; + size_t diff; + + while (l->pos > 0 && l->buf[l->pos-1] == ' ') + l->pos--; + while (l->pos > 0 && l->buf[l->pos-1] != ' ') + l->pos--; + diff = old_pos - l->pos; + memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); + l->len -= diff; + refreshLine(l); +} + +/* 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. + * + * The function returns the length of the current buffer. */ +static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) +{ + struct linenoiseState l; + + /* Populate the linenoise state that we pass to functions implementing + * specific editing functionalities. */ + l.ifd = stdin_fd; + l.ofd = stdout_fd; + l.buf = buf; + l.buflen = buflen; + l.prompt = prompt; + l.plen = strlen(prompt); + l.oldpos = l.pos = 0; + l.len = 0; + l.cols = getColumns(stdin_fd, stdout_fd); + l.maxrows = 0; + l.history_index = 0; + + /* Buffer starts empty. */ + l.buf[0] = '\0'; + l.buflen--; /* Make sure there is always space for the nulterm */ + + /* The latest history entry is always our current buffer, that + * initially is just an empty string. */ + linenoiseHistoryAdd(""); + + if (write(l.ofd,prompt,l.plen) == -1) return -1; + while(1) { + char c; + int nread; + char seq[3]; + + nread = read(l.ifd,&c,1); + if (nread <= 0) return l.len; + + /* Only autocomplete when the callback is set. It returns < 0 when + * there was an error reading from fd. Otherwise it will return the + * character that should be handled next. */ + if (c == 9 && completionCallback != NULL) { + c = completeLine(&l); + /* Return on errors */ + if (c < 0) return l.len; + /* Read next character when 0 */ + if (c == 0) continue; + } + + switch(c) { + case ENTER: /* enter */ + history_len--; + free(history[history_len]); + if (mlmode) linenoiseEditMoveEnd(&l); + if (hintsCallback) { + /* Force a refresh without hints to leave the previous + * line as the user typed it after a newline. */ + linenoiseHintsCallback *hc = hintsCallback; + hintsCallback = NULL; + refreshLine(&l); + hintsCallback = hc; + } + return (int)l.len; + case CTRL_C: /* ctrl-c */ + errno = EAGAIN; + return -1; + case BACKSPACE: /* backspace */ + case 8: /* ctrl-h */ + linenoiseEditBackspace(&l); + break; + case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the + line is empty, act as end-of-file. */ + if (l.len > 0) { + linenoiseEditDelete(&l); + } else { + history_len--; + free(history[history_len]); + return -1; + } + break; + case CTRL_T: /* ctrl-t, swaps current character with previous. */ + if (l.pos > 0 && l.pos < l.len) { + int aux = buf[l.pos-1]; + buf[l.pos-1] = buf[l.pos]; + buf[l.pos] = aux; + if (l.pos != l.len-1) l.pos++; + refreshLine(&l); + } + break; + case CTRL_B: /* ctrl-b */ + linenoiseEditMoveLeft(&l); + break; + case CTRL_F: /* ctrl-f */ + linenoiseEditMoveRight(&l); + break; + case CTRL_P: /* ctrl-p */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case CTRL_N: /* ctrl-n */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case ESC: /* escape sequence */ + /* Read the next two bytes representing the escape sequence. + * Use two calls to handle slow terminals returning the two + * chars at different times. */ + if (read(l.ifd,seq,1) == -1) break; + if (read(l.ifd,seq+1,1) == -1) break; + + /* ESC [ sequences. */ + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended escape, read additional byte. */ + if (read(l.ifd,seq+2,1) == -1) break; + if (seq[2] == '~') { + switch(seq[1]) { + case '3': /* Delete key. */ + linenoiseEditDelete(&l); + break; + } + } + } else { + switch(seq[1]) { + case 'A': /* Up */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case 'B': /* Down */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case 'C': /* Right */ + linenoiseEditMoveRight(&l); + break; + case 'D': /* Left */ + linenoiseEditMoveLeft(&l); + break; + case 'H': /* Home */ + linenoiseEditMoveHome(&l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(&l); + break; + } + } + } + + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch(seq[1]) { + case 'H': /* Home */ + linenoiseEditMoveHome(&l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(&l); + break; + } + } + break; + default: + if (linenoiseEditInsert(&l,c)) return -1; + break; + case CTRL_U: /* Ctrl+u, delete the whole line. */ + buf[0] = '\0'; + l.pos = l.len = 0; + refreshLine(&l); + break; + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ + buf[l.pos] = '\0'; + l.len = l.pos; + refreshLine(&l); + break; + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(&l); + break; + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(&l); + break; + case CTRL_L: /* ctrl+l, clear screen */ + linenoiseClearScreen(); + refreshLine(&l); + break; + case CTRL_W: /* ctrl+w, delete previous word */ + linenoiseEditDeletePrevWord(&l); + break; + } + } + return l.len; +} + +/* This special mode is used by linenoise in order to print scan codes + * on screen for debugging / development purposes. It is implemented + * by the linenoise_example program using the --keycodes option. */ +void linenoisePrintKeyCodes(void) { + char quit[4]; + + printf("Linenoise key codes debugging mode.\n" + "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); + if (enableRawMode(STDIN_FILENO) == -1) return; + memset(quit,' ',4); + while(1) { + char c; + int nread; + + nread = read(STDIN_FILENO,&c,1); + if (nread <= 0) continue; + memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ + quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ + if (memcmp(quit,"quit",sizeof(quit)) == 0) break; + + printf("'%c' %02x (%d) (type quit to exit)\n", + isprint(c) ? c : '?', (int)c, (int)c); + printf("\r"); /* Go left edge manually, we are in raw mode. */ + fflush(stdout); + } + disableRawMode(STDIN_FILENO); +} + +/* This function calls the line editing function linenoiseEdit() using + * the STDIN file descriptor set in raw mode. */ +static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { + int count; + + if (buflen == 0) { + errno = EINVAL; + return -1; + } + + if (enableRawMode(STDIN_FILENO) == -1) return -1; + count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); + disableRawMode(STDIN_FILENO); + printf("\n"); + return count; +} + +/* This function is called when linenoise() is called with the standard + * input file descriptor not attached to a TTY. So for example when the + * program using linenoise is called in pipe or with a file redirected + * to its standard input. In this case, we want to be able to return the + * line regardless of its length (by default we are limited to 4k). */ +static char *linenoiseNoTTY(void) { + char *line = NULL; + size_t len = 0, maxlen = 0; + + while(1) { + if (len == maxlen) { + if (maxlen == 0) maxlen = 16; + maxlen *= 2; + char *oldval = line; + line = realloc(line,maxlen); + if (line == NULL) { + if (oldval) free(oldval); + return NULL; + } + } + int c = fgetc(stdin); + if (c == EOF || c == '\n') { + if (c == EOF && len == 0) { + free(line); + return NULL; + } else { + line[len] = '\0'; + return line; + } + } else { + line[len] = c; + len++; + } + } +} + +/* 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 stupid 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. */ +char *linenoise(const char *prompt) { + char buf[LINENOISE_MAX_LINE]; + int count; + + if (!isatty(STDIN_FILENO)) { + /* Not a tty: read from file / pipe. In this mode we don't want any + * limit to the line size, so we call a function to handle that. */ + return linenoiseNoTTY(); + } else if (isUnsupportedTerm()) { + size_t len; + + printf("%s",prompt); + fflush(stdout); + if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; + len = strlen(buf); + while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { + len--; + buf[len] = '\0'; + } + return strdup(buf); + } else { + count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); + if (count == -1) return NULL; + return strdup(buf); + } +} + +/* This is just a wrapper the user may want to call in order to make sure + * the linenoise returned buffer is freed with the same allocator it was + * created with. Useful when the main program is using an alternative + * allocator. */ +void linenoiseFree(void *ptr) { + free(ptr); +} + +/* ================================ History ================================= */ + +/* Free the history, but does not reset it. Only used when we have to + * exit() to avoid memory leaks are reported by valgrind & co. */ +static void freeHistory(void) { + if (history) { + int j; + + for (j = 0; j < history_len; j++) + free(history[j]); + free(history); + } +} + +/* At exit we'll try to fix the terminal to the initial conditions. */ +static void linenoiseAtExit(void) { + disableRawMode(STDIN_FILENO); + freeHistory(); +} + +/* This is the API call to add a new entry in the linenoise history. + * It uses a fixed array of char pointers that are shifted (memmoved) + * when the history max length is reached in order to remove the older + * entry and make room for the new one, so it is not exactly suitable for huge + * histories, but will work well for a few hundred of entries. + * + * Using a circular buffer is smarter, but a bit more complex to handle. */ +int linenoiseHistoryAdd(const char *line) { + char *linecopy; + + if (history_max_len == 0) return 0; + + /* Initialization on first call. */ + if (history == NULL) { + history = malloc(sizeof(char*)*history_max_len); + if (history == NULL) return 0; + memset(history,0,(sizeof(char*)*history_max_len)); + } + + /* Don't add duplicated lines. */ + if (history_len && !strcmp(history[history_len-1], line)) return 0; + + /* Add an heap allocated copy of the line in the history. + * If we reached the max length, remove the older line. */ + linecopy = strdup(line); + if (!linecopy) return 0; + if (history_len == history_max_len) { + free(history[0]); + memmove(history,history+1,sizeof(char*)*(history_max_len-1)); + history_len--; + } + history[history_len] = linecopy; + history_len++; + return 1; +} + +/* Set the maximum length for the history. This function can be called even + * if there is already some history, the function will make sure to retain + * just the latest 'len' elements if the new history length value is smaller + * than the amount of items already inside the history. */ +int linenoiseHistorySetMaxLen(int len) { + char **new; + + if (len < 1) return 0; + if (history) { + int tocopy = history_len; + + new = malloc(sizeof(char*)*len); + if (new == NULL) return 0; + + /* If we can't copy everything, free the elements we'll not use. */ + if (len < tocopy) { + int j; + + for (j = 0; j < tocopy-len; j++) free(history[j]); + tocopy = len; + } + memset(new,0,sizeof(char*)*len); + memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); + free(history); + history = new; + } + history_max_len = len; + if (history_len > history_max_len) + history_len = history_max_len; + return 1; +} + +/* Save the history in the specified file. On success 0 is returned + * otherwise -1 is returned. */ +int linenoiseHistorySave(const char *filename) { + mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); + FILE *fp; + int j; + + fp = fopen(filename,"w"); + umask(old_umask); + if (fp == NULL) return -1; + chmod(filename,S_IRUSR|S_IWUSR); + for (j = 0; j < history_len; j++) + fprintf(fp,"%s\n",history[j]); + fclose(fp); + return 0; +} + +/* Load the history from the specified file. If the file does not exist + * zero is returned and no operation is performed. + * + * If the file exists and the operation succeeded 0 is returned, otherwise + * on error -1 is returned. */ +int linenoiseHistoryLoad(const char *filename) { + FILE *fp = fopen(filename,"r"); + char buf[LINENOISE_MAX_LINE]; + + if (fp == NULL) return -1; + + while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { + char *p; + + p = strchr(buf,'\r'); + if (!p) p = strchr(buf,'\n'); + if (p) *p = '\0'; + linenoiseHistoryAdd(buf); + } + fclose(fp); + return 0; +} diff --git a/implementations/C/linenoise.h b/implementations/C/linenoise.h new file mode 100644 index 0000000..6dfee73 --- /dev/null +++ b/implementations/C/linenoise.h @@ -0,0 +1,75 @@ +/* linenoise.h -- VERSION 1.0 + * + * Guerrilla line editing library against the idea that a line editing lib + * needs to be 20,000 lines of C code. + * + * See linenoise.c for more information. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010-2014, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __LINENOISE_H +#define __LINENOISE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct linenoiseCompletions { + size_t len; + char **cvec; +} linenoiseCompletions; + +typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); +typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold); +typedef void(linenoiseFreeHintsCallback)(void *); +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); +void linenoiseSetHintsCallback(linenoiseHintsCallback *); +void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); +void linenoiseAddCompletion(linenoiseCompletions *, const char *); + +char *linenoise(const char *prompt); +void linenoiseFree(void *ptr); +int linenoiseHistoryAdd(const char *line); +int linenoiseHistorySetMaxLen(int len); +int linenoiseHistorySave(const char *filename); +int linenoiseHistoryLoad(const char *filename); +void linenoiseClearScreen(void); +void linenoiseSetMultiLine(int ml); +void linenoisePrintKeyCodes(void); +void linenoiseMaskModeEnable(void); +void linenoiseMaskModeDisable(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __LINENOISE_H */