|  | /* 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: | 
|  | * | 
|  | *   http://github.com/antirez/linenoise | 
|  | * | 
|  | * Does a number of crazy assumptions that happen to be true in 99.9999% of | 
|  | * the 2010 UNIX computers around. | 
|  | * | 
|  | * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com> | 
|  | * All rights reserved. | 
|  | * | 
|  | * Redistribution and use in source and binary forms, with or without | 
|  | * modification, are permitted provided that the following conditions are met: | 
|  | * | 
|  | *   * Redistributions of source code must retain the above copyright notice, | 
|  | *     this list of conditions and the following disclaimer. | 
|  | *   * Redistributions in binary form must reproduce the above copyright | 
|  | *     notice, this list of conditions and the following disclaimer in the | 
|  | *     documentation and/or other materials provided with the distribution. | 
|  | *   * Neither the name of Redis nor the names of its contributors may be used | 
|  | *     to endorse or promote products derived from this software without | 
|  | *     specific prior written permission. | 
|  | * | 
|  | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
|  | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
|  | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 
|  | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | 
|  | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | 
|  | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | 
|  | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | 
|  | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | 
|  | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | 
|  | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | 
|  | * POSSIBILITY OF SUCH DAMAGE. | 
|  | * | 
|  | * References: | 
|  | * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html | 
|  | * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html | 
|  | * | 
|  | * Todo list: | 
|  | * - Switch to gets() if $TERM is something we can't support. | 
|  | * - Filter bogus Ctrl+<char> combinations. | 
|  | * - Win32 support | 
|  | * | 
|  | * Bloat: | 
|  | * - Completion? | 
|  | * - History search like Ctrl+r in readline? | 
|  | * | 
|  | * List of escape sequences used by this program, we do everything just | 
|  | * with three sequences. In order to be so cheap we may have some | 
|  | * flickering effect with some slow terminal, but the lesser sequences | 
|  | * the more compatible. | 
|  | * | 
|  | * CHA (Cursor Horizontal Absolute) | 
|  | *    Sequence: ESC [ n G | 
|  | *    Effect: moves cursor to column n | 
|  | * | 
|  | * EL (Erase Line) | 
|  | *    Sequence: ESC [ n K | 
|  | *    Effect: if n is 0 or missing, clear from cursor to end of line | 
|  | *    Effect: if n is 1, clear from beginning of line to cursor | 
|  | *    Effect: if n is 2, clear entire line | 
|  | * | 
|  | * CUF (CUrsor Forward) | 
|  | *    Sequence: ESC [ n C | 
|  | *    Effect: moves cursor forward of n chars | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include <termios.h> | 
|  | #include <unistd.h> | 
|  | #include <stdlib.h> | 
|  | #include <stdio.h> | 
|  | #include <errno.h> | 
|  | #include <string.h> | 
|  | #include <stdlib.h> | 
|  | #include <sys/types.h> | 
|  | #include <sys/ioctl.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #define LINENOISE_MAX_LINE 4096 | 
|  | static char *unsupported_term[] = {"dumb","cons25",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_len = 0; | 
|  | char **history = NULL; | 
|  |  | 
|  | static void linenoiseAtExit(void); | 
|  | int linenoiseHistoryAdd(const char *line); | 
|  |  | 
|  | static int isUnsupportedTerm(void) { | 
|  | char *term = getenv("TERM"); | 
|  | int j; | 
|  |  | 
|  | if (term == NULL) return 0; | 
|  | for (j = 0; unsupported_term[j]; j++) | 
|  | if (!strcasecmp(term,unsupported_term[j])) return 1; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void freeHistory(void) { | 
|  | if (history) { | 
|  | int j; | 
|  |  | 
|  | for (j = 0; j < history_len; j++) | 
|  | free(history[j]); | 
|  | free(history); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int enableRawMode(int fd) { | 
|  | struct termios raw; | 
|  |  | 
|  | if (!isatty(STDIN_FILENO)) goto fatal; | 
|  | if (!atexit_registered) { | 
|  | atexit(linenoiseAtExit); | 
|  | atexit_registered = 1; | 
|  | } | 
|  | if (tcgetattr(fd,&orig_termios) == -1) goto fatal; | 
|  |  | 
|  | raw = orig_termios;  /* modify the original mode */ | 
|  | /* 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, | 
|  | * no signal chars (^Z,^C) */ | 
|  | raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); | 
|  | /* control chars - set return condition: min number of bytes and timer. | 
|  | * We want read to return every single byte, without timeout. */ | 
|  | raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ | 
|  |  | 
|  | /* put terminal in raw mode */ | 
|  | if (tcsetattr(fd,TCSADRAIN,&raw) < 0) goto fatal; | 
|  | rawmode = 1; | 
|  | return 0; | 
|  |  | 
|  | fatal: | 
|  | errno = ENOTTY; | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | static void disableRawMode(int fd) { | 
|  | /* Don't even check the return value as it's too late. */ | 
|  | if (rawmode && tcsetattr(fd,TCSADRAIN,&orig_termios) != -1) | 
|  | rawmode = 0; | 
|  | } | 
|  |  | 
|  | /* At exit we'll try to fix the terminal to the initial conditions. */ | 
|  | static void linenoiseAtExit(void) { | 
|  | disableRawMode(STDIN_FILENO); | 
|  | freeHistory(); | 
|  | } | 
|  |  | 
|  | static int getColumns(void) { | 
|  | struct winsize ws; | 
|  |  | 
|  | if (ioctl(1, TIOCGWINSZ, &ws) == -1) return 4096; | 
|  | if (ws.ws_col == 0) { | 
|  | return 4096; | 
|  | } | 
|  | return ws.ws_col; | 
|  | } | 
|  |  | 
|  | static int effectiveLen(const char* prompt) { | 
|  | int col = 0; | 
|  | char c; | 
|  | // TODO: Handle escape sequences. | 
|  | while ( (c = *prompt++) != 0 ) { | 
|  | if (c == '\n') { | 
|  | col = 0; | 
|  | } else { | 
|  | col++; | 
|  | } | 
|  | } | 
|  | return col; | 
|  | } | 
|  |  | 
|  | static void refreshLine(int fd, const char *prompt, char *buf, size_t len, size_t pos, size_t cols) { | 
|  | char seq[64]; | 
|  | size_t plen = effectiveLen(prompt); | 
|  |  | 
|  | while((plen+pos) >= cols) { | 
|  | buf++; | 
|  | len--; | 
|  | pos--; | 
|  | } | 
|  | while (plen+len > cols) { | 
|  | len--; | 
|  | } | 
|  |  | 
|  | /* Cursor to left edge */ | 
|  | snprintf(seq,64,"\x1b[0G"); | 
|  | if (write(fd,seq,strlen(seq)) == -1) return; | 
|  | /* Write the prompt and the current buffer content */ | 
|  | if (write(fd,prompt,strlen(prompt)) == -1) return; | 
|  | if (write(fd,buf,len) == -1) return; | 
|  | /* Erase to right */ | 
|  | snprintf(seq,64,"\x1b[0K"); | 
|  | if (write(fd,seq,strlen(seq)) == -1) return; | 
|  | /* Move cursor to original position. */ | 
|  | snprintf(seq,64,"\x1b[0G\x1b[%dC", (int)(pos+plen)); | 
|  | if (write(fd,seq,strlen(seq)) == -1) return; | 
|  | } | 
|  |  | 
|  | static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) { | 
|  | size_t plen = strlen(prompt); | 
|  | size_t pos = 0; | 
|  | size_t len = 0; | 
|  | size_t cols = getColumns(); | 
|  | int history_index = 0; | 
|  |  | 
|  | buf[0] = '\0'; | 
|  | buflen--; /* Make sure there is always space for the nulterm */ | 
|  |  | 
|  | /* The latest history entry is always our current buffer, that | 
|  | * initially is just an empty string. */ | 
|  | linenoiseHistoryAdd(""); | 
|  |  | 
|  | if (write(fd,prompt,plen) == -1) return -1; | 
|  | while(1) { | 
|  | char c; | 
|  | int nread; | 
|  | char seq[2]; | 
|  |  | 
|  | nread = read(fd,&c,1); | 
|  | if (nread <= 0) return len; | 
|  | switch(c) { | 
|  | case 10:    /* line feed. */ | 
|  | case 13:    /* enter */ | 
|  | history_len--; | 
|  | return len; | 
|  | case 4:     /* ctrl-d */ | 
|  | history_len--; | 
|  | return (len == 0) ? -1 : (int)len; | 
|  | case 3:     /* ctrl-c */ | 
|  | errno = EAGAIN; | 
|  | return -1; | 
|  | case 127:   /* backspace */ | 
|  | case 8:     /* ctrl-h */ | 
|  | if (pos > 0 && len > 0) { | 
|  | memmove(buf+pos-1,buf+pos,len-pos); | 
|  | pos--; | 
|  | len--; | 
|  | buf[len] = '\0'; | 
|  | refreshLine(fd,prompt,buf,len,pos,cols); | 
|  | } | 
|  | break; | 
|  | case 20:    /* ctrl-t */ | 
|  | if (pos > 0 && pos < len) { | 
|  | int aux = buf[pos-1]; | 
|  | buf[pos-1] = buf[pos]; | 
|  | buf[pos] = aux; | 
|  | if (pos != len-1) pos++; | 
|  | refreshLine(fd,prompt,buf,len,pos,cols); | 
|  | } | 
|  | break; | 
|  | case 2:     /* ctrl-b */ | 
|  | goto left_arrow; | 
|  | case 6:     /* ctrl-f */ | 
|  | goto right_arrow; | 
|  | case 16:    /* ctrl-p */ | 
|  | seq[1] = 65; | 
|  | goto up_down_arrow; | 
|  | case 14:    /* ctrl-n */ | 
|  | seq[1] = 66; | 
|  | goto up_down_arrow; | 
|  | break; | 
|  | case 27:    /* escape sequence */ | 
|  | if (read(fd,seq,2) == -1) break; | 
|  | if (seq[0] == 91 && seq[1] == 68) { | 
|  | left_arrow: | 
|  | /* left arrow */ | 
|  | if (pos > 0) { | 
|  | pos--; | 
|  | refreshLine(fd,prompt,buf,len,pos,cols); | 
|  | } | 
|  | } else if (seq[0] == 91 && seq[1] == 67) { | 
|  | right_arrow: | 
|  | /* right arrow */ | 
|  | if (pos != len) { | 
|  | pos++; | 
|  | refreshLine(fd,prompt,buf,len,pos,cols); | 
|  | } | 
|  | } else if (seq[0] == 91 && (seq[1] == 65 || seq[1] == 66)) { | 
|  | up_down_arrow: | 
|  | /* up and down arrow: history */ | 
|  | if (history_len > 1) { | 
|  | /* Update the current history entry before to | 
|  | * overwrite it with tne next one. */ | 
|  | free(history[history_len-1-history_index]); | 
|  | history[history_len-1-history_index] = strdup(buf); | 
|  | /* Show the new entry */ | 
|  | history_index += (seq[1] == 65) ? 1 : -1; | 
|  | if (history_index < 0) { | 
|  | history_index = 0; | 
|  | break; | 
|  | } else if (history_index >= history_len) { | 
|  | history_index = history_len-1; | 
|  | break; | 
|  | } | 
|  | strncpy(buf,history[history_len-1-history_index],buflen); | 
|  | buf[buflen] = '\0'; | 
|  | len = pos = strlen(buf); | 
|  | refreshLine(fd,prompt,buf,len,pos,cols); | 
|  | } | 
|  | } | 
|  | break; | 
|  | default: | 
|  | if (len < buflen) { | 
|  | if (len == pos) { | 
|  | buf[pos] = c; | 
|  | pos++; | 
|  | len++; | 
|  | buf[len] = '\0'; | 
|  | if (plen+len < cols) { | 
|  | /* Avoid a full update of the line in the | 
|  | * trivial case. */ | 
|  | if (write(fd,&c,1) == -1) return -1; | 
|  | } else { | 
|  | refreshLine(fd,prompt,buf,len,pos,cols); | 
|  | } | 
|  | } else { | 
|  | memmove(buf+pos+1,buf+pos,len-pos); | 
|  | buf[pos] = c; | 
|  | len++; | 
|  | pos++; | 
|  | buf[len] = '\0'; | 
|  | refreshLine(fd,prompt,buf,len,pos,cols); | 
|  | } | 
|  | } | 
|  | break; | 
|  | case 21: /* Ctrl+u, delete the whole line. */ | 
|  | buf[0] = '\0'; | 
|  | pos = len = 0; | 
|  | refreshLine(fd,prompt,buf,len,pos,cols); | 
|  | break; | 
|  | case 11: /* Ctrl+k, delete from current to end of line. */ | 
|  | buf[pos] = '\0'; | 
|  | len = pos; | 
|  | refreshLine(fd,prompt,buf,len,pos,cols); | 
|  | break; | 
|  | case 1: /* Ctrl+a, go to the start of the line */ | 
|  | pos = 0; | 
|  | refreshLine(fd,prompt,buf,len,pos,cols); | 
|  | break; | 
|  | case 5: /* ctrl+e, go to the end of the line */ | 
|  | pos = len; | 
|  | refreshLine(fd,prompt,buf,len,pos,cols); | 
|  | break; | 
|  | } | 
|  | } | 
|  | return len; | 
|  | } | 
|  |  | 
|  | static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { | 
|  | int fd = STDIN_FILENO; | 
|  | int count; | 
|  |  | 
|  | if (buflen == 0) { | 
|  | errno = EINVAL; | 
|  | return -1; | 
|  | } | 
|  | 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); | 
|  | } | 
|  | return count; | 
|  | } | 
|  |  | 
|  | char *linenoise(const char *prompt) { | 
|  | char buf[LINENOISE_MAX_LINE]; | 
|  | int count; | 
|  |  | 
|  | if (isUnsupportedTerm()) { | 
|  | size_t len; | 
|  |  | 
|  | printf("%s",prompt); | 
|  | fflush(stdout); | 
|  | if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; | 
|  | len = strlen(buf); | 
|  | while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { | 
|  | len--; | 
|  | buf[len] = '\0'; | 
|  | } | 
|  | return strdup(buf); | 
|  | } else { | 
|  | count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); | 
|  | if (count == -1) return NULL; | 
|  | return strdup(buf); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* 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) { | 
|  | history = malloc(sizeof(char*)*history_max_len); | 
|  | if (history == NULL) return 0; | 
|  | memset(history,0,(sizeof(char*)*history_max_len)); | 
|  | } | 
|  | linecopy = strdup(line); | 
|  | if (!linecopy) return 0; | 
|  | if (history_len == history_max_len) { | 
|  | memmove(history,history+1,sizeof(char*)*(history_max_len-1)); | 
|  | history_len--; | 
|  | } | 
|  | history[history_len] = linecopy; | 
|  | history_len++; | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | int linenoiseHistorySetMaxLen(int len) { | 
|  | char **new; | 
|  |  | 
|  | if (len < 1) return 0; | 
|  | if (history) { | 
|  | int tocopy = history_len; | 
|  |  | 
|  | new = malloc(sizeof(char*)*len); | 
|  | if (new == NULL) return 0; | 
|  | if (len < tocopy) tocopy = len; | 
|  | memcpy(new,history+(history_max_len-tocopy), sizeof(char*)*tocopy); | 
|  | free(history); | 
|  | history = new; | 
|  | } | 
|  | history_max_len = len; | 
|  | if (history_len > history_max_len) | 
|  | history_len = history_max_len; | 
|  | return 1; | 
|  | } |