]>
git.saurik.com Git - redis.git/blob - linenoise.c
   1 /* linenoise.c -- guerrilla line editing library against the idea that a 
   2  * line editing lib needs to be 20,000 lines of C code. 
   4  * You can find the latest source code at: 
   6  *   http://github.com/antirez/linenoise 
   8  * Does a number of crazy assumptions that happen to be true in 99.9999% of 
   9  * the 2010 UNIX computers around. 
  11  * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com> 
  12  * All rights reserved. 
  14  * Redistribution and use in source and binary forms, with or without 
  15  * modification, are permitted provided that the following conditions are met: 
  17  *   * Redistributions of source code must retain the above copyright notice, 
  18  *     this list of conditions and the following disclaimer. 
  19  *   * Redistributions in binary form must reproduce the above copyright 
  20  *     notice, this list of conditions and the following disclaimer in the 
  21  *     documentation and/or other materials provided with the distribution. 
  22  *   * Neither the name of Redis nor the names of its contributors may be used 
  23  *     to endorse or promote products derived from this software without 
  24  *     specific prior written permission. 
  26  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
  27  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
  28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
  29  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
  30  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
  31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
  32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
  33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
  34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
  35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
  36  * POSSIBILITY OF SUCH DAMAGE. 
  39  * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html 
  40  * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html 
  43  * - Switch to gets() if $TERM is something we can't support. 
  44  * - Filter bogus Ctrl+<char> combinations. 
  49  * - History search like Ctrl+r in readline? 
  51  * List of escape sequences used by this program, we do everything just 
  52  * with three sequences. In order to be so cheap we may have some 
  53  * flickering effect with some slow terminal, but the lesser sequences 
  54  * the more compatible. 
  56  * CHA (Cursor Horizontal Absolute) 
  58  *    Effect: moves cursor to column n 
  62  *    Effect: if n is 0 or missing, clear from cursor to end of line 
  63  *    Effect: if n is 1, clear from beginning of line to cursor 
  64  *    Effect: if n is 2, clear entire line 
  66  * CUF (CUrsor Forward) 
  68  *    Effect: moves cursor forward of n chars 
  80 #include <sys/types.h> 
  81 #include <sys/ioctl.h> 
  84 #define LINENOISE_MAX_LINE 4096 
  86 static struct termios orig_termios
; /* in order to restore at exit */ 
  87 static int rawmode 
= 0; /* for atexit() function to check if restore is needed*/ 
  88 static int atexit_registered 
= 0; /* register atexit just 1 time */ 
  89 static int history_max_len 
= 100; 
  90 static int history_len 
= 0; 
  91 char **history 
