]> git.lizzy.rs Git - linenoise.git/blobdiff - linenoise.c
Ctrl-l support to clear the screen. New sequences added documented.
[linenoise.git] / linenoise.c
index 55eee639471c6756574609e509faf8379835b19f..dd43413661ca9677c51667ca657daeca50706307 100644 (file)
@@ -9,6 +9,8 @@
  * the 2010 UNIX computers around.
  *
  * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * CUF (CUrsor Forward)
  *    Sequence: ESC [ n C
  *    Effect: moves cursor forward of n chars
+ *
+ * The following are used to clear the screen: ESC [ H ESC [ 2 J
+ * This is actually composed of two sequences:
+ *
+ * cursorhome
+ *    Sequence: ESC [ H
+ *    Effect: moves the cursor to upper left corner
+ *
+ * ED2 (Clear entire screen)
+ *    Sequence: ESC [ 2 J
+ *    Effect: clear the whole screen
  * 
  */
 
 #include <sys/types.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
+#include "linenoise.h"
 
+#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
 #define LINENOISE_MAX_LINE 4096
-static char *unsupported_term[] = {"dumb","eterm","cons25",NULL};
+static char *unsupported_term[] = {"dumb","cons25",NULL};
+static linenoiseCompletionCallback *completionCallback = NULL;
 
 static struct termios orig_termios; /* in order to restore at exit */
 static int rawmode = 0; /* for atexit() function to check if restore is needed*/
 static int atexit_registered = 0; /* register atexit just 1 time */
-static int history_max_len = 100;
+static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
 static int history_len = 0;
 char **history = NULL;
 
@@ -194,6 +210,78 @@ static void refreshLine(int fd, const char *prompt, char *buf, size_t len, size_
     if (write(fd,seq,strlen(seq)) == -1) return;
 }
 
+static void beep() {
+    fprintf(stderr, "\x7");
+    fflush(stderr);
+}
+
+static void freeCompletions(linenoiseCompletions *lc) {
+    size_t i;
+    for (i = 0; i < lc->len; i++)
+        free(lc->cvec[i]);
+    if (lc->cvec != NULL)
+        free(lc->cvec);
+}
+
+static int completeLine(int fd, const char *prompt, char *buf, size_t buflen, size_t *len, size_t *pos, size_t cols) {
+    linenoiseCompletions lc = { 0, NULL };
+    int nread, nwritten;
+    char c = 0;
+
+    completionCallback(buf,&lc);
+    if (lc.len == 0) {
+        beep();
+    } else {
+        size_t stop = 0, i = 0;
+        size_t clen;
+
+        while(!stop) {
+            /* Show completion or original buffer */
+            if (i < lc.len) {
+                clen = strlen(lc.cvec[i]);
+                refreshLine(fd,prompt,lc.cvec[i],clen,clen,cols);
+            } else {
+                refreshLine(fd,prompt,buf,*len,*pos,cols);
+            }
+
+            nread = read(fd,&c,1);
+            if (nread <= 0) {
+                freeCompletions(&lc);
+                return -1;
+            }
+
+            switch(c) {
+                case 9: /* tab */
+                    i = (i+1) % (lc.len+1);
+                    if (i == lc.len) beep();
+                    break;
+                case 27: /* escape */
+                    /* Re-show original buffer */
+                    if (i < lc.len) {
+                        refreshLine(fd,prompt,buf,*len,*pos,cols);
+                    }
+                    stop = 1;
+                    break;
+                default:
+                    /* Update buffer and return */
+                    if (i < lc.len) {
+                        nwritten = snprintf(buf,buflen,"%s",lc.cvec[i]);
+                        *len = *pos = nwritten;
+                    }
+                    stop = 1;
+                    break;
+            }
+        }
+    }
+
+    freeCompletions(&lc);
+    return c; /* Return last read character */
+}
+
+void linenoiseClearScreen(void) {
+    write(STDIN_FILENO,"\x1b[H\x1b[2J",7);
+}
+
 static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) {
     size_t plen = strlen(prompt);
     size_t pos = 0;
@@ -212,17 +300,28 @@ static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt)
     while(1) {
         char c;
         int nread;
-        char seq[2];
+        char seq[2], seq2[2];
 
         nread = read(fd,&c,1);
         if (nread <= 0) return 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(fd,prompt,buf,buflen,&len,&pos,cols);
+            /* Return on errors */
+            if (c < 0) return len;
+            /* Read next character when 0 */
+            if (c == 0) continue;
+        }
+
         switch(c) {
         case 13:    /* enter */
-            history_len--;
-            return len;
         case 4:     /* ctrl-d */
             history_len--;
-            return (len == 0) ? -1 : (int)len;
+            free(history[history_len]);
+            return (len == 0 && c == 4) ? -1 : (int)len;
         case 3:     /* ctrl-c */
             errno = EAGAIN;
             return -1;
@@ -294,6 +393,18 @@ up_down_arrow:
                     len = pos = strlen(buf);
                     refreshLine(fd,prompt,buf,len,pos,cols);
                 }
+            } else if (seq[0] == 91 && seq[1] > 48 && seq[1] < 55) {
+                /* extended escape */
+                if (read(fd,seq2,2) == -1) break;
+                if (seq[1] == 51 && seq2[0] == 126) {
+                    /* delete */
+                    if (len > 0 && pos < len) {
+                        memmove(buf+pos,buf+pos+1,len-pos-1);
+                        len--;
+                        buf[len] = '\0';
+                        refreshLine(fd,prompt,buf,len,pos,cols);
+                    }
+                }
             }
             break;
         default:
@@ -338,6 +449,9 @@ up_down_arrow:
             pos = len;
             refreshLine(fd,prompt,buf,len,pos,cols);
             break;
+        case 12: /* ctrl+l, clear screen */
+            linenoiseClearScreen();
+            refreshLine(fd,prompt,buf,len,pos,cols);
         }
     }
     return len;
@@ -351,10 +465,19 @@ static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
         errno = EINVAL;
         return -1;
     }
