From 7c636e2c13a266685437932913f6a5f6d0e60b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=BB=D0=B0=20=D0=93=D0=BE=D1=80?= =?UTF-8?q?=D0=BD=D1=83=D1=88=D0=BA=D0=BE?= Date: Wed, 22 Nov 2023 17:50:57 +0300 Subject: [PATCH] modify getColumns and getCursorPosition --- components/linenoise-async/linenoise-async.c | 1208 ++++++++++-------- components/linenoise-async/linenoise-async.h | 18 +- 2 files changed, 676 insertions(+), 550 deletions(-) diff --git a/components/linenoise-async/linenoise-async.c b/components/linenoise-async/linenoise-async.c index 01770ce..7d6e1e9 100644 --- a/components/linenoise-async/linenoise-async.c +++ b/components/linenoise-async/linenoise-async.c @@ -10,7 +10,7 @@ * * ------------------------------------------------------------------------ * - * Copyright (c) 2010-2016, Salvatore Sanfilippo + * Copyright (c) 2010-2023, Salvatore Sanfilippo * Copyright (c) 2010-2013, Pieter Noordhuis * * All rights reserved. @@ -106,7 +106,7 @@ #include #include #include -#include +// #include #include #include #include @@ -117,7 +117,6 @@ #include #include #include "linenoise-async.h" -#include #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 #define LINENOISE_DEFAULT_MAX_LINE 4096 @@ -125,18 +124,23 @@ #define LINENOISE_COMMAND_MAX_LEN 32 #define LINENOISE_PASTE_KEY_DELAY 30 /* Delay, in milliseconds, between two characters being pasted from clipboard */ +// #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 +// #define LINENOISE_MAX_LINE 4096 +static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; static linenoiseCompletionCallback *completionCallback = NULL; static linenoiseHintsCallback *hintsCallback = NULL; static linenoiseFreeHintsCallback *freeHintsCallback = NULL; +static char *linenoiseNoTTY(void); +static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags); +static void refreshLineWithFlags(struct linenoiseState *l, int flags); -static size_t max_cmdline_length = LINENOISE_DEFAULT_MAX_LINE; +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 dumbmode = 0; /* Dumb mode where line editing is disabled. Off by default */ +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; -static bool allow_empty = true; - enum KEY_ACTION{ KEY_NULL = 0, /* NULL */ @@ -150,7 +154,7 @@ enum KEY_ACTION{ TAB = 9, /* Tab */ CTRL_K = 11, /* Ctrl+k */ CTRL_L = 12, /* Ctrl+l */ - ENTER = 10, /* Enter */ + ENTER = 13, /* Enter */ CTRL_N = 14, /* Ctrl-n */ CTRL_P = 16, /* Ctrl-p */ CTRL_T = 20, /* Ctrl-t */ @@ -160,7 +164,11 @@ enum KEY_ACTION{ BACKSPACE = 127 /* Backspace */ }; +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. */ @@ -184,21 +192,79 @@ FILE *lndebug_fp = NULL; /* ======================= Low level terminal handling ====================== */ +/* Enable "mask mode". When it is enabled, instead of the input that + * the user is typing, the terminal will just display a corresponding + * number of asterisks, like "****". This is useful for passwords and other + * secrets that should not be displayed. */ +void linenoiseMaskModeEnable(void) { + maskmode = 1; +} + +/* Disable mask mode. */ +void linenoiseMaskModeDisable(void) { + maskmode = 0; +} + /* Set if to use or not the multi line mode. */ void linenoiseSetMultiLine(int ml) { mlmode = ml; } -/* Set if terminal does not recognize escape sequences */ -void linenoiseSetDumbMode(int set) { - dumbmode = set; +/* Return true if the terminal name is in the list of terminals we know are + * not able to understand basic escape sequences. */ +static int isUnsupportedTerm(void) { + char *term = getenv("TERM"); + int j; + + if (term == NULL) return 0; + for (j = 0; unsupported_term[j]; j++) + if (!strcasecmp(term,unsupported_term[j])) return 1; + return 0; } -/* Returns whether the current mode is dumbmode or not. */ -bool linenoiseIsDumbMode(void) { - return dumbmode; +/* Raw mode: 1960 magic shit. */ +static int enableRawMode(int fd) { + struct termios raw; + + if (!isatty(STDIN_FILENO)) goto fatal; + if (!atexit_registered) { + atexit(linenoiseAtExit); + atexit_registered = 1; + } + if (tcgetattr(fd,&orig_termios) == -1) goto fatal; + + raw = orig_termios; /* modify the original mode */ + /* input modes: no break, no CR to NL, no parity check, no strip char, + * no start/stop output control. */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + /* output modes - disable post processing */ + raw.c_oflag &= ~(OPOST); + /* control modes - set 8 bit chars */ + raw.c_cflag |= (CS8); + /* local modes - choing off, canonical off, no extended functions, + * no signal chars (^Z,^C) */ + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + /* control chars - set return condition: min number of bytes and timer. + * We want read to return every single byte, without timeout. */ + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + + /* put terminal in raw mode after flushing */ + if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; + rawmode = 1; + return 0; + +fatal: + errno = ENOTTY; + return -1; } +static void disableRawMode(int fd) { + /* Don't even check the return value as it's too late. */ + if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) + rawmode = 0; +} + +// TODO: flush only when using USB CDC? static void flushWrite(void) { if (__fbufsize(stdout) > 0) { fflush(stdout); @@ -223,7 +289,7 @@ static int getCursorPosition(void) { /* Send the command to the TTY on the other end of the UART. * Let's use unistd's write function. Thus, data sent through it are raw * reducing the overhead compared to using fputs, fprintf, etc... */ - write(out_fd, get_cursor_cmd, sizeof(get_cursor_cmd)); + if (write(out_fd, get_cursor_cmd, sizeof(get_cursor_cmd))) return -1; /* For USB CDC, it is required to flush the output. */ flushWrite(); @@ -233,15 +299,15 @@ static int getCursorPosition(void) { * read for each byte. * Stop right before the last character of the buffer, to be able to NULL * terminate it. */ - while (i < sizeof(buf)-1) { + while (i < sizeof(buf) - 1) { /* Keep using unistd's functions. Here, using `read` instead of `fgets` * or `fgets` guarantees us that we we can read a byte regardless on - * whether the sender sent end of line character(s) (CR, CRLF, LF). */ - if (read(in_fd, buf + i, 1) != 1 || buf[i] == 'R') { - /* If we couldn't read a byte from STDIN or if 'R' was received, - * the transmission is finished. */ - break; - } + * whether the sender sent end of line character(s) (CR, CRLF, LF). + * + * If we couldn't read a byte from STDIN or if 'R' was received, + * the transmission is finished.*/ + if (read(in_fd, buf + i, 1) != 1) break; + if (buf[i] == 'R') break; /* For some reasons, it is possible that we receive new line character * after querying the cursor position on some UART. Let's ignore them, @@ -255,9 +321,8 @@ static int getCursorPosition(void) { buf[i] = '\0'; /* Parse the received data to get the position of the cursor. */ - if (buf[0] != ESC || buf[1] != '[' || sscanf(buf+2,"%d;%d",&rows,&cols) != 2) { - return -1; - } + if (buf[0] != ESC || buf[1] != '[') return -1; + if (sscanf(buf + 2,"%d;%d", &rows, &cols) != 2) return -1; return cols; } @@ -280,32 +345,25 @@ static int getColumns(void) { /* Get the initial position so we can restore it later. */ start = getCursorPosition(); - if (start == -1) { - goto failed; - } + if (start == -1) goto failed; /* Send the command to go to right margin. Use `write` function instead of * `fwrite` for the same reasons explained in `getCursorPosition()` */ - if (write(fd, move_cursor_right, cmd_len) != cmd_len) { - goto failed; - } + if (write(fd, move_cursor_right, cmd_len) != cmd_len) goto failed; flushWrite(); /* After sending this command, we can get the new position of the cursor, * we'd get the size, in columns, of the opened TTY. */ cols = getCursorPosition(); - if (cols == -1) { - goto failed; - } + if (cols == -1) goto failed; - /* Restore the position of the cursor back. */ + /* Restore position of the cursor. */ if (cols > start) { - /* Generate the move cursor command. */ - written = snprintf(seq, LINENOISE_COMMAND_MAX_LEN, set_cursor_pos, cols-start); - + written = snprintf(seq, LINENOISE_COMMAND_MAX_LEN, set_cursor_pos, cols - start); /* If `written` is equal or bigger than LINENOISE_COMMAND_MAX_LEN, it * means that the output has been truncated because the size provided * is too small. */ + // TODO: add ESP_LOGE here assert (written < LINENOISE_COMMAND_MAX_LEN); /* Send the command with `write`, which is not buffered. */ @@ -313,6 +371,7 @@ static int getColumns(void) { /* Can't recover... */ } flushWrite(); + } return cols; @@ -322,15 +381,16 @@ failed: /* Clear the screen. Used to handle ctrl+l */ void linenoiseClearScreen(void) { - fprintf(stdout,"\x1b[H\x1b[2J"); - flushWrite(); + if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { + /* nothing to do, just to avoid warning. */ + } } /* Beep, used for completion when there is nothing to complete or when all * the choices were already shown. */ static void linenoiseBeep(void) { - fprintf(stdout, "\x7"); - flushWrite(); + fprintf(stderr, "\x7"); + fflush(stderr); } /* ============================== Completion ================================ */ @@ -344,64 +404,94 @@ static void freeCompletions(linenoiseCompletions *lc) { free(lc->cvec); } -/* This is an helper function for linenoiseEdit() and is called when the +/* 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. */ -static int completeLine(struct linenoiseState *ls) { + * 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 nread, nwritten; - char c = 0; - int in_fd = fileno(stdin); + int nwritten; + char c = keypressed; completionCallback(ls->buf,&lc); if (lc.len == 0) { linenoiseBeep(); + ls->in_completion = 0; } else { - size_t stop = 0, i = 0; + 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; + } - while(!stop) { - /* Show completion or original buffer */ - if (i < lc.len) { - struct linenoiseState saved = *ls; - - ls->len = ls->pos = strlen(lc.cvec[i]); - ls->buf = lc.cvec[i]; - refreshLine(ls); - ls->len = saved.len; - ls->pos = saved.pos; - ls->buf = saved.buf; - } else { - refreshLine(ls); - } - - nread = read(in_fd, &c, 1); - if (nread <= 0) { - freeCompletions(&lc); - return -1; - } - - switch(c) { - case TAB: /* tab */ - i = (i+1) % (lc.len+1); - if (i == lc.len) linenoiseBeep(); - break; - case ESC: /* escape */ - /* Re-show original buffer */ - if (i < lc.len) refreshLine(ls); - stop = 1; - break; - default: - /* Update buffer and return */ - if (i < lc.len) { - nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); - ls->len = ls->pos = nwritten; - } - stop = 1; - break; - } + /* Show completion or original buffer */ + if (ls->in_completion && ls->completion_idx < lc.len) { + refreshLineWithCompletion(ls,&lc,REFRESH_ALL); + } else { + refreshLine(ls); } } @@ -487,10 +577,11 @@ void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { int hintmaxlen = l->cols-(plen+l->len); if (hintlen > hintmaxlen) hintlen = hintmaxlen; if (bold == 1 && color == -1) color = 37; - if (color != -1 || bold != 0) { + if (color != -1 || bold != 0) snprintf(seq,64,"\033[%d;%d;49m",bold,color); - abAppend(ab,seq,strlen(seq)); - } + else + seq[0] = '\0'; + abAppend(ab,seq,strlen(seq)); abAppend(ab,hint,hintlen); if (color != -1 || bold != 0) abAppend(ab,"\033[0m",4); @@ -503,11 +594,14 @@ void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { /* Single line low level line refresh. * * Rewrite the currently edited line accordingly to the buffer content, - * cursor position, and number of columns of the terminal. */ -static void refreshSingleLine(struct linenoiseState *l) { + * 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 = l->plen; - int fd = fileno(stdout); + size_t plen = strlen(l->prompt); + int fd = l->ofd; char *buf = l->buf; size_t len = l->len; size_t pos = l->pos; @@ -524,127 +618,171 @@ static void refreshSingleLine(struct linenoiseState *l) { abInit(&ab); /* Cursor to left edge */ - snprintf(seq,64,"\r"); + snprintf(seq,sizeof(seq),"\r"); abAppend(&ab,seq,strlen(seq)); - /* Write the prompt and the current buffer content */ - abAppend(&ab,l->prompt,strlen(l->prompt)); - abAppend(&ab,buf,len); - /* Show hits if any. */ - refreshShowHints(&ab,l,plen); + + 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); + } + /* Erase to right */ - snprintf(seq,64,"\x1b[0K"); + snprintf(seq,sizeof(seq),"\x1b[0K"); abAppend(&ab,seq,strlen(seq)); - /* Move cursor to original position. */ - snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); - abAppend(&ab,seq,strlen(seq)); - if (write(fd, ab.b, ab.len) == -1) {} /* Can't recover from write error. */ - flushWrite(); + + if (flags & REFRESH_WRITE) { + /* Move cursor to original position. */ + snprintf(seq,sizeof(seq),"\r\x1b[%dC", (int)(pos+plen)); + abAppend(&ab,seq,strlen(seq)); + } + + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ abFree(&ab); } /* Multi line low level line refresh. * * Rewrite the currently edited line accordingly to the buffer content, - * cursor position, and number of columns of the terminal. */ -static void refreshMultiLine(struct linenoiseState *l) { + * 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 = l->plen; + 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 j; - int fd = fileno(stdout); + int fd = l->ofd, j; struct abuf ab; - /* Update oldrows if needed. */ - if (rows > (int)l->oldrows) l->oldrows = rows; + l->oldrows = rows; /* First step: clear all the lines used before. To do so start by * going to the last row. */ abInit(&ab); - if (old_rows-rpos > 0) { - lndebug("go down %d", old_rows-rpos); - snprintf(seq,64,"\x1b[%dB", old_rows-rpos); + + 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)); + } + + /* 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)); + } + } + + if (flags & REFRESH_ALL) { + /* Clean the top line. */ + lndebug("clear"); + snprintf(seq,64,"\r\x1b[0K"); abAppend(&ab,seq,strlen(seq)); } - /* Now for every row clear it, go up. */ - for (j = 0; j < old_rows-1; j++) { - lndebug("clear+up"); - snprintf(seq,64,"\r\x1b[0K\x1b[1A"); + 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); + } + + /* 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; + } + + /* Move cursor to right position. */ + rpos2 = (plen+l->pos+l->cols)/l->cols; /* Current cursor relative row */ + lndebug("rpos2 %d", rpos2); + + /* Go up till we reach the expected positon. */ + if (rows-rpos2 > 0) { + lndebug("go-up %d", rows-rpos2); + snprintf(seq,64,"\x1b[%dA", rows-rpos2); + abAppend(&ab,seq,strlen(seq)); + } + + /* Set column. */ + col = (plen+(int)l->pos) % (int)l->cols; + lndebug("set col %d", 1+col); + if (col) + snprintf(seq,64,"\r\x1b[%dC", col); + else + snprintf(seq,64,"\r"); abAppend(&ab,seq,strlen(seq)); } - /* Clean the top line. */ - lndebug("clear"); - snprintf(seq,64,"\r\x1b[0K"); - abAppend(&ab,seq,strlen(seq)); - - /* Write the prompt and the current buffer content */ - abAppend(&ab,l->prompt,strlen(l->prompt)); - abAppend(&ab,l->buf,l->len); - - /* Show hits if any. */ - refreshShowHints(&ab,l,plen); - - /* If we are at the very end of the screen with our prompt, we need to - * emit a newline and move the prompt to the first column. */ - if (l->pos && - l->pos == l->len && - (l->pos+plen) % l->cols == 0) - { - lndebug(""); - abAppend(&ab,"\n",1); - snprintf(seq,64,"\r"); - abAppend(&ab,seq,strlen(seq)); - rows++; - if (rows > (int)l->oldrows) l->oldrows = rows; - } - - /* Move cursor to right position. */ - rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ - lndebug("rpos2 %d", rpos2); - - /* Go up till we reach the expected positon. */ - if (rows-rpos2 > 0) { - lndebug("go-up %d", rows-rpos2); - snprintf(seq,64,"\x1b[%dA", rows-rpos2); - abAppend(&ab,seq,strlen(seq)); - } - - /* Set column. */ - col = (plen+(int)l->pos) % (int)l->cols; - lndebug("set col %d", 1+col); - if (col) - snprintf(seq,64,"\r\x1b[%dC", col); - else - snprintf(seq,64,"\r"); - abAppend(&ab,seq,strlen(seq)); - lndebug("\n"); l->oldpos = l->pos; if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ - flushWrite(); abFree(&ab); } /* Calls the two low level functions refreshSingleLine() or * refreshMultiLine() according to the selected mode. */ -static void refreshLine(struct linenoiseState *l) { +static void refreshLineWithFlags(struct linenoiseState *l, int flags) { if (mlmode) - refreshMultiLine(l); + refreshMultiLine(l,flags); else - refreshSingleLine(l); + refreshSingleLine(l,flags); +} + +/* Utility function to avoid specifying REFRESH_ALL all the times. */ +static void refreshLine(struct linenoiseState *l) { + refreshLineWithFlags(l,REFRESH_ALL); +} + +/* 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); +} + +/* 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); + } } /* Insert the character 'c' at cursor current position. * * On error writing to the terminal -1 is returned, otherwise 0. */ int linenoiseEditInsert(struct linenoiseState *l, char c) { - int fd = fileno(stdout); if (l->len < l->buflen) { if (l->len == l->pos) { l->buf[l->pos] = c; @@ -654,10 +792,8 @@ int linenoiseEditInsert(struct linenoiseState *l, char c) { if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { /* Avoid a full update of the line in the * trivial case. */ - if (write(fd, &c,1) == -1) { - return -1; - } - flushWrite(); + char d = (maskmode==1) ? '*' : c; + if (write(l->ofd,&d,1) == -1) return -1; } else { refreshLine(l); } @@ -673,21 +809,6 @@ int linenoiseEditInsert(struct linenoiseState *l, char c) { return 0; } -int linenoiseInsertPastedChar(struct linenoiseState *l, char c) { - int fd = fileno(stdout); - if (l->len < l->buflen && l->len == l->pos) { - l->buf[l->pos] = c; - l->pos++; - l->len++; - l->buf[l->len] = '\0'; - if (write(fd, &c,1) == -1) { - return -1; - } - flushWrite(); - } - return 0; -} - /* Move cursor on the left. */ void linenoiseEditMoveLeft(struct linenoiseState *l) { if (l->pos > 0) { @@ -784,357 +905,370 @@ void linenoiseEditDeletePrevWord(struct linenoiseState *l) { refreshLine(l); } -uint32_t getMillis(void) { - struct timeval tv = { 0 }; - gettimeofday(&tv, NULL); - return tv.tv_sec * 1000 + tv.tv_usec / 1000; -} - -/* This function is the core of the line editing capability of linenoise. - * It expects 'fd' to be already in "raw mode" so that every key pressed - * will be returned ASAP to read(). +/* 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: * - * The resulting string is put into 'buf' when the user type enter, or - * when ctrl+d is typed. + * 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 function returns the length of the current buffer. */ -static int linenoiseEdit(char *buf, size_t buflen, const char *prompt) -{ - uint32_t t1 = 0; - struct linenoiseState l; - int out_fd = fileno(stdout); - int in_fd = fileno(stdin); - + * 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.buf = buf; - l.buflen = buflen; - l.prompt = prompt; - l.plen = strlen(prompt); - l.oldpos = l.pos = 0; - l.len = 0; - l.cols = getColumns(); - l.oldrows = 0; - l.history_index = 0; + 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; + l->cols = getColumns(); + 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 */ + 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; + + /* Enter raw mode. */ + if (enableRawMode(l->ifd) == -1) return -1; /* The latest history entry is always our current buffer, that * initially is just an empty string. */ linenoiseHistoryAdd(""); - int pos1 = getCursorPosition(); - if (write(out_fd, prompt,l.plen) == -1) { - return -1; - } - flushWrite(); - int pos2 = getCursorPosition(); - if (pos1 >= 0 && pos2 >= 0) { - l.plen = pos2 - pos1; - } - while(1) { - char c; - int nread; - char seq[3]; - - /** - * To determine whether the user is pasting data or typing itself, we - * need to calculate how many milliseconds elapsed between two key - * presses. Indeed, if there is less than LINENOISE_PASTE_KEY_DELAY - * (typically 30-40ms), then a paste is being performed, else, the - * user is typing. - * NOTE: pressing a key down without releasing it will also spend - * about 40ms (or even more) - */ - t1 = getMillis(); - nread = read(in_fd, &c, 1); - if (nread <= 0) return l.len; - - if ( (getMillis() - t1) < LINENOISE_PASTE_KEY_DELAY && c != ENTER) { - /* Pasting data, insert characters without formatting. - * This can only be performed when the cursor is at the end of the - * line. */ - if (linenoiseInsertPastedChar(&l,c)) { - return -1; - } - continue; - } - - /* Only autocomplete when the callback is set. It returns < 0 when - * there was an error reading from fd. Otherwise it will return the - * character that should be handled next. */ - if (c == 9 && completionCallback != NULL) { - int c2 = completeLine(&l); - /* Return on errors */ - if (c2 < 0) return l.len; - /* Read next character when 0 */ - if (c2 == 0) continue; - c = c2; - } - - switch(c) { - case ENTER: /* enter */ - history_len--; - free(history[history_len]); - if (mlmode) linenoiseEditMoveEnd(&l); - if (hintsCallback) { - /* Force a refresh without hints to leave the previous - * line as the user typed it after a newline. */ - linenoiseHintsCallback *hc = hintsCallback; - hintsCallback = NULL; - refreshLine(&l); - hintsCallback = hc; - } - return (int)l.len; - case CTRL_C: /* ctrl-c */ - errno = EAGAIN; - return -1; - case BACKSPACE: /* backspace */ - case 8: /* ctrl-h */ - linenoiseEditBackspace(&l); - break; - case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the - line is empty, act as end-of-file. */ - if (l.len > 0) { - linenoiseEditDelete(&l); - } else { - history_len--; - free(history[history_len]); - return -1; - } - break; - case CTRL_T: /* ctrl-t, swaps current character with previous. */ - if (l.pos > 0 && l.pos < l.len) { - int aux = buf[l.pos-1]; - buf[l.pos-1] = buf[l.pos]; - buf[l.pos] = aux; - if (l.pos != l.len-1) l.pos++; - refreshLine(&l); - } - break; - case CTRL_B: /* ctrl-b */ - linenoiseEditMoveLeft(&l); - break; - case CTRL_F: /* ctrl-f */ - linenoiseEditMoveRight(&l); - break; - case CTRL_P: /* ctrl-p */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); - break; - case CTRL_N: /* ctrl-n */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); - break; - case ESC: /* escape sequence */ - /* Read the next two bytes representing the escape sequence. */ - if (read(in_fd, seq, 2) < 2) { - break; - } - - /* ESC [ sequences. */ - if (seq[0] == '[') { - if (seq[1] >= '0' && seq[1] <= '9') { - /* Extended escape, read additional byte. */ - if (read(in_fd, seq+2, 1) == -1) { - break; - } - if (seq[2] == '~') { - switch(seq[1]) { - case '3': /* Delete key. */ - linenoiseEditDelete(&l); - break; - } - } - } else { - switch(seq[1]) { - case 'A': /* Up */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); - break; - case 'B': /* Down */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); - break; - case 'C': /* Right */ - linenoiseEditMoveRight(&l); - break; - case 'D': /* Left */ - linenoiseEditMoveLeft(&l); - break; - case 'H': /* Home */ - linenoiseEditMoveHome(&l); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(&l); - break; - } - } - } - - /* ESC O sequences. */ - else if (seq[0] == 'O') { - switch(seq[1]) { - case 'H': /* Home */ - linenoiseEditMoveHome(&l); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(&l); - break; - } - } - break; - default: - if (linenoiseEditInsert(&l,c)) return -1; - break; - case CTRL_U: /* Ctrl+u, delete the whole line. */ - buf[0] = '\0'; - l.pos = l.len = 0; - refreshLine(&l); - break; - case CTRL_K: /* Ctrl+k, delete from current to end of line. */ - buf[l.pos] = '\0'; - l.len = l.pos; - refreshLine(&l); - break; - case CTRL_A: /* Ctrl+a, go to the start of the line */ - linenoiseEditMoveHome(&l); - break; - case CTRL_E: /* ctrl+e, go to the end of the line */ - linenoiseEditMoveEnd(&l); - break; - case CTRL_L: /* ctrl+l, clear screen */ - linenoiseClearScreen(); - refreshLine(&l); - break; - case CTRL_W: /* ctrl+w, delete previous word */ - linenoiseEditDeletePrevWord(&l); - break; - } - flushWrite(); - } - return l.len; -} - -void linenoiseAllowEmpty(bool val) { - allow_empty = val; -} - -int linenoiseProbe(void) { - /* Switch to non-blocking mode */ - int stdin_fileno = fileno(stdin); - int flags = fcntl(stdin_fileno, F_GETFL); - flags |= O_NONBLOCK; - int res = fcntl(stdin_fileno, F_SETFL, flags); - if (res != 0) { - return -1; - } - /* Device status request */ - fprintf(stdout, "\x1b[5n"); - flushWrite(); - - /* Try to read response */ - int timeout_ms = 500; - const int retry_ms = 10; - size_t read_bytes = 0; - while (timeout_ms > 0 && read_bytes < 4) { // response is ESC[0n or ESC[3n - usleep(retry_ms * 1000); - timeout_ms -= retry_ms; - char c; - int cb = read(stdin_fileno, &c, 1); - if (cb < 0) { - continue; - } - if (read_bytes == 0 && c != '\x1b') { - /* invalid response */ - break; - } - read_bytes += cb; - } - /* Restore old mode */ - flags &= ~O_NONBLOCK; - res = fcntl(stdin_fileno, F_SETFL, flags); - if (res != 0) { - return -1; - } - if (read_bytes < 4) { - return -2; - } + if (write(l->ofd,prompt,l->plen) == -1) return -1; return 0; } -static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { - int count; +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. + */ +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; + } + 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; + } + 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); + } + break; + case CTRL_B: /* ctrl-b */ + linenoiseEditMoveLeft(l); + break; + case CTRL_F: /* ctrl-f */ + linenoiseEditMoveRight(l); + break; + case CTRL_P: /* ctrl-p */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_PREV); + break; + case CTRL_N: /* ctrl-n */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_NEXT); + break; + case ESC: /* escape sequence */ + /* Read the next two bytes representing the escape sequence. + * Use two calls to handle slow terminals returning the two + * chars at different times. */ + if (read(l->ifd,seq,1) == -1) break; + if (read(l->ifd,seq+1,1) == -1) break; + + /* ESC [ sequences. */ + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended escape, read additional byte. */ + if (read(l->ifd,seq+2,1) == -1) break; + if (seq[2] == '~') { + switch(seq[1]) { + case '3': /* Delete key. */ + linenoiseEditDelete(l); + break; + } + } + } else { + switch(seq[1]) { + case 'A': /* Up */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_PREV); + break; + case 'B': /* Down */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_NEXT); + break; + case 'C': /* Right */ + linenoiseEditMoveRight(l); + break; + case 'D': /* Left */ + linenoiseEditMoveLeft(l); + break; + case 'H': /* Home */ + linenoiseEditMoveHome(l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(l); + break; + } + } + } + + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch(seq[1]) { + case 'H': /* Home */ + linenoiseEditMoveHome(l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(l); + break; + } + } + break; + default: + if (linenoiseEditInsert(l,c)) return 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; + } + return linenoiseEditMore; +} + +/* 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"); +} + +/* 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; + + /* Editing without a buffer is invalid. */ if (buflen == 0) { errno = EINVAL; - return -1; + return NULL; } - count = linenoiseEdit(buf, buflen, prompt); - fputc('\n', stdout); - flushWrite(); - return count; + linenoiseEditStart(&l,stdin_fd,stdout_fd,buf,buflen,prompt); + char *res; + while((res = linenoiseEditFeed(&l)) == linenoiseEditMore); + linenoiseEditStop(&l); + return res; } -static int linenoiseDumb(char* buf, size_t buflen, const char* prompt) { - /* dumb terminal, fall back to fgets */ - fputs(prompt, stdout); - size_t count = 0; - while (count < buflen) { - int c = fgetc(stdin); - if (c == '\n') { - break; - } else if (c >= 0x1c && c <= 0x1f){ - continue; /* consume arrow keys */ - } else if (c == BACKSPACE || c == 0x8) { - if (count > 0) { - buf[count - 1] = 0; - count --; +/* This special mode is used by linenoise in order to print scan codes + * on screen for debugging / development purposes. It is implemented + * by the linenoise_example program using the --keycodes option. */ +void linenoisePrintKeyCodes(void) { + char quit[4]; + + printf("Linenoise key codes debugging mode.\n" + "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); + if (enableRawMode(STDIN_FILENO) == -1) return; + memset(quit,' ',4); + while(1) { + char c; + int nread; + + nread = read(STDIN_FILENO,&c,1); + if (nread <= 0) continue; + memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ + quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ + if (memcmp(quit,"quit",sizeof(quit)) == 0) break; + + printf("'%c' %02x (%d) (type quit to exit)\n", + isprint(c) ? c : '?', (int)c, (int)c); + printf("\r"); /* Go left edge manually, we are in raw mode. */ + fflush(stdout); + } + disableRawMode(STDIN_FILENO); +} + +/* This function is called when linenoise() is called with the standard + * input file descriptor not attached to a TTY. So for example when the + * program using linenoise is called in pipe or with a file redirected + * to its standard input. In this case, we want to be able to return the + * line regardless of its length (by default we are limited to 4k). */ +static char *linenoiseNoTTY(void) { + char *line = NULL; + size_t len = 0, maxlen = 0; + + while(1) { + if (len == maxlen) { + if (maxlen == 0) maxlen = 16; + maxlen *= 2; + char *oldval = line; + line = realloc(line,maxlen); + if (line == NULL) { + if (oldval) free(oldval); + return NULL; + } + } + int c = fgetc(stdin); + if (c == EOF || c == '\n') { + if (c == EOF && len == 0) { + free(line); + return NULL; + } else { + line[len] = '\0'; + return line; } - fputs("\x08 ", stdout); /* Windows CMD: erase symbol under cursor */ } else { - buf[count] = c; - ++count; - } - fputc(c, stdout); /* echo */ - } - fputc('\n', stdout); - flushWrite(); - return count; -} - -static void sanitize(char* src) { - char* dst = src; - for (int c = *src; c != 0; src++, c = *src) { - if (isprint(c)) { - *dst = c; - ++dst; + line[len] = c; + len++; } } - *dst = 0; } -/* The high level function that is the main API of the linenoise library. */ +/* 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 = calloc(1, max_cmdline_length); - int count = 0; - if (buf == NULL) { - return NULL; - } - if (!dumbmode) { - count = linenoiseRaw(buf, max_cmdline_length, prompt); + char buf[LINENOISE_MAX_LINE]; + + if (!isatty(STDIN_FILENO)) { + /* Not a tty: read from file / pipe. In this mode we don't want any + * limit to the line size, so we call a function to handle that. */ + return linenoiseNoTTY(); + } else if (isUnsupportedTerm()) { + size_t len; + + printf("%s",prompt); + fflush(stdout); + if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; + len = strlen(buf); + while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { + len--; + buf[len] = '\0'; + } + return strdup(buf); } else { - count = linenoiseDumb(buf, max_cmdline_length, prompt); + char *retval = linenoiseBlockingEdit(STDIN_FILENO,STDOUT_FILENO,buf,LINENOISE_MAX_LINE,prompt); + return retval; } - if (count > 0) { - sanitize(buf); - count = strlen(buf); - } else if (count == 0 && allow_empty) { - /* will return an empty (0-length) string */ - } else { - free(buf); - return NULL; - } - return buf; } /* This is just a wrapper the user may want to call in order to make sure @@ -1142,19 +1276,28 @@ char *linenoise(const char *prompt) { * 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); } /* ================================ History ================================= */ -void linenoiseHistoryFree(void) { +/* 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) { - for (int j = 0; j < history_len; j++) { + int j; + + for (j = 0; j < history_len; j++) free(history[j]); - } free(history); } - history = NULL; +} + +/* At exit we'll try to fix the terminal to the initial conditions. */ +static void linenoiseAtExit(void) { + disableRawMode(STDIN_FILENO); + freeHistory(); } /* This is the API call to add a new entry in the linenoise history. @@ -1228,11 +1371,14 @@ 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) { + mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); FILE *fp; int j; fp = fopen(filename,"w"); + umask(old_umask); if (fp == NULL) return -1; + chmod(filename,S_IRUSR|S_IWUSR); for (j = 0; j < history_len; j++) fprintf(fp,"%s\n",history[j]); fclose(fp); @@ -1245,18 +1391,12 @@ int linenoiseHistorySave(const char *filename) { * 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"); - if (fp == NULL) { - return -1; - } + FILE *fp = fopen(filename,"r"); + char buf[LINENOISE_MAX_LINE]; - char *buf = calloc(1, max_cmdline_length); - if (buf == NULL) { - fclose(fp); - return -1; - } + if (fp == NULL) return -1; - while (fgets(buf, max_cmdline_length, fp) != NULL) { + while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { char *p; p = strchr(buf,'\r'); @@ -1264,20 +1404,6 @@ int linenoiseHistoryLoad(const char *filename) { if (p) *p = '\0'; linenoiseHistoryAdd(buf); } - - free(buf); fclose(fp); - - return 0; -} - -/* Set line maximum length. If len parameter is smaller than - * LINENOISE_MINIMAL_MAX_LINE, -1 is returned - * otherwise 0 is returned. */ -int linenoiseSetMaxLineLen(size_t len) { - if (len < LINENOISE_MINIMAL_MAX_LINE) { - return -1; - } - max_cmdline_length = len; return 0; } diff --git a/components/linenoise-async/linenoise-async.h b/components/linenoise-async/linenoise-async.h index a3ec93e..246e56a 100644 --- a/components/linenoise-async/linenoise-async.h +++ b/components/linenoise-async/linenoise-async.h @@ -7,7 +7,7 @@ * * ------------------------------------------------------------------------ * - * Copyright (c) 2010-2014, Salvatore Sanfilippo + * Copyright (c) 2010-2023, Salvatore Sanfilippo * Copyright (c) 2010-2013, Pieter Noordhuis * * All rights reserved. @@ -44,7 +44,7 @@ extern "C" { #endif #include -#include +#include /* For size_t. */ extern char *linenoiseEditMore; @@ -55,8 +55,6 @@ 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. */ @@ -99,20 +97,22 @@ int linenoiseHistoryAdd(const char *line); int linenoiseHistorySetMaxLen(int len); int linenoiseHistorySave(const char *filename); int linenoiseHistoryLoad(const char *filename); +// ESP-specific void linenoiseHistoryFree(void); + /* Other utilities. */ void linenoiseClearScreen(void); void linenoiseSetMultiLine(int ml); void linenoisePrintKeyCodes(void); -// Allow empty input from console (Enter key) -void linenoiseAllowEmpty(bool); -int linenoiseSetMaxLineLen(size_t len); -// Testing console for escape codes support +void linenoiseMaskModeEnable(void); +void linenoiseMaskModeDisable(void); +// ESP-specific int linenoiseProbe(void); -// Dump mode for consoles withoud escape codes support void linenoiseSetDumbMode(int set); bool linenoiseIsDumbMode(void); +void linenoiseAllowEmpty(bool); +int linenoiseSetMaxLineLen(size_t len); #ifdef __cplusplus }