= NULL
; 
  93 static void linenoiseAtExit(void); 
  94 int linenoiseHistoryAdd(char *line
); 
  96 static void freeHistory(void) { 
 100         for (j 
= 0; j 
< history_len
; j
++) 
 106 static int enableRawMode(int fd
) { 
 109     if (!isatty(STDIN_FILENO
)) goto fatal
; 
 110     if (!atexit_registered
) { 
 111         atexit(linenoiseAtExit
); 
 112         atexit_registered 
= 1; 
 114     if (tcgetattr(fd
,&orig_termios
) == -1) goto fatal
; 
 116     raw 
= orig_termios
;  /* modify the original mode */ 
 117     /* input modes: no break, no CR to NL, no parity check, no strip char, 
 118      * no start/stop output control. */ 
 119     raw
.c_iflag 
&= ~(BRKINT 
| ICRNL 
| INPCK 
| ISTRIP 
| IXON
); 
 120     /* output modes - disable post processing */ 
 121     raw
.c_oflag 
&= ~(OPOST
); 
 122     /* control modes - set 8 bit chars */ 
 123     raw
.c_cflag 
|= (CS8
); 
 124     /* local modes - choing off, canonical off, no extended functions, 
 125      * no signal chars (^Z,^C) */ 
 126     raw
.c_lflag 
&= ~(ECHO 
| ICANON 
| IEXTEN 
| ISIG
); 
 127     /* control chars - set return condition: min number of bytes and timer. 
 128      * We want read to return every single byte, without timeout. */ 
 129     raw
.c_cc
[VMIN
] = 1; raw
.c_cc
[VTIME
] = 0; /* 1 byte, no timer */ 
 131     /* put terminal in raw mode after flushing */ 
 132     if (tcsetattr(fd
,TCSAFLUSH
,&raw
) < 0) goto fatal
; 
 141 static void disableRawMode(int fd
) { 
 142     /* Don't even check the return value as it's too late. */ 
 143     if (rawmode 
&& tcsetattr(fd
,TCSAFLUSH
,&orig_termios
) != -1) 
 147 /* At exit we'll try to fix the terminal to the initial conditions. */ 
 148 static void linenoiseAtExit(void) { 
 149     disableRawMode(STDIN_FILENO
); 
 153 static int getColumns(void) { 
 156     if (ioctl(1, TIOCGWINSZ
, &ws
) == -1) return 80; 
 160 static void refreshLine(int fd
, const char *prompt
, char *buf
, size_t len
, size_t pos
, size_t cols
) { 
 162     size_t plen 
= strlen(prompt
); 
 164     while((plen
+pos
) >= cols
) { 
 169     while (plen
+len 
> cols
) { 
 173     /* Cursor to left edge */ 
 174     snprintf(seq
,64,"\x1b[0G"); 
 175     if (write(fd
,seq
,strlen(seq
)) == -1) return; 
 176     /* Write the prompt and the current buffer content */ 
 177     if (write(fd
,prompt
,strlen(prompt
)) == -1) return; 
 178     if (write(fd
,buf
,len
) == -1) return; 
 180     snprintf(seq
,64,"\x1b[0K"); 
 181     if (write(fd
,seq
,strlen(seq
)) == -1) return; 
 182     /* Move cursor to original position. */ 
 183     snprintf(seq
,64,"\x1b[0G\x1b[%dC", (int)(pos
+plen
)); 
 184     if (write(fd
,seq
,strlen(seq
)) == -1) return; 
 187 static int linenoisePrompt(int fd
, char *buf
, size_t buflen
, const char *prompt
) { 
 188     size_t plen 
= strlen(prompt
); 
 191     size_t cols 
= getColumns(); 
 192     int history_index 
= 0; 
 195     buflen
--; /* Make sure there is always space for the nulterm */ 
 197     /* The latest history entry is always our current buffer, that 
 198      * initially is just an empty string. */ 
 199     linenoiseHistoryAdd(""); 
 201     if (write(fd
,prompt
,plen
) == -1) return -1; 
 207         nread 
= read(fd
,&c
,1); 
 208         if (nread 
<= 0) return len
; 
 215             return (len 
== 0) ? -1 : (int)len
; 
 219         case 127:   /* backspace */ 
 221             if (pos 
> 0 && len 
> 0) { 
 222                 memmove(buf
+pos
-1,buf
+pos
,len
-pos
); 
 226                 refreshLine(fd
,prompt
,buf
,len
,pos
,cols
); 
 229         case 20:    /* ctrl-t */ 
 230             if (pos 
> 0 && pos 
< len
) { 
 231                 int aux 
= buf
[pos
-1]; 
 232                 buf
[pos
-1] = buf
[pos
]; 
 234                 if (pos 
!= len
-1) pos
++; 
 235                 refreshLine(fd
,prompt
,buf
,len
,pos
,cols
); 
 242         case 16:    /* ctrl-p */ 
 245         case 14:    /* ctrl-n */ 
 249         case 27:    /* escape sequence */ 
 250             if (read(fd
,seq
,2) == -1) break; 
 251             if (seq
[0] == 91 && seq
[1] == 68) { 
 256                     refreshLine(fd
,prompt
,buf
,len
,pos
,cols
); 
 258             } else if (seq
[0] == 91 && seq
[1] == 67) { 
 263                     refreshLine(fd
,prompt
,buf
,len
,pos
,cols
); 
 265             } else if (seq
[0] == 91 && (seq
[1] == 65 || seq
[1] == 66)) { 
 267                 /* up and down arrow: history */ 
 268                 if (history_len 
> 1) { 
 269                     /* Update the current history entry before to 
 270                      * overwrite it with tne next one. */ 
 271                     free(history
[history_len
-1-history_index
]); 
 272                     history
[history_len
-1-history_index
] = strdup(buf
); 
 273                     /* Show the new entry */ 
 274                     history_index 
+= (seq
[1] == 65) ? 1 : -1; 
 275                     if (history_index 
< 0) { 
 278                     } else if (history_index 
>= history_len
) { 
 279                         history_index 
= history_len
-1; 
 282                     strncpy(buf
,history
[history_len
-1-history_index
],buflen
); 
 284                     len 
= pos 
= strlen(buf
); 
 285                     refreshLine(fd
,prompt
,buf
,len
,pos
,cols
); 
 296                     if (plen
+len 
< cols
) { 
 297                         /* Avoid a full update of the line in the 
 299                         if (write(fd
,&c
,1) == -1) return -1; 
 301                         refreshLine(fd
,prompt
,buf
,len
,pos
,cols
); 
 304                     memmove(buf
+pos
+1,buf
+pos
,len
-pos
); 
 309                     refreshLine(fd
,prompt
,buf
,len
,pos
,cols
); 
 313         case 21: /* Ctrl+u, delete the whole line. */ 
 316             refreshLine(fd
,prompt
,buf
,len
,pos
,cols
); 
 318         case 11: /* Ctrl+k, delete from current to end of line. */ 
 321             refreshLine(fd
,prompt
,buf
,len
,pos
,cols
); 
 323         case 1: /* Ctrl+a, go to the start of the line */ 
 325             refreshLine(fd
,prompt
,buf
,len
,pos
,cols
); 
 327         case 5: /* ctrl+e, go to the end of the line */ 
 329             refreshLine(fd
,prompt
,buf
,len
,pos
,cols
); 
 336 static int linenoiseRaw(char *buf
, size_t buflen
, const char *prompt
) { 
 337     int fd 
= STDIN_FILENO
; 
 344     if (enableRawMode(fd
) == -1) return -1; 
 345     count 
= linenoisePrompt(fd
, buf
, buflen
, prompt
); 
 351 char *linenoise(const char *prompt
) { 
 352     char buf
[LINENOISE_MAX_LINE
]; 
 355     count 
= linenoiseRaw(buf
,LINENOISE_MAX_LINE
,prompt
); 
 356     if (count 
== -1) return NULL
; 
 360 /* Using a circular buffer is smarter, but a bit more complex to handle. */ 
 361 int linenoiseHistoryAdd(char *line
) { 
 362     if (history_max_len 
== 0) return 0; 
 364         history 
= malloc(sizeof(char*)*history_max_len
); 
 365         if (history 
== NULL
) return 0; 
 366         memset(history
,0,(sizeof(char*)*history_max_len
)); 
 370     if (history_len 
== history_max_len
) { 
 371         memmove(history
,history
+1,sizeof(char*)*(history_max_len
-1)); 
 374     history
[history_len
] = line
; 
 379 int linenoiseHistorySetMaxLen(int len
) { 
 382     if (len 
< 1) return 0; 
 384         int tocopy 
= history_len
; 
 386         new = malloc(sizeof(char*)*len
); 
 387         if (new == NULL
) return 0; 
 388         if (len 
< tocopy
) tocopy 
= len
; 
 389         memcpy(new,history
+(history_max_len
-tocopy
), sizeof(char*)*tocopy
); 
 393     history_max_len 
= len
; 
 394     if (history_len 
> history_max_len
) 
 395         history_len 
= history_max_len
;