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. 3 * 4 * You can find the latest source code at: 5 * 6 * http://github.com/antirez/linenoise 7 * 8 * Does a number of crazy assumptions that happen to be true in 99.9999% of 9 * the 2010 UNIX computers around. 10 * 11 * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com> 12 * All rights reserved. 13 * 14 * Redistribution and use in source and binary forms, with or without 15 * modification, are permitted provided that the following conditions are met: 16 * 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. 25 * 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. 37 * 38 * References: 39 * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html 40 * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html 41 * 42 * Todo list: 43 * - Switch to gets() if $TERM is something we can't support. 44 * - Filter bogus Ctrl+<char> combinations. 45 * - Win32 support 46 * 47 * Bloat: 48 * - Completion? 49 * - History search like Ctrl+r in readline? 50 * 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. 55 * 56 * CHA (Cursor Horizontal Absolute) 57 * Sequence: ESC [ n G 58 * Effect: moves cursor to column n 59 * 60 * EL (Erase Line) 61 * Sequence: ESC [ n K 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 65 * 66 * CUF (CUrsor Forward) 67 * Sequence: ESC [ n C 68 * Effect: moves cursor forward of n chars 69 * 70 */ 71 72 #include <termios.h> 73 #include <unistd.h> 74 #include <stdlib.h> 75 #include <stdio.h> 76 #include <errno.h> 77 #include <string.h> 78 #include <stdlib.h> 79 #include <sys/types.h> 80 #include <sys/ioctl.h> 81 #include <unistd.h> 82 83 #define LINENOISE_MAX_LINE 4096 84 static char *unsupported_term[] = {"dumb","cons25",NULL}; 85 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; 92 93 static void linenoiseAtExit(void); 94 int linenoiseHistoryAdd(const char *line); 95 96 static int isUnsupportedTerm(void) { 97 char *term = getenv("TERM"); 98 int j; 99 100 if (term == NULL) return 0; 101 for (j = 0; unsupported_term[j]; j++) 102 if (!strcasecmp(term,unsupported_term[j])) return 1; 103 return 0; 104 } 105 106 static void freeHistory(void) { 107 if (history) { 108 int j; 109 110 for (j = 0; j < history_len; j++) 111 free(history[j]); 112 free(history); 113 } 114 } 115 116 static int enableRawMode(int fd) { 117 struct termios raw; 118 119 if (!isatty(STDIN_FILENO)) goto fatal; 120 if (!atexit_registered) { 121 atexit(linenoiseAtExit); 122 atexit_registered = 1; 123 } 124 if (tcgetattr(fd,&orig_termios) == -1) goto fatal; 125 126 raw = orig_termios; /* modify the original mode */ 127 /* input modes: no break, no CR to NL, no parity check, no strip char, 128 * no start/stop output control. */ 129 raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 130 /* output modes - disable post processing */ 131 raw.c_oflag &= ~(OPOST); 132 /* control modes - set 8 bit chars */ 133 raw.c_cflag |= (CS8); 134 /* local modes - choing off, canonical off, no extended functions, 135 * no signal chars (^Z,^C) */ 136 raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 137 /* control chars - set return condition: min number of bytes and timer. 138 * We want read to return every single byte, without timeout. */ 139 raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ 140 141 /* put terminal in raw mode */ 142 if (tcsetattr(fd,TCSADRAIN,&raw) < 0) goto fatal; 143 rawmode = 1; 144 return 0; 145 146 fatal: 147 errno = ENOTTY; 148 return -1; 149 } 150 151 static void disableRawMode(int fd) { 152 /* Don't even check the return value as it's too late. */ 153 if (rawmode && tcsetattr(fd,TCSADRAIN,&orig_termios) != -1) 154 rawmode = 0; 155 } 156 157 /* At exit we'll try to fix the terminal to the initial conditions. */ 158 static void linenoiseAtExit(void) { 159 disableRawMode(STDIN_FILENO); 160 freeHistory(); 161 } 162 163 static int getColumns(void) { 164 struct winsize ws; 165 166 if (ioctl(1, TIOCGWINSZ, &ws) == -1) return 4096; 167 if (ws.ws_col == 0) { 168 return 4096; 169 } 170 return ws.ws_col; 171 } 172 173 static int effectiveLen(const char* prompt) { 174 int col = 0; 175 char c; 176 // TODO: Handle escape sequences. 177 while ( (c = *prompt++) != 0 ) { 178 if (c == '\n') { 179 col = 0; 180 } else { 181 col++; 182 } 183 } 184 return col; 185 } 186 187 static void refreshLine(int fd, const char *prompt, char *buf, size_t len, size_t pos, size_t cols) { 188 char seq[64]; 189 size_t plen = effectiveLen(prompt); 190 191 while((plen+pos) >= cols) { 192 buf++; 193 len--; 194 pos--; 195 } 196 while (plen+len > cols) { 197 len--; 198 } 199 200 /* Cursor to left edge */ 201 snprintf(seq,64,"\x1b[0G"); 202 if (write(fd,seq,strlen(seq)) == -1) return; 203 /* Write the prompt and the current buffer content */ 204 if (write(fd,prompt,strlen(prompt)) == -1) return; 205 if (write(fd,buf,len) == -1) return; 206 /* Erase to right */ 207 snprintf(seq,64,"\x1b[0K"); 208 if (write(fd,seq,strlen(seq)) == -1) return; 209 /* Move cursor to original position. */ 210 snprintf(seq,64,"\x1b[0G\x1b[%dC", (int)(pos+plen)); 211 if (write(fd,seq,strlen(seq)) == -1) return; 212 } 213 214 static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) { 215 size_t plen = strlen(prompt); 216 size_t pos = 0; 217 size_t len = 0; 218 size_t cols = getColumns(); 219 int history_index = 0; 220 221 buf[0] = '\0'; 222 buflen--; /* Make sure there is always space for the nulterm */ 223 224 /* The latest history entry is always our current buffer, that 225 * initially is just an empty string. */ 226 linenoiseHistoryAdd(""); 227 228 if (write(fd,prompt,plen) == -1) return -1; 229 while(1) { 230 char c; 231 int nread; 232 char seq[2]; 233 234 nread = read(fd,&c,1); 235 if (nread <= 0) return len; 236 switch(c) { 237 case 10: /* line feed. */ 238 case 13: /* enter */ 239 history_len--; 240 return len; 241 case 4: /* ctrl-d */ 242 history_len--; 243 return (len == 0) ? -1 : (int)len; 244 case 3: /* ctrl-c */ 245 errno = EAGAIN; 246 return -1; 247 case 127: /* backspace */ 248 case 8: /* ctrl-h */ 249 if (pos > 0 && len > 0) { 250 memmove(buf+pos-1,buf+pos,len-pos); 251 pos--; 252 len--; 253 buf[len] = '\0'; 254 refreshLine(fd,prompt,buf,len,pos,cols); 255 } 256 break; 257 case 20: /* ctrl-t */ 258 if (pos > 0 && pos < len) { 259 int aux = buf[pos-1]; 260 buf[pos-1] = buf[pos]; 261 buf[pos] = aux; 262 if (pos != len-1) pos++; 263 refreshLine(fd,prompt,buf,len,pos,cols); 264 } 265 break; 266 case 2: /* ctrl-b */ 267 goto left_arrow; 268 case 6: /* ctrl-f */ 269 goto right_arrow; 270 case 16: /* ctrl-p */ 271 seq[1] = 65; 272 goto up_down_arrow; 273 case 14: /* ctrl-n */ 274 seq[1] = 66; 275 goto up_down_arrow; 276 break; 277 case 27: /* escape sequence */ 278 if (read(fd,seq,2) == -1) break; 279 if (seq[0] == 91 && seq[1] == 68) { 280 left_arrow: 281 /* left arrow */ 282 if (pos > 0) { 283 pos--; 284 refreshLine(fd,prompt,buf,len,pos,cols); 285 } 286 } else if (seq[0] == 91 && seq[1] == 67) { 287 right_arrow: 288 /* right arrow */ 289 if (pos != len) { 290 pos++; 291 refreshLine(fd,prompt,buf,len,pos,cols); 292 } 293 } else if (seq[0] == 91 && (seq[1] == 65 || seq[1] == 66)) { 294 up_down_arrow: 295 /* up and down arrow: history */ 296 if (history_len > 1) { 297 /* Update the current history entry before to 298 * overwrite it with tne next one. */ 299 free(history[history_len-1-history_index]); 300 history[history_len-1-history_index] = strdup(buf); 301 /* Show the new entry */ 302 history_index += (seq[1] == 65) ? 1 : -1; 303 if (history_index < 0) { 304 history_index = 0; 305 break; 306 } else if (history_index >= history_len) { 307 history_index = history_len-1; 308 break; 309 } 310 strncpy(buf,history[history_len-1-history_index],buflen); 311 buf[buflen] = '\0'; 312 len = pos = strlen(buf); 313 refreshLine(fd,prompt,buf,len,pos,cols); 314 } 315 } 316 break; 317 default: 318 if (len < buflen) { 319 if (len == pos) { 320 buf[pos] = c; 321 pos++; 322 len++; 323 buf[len] = '\0'; 324 if (plen+len < cols) { 325 /* Avoid a full update of the line in the 326 * trivial case. */ 327 if (write(fd,&c,1) == -1) return -1; 328 } else { 329 refreshLine(fd,prompt,buf,len,pos,cols); 330 } 331 } else { 332 memmove(buf+pos+1,buf+pos,len-pos); 333 buf[pos] = c; 334 len++; 335 pos++; 336 buf[len] = '\0'; 337 refreshLine(fd,prompt,buf,len,pos,cols); 338 } 339 } 340 break; 341 case 21: /* Ctrl+u, delete the whole line. */ 342 buf[0] = '\0'; 343 pos = len = 0; 344 refreshLine(fd,prompt,buf,len,pos,cols); 345 break; 346 case 11: /* Ctrl+k, delete from current to end of line. */ 347 buf[pos] = '\0'; 348 len = pos; 349 refreshLine(fd,prompt,buf,len,pos,cols); 350 break; 351 case 1: /* Ctrl+a, go to the start of the line */ 352 pos = 0; 353 refreshLine(fd,prompt,buf,len,pos,cols); 354 break; 355 case 5: /* ctrl+e, go to the end of the line */ 356 pos = len; 357 refreshLine(fd,prompt,buf,len,pos,cols); 358 break; 359 } 360 } 361 return len; 362 } 363 364 static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { 365 int fd = STDIN_FILENO; 366 int count; 367 368 if (buflen == 0) { 369 errno = EINVAL; 370 return -1; 371 } 372 if (!isatty(STDIN_FILENO)) { 373 if (fgets(buf, buflen, stdin) == NULL) return -1; 374 count = strlen(buf); 375 if (count && buf[count-1] == '\n') { 376 count--; 377 buf[count] = '\0'; 378 } 379 } else { 380 if (enableRawMode(fd) == -1) return -1; 381 count = linenoisePrompt(fd, buf, buflen, prompt); 382 disableRawMode(fd); 383 } 384 return count; 385 } 386 387 char *linenoise(const char *prompt) { 388 char buf[LINENOISE_MAX_LINE]; 389 int count; 390 391 if (isUnsupportedTerm()) { 392 size_t len; 393 394 printf("%s",prompt); 395 fflush(stdout); 396 if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; 397 len = strlen(buf); 398 while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { 399 len--; 400 buf[len] = '\0'; 401 } 402 return strdup(buf); 403 } else { 404 count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); 405 if (count == -1) return NULL; 406 return strdup(buf); 407 } 408 } 409 410 /* Using a circular buffer is smarter, but a bit more complex to handle. */ 411 int linenoiseHistoryAdd(const char *line) { 412 char *linecopy; 413 414 if (history_max_len == 0) return 0; 415 if (history == 0) { 416 history = malloc(sizeof(char*)*history_max_len); 417 if (history == NULL) return 0; 418 memset(history,0,(sizeof(char*)*history_max_len)); 419 } 420 linecopy = strdup(line); 421 if (!linecopy) return 0; 422 if (history_len == history_max_len) { 423 memmove(history,history+1,sizeof(char*)*(history_max_len-1)); 424 history_len--; 425 } 426 history[history_len] = linecopy; 427 history_len++; 428 return 1; 429 } 430 431 int linenoiseHistorySetMaxLen(int len) { 432 char **new; 433 434 if (len < 1) return 0; 435 if (history) { 436 int tocopy = history_len; 437 438 new = malloc(sizeof(char*)*len); 439 if (new == NULL) return 0; 440 if (len < tocopy) tocopy = len; 441 memcpy(new,history+(history_max_len-tocopy), sizeof(char*)*tocopy); 442 free(history); 443 history = new; 444 } 445 history_max_len = len; 446 if (history_len > history_max_len) 447 history_len = history_max_len; 448 return 1; 449 } 450