packages feed

linenoise 0.3.2 → 0.4.0

raw patch · 11 files changed

+555/−361 lines, 11 filesdep ~textsetup-changed

Dependency ranges changed: text

Files

README.md view
@@ -1,7 +1,5 @@ # linenoise -[![CircleCI](https://circleci.com/gh/ejconlon/haskell-linenoise/tree/master.svg?style=svg)](https://circleci.com/gh/ejconlon/haskell-linenoise/tree/master)- A lightweight readline-replacement library for Haskell based on the `linenoise` library. (Not production ready!) See the [demo app](https://github.com/ejconlon/haskell-linenoise/blob/master/app/Main.hs) for usage, or see the [climb](https://github.com/ejconlon/climb) library for higher-level building blocks for GHCi-like REPLs with colon-commands. @@ -37,7 +35,3 @@  In addition, there are targets starting with `ci` that are invoked in various CI phases. -## TODO--* Support unicode (may involve vendoring a `linenoise` [fork](https://github.com/yhirose/linenoise/tree/utf8-support))-* Verify that the FFI modifications for `ByteString` are memory-safe
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
app/Main.hs view
@@ -22,7 +22,7 @@  action :: Text -> Repl ReplDirective action x = do-  modify (x:)+  modify (x :)   liftIO (TIO.putStrLn x)   pure ReplContinue 
cbits/linenoise.c view
@@ -10,7 +10,7 @@  *  * ------------------------------------------------------------------------  *- * Copyright (c) 2010-2016, Salvatore Sanfilippo <antirez at gmail dot com>+ * Copyright (c) 2010-2023, Salvatore Sanfilippo <antirez at gmail dot com>  * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>  *  * All rights reserved.@@ -123,6 +123,9 @@ 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. */@@ -133,24 +136,6 @@ static int history_len = 0; static char **history = NULL; -/* The linenoiseState structure represents the state during line editing.- * We pass this state to functions implementing specific editing- * functionalities. */-struct linenoiseState {-    int ifd;            /* Terminal stdin file descriptor. */-    int ofd;            /* Terminal stdout file descriptor. */-    char *buf;          /* Edited line buffer. */-    size_t buflen;      /* Edited line buffer size. */-    const char *prompt; /* Prompt to display. */-    size_t plen;        /* Prompt length. */-    size_t pos;         /* Current cursor position. */-    size_t oldpos;      /* Previous refresh cursor position. */-    size_t len;         /* Current edited line length. */-    size_t cols;        /* Number of columns in terminal. */-    size_t maxrows;     /* Maximum num of rows used so far (multiline mode) */-    int history_index;  /* The history index we are currently editing. */-};- enum KEY_ACTION{ 	KEY_NULL = 0,	    /* NULL */ 	CTRL_A = 1,         /* Ctrl+a */@@ -175,6 +160,9 @@  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. */@@ -187,7 +175,7 @@             fprintf(lndebug_fp, \             "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \             (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \-            (int)l->maxrows,old_rows); \+            (int)l->oldrows,old_rows); \         } \         fprintf(lndebug_fp, ", " __VA_ARGS__); \         fflush(lndebug_fp); \@@ -355,63 +343,94 @@         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 <tab> key in order to complete the string currently in the  * input.  *  * The state of the editing is encapsulated into the pointed linenoiseState- * structure as described in the structure definition. */-static int 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 nwritten;+    char c = keypressed;      completionCallback(ls->buf,&lc);     if (lc.len == 0) {         linenoiseBeep();+        ls->in_completion = 0;     } else {-        size_t stop = 0, i = 0;--        while(!stop) {-            /* Show completion or original buffer */-            if (i < lc.len) {-                struct linenoiseState saved = *ls;--                ls->len = ls->pos = strlen(lc.cvec[i]);-                ls->buf = lc.cvec[i];-                refreshLine(ls);-                ls->len = saved.len;-                ls->pos = saved.pos;-                ls->buf = saved.buf;-            } else {-                refreshLine(ls);-            }--            nread = read(ls->ifd,&c,1);-            if (nread <= 0) {-                freeCompletions(&lc);-                return -1;-            }+        switch(c) {+            case 9: /* tab */+                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 9: /* tab */-                    i = (i+1) % (lc.len+1);-                    if (i == lc.len) linenoiseBeep();-                    break;-                case 27: /* escape */-                    /* Re-show original buffer */-                    if (i < lc.len) refreshLine(ls);-                    stop = 1;-                    break;-                default:-                    /* Update buffer and return */-                    if (i < lc.len) {-                        nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]);-                        ls->len = ls->pos = nwritten;-                    }-                    stop = 1;-                    break;-            }+        /* Show completion or original buffer */+        if (ls->in_completion && ls->completion_idx < lc.len) {+            refreshLineWithCompletion(ls,&lc,REFRESH_ALL);+        } else {+            refreshLine(ls);         }     } @@ -514,8 +533,11 @@ /* 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 = strlen(l->prompt);     int fd = l->ofd;@@ -535,23 +557,31 @@      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));-    if (maskmode == 1) {-        while (len--) abAppend(&ab,"*",1);-    } else {-        abAppend(&ab,buf,len);++    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);     }-    /* Show hits if any. */-    refreshShowHints(&ab,l,plen);+     /* Erase to right */-    snprintf(seq,64,"\x1b[0K");-    abAppend(&ab,seq,strlen(seq));-    /* Move cursor to original position. */-    snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen));+    snprintf(seq,sizeof(seq),"\x1b[0K");     abAppend(&ab,seq,strlen(seq));++    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); }@@ -559,88 +589,97 @@ /* 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 = strlen(l->prompt);     int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */     int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */     int rpos2; /* rpos after refresh. */     int col; /* colum position, zero-based. */-    int old_rows = l->maxrows;+    int old_rows = l->oldrows;     int fd = l->ofd, j;     struct abuf ab; -    /* Update maxrows if needed. */-    if (rows > (int)l->maxrows) l->maxrows = 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);-        abAppend(&ab,seq,strlen(seq));++    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));+        }     } -    /* 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_ALL) {+        /* Clean the top line. */+        lndebug("clear");+        snprintf(seq,64,"\r\x1b[0K");         abAppend(&ab,seq,strlen(seq));     } -    /* Clean the top line. */-    lndebug("clear");-    snprintf(seq,64,"\r\x1b[0K");-    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) {+            unsigned int i;+            for (i = 0; i < l->len; i++) abAppend(&ab,"*",1);+        } else {+            abAppend(&ab,l->buf,l->len);+        } -    /* 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); -    /* 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("<newline>");+            abAppend(&ab,"\n",1);+            snprintf(seq,64,"\r");+            abAppend(&ab,seq,strlen(seq));+            rows++;+            if (rows > (int)l->oldrows) l->oldrows = rows;+        } -    /* 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("<newline>");-        abAppend(&ab,"\n",1);-        snprintf(seq,64,"\r");-        abAppend(&ab,seq,strlen(seq));-        rows++;-        if (rows > (int)l->maxrows) l->maxrows = rows;-    }+        /* Move cursor to right position. */+        rpos2 = (plen+l->pos+l->cols)/l->cols; /* Current cursor relative row */+        lndebug("rpos2 %d", rpos2); -    /* 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));+        } -    /* 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);+        /* 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));     } -    /* 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; @@ -650,13 +689,35 @@  /* 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);+}++/* 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);+        refreshMultiLine(l,REFRESH_CLEAN);     else-        refreshSingleLine(l);+        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. */@@ -783,198 +844,278 @@     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().+/* 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(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt)-{-    struct linenoiseState l;-+ * 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.ifd = stdin_fd;-    l.ofd = stdout_fd;-    l.buf = buf;-    l.buflen = buflen;-    l.prompt = prompt;-    l.plen = strlen(prompt);-    l.oldpos = l.pos = 0;-    l.len = 0;-    l.cols = getColumns(stdin_fd, stdout_fd);-    l.maxrows = 0;-    l.history_index = 0;+    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(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 */+    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(""); -    if (write(l.ofd,prompt,l.plen) == -1) return -1;-    while(1) {-        char c;-        int nread;-        char seq[3];+    if (write(l->ofd,prompt,l->plen) == -1) return -1;+    return 0;+} -        nread = read(l.ifd,&c,1);-        if (nread <= 0) return l.len;+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."; -        /* Only autocomplete when the callback is set. It returns < 0 when-         * there was an error reading from fd. Otherwise it will return the-         * character that should be handled next. */-        if (c == 9 && completionCallback != NULL) {-            c = completeLine(&l);-            /* Return on errors */-            if (c < 0) return l.len;-            /* Read next character when 0 */-            if (c == 0) continue;-        }+/* 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(); -        switch(c) {-        case ENTER:    /* enter */+    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]);-            if (mlmode) linenoiseEditMoveEnd(&l);-            if (hintsCallback) {-                /* Force a refresh without hints to leave the previous-                 * line as the user typed it after a newline. */-                linenoiseHintsCallback *hc = hintsCallback;-                hintsCallback = NULL;-                refreshLine(&l);-                hintsCallback = hc;-            }-            return (int)l.len;-        case CTRL_C:     /* ctrl-c */-            errno = EAGAIN;-            return -1;-        case BACKSPACE:   /* backspace */-        case 8:     /* ctrl-h */-            linenoiseEditBackspace(&l);-            break;-        case CTRL_D:     /* ctrl-d, remove char at right of cursor, or if the-                            line is empty, act as end-of-file. */-            if (l.len > 0) {-                linenoiseEditDelete(&l);-            } else {-                history_len--;-                free(history[history_len]);-                return -1;-            }-            break;-        case CTRL_T:    /* ctrl-t, swaps current character with previous. */-            if (l.pos > 0 && l.pos < l.len) {-                int aux = buf[l.pos-1];-                buf[l.pos-1] = buf[l.pos];-                buf[l.pos] = aux;-                if (l.pos != l.len-1) l.pos++;-                refreshLine(&l);-            }-            break;-        case CTRL_B:     /* ctrl-b */-            linenoiseEditMoveLeft(&l);-            break;-        case CTRL_F:     /* ctrl-f */-            linenoiseEditMoveRight(&l);-            break;-        case CTRL_P:    /* ctrl-p */-            linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);-            break;-        case CTRL_N:    /* ctrl-n */-            linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);-            break;-        case ESC:    /* escape sequence */-            /* Read the next two bytes representing the escape sequence.-             * Use two calls to handle slow terminals returning the two-             * chars at different times. */-            if (read(l.ifd,seq,1) == -1) break;-            if (read(l.ifd,seq+1,1) == -1) break;+            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 {+        /* 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 '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);+                    case '3': /* Delete key. */+                        linenoiseEditDelete(l);                         break;                     }                 }-            }--            /* ESC O sequences. */-            else if (seq[0] == 'O') {+            } 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);+                    linenoiseEditMoveHome(l);                     break;                 case 'F': /* End*/-                    linenoiseEditMoveEnd(&l);+                    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;         }++        /* 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 l.len;+    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 NULL;+    }++    linenoiseEditStart(&l,stdin_fd,stdout_fd,buf,buflen,prompt);+    char *res;+    while((res = linenoiseEditFeed(&l)) == linenoiseEditMore);+    linenoiseEditStop(&l);+    return res;+}+ /* 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. */@@ -1003,23 +1144,6 @@     disableRawMode(STDIN_FILENO); } -/* This function calls the line editing function linenoiseEdit() using- * the STDIN file descriptor set in raw mode. */-static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {-    int count;--    if (buflen == 0) {-        errno = EINVAL;-        return -1;-    }--    if (enableRawMode(STDIN_FILENO) == -1) return -1;-    count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt);-    disableRawMode(STDIN_FILENO);-    printf("\n");-    return count;-}- /* This function is called when linenoise() is called with the standard  * input file descriptor not attached to a TTY. So for example when the  * program using linenoise is called in pipe or with a file redirected@@ -1063,7 +1187,6 @@  * something even in the most desperate of the conditions. */ char *linenoise(const char *prompt) {     char buf[LINENOISE_MAX_LINE];-    int count;      if (!isatty(STDIN_FILENO)) {         /* Not a tty: read from file / pipe. In this mode we don't want any@@ -1082,9 +1205,8 @@         }         return strdup(buf);     } else {-        count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt);-        if (count == -1) return NULL;-        return strdup(buf);+        char *retval = linenoiseBlockingEdit(STDIN_FILENO,STDOUT_FILENO,buf,LINENOISE_MAX_LINE,prompt);+        return retval;     } } @@ -1093,6 +1215,7 @@  * 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); } 
cbits/linenoise.h view
@@ -7,7 +7,7 @@  *  * ------------------------------------------------------------------------  *- * Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>+ * Copyright (c) 2010-2023, Salvatore Sanfilippo <antirez at gmail dot com>  * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>  *  * All rights reserved.@@ -43,11 +43,48 @@ extern "C" { #endif +#include <stddef.h> /* 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; +/* 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);++/* Blocking API. */+char *linenoise(const char *prompt);+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 *);@@ -56,12 +93,13 @@ void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); void linenoiseAddCompletion(linenoiseCompletions *, const char *); -char *linenoise(const char *prompt);-void linenoiseFree(void *ptr);+/* History API. */ int linenoiseHistoryAdd(const char *line); int linenoiseHistorySetMaxLen(int len); int linenoiseHistorySave(const char *filename); int linenoiseHistoryLoad(const char *filename);++/* Other utilities. */ void linenoiseClearScreen(void); void linenoiseSetMultiLine(int ml); void linenoisePrintKeyCodes(void);
linenoise.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.33.0.+-- This file has been generated from package.yaml by hpack version 0.36.0. -- -- see: https://github.com/sol/hpack ----- hash: 5a1085adefd24661dcb4f86110e45c0a9535af0b1e7189721c089f0eae60c5d2+-- hash: 0254782fbfe2bb0d8c7beef670cb12c6e6acd0d2993d01655a777fb0e3a5ac17  name:           linenoise-version:        0.3.2+version:        0.4.0 synopsis:       A lightweight readline-replacement library for Haskell description:    Please see the README on GitHub at <https://github.com/ejconlon/haskell-linenoise#readme> category:       User Interfaces@@ -48,7 +48,7 @@     , bytestring >=0.10 && <1     , exceptions >=0.10 && <1     , mtl >=2.2 && <3-    , text >=1.2 && <2+    , text >=1.2 && <3     , unliftio-core >=0.1 && <1   default-language: Haskell2010 @@ -65,6 +65,6 @@     , exceptions >=0.10 && <1     , linenoise     , mtl >=2.2 && <3-    , text >=1.2 && <2+    , text >=1.2 && <3     , unliftio-core >=0.1 && <1   default-language: Haskell2010
src/Linenoise.hs view
@@ -15,9 +15,20 @@   , setCompletion   , setMultiline   , stifleHistory-  ) where+  )+where  import Linenoise.Completion (byWord) import Linenoise.Repl (ReplDirective (..), ReplT (..), replM, runReplT)-import Linenoise.Unlift (InputResult (..), addHistory, clearScreen, getInputLine, historyLoad, historySave,-                         printKeycodes, setCompletion, setMultiline, stifleHistory)+import Linenoise.Unlift+  ( InputResult (..)+  , addHistory+  , clearScreen+  , getInputLine+  , historyLoad+  , historySave+  , printKeycodes+  , setCompletion+  , setMultiline+  , stifleHistory+  )
src/Linenoise/Completion.hs view
@@ -2,7 +2,8 @@  module Linenoise.Completion   ( byWord-  ) where+  )+where  import Data.Text (Text) import qualified Data.Text as Text@@ -15,7 +16,7 @@     [] -> f line     [_] -> f line     sp -> do-      let (x,xs) = (last sp, init sp)+      let (x, xs) = (last sp, init sp)       res <- f x       case res of         [] -> pure [line]
src/Linenoise/FFI.hs view
@@ -13,7 +13,8 @@   , setCompletion   , setMultiline   , stifleHistory-  ) where+  )+where  import Control.Monad (unless) import Data.ByteString (ByteString)@@ -27,27 +28,36 @@  foreign import ccall "linenoise.h linenoise"   linenoise :: CString -> IO CString+ foreign import ccall "linenoise.h linenoiseHistoryAdd"   linenoiseHistoryAdd :: Ptr CChar -> IO CInt+ foreign import ccall "linenoise.h linenoiseHistorySetMaxLen"   linenoiseHistorySetMaxLen :: CInt -> IO CInt+ foreign import ccall "linenoise.h linenoiseHistorySave"   linenoiseHistorySave :: CString -> IO ()+ foreign import ccall "linenoise.h linenoiseHistoryLoad"   linenoiseHistoryLoad :: CString -> IO ()+ foreign import ccall "linenoise.h linenoiseClearScreen"   linenoiseClearScreen :: IO ()+ foreign import ccall "linenoise.h linenoiseSetMultiLine"   linenoiseSetMultiLine :: CInt -> IO ()+ foreign import ccall "linenoise.h linenoisePrintKeyCodes"   linenoisePrintKeyCodes :: IO ()+ foreign import ccall "linenoise.h linenoiseSetCompletionCallback"   linenoiseSetCompletionCallback :: FunPtr CompleteFunc -> IO ()+ foreign import ccall "linenoise.h linenoiseAddCompletion"   linenoiseAddCompletion :: Completion -> CString -> IO ()  foreign import ccall "wrapper"-    makeFunPtr :: CompleteFunc -> IO (FunPtr CompleteFunc)+  makeFunPtr :: CompleteFunc -> IO (FunPtr CompleteFunc)  data CompletionType = CompletionType CSize (Ptr (Ptr CChar))   deriving (Show, Eq)@@ -75,9 +85,11 @@  -- | Result of getInputLine. data InputResult a-  = InterruptResult        -- ^ ctrl+c-  | EofResult              -- ^ ctrl+d-  | LineResult !a          -- Possibly empty line.+  = -- | ctrl+c+    InterruptResult+  | -- | ctrl+d+    EofResult+  | LineResult !a -- Possibly empty line.   deriving (Eq, Show, Functor, Foldable, Traversable)  -- | Run the prompt, yielding a string.
src/Linenoise/Repl.hs view
@@ -8,7 +8,8 @@   , ReplT (..)   , replM   , runReplT-  ) where+  )+where  import Control.Applicative (Alternative) import Control.Monad (MonadPlus)@@ -28,11 +29,21 @@ -- | Basic monad transformer with mutable state that can be used with all "Linenoise.Unlift" functions. --   You do not have to use this, but it's here to cover most of what you would need without having --   to roll your own newtype.-newtype ReplT r s m a = ReplT { unReplT :: ReaderT r (ReaderT (IORef s) m) a }-  deriving (Functor, Applicative, Monad, MonadIO,-            Alternative, MonadPlus, MonadFix, MonadZip,-            MonadFail, MonadThrow, MonadCatch,-            MonadReader r)+newtype ReplT r s m a = ReplT {unReplT :: ReaderT r (ReaderT (IORef s) m) a}+  deriving+    ( Functor+    , Applicative+    , Monad+    , MonadIO+    , Alternative+    , MonadPlus+    , MonadFix+    , MonadZip+    , MonadFail+    , MonadThrow+    , MonadCatch+    , MonadReader r+    )  askRef :: Applicative m => ReplT r s m (IORef s) askRef = ReplT (ReaderT (const (ReaderT pure)))@@ -46,7 +57,7 @@ instance MonadUnliftIO m => MonadUnliftIO (ReplT r s m) where   withRunInIO run = do     r <- ask-    ref <-askRef+    ref <- askRef     wrappedWithRunInIO lift (\n -> refReplT n r ref) run  instance MonadIO m => MonadState s (ReplT r s m) where@@ -70,12 +81,17 @@ -- | Run a simple REPL. replM   :: MonadUnliftIO m-  => ReplDirective                   -- ^ Directive on interrupt-  -> Text                            -- ^ Prompt-  -> (Text -> m ReplDirective)       -- ^ Action-  -> (Text -> m [Text])              -- ^ Completion+  => ReplDirective+  -- ^ Directive on interrupt+  -> Text+  -- ^ Prompt+  -> (Text -> m ReplDirective)+  -- ^ Action+  -> (Text -> m [Text])+  -- ^ Completion   -> m ()-replM onInterrupt prompt action comp = loop where+replM onInterrupt prompt action comp = loop+ where   loop = do     Unlift.setCompletion comp     res <- Unlift.getInputLine prompt
src/Linenoise/Unlift.hs view
@@ -10,7 +10,8 @@   , setCompletion   , setMultiline   , stifleHistory-  ) where+  )+where  import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.IO.Unlift (MonadUnliftIO, withRunInIO)@@ -47,7 +48,7 @@ setCompletion :: MonadUnliftIO m => (Text -> m [Text]) -> m () setCompletion f =   let g = fmap (fmap encodeUtf8) . f . decodeUtf8-  in withRunInIO (\runInIO -> FFI.setCompletion (runInIO . g))+  in  withRunInIO (\runInIO -> FFI.setCompletion (runInIO . g))  -- | Enable/Disable multiline input. setMultiline :: MonadIO m => Bool -> m ()