From 790cb745d7bacd3c1e654df94bb4bb9422f8a10b Mon Sep 17 00:00:00 2001 From: mckenney5 Date: Tue, 7 Aug 2018 00:37:30 -0400 Subject: [PATCH] Replaced linenoise lib with linenoise-mob Replaced the UI library with a more maintained version. The new library fixed this issue: https://github.com/antirez/linenoise/issues/158 --- README.md | 7 +- TODO.md | 4 +- src/libs/linenoise.c | 483 +++++++++++++++++++++++++++++++------------ src/libs/linenoise.h | 13 ++ src/ui.c | 5 +- 5 files changed, 375 insertions(+), 137 deletions(-) diff --git a/README.md b/README.md index 5dca0cd..47450ae 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ qsh — _A small, lightweight, command interpreter_ ## Description This is the rewrite of quick terminal, quick terminal server edition, and QTerminal. -Quick shell aims at replacing bash or zsh with a smaller and lighter program and follows +Quick Shell aims at replacing `bash` or `zsh` with a smaller and lighter program and follows the [suckless philosophy](https://suckless.org/philosophy/). (note this project is not associated with suckless). ## License This code (see exception) is licensed under the MIT license. See 'LICENSE' for details. -Note, the source code for [linenoise](https://github.com/antirez/linenoise) is also included in this repo which is under the BSD 2-Clause "Simplified" License. See [src/libs/LICENSE](https://raw.githubusercontent.com/mckenney5/qsh/master/src/libs/LICENSE) for more details. +Note, the source code for [linenoise-mob](https://github.com/rain-1/linenoise-mob) is also included in this repo which is under the BSD 2-Clause "Simplified" License. See [src/libs/LICENSE](https://raw.githubusercontent.com/mckenney5/qsh/master/src/libs/LICENSE) for more details. ## Compiling and Testing This program allows a couple of compiling options. @@ -24,7 +24,7 @@ The simplest way is to compile by `make` * `make GNU` : makes the user interface use [GNU readline](https://tiswww.case.edu/php/chet/readline/rltop.html) * `make TINY` : makes the UI only use fgets (aka no line editing, like sh(1)) * `make DEBUG` : adds debugging symbols to the program, along with verbose output -* `make` : makes the UI use the [linenoise libray](https://github.com/antirez/linenoise) +* `make` : makes the UI use the [linenoise-mob libray](https://github.com/rain-1/linenoise-mob) ##### (optional) Colors: * NO_COLOR (`-DNO_COLOR`) : disables color in the prompt **ONLY** @@ -41,3 +41,4 @@ Then: 6. After all of that **_then_** submit a merge/pull request Any questions, feel free to email me at mckenneyadam [at] gmail + diff --git a/TODO.md b/TODO.md index 20a7674..fdc8ef5 100644 --- a/TODO.md +++ b/TODO.md @@ -2,7 +2,7 @@ In order of importance ## General: -- [X] add info for GNU readline and author info on linenoise in README +- [X] add info for GNU readline and author info on linenoise-mob in README - [ ] be usable as a default shell - [ ] document and fix known bugs - [X] add to github account @@ -24,7 +24,7 @@ In order of importance - [ ] test other libc implementations (musl, uClibc, etc) - [ ] test in cygin - [ ] write a manpage -- [ ] fix [this bug](https://github.com/antirez/linenoise/issues/158) in linenoise lib +- [X] fix [this bug](https://github.com/antirez/linenoise/issues/158) in linenoise lib ## Features: - [X] basic features (ex run a command, change directories) diff --git a/src/libs/linenoise.c b/src/libs/linenoise.c index c7098ec..18cd135 100644 --- a/src/libs/linenoise.c +++ b/src/libs/linenoise.c @@ -1,8 +1,7 @@ /* linenoise.c -- guerrilla line editing library against the idea that a * line editing lib needs to be 20,000 lines of C code. * - * You can find the latest source code at: - * + * You can find the original source code at: * http://github.com/antirez/linenoise * * Does a number of crazy assumptions that happen to be true in 99.9999% of @@ -78,14 +77,14 @@ * 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. + * When multi line mode is enabled, we also use two additional escape + * sequences. However multi line editing is disabled by default. * - * CUU (Cursor Up) + * CUU (CUrsor Up) * Sequence: ESC [ n A * Effect: moves cursor up of n chars. * - * CUD (Cursor Down) + * CUD (CUrsor Down) * Sequence: ESC [ n B * Effect: moves cursor down of n chars. * @@ -93,11 +92,11 @@ * are used in order to clear the screen and position the cursor at home * position. * - * CUP (Cursor position) + * CUP (CUrsor Position) * Sequence: ESC [ H * Effect: moves the cursor to upper left corner * - * ED (Erase display) + * ED (Erase Display) * Sequence: ESC [ 2 J * Effect: clear the whole screen * @@ -109,17 +108,17 @@ #include #include #include -#include +#include #include #include #include #include -#include #include "linenoise.h" #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 #define LINENOISE_MAX_LINE 4096 -static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; +#define UNUSED(x) (void)(x) +static const char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; static linenoiseCompletionCallback *completionCallback = NULL; static linenoiseHintsCallback *hintsCallback = NULL; static linenoiseFreeHintsCallback *freeHintsCallback = NULL; @@ -143,7 +142,7 @@ struct linenoiseState { 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 oldcolpos; /* Previous refresh cursor column position. */ size_t len; /* Current edited line length. */ size_t cols; /* Number of columns in terminal. */ size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ @@ -160,6 +159,7 @@ enum KEY_ACTION{ CTRL_F = 6, /* Ctrl-f */ CTRL_H = 8, /* Ctrl-h */ TAB = 9, /* Tab */ + LINE_FEED = 10, /* Line Feed */ CTRL_K = 11, /* Ctrl+k */ CTRL_L = 12, /* Ctrl+l */ ENTER = 13, /* Enter */ @@ -185,7 +185,7 @@ FILE *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->len,(int)l->pos,(int)l->oldcolpos,plen,rows,rpos, \ (int)l->maxrows,old_rows); \ } \ fprintf(lndebug_fp, ", " __VA_ARGS__); \ @@ -195,6 +195,86 @@ FILE *lndebug_fp = NULL; #define lndebug(fmt, ...) #endif +/* ========================== Encoding functions ============================= */ + +/* Get byte length and column length of the previous character */ +static size_t defaultPrevCharLen(const char *buf, size_t buf_len, size_t pos, size_t *col_len) { + UNUSED(buf); UNUSED(buf_len); UNUSED(pos); + if (col_len != NULL) *col_len = 1; + return 1; +} + +/* Get byte length and column length of the next character */ +static size_t defaultNextCharLen(const char *buf, size_t buf_len, size_t pos, size_t *col_len) { + UNUSED(buf); UNUSED(buf_len); UNUSED(pos); + if (col_len != NULL) *col_len = 1; + return 1; +} + +/* Read bytes of the next character */ +static size_t defaultReadCode(int fd, char *buf, size_t buf_len, int* c) { + if (buf_len < 1) return -1; + int nread = read(fd,&buf[0],1); + if (nread == 1) *c = buf[0]; + return nread; +} + +/* Set default encoding functions */ +static linenoisePrevCharLen *prevCharLen = defaultPrevCharLen; +static linenoiseNextCharLen *nextCharLen = defaultNextCharLen; +static linenoiseReadCode *readCode = defaultReadCode; + +/* Set used defined encoding functions */ +void linenoiseSetEncodingFunctions( + linenoisePrevCharLen *prevCharLenFunc, + linenoiseNextCharLen *nextCharLenFunc, + linenoiseReadCode *readCodeFunc) { + prevCharLen = prevCharLenFunc; + nextCharLen = nextCharLenFunc; + readCode = readCodeFunc; +} + +/* Get column length from begining of buffer to current byte position */ +static size_t columnPos(const char *buf, size_t buf_len, size_t pos) { + size_t ret = 0; + size_t off = 0; + while (off < pos) { + size_t col_len; + size_t len = nextCharLen(buf,buf_len,off,&col_len); + off += len; + ret += col_len; + } + return ret; +} + +/* Get column length from begining of buffer to current byte position for multiline mode*/ +static size_t columnPosForMultiLine(const char *buf, size_t buf_len, size_t pos, size_t cols, size_t ini_pos) { + size_t ret = 0; + size_t colwid = ini_pos; + + size_t off = 0; + while (off < buf_len) { + size_t col_len; + size_t len = nextCharLen(buf,buf_len,off,&col_len); + + int dif = (int)(colwid + col_len) - (int)cols; + if (dif > 0) { + ret += dif; + colwid = col_len; + } else if (dif == 0) { + colwid = 0; + } else { + colwid += col_len; + } + + if (off >= pos) break; + off += len; + ret += col_len; + } + + return ret; +} + /* ======================= Low level terminal handling ====================== */ /* Set if to use or not the multi line mode. */ @@ -229,8 +309,6 @@ static int enableRawMode(int fd) { /* 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, @@ -316,9 +394,36 @@ static int getColumns(int ifd, int ofd) { return 80; } +/* Get the length of the string ignoring escape-sequences */ +// TODO merge with columnPos +static int strlenPerceived(const char* str) { + int len = 0; + if (str) { + int escaping = 0; + while(*str) { + if (escaping) { /* was terminating char reached? */ + if(*str >= 0x40 && *str <= 0x7E) + escaping = 0; + } + else if(*str == '\x1b') { + escaping = 1; + if (str[1] == '[') str++; + } + else { + len++; + } + str++; + } + } + return len; +} + /* Clear the screen. Used to handle ctrl+l */ void linenoiseClearScreen(void) { - if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { + int fd; + + fd = isatty(STDOUT_FILENO) ? STDOUT_FILENO : STDERR_FILENO; + if (write(fd,"\x1b[H\x1b[2J",7) <= 0) { /* nothing to do, just to avoid warning. */ } } @@ -347,10 +452,10 @@ static void freeCompletions(linenoiseCompletions *lc) { * * The state of the editing is encapsulated into the pointed linenoiseState * structure as described in the structure definition. */ -static int completeLine(struct linenoiseState *ls) { +static int completeLine(struct linenoiseState *ls, char *cbuf, size_t cbuf_len, int *c) { linenoiseCompletions lc = { 0, NULL }; - int nread, nwritten; - char c = 0; + int nread = 0, nwritten; + *c = 0; completionCallback(ls->buf,&lc); if (lc.len == 0) { @@ -373,18 +478,19 @@ static int completeLine(struct linenoiseState *ls) { refreshLine(ls); } - nread = read(ls->ifd,&c,1); + nread = readCode(ls->ifd,cbuf,cbuf_len,c); if (nread <= 0) { freeCompletions(&lc); - return -1; + *c = -1; + return nread; } - switch(c) { - case 9: /* tab */ + switch(*c) { + case TAB: /* tab */ i = (i+1) % (lc.len+1); if (i == lc.len) linenoiseBeep(); break; - case 27: /* escape */ + case ESC: /* escape */ /* Re-show original buffer */ if (i < lc.len) refreshLine(ls); stop = 1; @@ -402,7 +508,7 @@ static int completeLine(struct linenoiseState *ls) { } freeCompletions(&lc); - return c; /* Return last read character */ + return nread; } /* Register a callback function to be called for tab-completion. */ @@ -442,6 +548,19 @@ void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { lc->cvec[lc->len++] = copy; } +/* Adds matching history entries as tab completions */ +void linenoiseAddHistoryCompletions(const char* buf, linenoiseCompletions *lc) { + int i; + size_t n; + if (history == NULL) + return; + n = strlen(buf); + for (i = 0; i < history_len; ++i) + if (strncasecmp(history[i], buf, n) == 0) + linenoiseAddCompletion(lc, history[i]); +} + + /* =========================== Line editing ================================= */ /* We define a very simple "append buffer" structure, that is an heap @@ -450,7 +569,7 @@ void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { * output in a single call, to avoid flickering effects. */ struct abuf { char *b; - int len; + unsigned int len; }; static void abInit(struct abuf *ab) { @@ -458,7 +577,7 @@ static void abInit(struct abuf *ab) { ab->len = 0; } -static void abAppend(struct abuf *ab, const char *s, int len) { +static void abAppend(struct abuf *ab, const char *s, unsigned int len) { char *new = realloc(ab->b,ab->len+len); if (new == NULL) return; @@ -473,14 +592,15 @@ static void abFree(struct abuf *ab) { /* Helper of refreshSingleLine() and refreshMultiLine() to show hints * to the right of the prompt. */ -void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { +void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int pcollen) { char seq[64]; - if (hintsCallback && plen+l->len < l->cols) { + size_t collen = pcollen+columnPos(l->buf,l->len,l->len); + if (hintsCallback && collen < 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); + int hintmaxlen = l->cols-collen; if (hintlen > hintmaxlen) hintlen = hintmaxlen; if (bold == 1 && color == -1) color = 37; if (color != -1 || bold != 0) @@ -497,26 +617,62 @@ void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { } } +/* Check if text is an ANSI escape sequence + */ +static int isAnsiEscape(const char *buf, size_t buf_len, size_t* len) { + if (buf_len > 2 && !memcmp("\033[", buf, 2)) { + size_t off = 2; + while (off < buf_len) { + switch (buf[off++]) { + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'J': case 'K': + case 'S': case 'T': case 'f': case 'm': + *len = off; + return 1; + } + } + } + return 0; +} + +/* Get column length of prompt text + */ +static size_t promptTextColumnLen(const char *prompt, size_t plen) { + char buf[LINENOISE_MAX_LINE]; + size_t buf_len = 0; + size_t off = 0; + while (off < plen) { + size_t len; + if (isAnsiEscape(prompt + off, plen - off, &len)) { + off += len; + continue; + } + buf[buf_len++] = prompt[off++]; + } + return columnPos(buf,buf_len,buf_len); +} + /* Single line low level line refresh. * * Rewrite the currently edited line accordingly to the buffer content, * cursor position, and number of columns of the terminal. */ static void refreshSingleLine(struct linenoiseState *l) { char seq[64]; - size_t plen = strlen(l->prompt); + size_t pcollen = promptTextColumnLen(l->prompt,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((pcollen+columnPos(buf,len,pos)) >= l->cols) { + int chlen = nextCharLen(buf,len,0,NULL); + buf += chlen; + len -= chlen; + pos -= chlen; } - while (plen+len > l->cols) { - len--; + while (pcollen+columnPos(buf,len,len) > l->cols) { + len -= prevCharLen(buf,len,len,NULL); } abInit(&ab); @@ -527,14 +683,14 @@ static void refreshSingleLine(struct linenoiseState *l) { abAppend(&ab,l->prompt,strlen(l->prompt)); abAppend(&ab,buf,len); /* Show hits if any. */ - refreshShowHints(&ab,l,plen); + refreshShowHints(&ab,l,pcollen); /* Erase to right */ snprintf(seq,64,"\x1b[0K"); abAppend(&ab,seq,strlen(seq)); /* Move cursor to original position. */ - snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); +// snprintf(seq,64,"\r\x1b[%dC", (int)(pos+strlenPerceived(l->prompt))); + snprintf(seq,64,"\r\x1b[%dC", (int)(columnPos(buf,len,pos)+pcollen)); abAppend(&ab,seq,strlen(seq)); - /* BUG write moves cursor to the right if there are colors in prompt */ if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ abFree(&ab); } @@ -545,11 +701,13 @@ static void refreshSingleLine(struct linenoiseState *l) { * cursor position, and number of columns of the terminal. */ static void refreshMultiLine(struct linenoiseState *l) { char seq[64]; - int plen = strlen(l->prompt); - int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ - int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ + size_t pcollen = promptTextColumnLen(l->prompt,strlen(l->prompt)); + int colpos = columnPosForMultiLine(l->buf, l->len, l->len, l->cols, pcollen); + int colpos2; /* cursor column position. */ + int rows = (pcollen+colpos+l->cols-1)/l->cols; /* rows used by current buf. */ + int rpos = (pcollen+l->oldcolpos+l->cols)/l->cols; /* cursor relative row. */ int rpos2; /* rpos after refresh. */ - int col; /* colum position, zero-based. */ + int col; /* column position, zero-based. */ int old_rows = l->maxrows; int fd = l->ofd, j; struct abuf ab; @@ -568,13 +726,13 @@ static void refreshMultiLine(struct linenoiseState *l) { /* Now for every row clear it, go up. */ for (j = 0; j < old_rows-1; j++) { - lndebug("clear+up"); + lndebug("clear+up", NULL); snprintf(seq,64,"\r\x1b[0K\x1b[1A"); abAppend(&ab,seq,strlen(seq)); } /* Clean the top line. */ - lndebug("clear"); + lndebug("clear", NULL); snprintf(seq,64,"\r\x1b[0K"); abAppend(&ab,seq,strlen(seq)); @@ -583,15 +741,18 @@ static void refreshMultiLine(struct linenoiseState *l) { abAppend(&ab,l->buf,l->len); /* Show hits if any. */ - refreshShowHints(&ab,l,plen); + refreshShowHints(&ab,l,pcollen); + + /* Get column length to cursor position */ + colpos2 = columnPosForMultiLine(l->buf,l->len,l->pos,l->cols,pcollen); /* 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) + (colpos2+pcollen) % l->cols == 0) { - lndebug(""); + lndebug("", NULL); abAppend(&ab,"\n",1); snprintf(seq,64,"\r"); abAppend(&ab,seq,strlen(seq)); @@ -600,7 +761,7 @@ static void refreshMultiLine(struct linenoiseState *l) { } /* Move cursor to right position. */ - rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ + rpos2 = (pcollen+colpos2+l->cols)/l->cols; /* current cursor relative row. */ lndebug("rpos2 %d", rpos2); /* Go up till we reach the expected positon. */ @@ -611,7 +772,7 @@ static void refreshMultiLine(struct linenoiseState *l) { } /* Set column. */ - col = (plen+(int)l->pos) % (int)l->cols; + col = (pcollen + colpos2) % l->cols; lndebug("set col %d", 1+col); if (col) snprintf(seq,64,"\r\x1b[%dC", col); @@ -619,8 +780,8 @@ static void refreshMultiLine(struct linenoiseState *l) { snprintf(seq,64,"\r"); abAppend(&ab,seq,strlen(seq)); - lndebug("\n"); - l->oldpos = l->pos; + lndebug("\n", NULL); + l->oldcolpos = colpos2; if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ abFree(&ab); @@ -638,25 +799,25 @@ static void refreshLine(struct linenoiseState *l) { /* Insert the character 'c' at cursor current position. * * On error writing to the terminal -1 is returned, otherwise 0. */ -int linenoiseEditInsert(struct linenoiseState *l, char c) { - if (l->len < l->buflen) { +int linenoiseEditInsert(struct linenoiseState *l, const char *cbuf, int clen) { + if (l->len+clen <= l->buflen) { if (l->len == l->pos) { - l->buf[l->pos] = c; - l->pos++; - l->len++; + memcpy(&l->buf[l->pos],cbuf,clen); + l->pos+=clen; + l->len+=clen;; l->buf[l->len] = '\0'; - if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { + if ((!mlmode && promptTextColumnLen(l->prompt,l->plen)+columnPos(l->buf,l->len,l->len) < l->cols && !hintsCallback)) { /* Avoid a full update of the line in the * trivial case. */ - if (write(l->ofd,&c,1) == -1) return -1; + if (write(l->ofd,cbuf,clen) == -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++; + memmove(l->buf+l->pos+clen,l->buf+l->pos,l->len-l->pos); + memcpy(&l->buf[l->pos],cbuf,clen); + l->pos+=clen; + l->len+=clen; l->buf[l->len] = '\0'; refreshLine(l); } @@ -667,7 +828,7 @@ int linenoiseEditInsert(struct linenoiseState *l, char c) { /* Move cursor on the left. */ void linenoiseEditMoveLeft(struct linenoiseState *l) { if (l->pos > 0) { - l->pos--; + l->pos -= prevCharLen(l->buf,l->len,l->pos,NULL); refreshLine(l); } } @@ -675,11 +836,30 @@ void linenoiseEditMoveLeft(struct linenoiseState *l) { /* Move cursor on the right. */ void linenoiseEditMoveRight(struct linenoiseState *l) { if (l->pos != l->len) { - l->pos++; + l->pos += nextCharLen(l->buf,l->len,l->pos,NULL); refreshLine(l); } } +/* Move cursor to the end of the current word. */ +void linenoiseEditMoveWordEnd(struct linenoiseState *l) { + if (l->len == 0 || l->pos >= l->len) return; + if (l->buf[l->pos] == ' ') + while (l->pos < l->len && l->buf[l->pos] == ' ') ++l->pos; + while (l->pos < l->len && l->buf[l->pos] != ' ') ++l->pos; + refreshLine(l); +} + +/* Move cursor to the start of the current word. */ +void linenoiseEditMoveWordStart(struct linenoiseState *l) { + if (l->len == 0) return; + if (l->buf[l->pos-1] == ' ') --l->pos; + if (l->buf[l->pos] == ' ') + while (l->pos > 0 && l->buf[l->pos] == ' ') --l->pos; + while (l->pos > 0 && l->buf[l->pos-1] != ' ') --l->pos; + refreshLine(l); +} + /* Move cursor to the start of the line. */ void linenoiseEditMoveHome(struct linenoiseState *l) { if (l->pos != 0) { @@ -726,8 +906,9 @@ void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { * 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--; + int chlen = nextCharLen(l->buf,l->len,l->pos,NULL); + memmove(l->buf+l->pos,l->buf+l->pos+chlen,l->len-l->pos-chlen); + l->len-=chlen; l->buf[l->len] = '\0'; refreshLine(l); } @@ -736,15 +917,16 @@ void linenoiseEditDelete(struct linenoiseState *l) { /* Backspace implementation. */ void linenoiseEditBackspace(struct linenoiseState *l) { if (l->pos > 0 && l->len > 0) { - memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); - l->pos--; - l->len--; + int chlen = prevCharLen(l->buf,l->len,l->pos,NULL); + memmove(l->buf+l->pos-chlen,l->buf+l->pos,l->len-l->pos); + l->pos-=chlen; + l->len-=chlen; l->buf[l->len] = '\0'; refreshLine(l); } } -/* Delete the previosu word, maintaining the cursor at the start of the +/* Delete the previous word, maintaining the cursor at the start of the * current word. */ void linenoiseEditDeletePrevWord(struct linenoiseState *l) { size_t old_pos = l->pos; @@ -760,6 +942,16 @@ void linenoiseEditDeletePrevWord(struct linenoiseState *l) { refreshLine(l); } +/* Delete the next word, maintaining the cursor at the same position */ +void linenoiseEditDeleteNextWord(struct linenoiseState *l) { + size_t next_word_end = l->pos; + while (next_word_end < l->len && l->buf[next_word_end] == ' ') ++next_word_end; + while (next_word_end < l->len && l->buf[next_word_end] != ' ') ++next_word_end; + memmove(l->buf+l->pos, l->buf+next_word_end, l->len-next_word_end); + l->len -= next_word_end - l->pos; + refreshLine(l); +} + /* This function is the core of the line editing capability of linenoise. * It expects 'fd' to be already in "raw mode" so that every key pressed * will be returned ASAP to read(). @@ -778,10 +970,9 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, l.ofd = stdout_fd; l.buf = buf; l.buflen = buflen; - //TODO change prompt count to prompt-colors l.prompt = prompt; l.plen = strlen(prompt); - l.oldpos = l.pos = 0; + l.oldcolpos = l.pos = 0; l.len = 0; l.cols = getColumns(stdin_fd, stdout_fd); l.maxrows = 0; @@ -797,18 +988,25 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, if (write(l.ofd,prompt,l.plen) == -1) return -1; while(1) { - char c; + signed int c; + char cbuf[32]; // large enough for any encoding? int nread; char seq[3]; - nread = read(l.ifd,&c,1); + /* Continue reading if interrupted by a signal */ +// TODO +// do { +// nread = read(l.ifd,&c,1); +// } while((nread == -1) && (errno == EINTR)); + nread = readCode(l.ifd,cbuf,sizeof(cbuf),&c); if (nread <= 0) return l.len; + /* Only autocomplete when the callback is set. It returns < 0 when * there was an error reading from fd. Otherwise it will return the * character that should be handled next. */ - if (c == 9 && completionCallback != NULL) { - c = completeLine(&l); + if (c == TAB && completionCallback != NULL) { + nread = completeLine(&l,cbuf,sizeof(cbuf),&c); /* Return on errors */ if (c < 0) return l.len; /* Read next character when 0 */ @@ -816,6 +1014,7 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, } switch(c) { + case LINE_FEED:/* line feed */ case ENTER: /* enter */ history_len--; free(history[history_len]); @@ -868,38 +1067,69 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, 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] == '~') { + /* ESC ? sequences */ + if (seq[0] != '[' && seq[0] != '0') { + switch (seq[0]) { + case 'f': + linenoiseEditMoveWordEnd(&l); + break; + case 'b': + linenoiseEditMoveWordStart(&l); + break; + case 'd': + linenoiseEditDeleteNextWord(&l); + break; + } + } else { + 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 '3': /* Delete key. */ - linenoiseEditDelete(&l); + 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; + case 'd': /* End*/ + linenoiseEditDeleteNextWord(&l); + break; + case '1': /* Home */ + linenoiseEditMoveHome(&l); + break; + case '4': /* End */ + linenoiseEditMoveEnd(&l); break; } } - } else { + } + /* ESC O sequences. */ + else if (seq[0] == 'O') { 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; @@ -909,21 +1139,9 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, } } } - - /* 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; + if (linenoiseEditInsert(&l,cbuf,nread)) return -1; break; case CTRL_U: /* Ctrl+u, delete the whole line. */ buf[0] = '\0'; @@ -974,7 +1192,7 @@ void linenoisePrintKeyCodes(void) { if (memcmp(quit,"quit",sizeof(quit)) == 0) break; printf("'%c' %02x (%d) (type quit to exit)\n", - isprint(c) ? c : '?', (int)c, (int)c); + isprint((int)c) ? c : '?', (int)c, (int)c); printf("\r"); /* Go left edge manually, we are in raw mode. */ fflush(stdout); } @@ -983,18 +1201,21 @@ void linenoisePrintKeyCodes(void) { /* This function calls the line editing function linenoiseEdit() using * the STDIN file descriptor set in raw mode. */ -static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { - int count; +static int linenoiseRaw(char *buf, FILE *out, size_t buflen, const char *prompt) { + int outfd, count; if (buflen == 0) { errno = EINVAL; return -1; } + if ((outfd = fileno(out)) == -1) + return -1; + if (enableRawMode(STDIN_FILENO) == -1) return -1; - count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); + count = linenoiseEdit(STDIN_FILENO, outfd, buf, buflen, prompt); disableRawMode(STDIN_FILENO); - printf("\n"); + fprintf(out, "\n"); return count; } @@ -1008,17 +1229,18 @@ static char *linenoiseNoTTY(void) { size_t len = 0, maxlen = 0; while(1) { + int c; if (len == maxlen) { + char *oldval = line; 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); + c = fgetc(stdin); if (c == EOF || c == '\n') { if (c == EOF && len == 0) { free(line); @@ -1041,8 +1263,10 @@ static char *linenoiseNoTTY(void) { * something even in the most desperate of the conditions. */ char *linenoise(const char *prompt) { char buf[LINENOISE_MAX_LINE]; + FILE *stream; int count; + stream = isatty(STDOUT_FILENO) ? stdout : stderr; 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. */ @@ -1050,8 +1274,8 @@ char *linenoise(const char *prompt) { } else if (isUnsupportedTerm()) { size_t len; - printf("%s",prompt); - fflush(stdout); + fprintf(stream, "%s",prompt); + fflush(stream); if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; len = strlen(buf); while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { @@ -1060,7 +1284,7 @@ char *linenoise(const char *prompt) { } return strdup(buf); } else { - count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); + count = linenoiseRaw(buf,stream,LINENOISE_MAX_LINE,prompt); if (count == -1) return NULL; return strdup(buf); } @@ -1172,7 +1396,7 @@ int linenoiseHistorySave(const char *filename) { fp = fopen(filename,"w"); umask(old_umask); if (fp == NULL) return -1; - chmod(filename,S_IRUSR|S_IWUSR); + (void)chmod(filename,S_IRUSR|S_IWUSR); for (j = 0; j < history_len; j++) fprintf(fp,"%s\n",history[j]); fclose(fp); @@ -1180,7 +1404,7 @@ int linenoiseHistorySave(const char *filename) { } /* Load the history from the specified file. If the file does not exist - * zero is returned and no operation is performed. + * -1 is returned and no operation is performed. * * If the file exists and the operation succeeded 0 is returned, otherwise * on error -1 is returned. */ @@ -1201,3 +1425,4 @@ int linenoiseHistoryLoad(const char *filename) { fclose(fp); return 0; } + diff --git a/src/libs/linenoise.h b/src/libs/linenoise.h index ed20232..5a1d390 100644 --- a/src/libs/linenoise.h +++ b/src/libs/linenoise.h @@ -39,10 +39,13 @@ #ifndef __LINENOISE_H #define __LINENOISE_H +#include + #ifdef __cplusplus extern "C" { #endif +#include typedef struct linenoiseCompletions { size_t len; char **cvec; @@ -55,6 +58,7 @@ void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); void linenoiseSetHintsCallback(linenoiseHintsCallback *); void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); void linenoiseAddCompletion(linenoiseCompletions *, const char *); +void linenoiseAddHistoryCompletions(const char*, linenoiseCompletions *); char *linenoise(const char *prompt); void linenoiseFree(void *ptr); @@ -66,6 +70,15 @@ void linenoiseClearScreen(void); void linenoiseSetMultiLine(int ml); void linenoisePrintKeyCodes(void); +typedef size_t (linenoisePrevCharLen)(const char *buf, size_t buf_len, size_t pos, size_t *col_len); +typedef size_t (linenoiseNextCharLen)(const char *buf, size_t buf_len, size_t pos, size_t *col_len); +typedef size_t (linenoiseReadCode)(int fd, char *buf, size_t buf_len, int* c); + +void linenoiseSetEncodingFunctions( + linenoisePrevCharLen *prevCharLenFunc, + linenoiseNextCharLen *nextCharLenFunc, + linenoiseReadCode *readCodeFunc); + #ifdef __cplusplus } #endif diff --git a/src/ui.c b/src/ui.c index ba0ce9a..5f7c4a8 100644 --- a/src/ui.c +++ b/src/ui.c @@ -52,9 +52,8 @@ int main(void){ #ifdef GNU //get user input strncpy(inpt, readline(prompt), MAX_USER_INPUT); - #else - printf("%s\n", prompt); //Workaround for https://github.com/antirez/linenoise/issues/158 - input = linenoise("-> "); + #else + input = linenoise(prompt); strncpy(inpt, input, MAX_USER_INPUT); #endif #endif