-    if (enableRawMode(fd) == -1) return -1;
-    count = linenoisePrompt(fd, buf, buflen, prompt);
-    disableRawMode(fd);
-    printf("\n");
+    if (!isatty(STDIN_FILENO)) {
+        if (fgets(buf, buflen, stdin) == NULL) return -1;
+        count = strlen(buf);
+        if (count && buf[count-1] == '\n') {
+            count--;
+            buf[count] = '\0';
+        }
+    } else {
+        if (enableRawMode(fd) == -1) return -1;
+        count = linenoisePrompt(fd, buf, buflen, prompt);
+        disableRawMode(fd);
+        printf("\n");
+    }
     return count;
 }
 
@@ -381,12 +504,25 @@ char *linenoise(const char *prompt) {
     }
 }
 
+/* Register a callback function to be called for tab-completion. */
+void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
+    completionCallback = fn;
+}
+
+void linenoiseAddCompletion(linenoiseCompletions *lc, char *str) {
+    size_t len = strlen(str);
+    char *copy = malloc(len+1);
+    memcpy(copy,str,len+1);
+    lc->cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
+    lc->cvec[lc->len++] = copy;
+}
+
 /* Using a circular buffer is smarter, but a bit more complex to handle. */
 int linenoiseHistoryAdd(const char *line) {
     char *linecopy;
 
     if (history_max_len == 0) return 0;
-    if (history == 0) {
+    if (history == NULL) {
         history = malloc(sizeof(char*)*history_max_len);
         if (history == NULL) return 0;
         memset(history,0,(sizeof(char*)*history_max_len));
@@ -394,6 +530,7 @@ int linenoiseHistoryAdd(const char *line) {
     linecopy = strdup(line);
     if (!linecopy) return 0;
     if (history_len == history_max_len) {
+        free(history[0]);
         memmove(history,history+1,sizeof(char*)*(history_max_len-1));
         history_len--;
     }
@@ -421,3 +558,39 @@ int linenoiseHistorySetMaxLen(int len) {
         history_len = history_max_len;
     return 1;
 }
+
+/* Save the history in the specified file. On success 0 is returned
+ * otherwise -1 is returned. */
+int linenoiseHistorySave(char *filename) {
+    FILE *fp = fopen(filename,"w");
+    int j;
+    
+    if (fp == NULL) return -1;
+    for (j = 0; j < history_len; j++)
+        fprintf(fp,"%s\n",history[j]);
+    fclose(fp);
+    return 0;
+}
+
+/* Load the history from the specified file. If the file does not exist
+ * zero is returned and no operation is performed.
+ *
+ * If the file exists and the operation succeeded 0 is returned, otherwise
+ * on error -1 is returned. */
+int linenoiseHistoryLoad(char *filename) {
+    FILE *fp = fopen(filename,"r");
+    char buf[LINENOISE_MAX_LINE];
+    
+    if (fp == NULL) return -1;
+
+    while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
+        char *p;
+        
+        p = strchr(buf,'\r');
+        if (!p) p = strchr(buf,'\n');
+        if (p) *p = '\0';
+        linenoiseHistoryAdd(buf);
+    }
+    fclose(fp);
+    return 0;
+}