From d87904fcc9aeeaff643bdcf9a5f4cd69b9ab1147 Mon Sep 17 00:00:00 2001 From: Eloi Torrents Date: Thu, 26 Sep 2024 12:41:11 +0200 Subject: [PATCH 1/9] Experiment with linenoise --- src/extras/linenoise/linenoise.c | 2736 +++++++++++------------------- src/extras/linenoise/linenoise.h | 151 +- 2 files changed, 1065 insertions(+), 1822 deletions(-) diff --git a/src/extras/linenoise/linenoise.c b/src/extras/linenoise/linenoise.c index 6b16b77e10..19b91bf904 100644 --- a/src/extras/linenoise/linenoise.c +++ b/src/extras/linenoise/linenoise.c @@ -3,17 +3,15 @@ * * You can find the latest source code at: * - * http://github.com/msteveb/linenoise - * (forked from http://github.com/antirez/linenoise) + * 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, Salvatore Sanfilippo - * Copyright (c) 2010, Pieter Noordhuis - * Copyright (c) 2011, Steve Bennett + * Copyright (c) 2010-2023, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis * * All rights reserved. * @@ -46,376 +44,195 @@ * - 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: - * - Completion? + * - History search like Ctrl+r in readline? * - * Unix/termios - * ------------ * List of escape sequences used by this program, we do everything just - * a few sequences. In order to be so cheap we may have some + * 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 [ 0 K - * Effect: clear from cursor to end of 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 * - * CR (Carriage Return) - * Sequence: \r - * Effect: moves cursor to column 1 - * - * The following are used to clear the screen: ESC [ H ESC [ 2 J - * This is actually composed of two sequences: - * - * cursorhome - * Sequence: ESC [ H - * Effect: moves the cursor to upper left corner - * - * ED2 (Clear entire screen) - * Sequence: ESC [ 2 J - * Effect: clear the whole screen + * CUB (CUrsor Backward) + * Sequence: ESC [ n D + * Effect: moves cursor backward n chars * - * == For highlighting control characters, we also use the following two == - * SO (enter StandOut) - * Sequence: ESC [ 7 m - * Effect: Uses some standout mode such as reverse video + * The following is used to get the terminal width if getting + * the width with the TIOCGWINSZ ioctl fails * - * SE (Standout End) - * Sequence: ESC [ 0 m - * Effect: Exit standout mode - * - * == Only used if TIOCGWINSZ fails == - * DSR/CPR (Report cursor position) + * DSR (Device Status Report) * Sequence: ESC [ 6 n - * Effect: reports current cursor position as ESC [ NNN ; MMM R + * 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. * - * == Only used in multiline mode == * CUU (Cursor Up) * Sequence: ESC [ n A - * Effect: moves cursor up n chars. + * Effect: moves cursor up of n chars. * * CUD (Cursor Down) * Sequence: ESC [ n B - * Effect: moves cursor down n chars. + * 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 * - * win32/console - * ------------- - * If __MINGW32__ is defined, the win32 console API is used. - * This could probably be made to work for the msvc compiler too. - * This support based in part on work by Jon Griffiths. */ -#ifdef _WIN32 /* Windows platform, either MinGW or Visual Studio (MSVC) */ -#include -#include -#define USE_WINCONSOLE -#ifdef __MINGW32__ -#define HAVE_UNISTD_H -#endif -#else #include -#include -#include -#define USE_TERMIOS -#define HAVE_UNISTD_H -#endif - -#ifdef HAVE_UNISTD_H #include -#endif #include -#include #include -#include #include #include -#include #include +#include +#include #include - -#if defined(_WIN32) && !defined(__MINGW32__) -/* Microsoft headers don't like old POSIX names */ -#define strdup _strdup -#define snprintf _snprintf -#endif - +#include +#include #include "linenoise.h" -#ifndef STRINGBUF_H -#include "stringbuf.h" -#endif -#ifndef UTF8_UTIL_H -#include "utf8.h" -#endif #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 - -/* ctrl('A') -> 0x01 */ -#define ctrl(C) ((C) - '@') -/* meta('a') -> 0xe1 */ -#define meta(C) ((C) | 0x80) - -/* Use -ve numbers here to co-exist with normal unicode chars */ -enum { - SPECIAL_NONE, - /* don't use -1 here since that indicates error */ - SPECIAL_UP = -20, - SPECIAL_DOWN = -21, - SPECIAL_LEFT = -22, - SPECIAL_RIGHT = -23, - SPECIAL_DELETE = -24, - SPECIAL_HOME = -25, - SPECIAL_END = -26, - SPECIAL_INSERT = -27, - SPECIAL_PAGE_UP = -28, - SPECIAL_PAGE_DOWN = -29, - - /* Some handy names for other special keycodes */ - CHAR_ESCAPE = 27, - CHAR_DELETE = 127, -}; - +#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 char *linenoiseNoTTY(void); +static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags); +static void refreshLineWithFlags(struct linenoiseState *l, int flags); + +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; -/* Structure to contain the status of the current (being edited) line */ -struct current { - stringbuf *buf; /* Current buffer. Always null terminated */ - int pos; /* Cursor position, measured in chars */ - int cols; /* Size of the window, in chars */ - int nrows; /* How many rows are being used in multiline mode (>= 1) */ - int rpos; /* The current row containing the cursor - multiline mode only */ - int colsright; /* refreshLine() cached cols for insert_char() optimisation */ - int colsleft; /* refreshLine() cached cols for remove_char() optimisation */ - const char *prompt; - stringbuf *capture; /* capture buffer, or NULL for none. Always null terminated */ - stringbuf *output; /* used only during refreshLine() - output accumulator */ -#if defined(USE_TERMIOS) - int fd; /* Terminal fd */ -#elif defined(USE_WINCONSOLE) - HANDLE outh; /* Console output handle */ - HANDLE inh; /* Console input handle */ - int rows; /* Screen rows */ - int x; /* Current column during output */ - int y; /* Current row */ -#ifdef USE_UTF8 - #define UBUF_MAX_CHARS 132 - WORD ubuf[UBUF_MAX_CHARS + 1]; /* Accumulates utf16 output - one extra for final surrogate pairs */ - int ubuflen; /* length used in ubuf */ - int ubufcols; /* how many columns are represented by the chars in ubuf? */ -#endif -#endif +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 int fd_read(struct current *current); -static int getWindowSize(struct current *current); -static void cursorDown(struct current *current, int n); -static void cursorUp(struct current *current, int n); -static void eraseEol(struct current *current); -static void refreshLine(struct current *current); -static void refreshLineAlt(struct current *current, const char *prompt, const char *buf, int cursor_pos); -static void setCursorPos(struct current *current, int x); -static void setOutputHighlight(struct current *current, const int *props, int nprops); -static void set_current(struct current *current, const char *str); - -static int fd_isatty(struct current *current) -{ -#ifdef USE_TERMIOS - return isatty(current->fd); +static void linenoiseAtExit(void); +int linenoiseHistoryAdd(const char *line); +#define REFRESH_CLEAN (1<<0) // Clean the old prompt from the screen +#define REFRESH_WRITE (1<<1) // Rewrite the prompt on the screen. +#define REFRESH_ALL (REFRESH_CLEAN|REFRESH_WRITE) // Do both. +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->oldrows,old_rows); \ + } \ + fprintf(lndebug_fp, ", " __VA_ARGS__); \ + fflush(lndebug_fp); \ + } while (0) #else - (void)current; - return 0; +#define lndebug(fmt, ...) #endif -} - -void linenoiseHistoryFree(void) { - if (history) { - int j; - - for (j = 0; j < history_len; j++) - free(history[j]); - free(history); - history = NULL; - history_len = 0; - } -} -typedef enum { - EP_START, /* looking for ESC */ - EP_ESC, /* looking for [ */ - EP_DIGITS, /* parsing digits */ - EP_PROPS, /* parsing digits or semicolons */ - EP_END, /* ok */ - EP_ERROR, /* error */ -} ep_state_t; - -struct esc_parser { - ep_state_t state; - int props[5]; /* properties are stored here */ - int maxprops; /* size of the props[] array */ - int numprops; /* number of properties found */ - int termchar; /* terminator char, or 0 for any alpha */ - int current; /* current (partial) property value */ -}; +/* ======================= Low level terminal handling ====================== */ -/** - * Initialise the escape sequence parser at *parser. - * - * If termchar is 0 any alpha char terminates ok. Otherwise only the given - * char terminates successfully. - * Run the parser state machine with calls to parseEscapeSequence() for each char. - */ -static void initParseEscapeSeq(struct esc_parser *parser, int termchar) -{ - parser->state = EP_START; - parser->maxprops = sizeof(parser->props) / sizeof(*parser->props); - parser->numprops = 0; - parser->current = 0; - parser->termchar = termchar; +/* 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; } -/** - * Pass character 'ch' into the state machine to parse: - * 'ESC' '[' (';' )* - * - * The first character must be ESC. - * Returns the current state. The state machine is done when it returns either EP_END - * or EP_ERROR. - * - * On EP_END, the "property/attribute" values can be read from parser->props[] - * of length parser->numprops. - */ -static int parseEscapeSequence(struct esc_parser *parser, int ch) -{ - switch (parser->state) { - case EP_START: - parser->state = (ch == '\x1b') ? EP_ESC : EP_ERROR; - break; - case EP_ESC: - parser->state = (ch == '[') ? EP_DIGITS : EP_ERROR; - break; - case EP_PROPS: - if (ch == ';') { - parser->state = EP_DIGITS; -donedigits: - if (parser->numprops + 1 < parser->maxprops) { - parser->props[parser->numprops++] = parser->current; - parser->current = 0; - } - break; - } - /* fall through */ - case EP_DIGITS: - if (ch >= '0' && ch <= '9') { - parser->current = parser->current * 10 + (ch - '0'); - parser->state = EP_PROPS; - break; - } - /* must be terminator */ - if (parser->termchar != ch) { - if (parser->termchar != 0 || !((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))) { - parser->state = EP_ERROR; - break; - } - } - parser->state = EP_END; - goto donedigits; - case EP_END: - parser->state = EP_ERROR; - break; - case EP_ERROR: - break; - } - return parser->state; +/* Disable mask mode. */ +void linenoiseMaskModeDisable(void) { + maskmode = 0; } -/*#define DEBUG_REFRESHLINE*/ - -#ifdef DEBUG_REFRESHLINE -#define DRL(ARGS...) fprintf(dfh, ARGS) -static FILE *dfh; - -static void DRL_CHAR(int ch) -{ - if (ch < ' ') { - DRL("^%c", ch + '@'); - } - else if (ch > 127) { - DRL("\\u%04x", ch); - } - else { - DRL("%c", ch); - } +/* Set if to use or not the multi line mode. */ +void linenoiseSetMultiLine(int ml) { + mlmode = ml; } -static void DRL_STR(const char *str) -{ - while (*str) { - int ch; - int n = utf8_tounicode(str, &ch); - str += n; - DRL_CHAR(ch); - } -} -#else -#define DRL(...) -#define DRL_CHAR(ch) -#define DRL_STR(str) -#endif - -#if defined(USE_WINCONSOLE) -#include "linenoise-win32.c" -#endif - -#if defined(USE_TERMIOS) -static void linenoiseAtExit(void); -static struct termios orig_termios; /* in order to restore at exit */ -static int rawmode = 0; /* for atexit() function to check if restore is needed*/ -static int atexit_registered = 0; /* register atexit just 1 time */ - -static const char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; +/* 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) { - int j; - for (j = 0; unsupported_term[j]; j++) { - if (strcmp(term, unsupported_term[j]) == 0) { - return 1; - } - } - } + if (term == NULL) return 0; + for (j = 0; unsupported_term[j]; j++) + if (!strcasecmp(term,unsupported_term[j])) return 1; return 0; } -static int enableRawMode(struct current *current) { +/* Raw mode: 1960 magic shit. */ +static int enableRawMode(int fd) { struct termios raw; - current->fd = STDIN_FILENO; - current->cols = 0; - - if (!isatty(current->fd) || isUnsupportedTerm() || - tcgetattr(current->fd, &orig_termios) == -1) { -fatal: - errno = ENOTTY; - return -1; - } - + 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 - actually, no need to disable post processing */ - /*raw.c_oflag &= ~(OPOST);*/ + /* 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, @@ -426,452 +243,194 @@ static int enableRawMode(struct current *current) { raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ /* put terminal in raw mode after flushing */ - if (tcsetattr(current->fd,TCSADRAIN,&raw) < 0) { - goto fatal; - } + if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; rawmode = 1; return 0; + +fatal: + errno = ENOTTY; + return -1; } -static void disableRawMode(struct current *current) { +static void disableRawMode(int fd) { /* Don't even check the return value as it's too late. */ - if (rawmode && tcsetattr(current->fd,TCSADRAIN,&orig_termios) != -1) + if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) rawmode = 0; } -/* At exit we'll try to fix the terminal to the initial conditions. */ -static void linenoiseAtExit(void) { - if (rawmode) { - tcsetattr(STDIN_FILENO, TCSADRAIN, &orig_termios); - } - linenoiseHistoryFree(); -} - -/* gcc/glibc insists that we care about the return code of write! - * Clarification: This means that a void-cast like "(void) (EXPR)" - * does not work. - */ -#define IGNORE_RC(EXPR) if (EXPR) {} - -/** - * Output bytes directly, or accumulate output (if current->output is set) - */ -static void outputChars(struct current *current, const char *buf, int len) -{ - if (len < 0) { - len = strlen(buf); - } - if (current->output) { - sb_append_len(current->output, buf, len); - } - else { - IGNORE_RC(write(current->fd, buf, len)); - } -} - -/* Like outputChars, but using printf-style formatting - */ -static void outputFormatted(struct current *current, const char *format, ...) -{ - va_list args; - char buf[64]; - int n; - - va_start(args, format); - n = vsnprintf(buf, sizeof(buf), format, args); - /* This will never happen because we are sure to use outputFormatted() only for short sequences */ - assert(n < (int)sizeof(buf)); - va_end(args); - outputChars(current, buf, n); -} - -static void cursorToLeft(struct current *current) -{ - outputChars(current, "\r", -1); -} - -static void setOutputHighlight(struct current *current, const int *props, int nprops) -{ - outputChars(current, "\x1b[", -1); - while (nprops--) { - outputFormatted(current, "%d%c", *props, (nprops == 0) ? 'm' : ';'); - props++; - } -} - -static void eraseEol(struct current *current) -{ - outputChars(current, "\x1b[0K", -1); -} - -static void setCursorPos(struct current *current, int x) -{ - if (x == 0) { - cursorToLeft(current); - } - else { - outputFormatted(current, "\r\x1b[%dC", x); - } -} - -static void cursorUp(struct current *current, int n) -{ - if (n) { - outputFormatted(current, "\x1b[%dA", n); - } -} - -static void cursorDown(struct current *current, int n) -{ - if (n) { - outputFormatted(current, "\x1b[%dB", n); - } -} - -void linenoiseClearScreen(void) -{ - IGNORE_RC(write(STDOUT_FILENO, "\x1b[H\x1b[2J", 7)); -} - -/** - * Reads a char from 'fd', waiting at most 'timeout' milliseconds. - * - * A timeout of -1 means to wait forever. - * - * Returns -1 if no char is received within the time or an error occurs. - */ -static int fd_read_char(int fd, int timeout) -{ - struct pollfd p; - unsigned char c; - - p.fd = fd; - p.events = POLLIN; +/* 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; - if (poll(&p, 1, timeout) == 0) { - /* timeout */ - return -1; - } - if (read(fd, &c, 1) != 1) { - return -1; - } - return c; -} + /* Report cursor location */ + if (write(ofd, "\x1b[6n", 4) != 4) return -1; -/** - * Reads a complete utf-8 character - * and returns the unicode value, or -1 on error. - */ -static int fd_read(struct current *current) -{ -#ifdef USE_UTF8 - char buf[MAX_UTF8_LEN]; - int n; - int i; - int c; - - if (read(current->fd, &buf[0], 1) != 1) { - return -1; - } - n = utf8_charlen(buf[0]); - if (n < 1) { - 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++; } - for (i = 1; i < n; i++) { - if (read(current->fd, &buf[i], 1) != 1) { - return -1; - } - } - /* decode and return the character */ - utf8_tounicode(buf, &c); - return c; -#else - return fd_read_char(current->fd, -1); -#endif -} - + buf[i] = '\0'; -/** - * Stores the current cursor column in '*cols'. - * Returns 1 if OK, or 0 if failed to determine cursor pos. - */ -static int queryCursor(struct current *current, int* cols) -{ - struct esc_parser parser; - int ch; - - /* Should not be buffering this output, it needs to go immediately */ - assert(current->output == NULL); - - /* control sequence - report cursor location */ - outputChars(current, "\x1b[6n", -1); - - /* Parse the response: ESC [ rows ; cols R */ - initParseEscapeSeq(&parser, 'R'); - while ((ch = fd_read_char(current->fd, 100)) > 0) { - switch (parseEscapeSequence(&parser, ch)) { - default: - continue; - case EP_END: - if (parser.numprops == 2 && parser.props[1] < 1000) { - *cols = parser.props[1]; - return 1; - } - break; - case EP_ERROR: - break; - } - /* failed */ - break; - } - return 0; + /* Parse it. */ + if (buf[0] != ESC || buf[1] != '[') return -1; + if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; + return cols; } -/** - * Updates current->cols with the current window size (width) - */ -static int getWindowSize(struct current *current) -{ +/* 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(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0) { - current->cols = ws.ws_col; - return 0; - } - - /* Failed to query the window size. Perhaps we are on a serial terminal. - * Try to query the width by sending the cursor as far to the right - * and reading back the cursor position. - * Note that this is only done once per call to linenoise rather than - * every time the line is refreshed for efficiency reasons. - * - * In more detail, we: - * (a) request current cursor position, - * (b) move cursor far right, - * (c) request cursor position again, - * (d) at last move back to the old position. - * This gives us the width without messing with the externally - * visible cursor position. - */ - - if (current->cols == 0) { - int here; - - /* If anything fails => default 80 */ - current->cols = 80; - - /* (a) */ - if (queryCursor (current, &here)) { - /* (b) */ - setCursorPos(current, 999); - - /* (c). Note: If (a) succeeded, then (c) should as well. - * For paranoia we still check and have a fallback action - * for (d) in case of failure.. - */ - if (queryCursor (current, ¤t->cols)) { - /* (d) Reset the cursor back to the original location. */ - if (current->cols > here) { - setCursorPos(current, here); - } + 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; } - return 0; -} - -/** - * If CHAR_ESCAPE was received, reads subsequent - * chars to determine if this is a known special key. - * - * Returns SPECIAL_NONE if unrecognised, or -1 if EOF. - * - * If no additional char is received within a short time, - * CHAR_ESCAPE is returned. - */ -static int check_special(int fd) -{ - int c = fd_read_char(fd, 50); - int c2; - - if (c < 0) { - return CHAR_ESCAPE; - } - else if (c >= 'a' && c <= 'z') { - /* esc-a => meta-a */ - return meta(c); - } - - c2 = fd_read_char(fd, 50); - if (c2 < 0) { - return c2; - } - if (c == '[' || c == 'O') { - /* Potential arrow key */ - switch (c2) { - case 'A': - return SPECIAL_UP; - case 'B': - return SPECIAL_DOWN; - case 'C': - return SPECIAL_RIGHT; - case 'D': - return SPECIAL_LEFT; - case 'F': - return SPECIAL_END; - case 'H': - return SPECIAL_HOME; - } - } - if (c == '[' && c2 >= '1' && c2 <= '8') { - /* extended escape */ - c = fd_read_char(fd, 50); - if (c == '~') { - switch (c2) { - case '2': - return SPECIAL_INSERT; - case '3': - return SPECIAL_DELETE; - case '5': - return SPECIAL_PAGE_UP; - case '6': - return SPECIAL_PAGE_DOWN; - case '7': - return SPECIAL_HOME; - case '8': - return SPECIAL_END; - } - } - while (c != -1 && c != '~') { - /* .e.g \e[12~ or '\e[11;2~ discard the complete sequence */ - c = fd_read_char(fd, 50); - } - } - - return SPECIAL_NONE; -} -#endif - -static void clearOutputHighlight(struct current *current) -{ - int nohighlight = 0; - setOutputHighlight(current, &nohighlight, 1); -} - -static void outputControlChar(struct current *current, char ch) -{ - int reverse = 7; - setOutputHighlight(current, &reverse, 1); - outputChars(current, "^", 1); - outputChars(current, &ch, 1); - clearOutputHighlight(current); -} - -#ifndef utf8_getchars -static int utf8_getchars(char *buf, int c) -{ -#ifdef USE_UTF8 - return utf8_fromunicode(buf, c); -#else - *buf = c; - return 1; -#endif -} -#endif - -/** - * Returns the unicode character at the given offset, - * or -1 if none. - */ -static int get_char(struct current *current, int pos) -{ - if (pos >= 0 && pos < sb_chars(current->buf)) { - int c; - int i = utf8_index(sb_str(current->buf), pos); - (void)utf8_tounicode(sb_str(current->buf) + i, &c); - return c; - } - return -1; +failed: + return 80; } -static int char_display_width(int ch) -{ - if (ch < ' ') { - /* control chars take two positions */ - return 2; - } - else { - return utf8_width(ch); +/* 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. */ } } -#ifndef NO_COMPLETION -static linenoiseCompletionCallback *completionCallback = NULL; -static void *completionUserdata = NULL; -static int showhints = 1; -static linenoiseHintsCallback *hintsCallback = NULL; -static linenoiseFreeHintsCallback *freeHintsCallback = NULL; -static void *hintsUserdata = NULL; - -static void beep() { -#ifdef USE_TERMIOS +/* 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); -#endif } +/* ============================== 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]); - free(lc->cvec); + if (lc->cvec != NULL) + free(lc->cvec); } -static int completeLine(struct current *current) { +/* Called by completeLine() and linenoiseShow() to render the current + * edited line with the proposed completion. If the current completion table + * is already available, it is passed as second argument, otherwise the + * function will use the callback to obtain it. + * + * Flags are the same as refreshLine*(), that is REFRESH_* macros. */ +static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags) { + /* Obtain the table of completions if the caller didn't provide one. */ + linenoiseCompletions ctable = { 0, NULL }; + if (lc == NULL) { + completionCallback(ls->buf,&ctable); + lc = &ctable; + } + + /* Show the edited line with completion if possible, or just refresh. */ + if (ls->completion_idx < lc->len) { + struct linenoiseState saved = *ls; + ls->len = ls->pos = strlen(lc->cvec[ls->completion_idx]); + ls->buf = lc->cvec[ls->completion_idx]; + refreshLineWithFlags(ls,flags); + ls->len = saved.len; + ls->pos = saved.pos; + ls->buf = saved.buf; + } else { + refreshLineWithFlags(ls,flags); + } + + /* Free the completions table if needed. */ + if (lc != &ctable) freeCompletions(&ctable); +} + +/* 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. + * + * If the function returns non-zero, the caller should handle the + * returned value as a byte read from the standard input, and process + * it as usually: this basically means that the function may return a byte + * read from the termianl but not processed. Otherwise, if zero is returned, + * the input was consumed by the completeLine() function to navigate the + * possible completions, and the caller should read for the next characters + * from stdin. */ +static int completeLine(struct linenoiseState *ls, int keypressed) { linenoiseCompletions lc = { 0, NULL }; - int c = 0; + int nwritten; + char c = keypressed; - completionCallback(sb_str(current->buf),&lc,completionUserdata); + completionCallback(ls->buf,&lc); if (lc.len == 0) { - beep(); + linenoiseBeep(); + ls->in_completion = 0; } else { - size_t stop = 0, i = 0; - - while(!stop) { - /* Show completion or original buffer */ - if (i < lc.len) { - int chars = utf8_strlen(lc.cvec[i], -1); - refreshLineAlt(current, current->prompt, lc.cvec[i], chars); - } else { - refreshLine(current); - } - - c = fd_read(current); - if (c == -1) { + switch(c) { + case 9: /* tab */ + if (ls->in_completion == 0) { + ls->in_completion = 1; + ls->completion_idx = 0; + } else { + ls->completion_idx = (ls->completion_idx+1) % (lc.len+1); + if (ls->completion_idx == lc.len) linenoiseBeep(); + } + c = 0; break; - } + case 27: /* escape */ + /* Re-show original buffer */ + if (ls->completion_idx < lc.len) refreshLine(ls); + ls->in_completion = 0; + c = 0; + break; + default: + /* Update buffer and return */ + if (ls->completion_idx < lc.len) { + nwritten = snprintf(ls->buf,ls->buflen,"%s", + lc.cvec[ls->completion_idx]); + ls->len = ls->pos = nwritten; + } + ls->in_completion = 0; + break; + } - switch(c) { - case '\t': /* tab */ - i = (i+1) % (lc.len+1); - if (i == lc.len) beep(); - break; - case CHAR_ESCAPE: /* escape */ - /* Re-show original buffer */ - if (i < lc.len) { - refreshLine(current); - } - stop = 1; - break; - default: - /* Update buffer and return */ - if (i < lc.len) { - set_current(current,lc.cvec[i]); - } - stop = 1; - break; - } + /* Show completion or original buffer */ + if (ls->in_completion && ls->completion_idx < lc.len) { + refreshLineWithCompletion(ls,&lc,REFRESH_ALL); + } else { + refreshLine(ls); } } @@ -879,1095 +438,857 @@ static int completeLine(struct current *current) { return c; /* Return last read character */ } -/* Register a callback function to be called for tab-completion. - Returns the prior callback so that the caller may (if needed) - restore it when done. */ -linenoiseCompletionCallback * linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn, void *userdata) { - linenoiseCompletionCallback * old = completionCallback; +/* Register a callback function to be called for tab-completion. */ +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { completionCallback = fn; - completionUserdata = userdata; - return old; } -void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { - lc->cvec = (char **)realloc(lc->cvec,sizeof(char*)*(lc->len+1)); - lc->cvec[lc->len++] = strdup(str); +/* 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; } -void linenoiseSetHintsCallback(linenoiseHintsCallback *callback, void *userdata) -{ - hintsCallback = callback; - hintsUserdata = userdata; +/* Register a function to free the hints returned by the hints callback + * registered with linenoiseSetHintsCallback(). */ +void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { + freeHintsCallback = fn; } -void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *callback) -{ - freeHintsCallback = callback; -} - -#endif - - -static const char *reduceSingleBuf(const char *buf, int availcols, int *cursor_pos) -{ - /* We have availcols columns available. - * If necessary, strip chars off the front of buf until *cursor_pos - * fits within availcols - */ - int needcols = 0; - int pos = 0; - int new_cursor_pos = *cursor_pos; - const char *pt = buf; - - DRL("reduceSingleBuf: availcols=%d, cursor_pos=%d\n", availcols, *cursor_pos); - - while (*pt) { - int ch; - int n = utf8_tounicode(pt, &ch); - pt += n; - - needcols += char_display_width(ch); - - /* If we need too many cols, strip - * chars off the front of buf to make it fit. - * We keep 3 extra cols to the right of the cursor. - * 2 for possible wide chars, 1 for the last column that - * can't be used. - */ - while (needcols >= availcols - 3) { - n = utf8_tounicode(buf, &ch); - buf += n; - needcols -= char_display_width(ch); - DRL_CHAR(ch); - - /* and adjust the apparent cursor position */ - new_cursor_pos--; - - if (buf == pt) { - /* can't remove more than this */ - break; - } - } - - if (pos++ == *cursor_pos) { - break; - } - - } - DRL(""); - DRL_STR(buf); - DRL("\nafter reduce, needcols=%d, new_cursor_pos=%d\n", needcols, new_cursor_pos); - - /* Done, now new_cursor_pos contains the adjusted cursor position - * and buf points to he adjusted start - */ - *cursor_pos = new_cursor_pos; - return buf; -} - -static int mlmode = 0; - -void linenoiseSetMultiLine(int enableml) -{ - mlmode = enableml; -} - -/* Helper of refreshSingleLine() and refreshMultiLine() to show hints - * to the right of the prompt. - * Returns 1 if a hint was shown, or 0 if not - * If 'display' is 0, does no output. Just returns the appropriate return code. - */ -static int refreshShowHints(struct current *current, const char *buf, int availcols, int display) -{ - int rc = 0; - if (showhints && hintsCallback && availcols > 0) { - int bold = 0; - int color = -1; - char *hint = hintsCallback(buf, &color, &bold, hintsUserdata); - if (hint) { - rc = 1; - if (display) { - const char *pt; - if (bold == 1 && color == -1) color = 37; - if (bold || color > 0) { - int props[3] = { bold, color, 49 }; /* bold, color, fgnormal */ - setOutputHighlight(current, props, 3); - } - DRL("", bold, color); - pt = hint; - while (*pt) { - int ch; - int n = utf8_tounicode(pt, &ch); - int width = char_display_width(ch); - - if (width >= availcols) { - DRL(""); - break; - } - DRL_CHAR(ch); - - availcols -= width; - outputChars(current, pt, n); - pt += n; - } - if (bold || color > 0) { - clearOutputHighlight(current); - } - /* Call the function to free the hint returned. */ - if (freeHintsCallback) freeHintsCallback(hint, hintsUserdata); - } - } - } - return rc; -} - -#ifdef USE_TERMIOS -static void refreshStart(struct current *current) -{ - /* We accumulate all output here */ - assert(current->output == NULL); - current->output = sb_alloc(); -} +/* 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 refreshEnd(struct current *current) -{ - /* Output everything at once */ - IGNORE_RC(write(current->fd, sb_str(current->output), sb_len(current->output))); - sb_free(current->output); - current->output = NULL; +static void abInit(struct abuf *ab) { + ab->b = NULL; + ab->len = 0; } -static void refreshStartChars(struct current *current) -{ - (void)current; -} +static void abAppend(struct abuf *ab, const char *s, int len) { + char *new = realloc(ab->b,ab->len+len); -static void refreshNewline(struct current *current) -{ - DRL(""); - outputChars(current, "\n", 1); + if (new == NULL) return; + memcpy(new+ab->len,s,len); + ab->b = new; + ab->len += len; } -static void refreshEndChars(struct current *current) -{ - (void)current; +static void abFree(struct abuf *ab) { + free(ab->b); } -#endif - -static void refreshLineAlt(struct current *current, const char *prompt, const char *buf, int cursor_pos) -{ - int i; - const char *pt; - int displaycol; - int displayrow; - int visible; - int currentpos; - int notecursor; - int cursorcol = 0; - int cursorrow = 0; - int hint; - struct esc_parser parser; - -#ifdef DEBUG_REFRESHLINE - dfh = fopen("linenoise.debuglog", "a"); -#endif - /* Should intercept SIGWINCH. For now, just get the size every time */ - getWindowSize(current); - - refreshStart(current); - - DRL("wincols=%d, cursor_pos=%d, nrows=%d, rpos=%d\n", current->cols, cursor_pos, current->nrows, current->rpos); - - /* Here is the plan: - * (a) move the the bottom row, going down the appropriate number of lines - * (b) move to beginning of line and erase the current line - * (c) go up one line and do the same, until we have erased up to the first row - * (d) output the prompt, counting cols and rows, taking into account escape sequences - * (e) output the buffer, counting cols and rows - * (e') when we hit the current pos, save the cursor position - * (f) move the cursor to the saved cursor position - * (g) save the current cursor row and number of rows - */ - - /* (a) - The cursor is currently at row rpos */ - cursorDown(current, current->nrows - current->rpos - 1); - DRL("", current->nrows - current->rpos - 1); - - /* (b), (c) - Erase lines upwards until we get to the first row */ - for (i = 0; i < current->nrows; i++) { - if (i) { - DRL(""); - cursorUp(current, 1); +/* 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); } - DRL(""); - cursorToLeft(current); - eraseEol(current); } - DRL("\n"); - - /* (d) First output the prompt. control sequences don't take up display space */ - pt = prompt; - displaycol = 0; /* current display column */ - displayrow = 0; /* current display row */ - visible = 1; - - refreshStartChars(current); - - while (*pt) { - int width; - int ch; - int n = utf8_tounicode(pt, &ch); - - if (visible && ch == CHAR_ESCAPE) { - /* The start of an escape sequence, so not visible */ - visible = 0; - initParseEscapeSeq(&parser, 'm'); - DRL(""); - } - - if (ch == '\n' || ch == '\r') { - /* treat both CR and NL the same and force wrap */ - refreshNewline(current); - displaycol = 0; - displayrow++; - } - else { - width = visible * utf8_width(ch); - - displaycol += width; - if (displaycol >= current->cols) { - /* need to wrap to the next line because of newline or if it doesn't fit - * XXX this is a problem in single line mode - */ - refreshNewline(current); - displaycol = width; - displayrow++; - } +} - DRL_CHAR(ch); -#ifdef USE_WINCONSOLE - if (visible) { - outputChars(current, pt, n); - } -#else - outputChars(current, pt, n); -#endif - } - pt += n; - - if (!visible) { - switch (parseEscapeSequence(&parser, ch)) { - case EP_END: - visible = 1; - setOutputHighlight(current, parser.props, parser.numprops); - DRL("", parser.numprops); - break; - case EP_ERROR: - DRL(""); - visible = 1; - break; - } +/* 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. + * + * Flags is REFRESH_* macros. The function can just remove the old + * prompt, just write it, or both. */ +static void refreshSingleLine(struct linenoiseState *l, int flags) { + 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,sizeof(seq),"\r"); + abAppend(&ab,seq,strlen(seq)); + + if (flags & REFRESH_WRITE) { + /* 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); } - /* Now we are at the first line with all lines erased */ - DRL("\nafter prompt: displaycol=%d, displayrow=%d\n", displaycol, displayrow); + /* Erase to right */ + snprintf(seq,sizeof(seq),"\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); - - /* (e) output the buffer, counting cols and rows */ - if (mlmode == 0) { - /* In this mode we may need to trim chars from the start of the buffer until the - * cursor fits in the window. - */ - pt = reduceSingleBuf(buf, current->cols - displaycol, &cursor_pos); - } - else { - pt = buf; + if (flags & REFRESH_WRITE) { + /* Move cursor to original position. */ + snprintf(seq,sizeof(seq),"\r\x1b[%dC", (int)(pos+plen)); + abAppend(&ab,seq,strlen(seq)); } - currentpos = 0; - notecursor = -1; - - while (*pt) { - int ch; - int n = utf8_tounicode(pt, &ch); - int width = char_display_width(ch); - - if (currentpos == cursor_pos) { - /* (e') wherever we output this character is where we want the cursor */ - notecursor = 1; - } - - if (displaycol + width >= current->cols) { - if (mlmode == 0) { - /* In single line mode stop once we print as much as we can on one line */ - DRL(""); - break; - } - /* need to wrap to the next line since it doesn't fit */ - refreshNewline(current); - displaycol = 0; - displayrow++; - } + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ + abFree(&ab); +} - if (notecursor == 1) { - /* (e') Save this position as the current cursor position */ - cursorcol = displaycol; - cursorrow = displayrow; - notecursor = 0; - DRL(""); +/* 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. + * + * Flags is REFRESH_* macros. The function can just remove the old + * prompt, just write it, or both. */ +static void refreshMultiLine(struct linenoiseState *l, int flags) { + 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->oldrows; + int fd = l->ofd, j; + struct abuf ab; + + l->oldrows = rows; + + /* First step: clear all the lines used before. To do so start by + * going to the last row. */ + abInit(&ab); + + if (flags & REFRESH_CLEAN) { + 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)); } - displaycol += width; - - if (ch < ' ') { - outputControlChar(current, ch + '@'); - } - else { - outputChars(current, pt, n); + /* 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)); } - DRL_CHAR(ch); - if (width != 1) { - DRL("", width); - } - - pt += n; - currentpos++; } - /* If we didn't see the cursor, it is at the current location */ - if (notecursor) { - DRL(""); - cursorcol = displaycol; - cursorrow = displayrow; + if (flags & REFRESH_ALL) { + /* Clean the top line. */ + lndebug("clear"); + snprintf(seq,64,"\r\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); } - DRL("\nafter buf: displaycol=%d, displayrow=%d, cursorcol=%d, cursorrow=%d\n", displaycol, displayrow, cursorcol, cursorrow); - - /* (f) show hints */ - hint = refreshShowHints(current, buf, current->cols - displaycol, 1); + if (flags & REFRESH_WRITE) { + /* 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); + } - /* Remember how many many cols are available for insert optimisation */ - if (prompt == current->prompt && hint == 0) { - current->colsright = current->cols - displaycol; - current->colsleft = displaycol; - } - else { - /* Can't optimise */ - current->colsright = 0; - current->colsleft = 0; - } - DRL("\nafter hints: colsleft=%d, colsright=%d\n\n", current->colsleft, current->colsright); + /* 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->oldrows) l->oldrows = rows; + } - refreshEndChars(current); + /* Move cursor to right position. */ + rpos2 = (plen+l->pos+l->cols)/l->cols; /* Current cursor relative row */ + lndebug("rpos2 %d", rpos2); - /* (g) move the cursor to the correct place */ - cursorUp(current, displayrow - cursorrow); - setCursorPos(current, cursorcol); + /* 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)); + } - /* (h) Update the number of rows if larger, but never reduce this */ - if (displayrow >= current->nrows) { - current->nrows = displayrow + 1; + /* 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)); } - /* And remember the row that the cursor is on */ - current->rpos = cursorrow; - refreshEnd(current); + lndebug("\n"); + l->oldpos = l->pos; -#ifdef DEBUG_REFRESHLINE - fclose(dfh); -#endif + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ + abFree(&ab); } -static void refreshLine(struct current *current) -{ - refreshLineAlt(current, current->prompt, sb_str(current->buf), current->pos); +/* Calls the two low level functions refreshSingleLine() or + * refreshMultiLine() according to the selected mode. */ +static void refreshLineWithFlags(struct linenoiseState *l, int flags) { + if (mlmode) + refreshMultiLine(l,flags); + else + refreshSingleLine(l,flags); } -static void set_current(struct current *current, const char *str) -{ - sb_clear(current->buf); - sb_append(current->buf, str); - current->pos = sb_chars(current->buf); +/* Utility function to avoid specifying REFRESH_ALL all the times. */ +static void refreshLine(struct linenoiseState *l) { + refreshLineWithFlags(l,REFRESH_ALL); } -/** - * Removes the char at 'pos'. - * - * Returns 1 if the line needs to be refreshed, 2 if not - * and 0 if nothing was removed - */ -static int remove_char(struct current *current, int pos) -{ - if (pos >= 0 && pos < sb_chars(current->buf)) { - int offset = utf8_index(sb_str(current->buf), pos); - int nbytes = utf8_index(sb_str(current->buf) + offset, 1); - int rc = 1; - - /* Now we try to optimise in the simple but very common case that: - * - outputChars() can be used directly (not win32) - * - we are removing the char at EOL - * - the buffer is not empty - * - there are columns available to the left - * - the char being deleted is not a wide or utf-8 character - * - no hints are being shown - */ - if (current->output && current->pos == pos + 1 && current->pos == sb_chars(current->buf) && pos > 0) { -#ifdef USE_UTF8 - /* Could implement utf8_prev_len() but simplest just to not optimise this case */ - char last = sb_str(current->buf)[offset]; -#else - char last = 0; -#endif - if (current->colsleft > 0 && (last & 0x80) == 0) { - /* Have cols on the left and not a UTF-8 char or continuation */ - /* Yes, can optimise */ - current->colsleft--; - current->colsright++; - rc = 2; - } - } - - sb_delete(current->buf, offset, nbytes); +/* Hide the current line, when using the multiplexing API. */ +void linenoiseHide(struct linenoiseState *l) { + if (mlmode) + refreshMultiLine(l,REFRESH_CLEAN); + else + refreshSingleLine(l,REFRESH_CLEAN); +} - if (current->pos > pos) { - current->pos--; - } - if (rc == 2) { - if (refreshShowHints(current, sb_str(current->buf), current->colsright, 0)) { - /* A hint needs to be shown, so can't optimise after all */ - rc = 1; - } - else { - /* optimised output */ - outputChars(current, "\b \b", 3); - } - } - return rc; - return 1; +/* Show the current line, when using the multiplexing API. */ +void linenoiseShow(struct linenoiseState *l) { + if (l->in_completion) { + refreshLineWithCompletion(l,NULL,REFRESH_WRITE); + } else { + refreshLineWithFlags(l,REFRESH_WRITE); } - return 0; } -/** - * Insert 'ch' at position 'pos' +/* Insert the character 'c' at cursor current position. * - * Returns 1 if the line needs to be refreshed, 2 if not - * and 0 if nothing was inserted (no room) - */ -static int insert_char(struct current *current, int pos, int ch) -{ - if (pos >= 0 && pos <= sb_chars(current->buf)) { - char buf[MAX_UTF8_LEN + 1]; - int offset = utf8_index(sb_str(current->buf), pos); - int n = utf8_getchars(buf, ch); - int rc = 1; - - /* null terminate since sb_insert() requires it */ - buf[n] = 0; - - /* Now we try to optimise in the simple but very common case that: - * - outputChars() can be used directly (not win32) - * - we are inserting at EOL - * - there are enough columns available - * - no hints are being shown - */ - if (current->output && pos == current->pos && pos == sb_chars(current->buf)) { - int width = char_display_width(ch); - if (current->colsright > width) { - /* Yes, can optimise */ - current->colsright -= width; - current->colsleft -= width; - rc = 2; - } - } - sb_insert(current->buf, offset, buf); - if (current->pos >= pos) { - current->pos++; - } - if (rc == 2) { - if (refreshShowHints(current, sb_str(current->buf), current->colsright, 0)) { - /* A hint needs to be shown, so can't optimise after all */ - rc = 1; - } - else { - /* optimised output */ - outputChars(current, buf, n); + * 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 rc; } return 0; } -/** - * Captures up to 'n' characters starting at 'pos' for the cut buffer. - * - * This replaces any existing characters in the cut buffer. - */ -static void capture_chars(struct current *current, int pos, int nchars) -{ - if (pos >= 0 && (pos + nchars - 1) < sb_chars(current->buf)) { - int offset = utf8_index(sb_str(current->buf), pos); - int nbytes = utf8_index(sb_str(current->buf) + offset, nchars); - - if (nbytes > 0) { - if (current->capture) { - sb_clear(current->capture); - } - else { - current->capture = sb_alloc(); - } - sb_append_len(current->capture, sb_str(current->buf) + offset, nbytes); - } +/* Move cursor on the left. */ +void linenoiseEditMoveLeft(struct linenoiseState *l) { + if (l->pos > 0) { + l->pos--; + refreshLine(l); } } -/** - * Removes up to 'n' characters at cursor position 'pos'. - * - * Returns 0 if no chars were removed or non-zero otherwise. - */ -static int remove_chars(struct current *current, int pos, int n) -{ - int removed = 0; +/* Move cursor on the right. */ +void linenoiseEditMoveRight(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos++; + refreshLine(l); + } +} - /* First save any chars which will be removed */ - capture_chars(current, pos, n); +/* Move cursor to the start of the line. */ +void linenoiseEditMoveHome(struct linenoiseState *l) { + if (l->pos != 0) { + l->pos = 0; + refreshLine(l); + } +} - while (n-- && remove_char(current, pos)) { - removed++; +/* Move cursor to the end of the line. */ +void linenoiseEditMoveEnd(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos = l->len; + refreshLine(l); } - return removed; } -/** - * Inserts the characters (string) 'chars' at the cursor position 'pos'. - * - * Returns 0 if no chars were inserted or non-zero otherwise. - */ -static int insert_chars(struct current *current, int pos, const char *chars) -{ - int inserted = 0; - while (*chars) { - int ch; - int n = utf8_tounicode(chars, &ch); - if (insert_char(current, pos, ch) == 0) { - break; +/* 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; } - inserted++; - pos++; - chars += n; + 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); } - return inserted; } -static int skip_space_nonspace(struct current *current, int dir, int check_is_space) -{ - int moved = 0; - int checkoffset = (dir < 0) ? -1 : 0; - int limit = (dir < 0) ? 0 : sb_chars(current->buf); - while (current->pos != limit && (get_char(current, current->pos + checkoffset) == ' ') == check_is_space) { - current->pos += dir; - moved++; +/* 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); } - return moved; } -static int skip_space(struct current *current, int dir) -{ - return skip_space_nonspace(current, dir, 1); +/* 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); + } } -static int skip_nonspace(struct current *current, int dir) -{ - return skip_space_nonspace(current, dir, 0); +/* 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 part of the multiplexed API of Linenoise, that is used + * in order to implement the blocking variant of the API but can also be + * called by the user directly in an event driven program. It will: + * + * 1. Initialize the linenoise state passed by the user. + * 2. Put the terminal in RAW mode. + * 3. Show the prompt. + * 4. Return control to the user, that will have to call linenoiseEditFeed() + * each time there is some data arriving in the standard input. + * + * The user can also call linenoiseEditHide() and linenoiseEditShow() if it + * is required to show some input arriving asyncronously, without mixing + * it with the currently edited line. + * + * When linenoiseEditFeed() returns non-NULL, the user finished with the + * line editing session (pressed enter CTRL-D/C): in this case the caller + * needs to call linenoiseEditStop() to put back the terminal in normal + * mode. This will not destroy the buffer, as long as the linenoiseState + * is still valid in the context of the caller. + * + * The function returns 0 on success, or -1 if writing to standard output + * fails. If stdin_fd or stdout_fd are set to -1, the default is to use + * STDIN_FILENO and STDOUT_FILENO. + */ +int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) { + /* Populate the linenoise state that we pass to functions implementing + * specific editing functionalities. */ + l->in_completion = 0; + l->ifd = stdin_fd != -1 ? stdin_fd : STDIN_FILENO; + l->ofd = stdout_fd != -1 ? stdout_fd : STDOUT_FILENO; + l->buf = buf; + l->buflen = buflen; + l->prompt = prompt; + l->plen = strlen(prompt); + l->oldpos = l->pos = 0; + l->len = 0; + + /* Enter raw mode. */ + if (enableRawMode(l->ifd) == -1) return -1; + + l->cols = getColumns(stdin_fd, stdout_fd); + l->oldrows = 0; + l->history_index = 0; + + /* Buffer starts empty. */ + l->buf[0] = '\0'; + l->buflen--; /* Make sure there is always space for the nulterm */ + + /* If stdin is not a tty, stop here with the initialization. We + * will actually just read a line from standard input in blocking + * mode later, in linenoiseEditFeed(). */ + if (!isatty(l->ifd)) return 0; + + /* 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; + return 0; } -/** - * Returns the keycode to process, or 0 if none. +char *linenoiseEditMore = "If you see this, you are misusing the API: when linenoiseEditFeed() is called, if it returns linenoiseEditMore the user is yet editing the line. See the README file for more information."; + +/* This function is part of the multiplexed API of linenoise, see the top + * comment on linenoiseEditStart() for more information. Call this function + * each time there is some data to read from the standard input file + * descriptor. In the case of blocking operations, this function can just be + * called in a loop, and block. + * + * The function returns linenoiseEditMore to signal that line editing is still + * in progress, that is, the user didn't yet pressed enter / CTRL-D. Otherwise + * the function returns the pointer to the heap-allocated buffer with the + * edited line, that the user should free with linenoiseFree(). + * + * On special conditions, NULL is returned and errno is populated: + * + * EAGAIN if the user pressed Ctrl-C + * ENOENT if the user pressed Ctrl-D + * + * Some other errno: I/O error. */ -static int reverseIncrementalSearch(struct current *current) -{ - /* Display the reverse-i-search prompt and process chars */ - char rbuf[50]; - char rprompt[80]; - int rchars = 0; - int rlen = 0; - int searchpos = history_len - 1; - int c; - - rbuf[0] = 0; - while (1) { - int n = 0; - const char *p = NULL; - int skipsame = 0; - int searchdir = -1; - - snprintf(rprompt, sizeof(rprompt), "(reverse-i-search)'%s': ", rbuf); - refreshLineAlt(current, rprompt, sb_str(current->buf), current->pos); - c = fd_read(current); - if (c == ctrl('H') || c == CHAR_DELETE) { - if (rchars) { - int p_ind = utf8_index(rbuf, --rchars); - rbuf[p_ind] = 0; - rlen = strlen(rbuf); - } - continue; - } -#ifdef USE_TERMIOS - if (c == CHAR_ESCAPE) { - c = check_special(current->fd); +char *linenoiseEditFeed(struct linenoiseState *l) { + /* Not a TTY, pass control to line reading without character + * count limits. */ + if (!isatty(l->ifd)) return linenoiseNoTTY(); + + char c; + int nread; + char seq[3]; + + nread = read(l->ifd,&c,1); + if (nread <= 0) return NULL; + + /* 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 ((l->in_completion || c == 9) && completionCallback != NULL) { + c = completeLine(l,c); + /* Return on errors */ + if (c < 0) return NULL; + /* Read next character when 0 */ + if (c == 0) return linenoiseEditMore; + } + + 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; } -#endif - if (c == ctrl('P') || c == SPECIAL_UP) { - /* Search for the previous (earlier) match */ - if (searchpos > 0) { - searchpos--; - } - skipsame = 1; + return strdup(l->buf); + case CTRL_C: /* ctrl-c */ + errno = EAGAIN; + return NULL; + 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]); + errno = ENOENT; + return NULL; } - else if (c == ctrl('N') || c == SPECIAL_DOWN) { - /* Search for the next (later) match */ - if (searchpos < history_len) { - searchpos++; - } - searchdir = 1; - skipsame = 1; + break; + case CTRL_T: /* ctrl-t, swaps current character with previous. */ + if (l->pos > 0 && l->pos < l->len) { + int aux = l->buf[l->pos-1]; + l->buf[l->pos-1] = l->buf[l->pos]; + l->buf[l->pos] = aux; + if (l->pos != l->len-1) l->pos++; + refreshLine(l); } - else if (c >= ' ' && c <= '~') { - /* >= here to allow for null terminator */ - if (rlen >= (int)sizeof(rbuf) - MAX_UTF8_LEN) { - continue; + 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; + } } - - n = utf8_getchars(rbuf + rlen, c); - rlen += n; - rchars++; - rbuf[rlen] = 0; - - /* Adding a new char resets the search location */ - searchpos = history_len - 1; - } - else { - /* Exit from incremental search mode */ - break; } - /* Now search through the history for a match */ - for (; searchpos >= 0 && searchpos < history_len; searchpos += searchdir) { - p = strstr(history[searchpos], rbuf); - if (p) { - /* Found a match */ - if (skipsame && strcmp(history[searchpos], sb_str(current->buf)) == 0) { - /* But it is identical, so skip it */ - continue; - } - /* Copy the matching line and set the cursor position */ - set_current(current,history[searchpos]); - current->pos = utf8_strlen(history[searchpos], p - history[searchpos]); + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch(seq[1]) { + case 'H': /* Home */ + linenoiseEditMoveHome(l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(l); break; } } - if (!p && n) { - /* No match, so don't add it */ - rchars--; - rlen -= n; - rbuf[rlen] = 0; - } - } - if (c == ctrl('G') || c == ctrl('C')) { - /* ctrl-g terminates the search with no effect */ - set_current(current, ""); - c = 0; - } - else if (c == ctrl('J')) { - /* ctrl-j terminates the search leaving the buffer in place */ - c = 0; + break; + default: + if (linenoiseEditInsert(l,c)) return NULL; + break; + case CTRL_U: /* Ctrl+u, delete the whole line. */ + l->buf[0] = '\0'; + l->pos = l->len = 0; + refreshLine(l); + break; + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ + l->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; } - - /* Go process the char normally */ - refreshLine(current); - return c; + return linenoiseEditMore; } -static int linenoiseEdit(struct current *current) { - int history_index = 0; - - refreshLine(current); +/* This is part of the multiplexed linenoise API. See linenoiseEditStart() + * for more information. This function is called when linenoiseEditFeed() + * returns something different than NULL. At this point the user input + * is in the buffer, and we can restore the terminal in normal mode. */ +void linenoiseEditStop(struct linenoiseState *l) { + if (!isatty(l->ifd)) return; + disableRawMode(l->ifd); + printf("\n"); +} - while(1) { - int dir = -1; - int c = fd_read(current); - -#ifndef NO_COMPLETION - /* 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 == '\t' && current->pos == sb_chars(current->buf) && completionCallback != NULL) { - c = completeLine(current); - } -#endif - if (c == ctrl('R')) { - /* reverse incremental search will provide an alternative keycode or 0 for none */ - c = reverseIncrementalSearch(current); - /* go on to process the returned char normally */ - } +/* This just implements a blocking loop for the multiplexed API. + * In many applications that are not event-drivern, we can just call + * the blocking linenoise API, wait for the user to complete the editing + * and return the buffer. */ +static char *linenoiseBlockingEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) +{ + struct linenoiseState l; -#ifdef USE_TERMIOS - if (c == CHAR_ESCAPE) { /* escape sequence */ - c = check_special(current->fd); - } -#endif - if (c == -1) { - /* Return on errors */ - return sb_len(current->buf); - } + /* Editing without a buffer is invalid. */ + if (buflen == 0) { + errno = EINVAL; + return NULL; + } - switch(c) { - case SPECIAL_NONE: - break; - case '\r': /* enter/CR */ - case '\n': /* LF */ - history_len--; - free(history[history_len]); - current->pos = sb_chars(current->buf); - if (mlmode || hintsCallback) { - showhints = 0; - refreshLine(current); - showhints = 1; - } - return sb_len(current->buf); - case ctrl('C'): /* ctrl-c */ - errno = EAGAIN; - return -1; - case ctrl('Z'): /* ctrl-z */ -#ifdef SIGTSTP - /* send ourselves SIGSUSP */ - disableRawMode(current); - raise(SIGTSTP); - /* and resume */ - enableRawMode(current); - refreshLine(current); -#endif - continue; - case CHAR_DELETE: /* backspace */ - case ctrl('H'): - if (remove_char(current, current->pos - 1) == 1) { - refreshLine(current); - } - break; - case ctrl('D'): /* ctrl-d */ - if (sb_len(current->buf) == 0) { - /* Empty line, so EOF */ - history_len--; - free(history[history_len]); - return -1; - } - /* Otherwise fall through to delete char to right of cursor */ - /* fall-thru */ - case SPECIAL_DELETE: - if (remove_char(current, current->pos) == 1) { - refreshLine(current); - } - break; - case SPECIAL_INSERT: - /* Ignore. Expansion Hook. - * Future possibility: Toggle Insert/Overwrite Modes - */ - break; - case meta('b'): /* meta-b, move word left */ - if (skip_nonspace(current, -1)) { - refreshLine(current); - } - else if (skip_space(current, -1)) { - skip_nonspace(current, -1); - refreshLine(current); - } - break; - case meta('f'): /* meta-f, move word right */ - if (skip_space(current, 1)) { - refreshLine(current); - } - else if (skip_nonspace(current, 1)) { - skip_space(current, 1); - refreshLine(current); - } - break; - case ctrl('W'): /* ctrl-w, delete word at left. save deleted chars */ - /* eat any spaces on the left */ - { - int pos = current->pos; - while (pos > 0 && get_char(current, pos - 1) == ' ') { - pos--; - } + linenoiseEditStart(&l,stdin_fd,stdout_fd,buf,buflen,prompt); + char *res; + while((res = linenoiseEditFeed(&l)) == linenoiseEditMore); + linenoiseEditStop(&l); + return res; +} - /* now eat any non-spaces on the left */ - while (pos > 0 && get_char(current, pos - 1) != ' ') { - pos--; - } +/* 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]; - if (remove_chars(current, pos, current->pos - pos)) { - refreshLine(current); - } - } - break; - case ctrl('T'): /* ctrl-t */ - if (current->pos > 0 && current->pos <= sb_chars(current->buf)) { - /* If cursor is at end, transpose the previous two chars */ - int fixer = (current->pos == sb_chars(current->buf)); - c = get_char(current, current->pos - fixer); - remove_char(current, current->pos - fixer); - insert_char(current, current->pos - 1, c); - refreshLine(current); - } - break; - case ctrl('V'): /* ctrl-v */ - /* Insert the ^V first */ - if (insert_char(current, current->pos, c)) { - refreshLine(current); - /* Now wait for the next char. Can insert anything except \0 */ - c = fd_read(current); - - /* Remove the ^V first */ - remove_char(current, current->pos - 1); - if (c > 0) { - /* Insert the actual char, can't be error or null */ - insert_char(current, current->pos, c); - } - refreshLine(current); - } - break; - case ctrl('B'): - case SPECIAL_LEFT: - if (current->pos > 0) { - current->pos--; - refreshLine(current); - } - break; - case ctrl('F'): - case SPECIAL_RIGHT: - if (current->pos < sb_chars(current->buf)) { - current->pos++; - refreshLine(current); - } - break; - case SPECIAL_PAGE_UP: - dir = history_len - history_index - 1; /* move to start of history */ - goto history_navigation; - case SPECIAL_PAGE_DOWN: - dir = -history_index; /* move to 0 == end of history, i.e. current */ - goto history_navigation; - case ctrl('P'): - case SPECIAL_UP: - dir = 1; - goto history_navigation; - case ctrl('N'): - case SPECIAL_DOWN: -history_navigation: - if (history_len > 1) { - /* Update the current history entry before to - * overwrite it with tne next one. */ - free(history[history_len - 1 - history_index]); - history[history_len - 1 - history_index] = strdup(sb_str(current->buf)); - /* Show the new entry */ - history_index += dir; - if (history_index < 0) { - history_index = 0; - break; - } else if (history_index >= history_len) { - history_index = history_len - 1; - break; - } - set_current(current, history[history_len - 1 - history_index]); - refreshLine(current); - } - break; - case ctrl('A'): /* Ctrl+a, go to the start of the line */ - case SPECIAL_HOME: - current->pos = 0; - refreshLine(current); - break; - case ctrl('E'): /* ctrl+e, go to the end of the line */ - case SPECIAL_END: - current->pos = sb_chars(current->buf); - refreshLine(current); - break; - case ctrl('U'): /* Ctrl+u, delete to beginning of line, save deleted chars. */ - if (remove_chars(current, 0, current->pos)) { - refreshLine(current); - } - break; - case ctrl('K'): /* Ctrl+k, delete from current to end of line, save deleted chars. */ - if (remove_chars(current, current->pos, sb_chars(current->buf) - current->pos)) { - refreshLine(current); - } - break; - case ctrl('Y'): /* Ctrl+y, insert saved chars at current position */ - if (current->capture && insert_chars(current, current->pos, sb_str(current->capture))) { - refreshLine(current); - } - break; - case ctrl('L'): /* Ctrl+L, clear screen */ - linenoiseClearScreen(); - /* Force recalc of window size for serial terminals */ - current->cols = 0; - current->rpos = 0; - refreshLine(current); - break; - default: - if (c >= meta('a') && c <= meta('z')) { - /* Don't insert meta chars that are not bound */ - break; - } - /* Only tab is allowed without ^V */ - if (c == '\t' || c >= ' ') { - if (insert_char(current, current->pos, c) == 1) { - refreshLine(current); - } - } - break; - } + 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); } - return sb_len(current->buf); + disableRawMode(STDIN_FILENO); } -int linenoiseColumns(void) -{ - struct current current; - current.output = NULL; - enableRawMode (¤t); - getWindowSize (¤t); - disableRawMode (¤t); - return current.cols; -} +/* 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; -/** - * Reads a line from the file handle (without the trailing NL or CRNL) - * and returns it in a stringbuf. - * Returns NULL if no characters are read before EOF or error. - * - * Note that the character count will *not* be correct for lines containing - * utf8 sequences. Do not rely on the character count. - */ -static stringbuf *sb_getline(FILE *fh) -{ - stringbuf *sb = sb_alloc(); - int c; - int n = 0; - - while ((c = getc(fh)) != EOF) { - char ch; - n++; - if (c == '\r') { - /* CRLF -> LF */ - continue; + 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; + } } - if (c == '\n' || c == '\r') { - break; + 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++; } - ch = c; - /* ignore the effect of character count for partial utf8 sequences */ - sb_append_len(sb, &ch, 1); - } - if (n == 0 || sb->data == NULL) { - sb_free(sb); - return NULL; } - return sb; } -char *linenoiseWithInitial(const char *prompt, const char *initial) -{ - int count; - struct current current; - stringbuf *sb; +/* 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]; - memset(¤t, 0, sizeof(current)); + 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; - if (enableRawMode(¤t) == -1) { - printf("%s", prompt); + printf("%s",prompt); fflush(stdout); - sb = sb_getline(stdin); - if (sb && !fd_isatty(¤t)) { - printf("%s\n", sb_str(sb)); - 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 { + char *retval = linenoiseBlockingEdit(STDIN_FILENO,STDOUT_FILENO,buf,LINENOISE_MAX_LINE,prompt); + return retval; } - else { - current.buf = sb_alloc(); - current.pos = 0; - current.nrows = 1; - current.prompt = prompt; +} - /* The latest history entry is always our current buffer */ - linenoiseHistoryAdd(initial); - set_current(¤t, initial); +/* 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) { + if (ptr == linenoiseEditMore) return; // Protect from API misuse. + free(ptr); +} - count = linenoiseEdit(¤t); +/* ================================ History ================================= */ - disableRawMode(¤t); - printf("\n"); +/* 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; - sb_free(current.capture); - if (count == -1) { - sb_free(current.buf); - return NULL; - } - sb = current.buf; + for (j = 0; j < history_len; j++) + free(history[j]); + free(history); } - return sb ? sb_to_string(sb) : NULL; } -char *linenoise(const char *prompt) -{ - return linenoiseWithInitial(prompt, ""); +/* At exit we'll try to fix the terminal to the initial conditions. */ +static void linenoiseAtExit(void) { + disableRawMode(STDIN_FILENO); + freeHistory(); } -/* Using a circular buffer is smarter, but a bit more complex to handle. */ -static int linenoiseHistoryAddAllocated(char *line) { +/* 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) { -notinserted: - free(line); - return 0; - } + if (history_max_len == 0) return 0; + + /* Initialization on first call. */ if (history == NULL) { - history = (char **)calloc(sizeof(char*), history_max_len); + history = malloc(sizeof(char*)*history_max_len); + if (history == NULL) return 0; + memset(history,0,(sizeof(char*)*history_max_len)); } - /* do not insert duplicate lines into history */ - if (history_len > 0 && strcmp(line, history[history_len - 1]) == 0) { - goto notinserted; - } + /* 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] = line; + history[history_len] = linecopy; history_len++; return 1; } -int linenoiseHistoryAdd(const char *line) { - return linenoiseHistoryAddAllocated(strdup(line)); -} - -int linenoiseHistoryGetMaxLen(void) { - return history_max_len; -} - +/* 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 **newHistory; + char **new; if (len < 1) return 0; if (history) { int tocopy = history_len; - newHistory = (char **)calloc(sizeof(char*), 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) { @@ -1976,9 +1297,10 @@ int linenoiseHistorySetMaxLen(int len) { for (j = 0; j < tocopy-len; j++) free(history[j]); tocopy = len; } - memcpy(newHistory,history+(history_len-tocopy), sizeof(char*)*tocopy); + memset(new,0,sizeof(char*)*len); + memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); free(history); - history = newHistory; + history = new; } history_max_len = len; if (history_len > history_max_len) @@ -1989,84 +1311,40 @@ int linenoiseHistorySetMaxLen(int len) { /* Save the history in the specified file. On success 0 is returned * otherwise -1 is returned. */ int linenoiseHistorySave(const char *filename) { - FILE *fp = fopen(filename,"w"); + 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; - for (j = 0; j < history_len; j++) { - const char *str = history[j]; - /* Need to encode backslash, nl and cr */ - while (*str) { - if (*str == '\\') { - fputs("\\\\", fp); - } - else if (*str == '\n') { - fputs("\\n", fp); - } - else if (*str == '\r') { - fputs("\\r", fp); - } - else { - fputc(*str, fp); - } - str++; - } - fputc('\n', fp); - } - + 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. +/* Load the history from the specified file. If the file does not exist + * zero is returned and no operation is performed. * - * If the file does not exist or can't be opened, no operation is performed - * and -1 is returned. - * Otherwise 0 is returned. - */ + * 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"); - stringbuf *sb; + char buf[LINENOISE_MAX_LINE]; if (fp == NULL) return -1; - while ((sb = sb_getline(fp)) != NULL) { - /* Take the stringbuf and decode backslash escaped values */ - char *buf = sb_to_string(sb); - char *dest = buf; - const char *src; - - for (src = buf; *src; src++) { - char ch = *src; - - if (ch == '\\') { - src++; - if (*src == 'n') { - ch = '\n'; - } - else if (*src == 'r') { - ch = '\r'; - } else { - ch = *src; - } - } - *dest++ = ch; - } - *dest = 0; + while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { + char *p; - linenoiseHistoryAddAllocated(buf); + p = strchr(buf,'\r'); + if (!p) p = strchr(buf,'\n'); + if (p) *p = '\0'; + linenoiseHistoryAdd(buf); } fclose(fp); return 0; } -/* Provide access to the history buffer. - * - * If 'len' is not NULL, the length is stored in *len. - */ -char **linenoiseHistory(int *len) { - if (len) { - *len = history_len; - } - return history; -} \ No newline at end of file diff --git a/src/extras/linenoise/linenoise.h b/src/extras/linenoise/linenoise.h index 9c06f185e8..a5fe7aae69 100644 --- a/src/extras/linenoise/linenoise.h +++ b/src/extras/linenoise/linenoise.h @@ -1,12 +1,14 @@ -/* linenoise.h -- guerrilla line editing library against the idea that a - * line editing lib needs to be 20,000 lines of C code. +/* 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, Salvatore Sanfilippo - * Copyright (c) 2010, Pieter Noordhuis + * Copyright (c) 2010-2023, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis * * All rights reserved. * @@ -41,109 +43,72 @@ extern "C" { #endif -#ifndef NO_COMPLETION +#include /* For size_t. */ + +extern char *linenoiseEditMore; + +/* The linenoiseState structure represents the state during line editing. + * We pass this state to functions implementing specific editing + * functionalities. */ +struct linenoiseState { + int in_completion; /* The user pressed TAB and we are now in completion + * mode, so input is handled by completeLine(). */ + size_t completion_idx; /* Index of next completion to propose. */ + 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 oldrows; /* Rows used by last refrehsed line (multiline mode) */ + int history_index; /* The history index we are currently editing. */ +}; + typedef struct linenoiseCompletions { size_t len; char **cvec; } linenoiseCompletions; -/* - * The callback type for tab completion handlers. - */ -typedef void(linenoiseCompletionCallback)(const char *prefix, linenoiseCompletions *comp, void *userdata); - -/* - * Sets the current tab completion handler and returns the previous one, or NULL - * if no prior one has been set. - */ -linenoiseCompletionCallback * linenoiseSetCompletionCallback(linenoiseCompletionCallback *comp, void *userdata); - -/* - * Adds a copy of the given string to the given completion list. The copy is owned - * by the linenoiseCompletions object. - */ -void linenoiseAddCompletion(linenoiseCompletions *comp, const char *str); - -typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold, void *userdata); -typedef void(linenoiseFreeHintsCallback)(void *hint, void *userdata); -void linenoiseSetHintsCallback(linenoiseHintsCallback *callback, void *userdata); -void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *callback); - -#endif +/* Non blocking API. */ +int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt); +char *linenoiseEditFeed(struct linenoiseState *l); +void linenoiseEditStop(struct linenoiseState *l); +void linenoiseHide(struct linenoiseState *l); +void linenoiseShow(struct linenoiseState *l); -/* - * Prompts for input using the given string as the input - * prompt. Returns when the user has tapped ENTER or (on an empty - * line) EOF (Ctrl-D on Unix, Ctrl-Z on Windows). Returns either - * a copy of the entered string (for ENTER) or NULL (on EOF). The - * caller owns the returned string and must eventually free() it. - */ +/* Blocking API. */ char *linenoise(const char *prompt); - -/** - * Like linenoise() but starts with an initial buffer. - */ -char *linenoiseWithInitial(const char *prompt, const char *initial); - -/** - * Clear the screen. - */ -void linenoiseClearScreen(void); - -/* - * Adds a copy of the given line of the command history. - */ +void linenoiseFree(void *ptr); + +/* Completion API. */ +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 *); + +/* History API. */ int linenoiseHistoryAdd(const char *line); - -/* - * Sets the maximum length of the command history, in lines. - * If the history is currently longer, it will be trimmed, - * retaining only the most recent entries. If len is 0 or less - * then this function does nothing. - */ int linenoiseHistorySetMaxLen(int len); - -/* - * Returns the current maximum length of the history, in lines. - */ -int linenoiseHistoryGetMaxLen(void); - -/* - * Saves the current contents of the history to the given file. - * Returns 0 on success. - */ int linenoiseHistorySave(const char *filename); - -/* - * Replaces the current history with the contents - * of the given file. Returns 0 on success. - */ int linenoiseHistoryLoad(const char *filename); -/* - * Frees all history entries, clearing the history. - */ -void linenoiseHistoryFree(void); - -/* - * Returns a pointer to the list of history entries, writing its - * length to *len if len is not NULL. The memory is owned by linenoise - * and must not be freed. - */ -char **linenoiseHistory(int *len); - -/* - * Returns the number of display columns in the current terminal. - */ -int linenoiseColumns(void); - -/** - * Enable or disable multiline mode (disabled by default) - */ -void linenoiseSetMultiLine(int enableml); +/* Other utilities. */ +void linenoiseClearScreen(void); +void linenoiseSetMultiLine(int ml); +void linenoisePrintKeyCodes(void); +void linenoiseMaskModeEnable(void); +void linenoiseMaskModeDisable(void); #ifdef __cplusplus } #endif -#endif /* __LINENOISE_H */ \ No newline at end of file +#endif /* __LINENOISE_H */ + From 00800ff1337761aeca6b80f010dcffc70da7b9e7 Mon Sep 17 00:00:00 2001 From: Eloi Torrents Date: Thu, 26 Sep 2024 12:58:34 +0200 Subject: [PATCH 2/9] Experiment with linenoise on windows --- src/extras/linenoise/linenoise-win32.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/extras/linenoise/linenoise-win32.c b/src/extras/linenoise/linenoise-win32.c index 8d1c843bce..716b26cb04 100644 --- a/src/extras/linenoise/linenoise-win32.c +++ b/src/extras/linenoise/linenoise-win32.c @@ -345,6 +345,8 @@ static int fd_read(struct current *current) return SPECIAL_PAGE_UP; case VK_NEXT: return SPECIAL_PAGE_DOWN; + case VK_RETURN: + return k->uChar.UnicodeChar; } } /* Note that control characters are already translated in AsciiChar */ @@ -374,4 +376,4 @@ static int getWindowSize(struct current *current) current->y = info.dwCursorPosition.Y; current->x = info.dwCursorPosition.X; return 0; -} \ No newline at end of file +} From 8c8e5d93576b117f98f2d6c6b1a662fb150b79d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yanis=20Zafir=C3=B3pulos?= <1265028+drkameleon@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:09:56 +0200 Subject: [PATCH 3/9] fixed function signatures --- src/extras/linenoise.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extras/linenoise.nim b/src/extras/linenoise.nim index 03c860318d..534450cc58 100644 --- a/src/extras/linenoise.nim +++ b/src/extras/linenoise.nim @@ -51,8 +51,8 @@ type {.push header: "linenoise/linenoise.h", cdecl.} -proc linenoiseSetCompletionCallback*(cback: LinenoiseCompletionCallback, userdata: pointer): LinenoiseCompletionCallback {.importc: "linenoiseSetCompletionCallback".} -proc linenoiseSetHintsCallback*(callback: LinenoiseHintsCallback, userdata: pointer) {.importc: "linenoiseSetHintsCallback".} +proc linenoiseSetCompletionCallback*(cback: LinenoiseCompletionCallback): LinenoiseCompletionCallback {.importc: "linenoiseSetCompletionCallback".} +proc linenoiseSetHintsCallback*(callback: LinenoiseHintsCallback) {.importc: "linenoiseSetHintsCallback".} proc linenoiseAddCompletion*(a2: ptr LinenoiseCompletions; a3: cstring) {.importc: "linenoiseAddCompletion".} proc linenoiseReadLine*(prompt: cstring): cstring {.importc: "linenoise".} proc linenoiseHistoryAdd*(line: cstring): cint {.importc: "linenoiseHistoryAdd", discardable.} From 0deb5eb2be73f95ac5c7d2fc5d4c6e351f5bf875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yanis=20Zafir=C3=B3pulos?= <1265028+drkameleon@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:10:50 +0200 Subject: [PATCH 4/9] also fixed function calls --- src/helpers/repl.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/repl.nim b/src/helpers/repl.nim index 434ea2d101..9cf4450d3b 100644 --- a/src/helpers/repl.nim +++ b/src/helpers/repl.nim @@ -80,9 +80,9 @@ when not defined(WEB): createDir(parentDir(path)) discard linenoiseHistoryLoad(path) - discard linenoiseSetCompletionCallback(completionsCback, nil) + discard linenoiseSetCompletionCallback(completionsCback) - linenoiseSetHintsCallback(hintsCback, nil) + linenoiseSetHintsCallback(hintsCback) ReplInitialized = true From b940765a69022d492e570f4ff302d0a0d96b1ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yanis=20Zafir=C3=B3pulos?= <1265028+drkameleon@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:13:34 +0200 Subject: [PATCH 5/9] why would it return *that*?! --- src/extras/linenoise.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extras/linenoise.nim b/src/extras/linenoise.nim index 534450cc58..add9ba7f2a 100644 --- a/src/extras/linenoise.nim +++ b/src/extras/linenoise.nim @@ -51,7 +51,7 @@ type {.push header: "linenoise/linenoise.h", cdecl.} -proc linenoiseSetCompletionCallback*(cback: LinenoiseCompletionCallback): LinenoiseCompletionCallback {.importc: "linenoiseSetCompletionCallback".} +proc linenoiseSetCompletionCallback*(cback: LinenoiseCompletionCallback) {.importc: "linenoiseSetCompletionCallback".} proc linenoiseSetHintsCallback*(callback: LinenoiseHintsCallback) {.importc: "linenoiseSetHintsCallback".} proc linenoiseAddCompletion*(a2: ptr LinenoiseCompletions; a3: cstring) {.importc: "linenoiseAddCompletion".} proc linenoiseReadLine*(prompt: cstring): cstring {.importc: "linenoise".} From 23e52a88624bbdecdae4b29194886816e6ad9f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yanis=20Zafir=C3=B3pulos?= <1265028+drkameleon@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:14:10 +0200 Subject: [PATCH 6/9] re-fix this too (= nothing to `discard`) --- src/helpers/repl.nim | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/helpers/repl.nim b/src/helpers/repl.nim index 9cf4450d3b..6187a8da04 100644 --- a/src/helpers/repl.nim +++ b/src/helpers/repl.nim @@ -80,8 +80,7 @@ when not defined(WEB): createDir(parentDir(path)) discard linenoiseHistoryLoad(path) - discard linenoiseSetCompletionCallback(completionsCback) - + linenoiseSetCompletionCallback(completionsCback) linenoiseSetHintsCallback(hintsCback) ReplInitialized = true From 35d670dccba554a2e0b15e93ae04c54b9965d0a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yanis=20Zafir=C3=B3pulos?= <1265028+drkameleon@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:20:02 +0200 Subject: [PATCH 7/9] so, no `userdata` anymore... --- src/extras/linenoise.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/extras/linenoise.nim b/src/extras/linenoise.nim index add9ba7f2a..84957734ab 100644 --- a/src/extras/linenoise.nim +++ b/src/extras/linenoise.nim @@ -41,9 +41,9 @@ type len*: csize_t cvec*: cstringArray - LinenoiseCompletionCallback* = proc (buf: constChar; lc: ptr LinenoiseCompletions, userdata: pointer) {.cdecl.} - LinenoiseHintsCallback* = proc (buf: constChar; color: var cint; bold: var cint, userdata: pointer): cstring {.cdecl.} - LinenoiseFreeHintsCallback* = proc (buf: constChar; color: var cint; bold: var cint, userdata: pointer) {.cdecl.} + LinenoiseCompletionCallback* = proc (buf: constChar; lc: ptr LinenoiseCompletions) {.cdecl.} + LinenoiseHintsCallback* = proc (buf: constChar; color: var cint; bold: var cint): cstring {.cdecl.} + LinenoiseFreeHintsCallback* = proc (buf: constChar; color: var cint; bold: var cint) {.cdecl.} #======================================= # Function prototypes From 754d0766f2a100f8b4db367c94de790dff9e0329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yanis=20Zafir=C3=B3pulos?= <1265028+drkameleon@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:20:40 +0200 Subject: [PATCH 8/9] and no `userdata` here either... --- src/helpers/repl.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/repl.nim b/src/helpers/repl.nim index 6187a8da04..e088b76fbf 100644 --- a/src/helpers/repl.nim +++ b/src/helpers/repl.nim @@ -53,7 +53,7 @@ when not defined(WEB): completions = completionsArray hints = hintsTable - proc completionsCback(buf: constChar; lc: ptr LinenoiseCompletions, userdata: pointer) {.cdecl.} = + proc completionsCback(buf: constChar; lc: ptr LinenoiseCompletions) {.cdecl.} = var token = $(buf) var copied = strip($(buf)) let tokenParts = splitWhitespace(token) @@ -64,7 +64,7 @@ when not defined(WEB): linenoiseAddCompletion(lc, (copied & item).cstring) - proc hintsCback(buf: constChar; color: var cint; bold: var cint, userdata: pointer): cstring {.cdecl.} = + proc hintsCback(buf: constChar; color: var cint; bold: var cint): cstring {.cdecl.} = var token = $(buf) let tokenParts = splitWhitespace(token) if tokenParts.len >= 1: From 3e315fbef52a96473235c3d5b693f9002078c136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yanis=20Zafir=C3=B3pulos?= <1265028+drkameleon@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:27:56 +0200 Subject: [PATCH 9/9] temporarily disable Windows builds So, that we can at least reach the point where the other OS'es complete the process and upload some artifacts (that we could then use for testing) --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dea6b8bfd2..cbd24db38e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,8 +31,8 @@ jobs: - {name: "Linux (amd64 / mini)", os: ubuntu-latest, arch: amd64, mode: mini, shell: bash} - {name: "Linux (arm64 / mini)", os: ubuntu-latest, arch: arm64, mode: mini, shell: bash} - {name: "JS (web / mini)", os: ubuntu-latest, arch: amd64, mode: web, shell: bash} - - {name: "Windows (amd64 / full)", os: windows-latest, arch: amd64, mode: full, shell: "msys2 {0}"} - - {name: "Windows (amd64 / mini)", os: windows-latest, arch: amd64, mode: mini, shell: "msys2 {0}"} + #- {name: "Windows (amd64 / full)", os: windows-latest, arch: amd64, mode: full, shell: "msys2 {0}"} + #- {name: "Windows (amd64 / mini)", os: windows-latest, arch: amd64, mode: mini, shell: "msys2 {0}"} - {name: "macOS (amd64 / full)", os: macOS-12, arch: amd64, mode: full, shell: bash} - {name: "macOS (amd64 / mini)", os: macOS-12, arch: amd64, mode: mini, shell: bash} - {name: "macOS (arm64 / full)", os: macos-latest, arch: arm64, mode: full, shell: bash} @@ -106,4 +106,4 @@ jobs: uses: 'actions/upload-artifact@v4' with: name: ${{ steps.artifact-details.outputs.ARTIFACT_NAME }} - path: ${{ steps.artifact-details.outputs.BINARY_PATH }} \ No newline at end of file + path: ${{ steps.artifact-details.outputs.BINARY_PATH }}