diff options
author | Andreas Baumann <mail@andreasbaumann.cc> | 2014-11-15 09:42:53 +0100 |
---|---|---|
committer | Andreas Baumann <mail@andreasbaumann.cc> | 2014-11-15 09:42:53 +0100 |
commit | 75df5abae42888bd5bf01fc4a8bd1215413cab67 (patch) | |
tree | 0784bd39b43b301632751d2f68f093240f437990 | |
parent | 8f772dc3aa8df1ed52aeb83757136b508084e32d (diff) | |
download | biruda-75df5abae42888bd5bf01fc4a8bd1215413cab67.tar.gz biruda-75df5abae42888bd5bf01fc4a8bd1215413cab67.tar.bz2 |
updated and fixed linenoise
history now gets read from file
started to document the protocol of biruda
-rw-r--r-- | PROTOCOL | 41 | ||||
-rw-r--r-- | TODOS | 5 | ||||
-rw-r--r-- | src/biruda.c | 37 | ||||
-rw-r--r-- | src/biruda.ggo | 4 | ||||
-rw-r--r-- | src/linenoise.c | 519 | ||||
-rw-r--r-- | src/linenoise.h | 25 |
6 files changed, 460 insertions, 171 deletions
diff --git a/PROTOCOL b/PROTOCOL new file mode 100644 index 0000000..0b17c3c --- /dev/null +++ b/PROTOCOL @@ -0,0 +1,41 @@ +Messages are sent over nanomsg. + +Message format is JSON. + +All messages have one string key 'op', a string key 'role' and a +unique name field, currently 'host'. + +'op' can be: +- 'discover' +- 'register' + +'role' can only be: +- 'master' +- 'coordinator' +- 'worker' + +'host' is the FQDN or hostname of a coordinator (depends on the network +setup). + +Operations: + +Discovery: + +Master sends: + +{ "op": "discover" } + +Coordinators answer with: + +All coordinators send: + +{ "op": "register", "role": "coordinator", "host": "server1", + "cpus": 2, "os": "cpe:\/o:arch:arch:rolling", "arch": "x86_64" } + +The coordinator sends its own configuration to the master. + +On receiving a 'register' operation the master must handle accordingly, +usually adding the coordinator as known and alive and provide new +platforms and architectures to run workers on. Also currently scheduled +jobs must be examined. + @@ -5,3 +5,8 @@ - links http://blog.borovsak.si/2009/07/spawning-processes-using-glib.html http://www.codeguru.com/cpp/misc/misc/system/article.php/c8973/Determine-Windows-Version-and-Edition.htm + +- surveyor -> bus, maybe also Windows blocking problem disappears + - every member of the net is allowed to receive data and to send + answers or to request or inform about something. + diff --git a/src/biruda.c b/src/biruda.c index 6bbcafe..3543d86 100644 --- a/src/biruda.c +++ b/src/biruda.c @@ -215,10 +215,10 @@ static void terminate_foreground_func( int sig ) #ifndef _WIN32 -#define HISTORY_FILE ".biruda.history" +#define HISTORY_FILE ".biruda_history" static char *commands[] = { - "help", "quit", NULL + "help", "quit", "status", NULL }; static void completion_func( const char *buf, linenoiseCompletions *lc ) @@ -240,22 +240,33 @@ static void print_help( ) puts( "\n" " help - show this help page\n" " quit - quit the client\n" + " status - status of the biruda network\n" ); } -static int start_interactive( ) +static void print_status( ) +{ + puts( "\nStatus\n" ); +} + +static void print_error( const char *error, bool colors ) +{ + if( colors ) { + printf( "%c[9%dmERROR: %s%c[0m\n", 27, 1, error, 27 ); + } else { + puts( error ); + } +} + +static int start_interactive( bool colors ) { char history_filename[1024]; char *home = getenv( "HOME" ); if( home != NULL ) { snprintf( history_filename, sizeof( history_filename ), "%s/%s", home, HISTORY_FILE ); - FILE *history_file = fopen( history_filename, "w+" ); - if( history_file != NULL ) { - fclose( history_file ); - linenoiseHistoryLoad( HISTORY_FILE ); - linenoiseSetCompletionCallback( completion_func ); - } + linenoiseHistoryLoad( history_filename ); + linenoiseSetCompletionCallback( completion_func ); } char *context = "biruda"; @@ -279,8 +290,12 @@ static int start_interactive( ) if( strncasecmp( line, "quit", 4 ) == 0 ) { linenoiseHistorySave( history_filename ); return EXIT_SUCCESS; - } else if( strncasecmp( line, "help", 4 ) == 0 ){ + } else if( strncasecmp( line, "help", 4 ) == 0 ) { print_help( ); + } else if( strncasecmp( line, "status", 6 ) == 0 ) { + print_status( ); + } else { + print_error( "Bad command.", colors ); } } @@ -318,7 +333,7 @@ int main( int argc, char *argv[] ) } if( args_info.cli_given ) { - return start_interactive( ); + return start_interactive( !args_info.no_colors_given ); } unsigned int has_master = cfg_size( cfg, "master" ); diff --git a/src/biruda.ggo b/src/biruda.ggo index b6758fd..c204713 100644 --- a/src/biruda.ggo +++ b/src/biruda.ggo @@ -24,6 +24,10 @@ section "Main Options" option "cli" i "start in command line interface (CLI), interactive mode" optional + + option "no-colors" - + "disable colors in CLI mode" + optional section "Unix Daemon" diff --git a/src/linenoise.c b/src/linenoise.c index 42b9382..e88fa4a 100644 --- a/src/linenoise.c +++ b/src/linenoise.c @@ -2,7 +2,7 @@ * 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 @@ -10,22 +10,22 @@ * * ------------------------------------------------------------------------ * - * Copyright (c) 2010-2013, Salvatore Sanfilippo <antirez at gmail dot com> + * Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com> * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis 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. - * + * * 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 @@ -37,7 +37,7 @@ * 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: @@ -56,10 +56,6 @@ * 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 @@ -68,7 +64,19 @@ * * CUF (CUrsor Forward) * Sequence: ESC [ n C - * Effect: moves cursor forward of n chars + * Effect: moves cursor forward n chars + * + * CUB (CUrsor Backward) + * Sequence: ESC [ n D + * Effect: moves cursor backward n chars + * + * The following is used to get the terminal width if getting + * the width with the TIOCGWINSZ ioctl fails + * + * DSR (Device Status Report) + * Sequence: ESC [ 6 n + * Effect: reports the current cusor position as ESC [ n ; m R + * where n is the row and m is the column * * When multi line mode is enabled, we also use an additional escape * sequence. However multi line editing is disabled by default. @@ -81,17 +89,18 @@ * Sequence: ESC [ n B * Effect: moves cursor down of n chars. * - * The following are used to clear the screen: ESC [ H ESC [ 2 J - * This is actually composed of two sequences: + * When linenoiseClearScreen() is called, two additional escape sequences + * are used in order to clear the screen and position the cursor at home + * position. * - * cursorhome + * CUP (Cursor position) * Sequence: ESC [ H * Effect: moves the cursor to upper left corner * - * ED2 (Clear entire screen) + * ED (Erase display) * Sequence: ESC [ 2 J * Effect: clear the whole screen - * + * */ #include <termios.h> @@ -102,6 +111,7 @@ #include <string.h> #include <strings.h> #include <stdlib.h> +#include <ctype.h> #include <sys/types.h> #include <sys/ioctl.h> #include <unistd.h> @@ -109,7 +119,7 @@ #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 #define LINENOISE_MAX_LINE 4096 -static char *unsupported_term[] = {"dumb","cons25",NULL}; +static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; static linenoiseCompletionCallback *completionCallback = NULL; static struct termios orig_termios; /* In order to restore at exit.*/ @@ -118,13 +128,14 @@ static int mlmode = 0; /* Multi line mode. Default is single line. */ static int atexit_registered = 0; /* Register atexit just 1 time. */ static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; static int history_len = 0; -char **history = NULL; +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 fd; /* Terminal file descriptor. */ + 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. */ @@ -137,16 +148,52 @@ struct linenoiseState { int history_index; /* The history index we are currently editing. */ }; +enum KEY_ACTION{ + KEY_NULL = 0, /* NULL */ + CTRL_A = 1, /* Ctrl+a */ + CTRL_B = 2, /* Ctrl-b */ + CTRL_C = 3, /* Ctrl-c */ + CTRL_D = 4, /* Ctrl-d */ + CTRL_E = 5, /* Ctrl-e */ + CTRL_F = 6, /* Ctrl-f */ + CTRL_H = 8, /* Ctrl-h */ + TAB = 9, /* Tab */ + CTRL_K = 11, /* Ctrl+k */ + CTRL_L = 12, /* Ctrl+l */ + ENTER = 13, /* Enter */ + CTRL_N = 14, /* Ctrl-n */ + CTRL_P = 16, /* Ctrl-p */ + CTRL_T = 20, /* Ctrl-t */ + CTRL_U = 21, /* Ctrl+u */ + CTRL_W = 23, /* Ctrl+w */ + ESC = 27, /* Escape */ + BACKSPACE = 127 /* Backspace */ +}; + static void linenoiseAtExit(void); int linenoiseHistoryAdd(const char *line); static void refreshLine(struct linenoiseState *l); -int linenoiseEditInsert(struct linenoiseState *l, int c); -void linenoiseEditMoveLeft(struct linenoiseState *l); -void linenoiseEditMoveRight(struct linenoiseState *l); -void linenoiseEditHistoryNext(struct linenoiseState *l, int dir); -void linenoiseEditDelete(struct linenoiseState *l); -void linenoiseEditBackspace(struct linenoiseState *l); -void linenoiseEditDeletePrevWord(struct linenoiseState *l); + +/* Debugging macro. */ +#if 0 +#define lndebugs(str) lndebug(str, NULL) +FILE *lndebug_fp = NULL; +#define lndebug(...) \ + do { \ + if (lndebug_fp == NULL) { \ + lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ + 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); \ + } \ + fprintf(lndebug_fp, ", " __VA_ARGS__); \ + fflush(lndebug_fp); \ + } while (0) +#else +#define lndebugs(str) lndebug(str, NULL) +#define lndebug(fmt, ...) +#endif /* ======================= Low level terminal handling ====================== */ @@ -209,18 +256,69 @@ static void disableRawMode(int fd) { rawmode = 0; } +/* Use the ESC [6n escape sequence to query the horizontal cursor position + * and return it. On error -1 is returned, on success the position of the + * cursor. */ +static int getCursorPosition(int ifd, int ofd) { + char buf[32]; + int cols, rows; + unsigned int i = 0; + + /* Report cursor location */ + if (write(ofd, "\x1b[6n", 4) != 4) return -1; + + /* Read the response: ESC [ rows ; cols R */ + while (i < sizeof(buf)-1) { + if (read(ifd,buf+i,1) != 1) break; + if (buf[i] == 'R') break; + i++; + } + buf[i] = '\0'; + + /* Parse it. */ + if (buf[0] != ESC || buf[1] != '[') return -1; + if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; + return cols; +} + /* Try to get the number of columns in the current terminal, or assume 80 * if it fails. */ -static int getColumns(void) { +static int getColumns(int ifd, int ofd) { struct winsize ws; - if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) return 80; - return ws.ws_col; + if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { + /* ioctl() failed. Try to query the terminal itself. */ + int start, cols; + + /* Get the initial position so we can restore it later. */ + start = getCursorPosition(ifd,ofd); + if (start == -1) goto failed; + + /* Go to right margin and get position. */ + if (write(ofd,"\x1b[999C",6) != 6) goto failed; + cols = getCursorPosition(ifd,ofd); + if (cols == -1) goto failed; + + /* Restore position. */ + if (cols > start) { + char seq[32]; + snprintf(seq,32,"\x1b[%dD",cols-start); + if (write(ofd,seq,strlen(seq)) == -1) { + /* Can't recover... */ + } + } + return cols; + } else { + return ws.ws_col; + } + +failed: + return 80; } /* Clear the screen. Used to handle ctrl+l */ void linenoiseClearScreen(void) { - if (write(STDIN_FILENO,"\x1b[H\x1b[2J",7) <= 0) { + if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { /* nothing to do, just to avoid warning. */ } } @@ -246,7 +344,7 @@ static void freeCompletions(linenoiseCompletions *lc) { /* 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) { @@ -275,7 +373,7 @@ static int completeLine(struct linenoiseState *ls) { refreshLine(ls); } - nread = read(ls->fd,&c,1); + nread = read(ls->ifd,&c,1); if (nread <= 0) { freeCompletions(&lc); return -1; @@ -316,16 +414,51 @@ void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { * in order to add completion options given the input string when the * user typed <tab>. See the example.c source code for a very easy to * understand example. */ -void linenoiseAddCompletion(linenoiseCompletions *lc, char *str) { +void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { size_t len = strlen(str); - char *copy = malloc(len+1); + char *copy, **cvec; + + copy = malloc(len+1); + if (copy == NULL) return; memcpy(copy,str,len+1); - lc->cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); + cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); + if (cvec == NULL) { + free(copy); + return; + } + lc->cvec = cvec; lc->cvec[lc->len++] = copy; } /* =========================== Line editing ================================= */ +/* We define a very simple "append buffer" structure, that is an heap + * allocated string where we can append to. This is useful in order to + * write all the escape sequences in a buffer and flush them to the standard + * output in a single call, to avoid flickering effects. */ +struct abuf { + char *b; + int len; +}; + +static void abInit(struct abuf *ab) { + ab->b = NULL; + ab->len = 0; +} + +static void abAppend(struct abuf *ab, const char *s, int len) { + char *new = realloc(ab->b,ab->len+len); + + if (new == NULL) return; + memcpy(new+ab->len,s,len); + ab->b = new; + ab->len += len; +} + +static void abFree(struct abuf *ab) { + free(ab->b); +} + /* Single line low level line refresh. * * Rewrite the currently edited line accordingly to the buffer content, @@ -333,11 +466,12 @@ void linenoiseAddCompletion(linenoiseCompletions *lc, char *str) { static void refreshSingleLine(struct linenoiseState *l) { char seq[64]; size_t plen = strlen(l->prompt); - int fd = l->fd; + int fd = l->ofd; char *buf = l->buf; size_t len = l->len; size_t pos = l->pos; - + struct abuf ab; + while((plen+pos) >= l->cols) { buf++; len--; @@ -347,18 +481,21 @@ static void refreshSingleLine(struct linenoiseState *l) { len--; } + abInit(&ab); /* Cursor to left edge */ - snprintf(seq,64,"\x1b[0G"); - if (write(fd,seq,strlen(seq)) == -1) return; + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); /* Write the prompt and the current buffer content */ - if (write(fd,l->prompt,strlen(l->prompt)) == -1) return; - if (write(fd,buf,len) == -1) return; + abAppend(&ab,l->prompt,strlen(l->prompt)); + abAppend(&ab,buf,len); /* Erase to right */ snprintf(seq,64,"\x1b[0K"); - if (write(fd,seq,strlen(seq)) == -1) return; + abAppend(&ab,seq,strlen(seq)); /* Move cursor to original position. */ - snprintf(seq,64,"\x1b[0G\x1b[%dC", (int)(pos+plen)); - if (write(fd,seq,strlen(seq)) == -1) return; + snprintf(seq,64,"\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); } /* Multi line low level line refresh. @@ -371,47 +508,38 @@ static void refreshMultiLine(struct linenoiseState *l) { 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 fd = l->fd, j; + int fd = l->ofd, j; + struct abuf ab; /* Update maxrows if needed. */ if (rows > (int)l->maxrows) l->maxrows = rows; -#ifdef LN_DEBUG - FILE *fp = fopen("/tmp/debug.txt","a"); - fprintf(fp,"[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d", - (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos,(int)l->maxrows,old_rows); -#endif - /* 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) { -#ifdef LN_DEBUG - fprintf(fp,", go down %d", old_rows-rpos); -#endif + lndebug("go down %d", old_rows-rpos); snprintf(seq,64,"\x1b[%dB", old_rows-rpos); - if (write(fd,seq,strlen(seq)) == -1) return; + abAppend(&ab,seq,strlen(seq)); } /* Now for every row clear it, go up. */ for (j = 0; j < old_rows-1; j++) { -#ifdef LN_DEBUG - fprintf(fp,", clear+up"); -#endif - snprintf(seq,64,"\x1b[0G\x1b[0K\x1b[1A"); - if (write(fd,seq,strlen(seq)) == -1) return; + lndebugs("clear+up"); + snprintf(seq,64,"\r\x1b[0K\x1b[1A"); + abAppend(&ab,seq,strlen(seq)); } /* Clean the top line. */ -#ifdef LN_DEBUG - fprintf(fp,", clear"); -#endif - snprintf(seq,64,"\x1b[0G\x1b[0K"); - if (write(fd,seq,strlen(seq)) == -1) return; - + lndebugs("clear"); + snprintf(seq,64,"\r\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); + /* Write the prompt and the current buffer content */ - if (write(fd,l->prompt,strlen(l->prompt)) == -1) return; - if (write(fd,l->buf,l->len) == -1) return; + abAppend(&ab,l->prompt,strlen(l->prompt)); + abAppend(&ab,l->buf,l->len); /* 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. */ @@ -419,42 +547,39 @@ static void refreshMultiLine(struct linenoiseState *l) { l->pos == l->len && (l->pos+plen) % l->cols == 0) { -#ifdef LN_DEBUG - fprintf(fp,", <newline>"); -#endif - if (write(fd,"\n",1) == -1) return; - snprintf(seq,64,"\x1b[0G"); - if (write(fd,seq,strlen(seq)) == -1) return; + lndebugs("<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. */ -#ifdef LN_DEBUG - fprintf(fp,", rpos2 %d", rpos2); -#endif + lndebug("rpos2 %d", rpos2); + /* Go up till we reach the expected positon. */ if (rows-rpos2 > 0) { -#ifdef LN_DEBUG - fprintf(fp,", go-up %d", rows-rpos2); -#endif + lndebug("go-up %d", rows-rpos2); snprintf(seq,64,"\x1b[%dA", rows-rpos2); - if (write(fd,seq,strlen(seq)) == -1) return; + abAppend(&ab,seq,strlen(seq)); } + /* Set column. */ -#ifdef LN_DEBUG - fprintf(fp,", set col %d", 1+((plen+(int)l->pos) % (int)l->cols)); -#endif - snprintf(seq,64,"\x1b[%dG", 1+((plen+(int)l->pos) % (int)l->cols)); - if (write(fd,seq,strlen(seq)) == -1) return; + 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)); + lndebugs("\n"); l->oldpos = l->pos; -#ifdef LN_DEBUG - fprintf(fp,"\n"); - fclose(fp); -#endif + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ + abFree(&ab); } /* Calls the two low level functions refreshSingleLine() or @@ -469,7 +594,7 @@ static void refreshLine(struct linenoiseState *l) { /* Insert the character 'c' at cursor current position. * * On error writing to the terminal -1 is returned, otherwise 0. */ -int linenoiseEditInsert(struct linenoiseState *l, int c) { +int linenoiseEditInsert(struct linenoiseState *l, char c) { if (l->len < l->buflen) { if (l->len == l->pos) { l->buf[l->pos] = c; @@ -479,7 +604,7 @@ int linenoiseEditInsert(struct linenoiseState *l, int c) { if ((!mlmode && l->plen+l->len < l->cols) /* || mlmode */) { /* Avoid a full update of the line in the * trivial case. */ - if (write(l->fd,&c,1) == -1) return -1; + if (write(l->ofd,&c,1) == -1) return -1; } else { refreshLine(l); } @@ -511,6 +636,22 @@ void linenoiseEditMoveRight(struct linenoiseState *l) { } } +/* Move cursor to the start of the line. */ +void linenoiseEditMoveHome(struct linenoiseState *l) { + if (l->pos != 0) { + l->pos = 0; + refreshLine(l); + } +} + +/* Move cursor to the end of the line. */ +void linenoiseEditMoveEnd(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos = l->len; + refreshLine(l); + } +} + /* Substitute the currently edited line with the next or previous history * entry as specified by 'dir'. */ #define LINENOISE_HISTORY_NEXT 0 @@ -583,38 +724,39 @@ void linenoiseEditDeletePrevWord(struct linenoiseState *l) { * when ctrl+d is typed. * * The function returns the length of the current buffer. */ -static int linenoiseEdit(int fd, char *buf, size_t buflen, const char *prompt) +static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) { struct linenoiseState l; /* Populate the linenoise state that we pass to functions implementing * specific editing functionalities. */ - l.fd = fd; + 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(); + l.cols = getColumns(stdin_fd, stdout_fd); l.maxrows = 0; l.history_index = 0; /* Buffer starts empty. */ - buf[0] = '\0'; - 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 */ /* The latest history entry is always our current buffer, that * initially is just an empty string. */ linenoiseHistoryAdd(""); - - if (write(fd,prompt,l.plen) == -1) return -1; + + if (write(l.ofd,prompt,l.plen) == -1) return -1; while(1) { char c; int nread; - char seq[2], seq2[2]; + char seq[3]; - nread = read(fd,&c,1); + nread = read(l.ifd,&c,1); if (nread <= 0) return l.len; /* Only autocomplete when the callback is set. It returns < 0 when @@ -629,19 +771,20 @@ static int linenoiseEdit(int fd, char *buf, size_t buflen, const char *prompt) } switch(c) { - case 13: /* enter */ + case ENTER: /* enter */ history_len--; free(history[history_len]); + if (mlmode) linenoiseEditMoveEnd(&l); return (int)l.len; - case 3: /* ctrl-c */ + case CTRL_C: /* ctrl-c */ errno = EAGAIN; return -1; - case 127: /* backspace */ + case BACKSPACE: /* backspace */ case 8: /* ctrl-h */ linenoiseEditBackspace(&l); break; - case 4: /* ctrl-d, remove char at right of cursor, or of the - line is empty, act as end-of-file. */ + 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 { @@ -650,7 +793,7 @@ static int linenoiseEdit(int fd, char *buf, size_t buflen, const char *prompt) return -1; } break; - case 20: /* ctrl-t, swaps current character with previous. */ + 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]; @@ -659,68 +802,97 @@ static int linenoiseEdit(int fd, char *buf, size_t buflen, const char *prompt) refreshLine(&l); } break; - case 2: /* ctrl-b */ + case CTRL_B: /* ctrl-b */ linenoiseEditMoveLeft(&l); break; - case 6: /* ctrl-f */ + case CTRL_F: /* ctrl-f */ linenoiseEditMoveRight(&l); break; - case 16: /* ctrl-p */ + case CTRL_P: /* ctrl-p */ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); break; - case 14: /* ctrl-n */ + case CTRL_N: /* ctrl-n */ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); break; - case 27: /* escape sequence */ - /* Read the next two bytes representing the escape sequence. */ - if (read(fd,seq,2) == -1) break; - - if (seq[0] == 91 && seq[1] == 68) { - /* Left arrow */ - linenoiseEditMoveLeft(&l); - } else if (seq[0] == 91 && seq[1] == 67) { - /* Right arrow */ - linenoiseEditMoveRight(&l); - } else if (seq[0] == 91 && (seq[1] == 65 || seq[1] == 66)) { - /* Up and Down arrows */ - linenoiseEditHistoryNext(&l, - (seq[1] == 65) ? LINENOISE_HISTORY_PREV : - LINENOISE_HISTORY_NEXT); - } else if (seq[0] == 91 && seq[1] > 48 && seq[1] < 55) { - /* extended escape, read additional two bytes. */ - if (read(fd,seq2,2) == -1) break; - if (seq[1] == 51 && seq2[0] == 126) { - /* Delete key. */ - linenoiseEditDelete(&l); + 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 { + 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); + 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 -1; break; - case 21: /* Ctrl+u, delete the whole line. */ + case CTRL_U: /* Ctrl+u, delete the whole line. */ buf[0] = '\0'; l.pos = l.len = 0; refreshLine(&l); break; - case 11: /* Ctrl+k, delete from current to end of line. */ + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ buf[l.pos] = '\0'; l.len = l.pos; refreshLine(&l); break; - case 1: /* Ctrl+a, go to the start of the line */ - l.pos = 0; - refreshLine(&l); + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(&l); break; - case 5: /* ctrl+e, go to the end of the line */ - l.pos = l.len; - refreshLine(&l); + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(&l); break; - case 12: /* ctrl+l, clear screen */ + case CTRL_L: /* ctrl+l, clear screen */ linenoiseClearScreen(); refreshLine(&l); break; - case 23: /* ctrl+w, delete previous word */ + case CTRL_W: /* ctrl+w, delete previous word */ linenoiseEditDeletePrevWord(&l); break; } @@ -728,10 +900,37 @@ static int linenoiseEdit(int fd, char *buf, size_t buflen, const char *prompt) return l.len; } +/* 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. */ +void linenoisePrintKeyCodes(void) { + char quit[4]; + + printf("Linenoise key codes debugging mode.\n" + "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); + if (enableRawMode(STDIN_FILENO) == -1) return; + memset(quit,' ',4); + while(1) { + char c; + int nread; + + nread = read(STDIN_FILENO,&c,1); + if (nread <= 0) continue; + memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ + quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ + if (memcmp(quit,"quit",sizeof(quit)) == 0) break; + + printf("'%c' %02x (%d) (type quit to exit)\n", + isprint(c) ? c : '?', (int)c, (int)c); + printf("\r"); /* Go left edge manually, we are in raw mode. */ + fflush(stdout); + } + 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 fd = STDIN_FILENO; int count; if (buflen == 0) { @@ -739,6 +938,7 @@ static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { return -1; } if (!isatty(STDIN_FILENO)) { + /* Not a tty: read from file / pipe. */ if (fgets(buf, buflen, stdin) == NULL) return -1; count = strlen(buf); if (count && buf[count-1] == '\n') { @@ -746,9 +946,10 @@ static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { buf[count] = '\0'; } } else { - if (enableRawMode(fd) == -1) return -1; - count = linenoiseEdit(fd, buf, buflen, prompt); - disableRawMode(fd); + /* Interactive editing. */ + if (enableRawMode(STDIN_FILENO) == -1) return -1; + count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); + disableRawMode(STDIN_FILENO); printf("\n"); } return count; @@ -802,16 +1003,30 @@ static void linenoiseAtExit(void) { freeHistory(); } -/* Using a circular buffer is smarter, but a bit more complex to handle. */ +/* This is the API call to add a new entry in the linenoise history. + * It uses a fixed array of char pointers that are shifted (memmoved) + * when the history max length is reached in order to remove the older + * entry and make room for the new one, so it is not exactly suitable for huge + * histories, but will work well for a few hundred of entries. + * + * 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; + + /* Initialization on first call. */ if (history == NULL) { history = malloc(sizeof(char*)*history_max_len); if (history == NULL) return 0; memset(history,0,(sizeof(char*)*history_max_len)); } + + /* Don't add duplicated lines. */ + if (history_len && !strcmp(history[history_len-1], line)) return 0; + + /* Add an heap allocated copy of the line in the history. + * If we reached the max length, remove the older line. */ linecopy = strdup(line); if (!linecopy) return 0; if (history_len == history_max_len) { @@ -858,10 +1073,10 @@ int linenoiseHistorySetMaxLen(int len) { /* Save the history in the specified file. On success 0 is returned * otherwise -1 is returned. */ -int linenoiseHistorySave(char *filename) { +int linenoiseHistorySave(const 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]); @@ -874,15 +1089,15 @@ int linenoiseHistorySave(char *filename) { * * If the file exists and the operation succeeded 0 is returned, otherwise * on error -1 is returned. */ -int linenoiseHistoryLoad(char *filename) { +int linenoiseHistoryLoad(const 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'; diff --git a/src/linenoise.h b/src/linenoise.h index f28824d..0e89179 100644 --- a/src/linenoise.h +++ b/src/linenoise.h @@ -5,22 +5,22 @@ * * ------------------------------------------------------------------------ * - * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com> - * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com> + * Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com> + * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis 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. - * + * * 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 @@ -37,6 +37,10 @@ #ifndef __LINENOISE_H #define __LINENOISE_H +#ifdef __cplusplus +extern "C" { +#endif + typedef struct linenoiseCompletions { size_t len; char **cvec; @@ -44,14 +48,19 @@ typedef struct linenoiseCompletions { typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); -void linenoiseAddCompletion(linenoiseCompletions *, char *); +void linenoiseAddCompletion(linenoiseCompletions *, const char *); char *linenoise(const char *prompt); int linenoiseHistoryAdd(const char *line); int linenoiseHistorySetMaxLen(int len); -int linenoiseHistorySave(char *filename); -int linenoiseHistoryLoad(char *filename); +int linenoiseHistorySave(const char *filename); +int linenoiseHistoryLoad(const char *filename); void linenoiseClearScreen(void); void linenoiseSetMultiLine(int ml); +void linenoisePrintKeyCodes(void); + +#ifdef __cplusplus +} +#endif #endif /* __LINENOISE_H */ |