update linenoise (#5357)

This commit is contained in:
Arne Döring 2017-03-25 09:28:26 +01:00 • committed by Andreas Rumpf
commit 21b03257ef
3 changed files with 159 additions and 57 deletions

View file

@ -1,7 +1,5 @@
/* linenoise.c -- VERSION 1.0 /* linenoise.c -- guerrilla line editing library against the idea that a
* * line editing lib needs to be 20,000 lines of C code.
* 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: * You can find the latest source code at:
* *
@ -12,7 +10,7 @@
* *
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
* *
* Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com> * Copyright (c) 2010-2016, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com> * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* *
* All rights reserved. * All rights reserved.
@ -113,17 +111,18 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h> #include <ctype.h>
#include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <unistd.h> #include <unistd.h>
#ifndef __LINENOISE_H #include "linenoise.h"
# include "clinenoise.h"
#endif
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
#define LINENOISE_MAX_LINE 4096 #define LINENOISE_MAX_LINE 4096
static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; static char *unsupported_term[] = {"dumb","cons25","emacs",NULL};
static linenoiseCompletionCallback *completionCallback = NULL; static linenoiseCompletionCallback *completionCallback = NULL;
static linenoiseHintsCallback *hintsCallback = NULL;
static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
static struct termios orig_termios; /* In order to restore at exit.*/ 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 rawmode = 0; /* For atexit() function to check if restore is needed*/
@ -141,7 +140,7 @@ struct linenoiseState {
int ofd; /* Terminal stdout file descriptor. */ int ofd; /* Terminal stdout file descriptor. */
char *buf; /* Edited line buffer. */ char *buf; /* Edited line buffer. */
size_t buflen; /* Edited line buffer size. */ size_t buflen; /* Edited line buffer size. */
char *prompt; /* Prompt to display. */ const char *prompt; /* Prompt to display. */
size_t plen; /* Prompt length. */ size_t plen; /* Prompt length. */
size_t pos; /* Current cursor position. */ size_t pos; /* Current cursor position. */
size_t oldpos; /* Previous refresh cursor position. */ size_t oldpos; /* Previous refresh cursor position. */
@ -174,7 +173,7 @@ enum KEY_ACTION{
}; };
static void linenoiseAtExit(void); static void linenoiseAtExit(void);
int linenoiseHistoryAdd(char *line); int linenoiseHistoryAdd(const char *line);
static void refreshLine(struct linenoiseState *l); static void refreshLine(struct linenoiseState *l);
/* Debugging macro. */ /* Debugging macro. */
@ -411,18 +410,30 @@ void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
completionCallback = fn; completionCallback = fn;
} }
/* Register a hits function to be called to show hits to the user at the
* right of the prompt. */
void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) {
hintsCallback = fn;
}
/* Register a function to free the hints returned by the hints callback
* registered with linenoiseSetHintsCallback(). */
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) {
freeHintsCallback = fn;
}
/* This function is used by the callback function registered by the user /* This function is used by the callback function registered by the user
* in order to add completion options given the input string when the * 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 * user typed <tab>. See the example.c source code for a very easy to
* understand example. */ * understand example. */
void linenoiseAddCompletion(linenoiseCompletions *lc, char *str) { void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
size_t len = strlen(str); size_t len = strlen(str);
char *copy, **cvec; char *copy, **cvec;
copy = (char*)malloc(len+1); copy = malloc(len+1);
if (copy == NULL) return; if (copy == NULL) return;
memcpy(copy,str,len+1); memcpy(copy,str,len+1);
cvec = (char**)realloc(lc->cvec,sizeof(char*)*(lc->len+1)); cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
if (cvec == NULL) { if (cvec == NULL) {
free(copy); free(copy);
return; return;
@ -447,12 +458,12 @@ static void abInit(struct abuf *ab) {
ab->len = 0; ab->len = 0;
} }
static void abAppend(struct abuf *ab, char *s, int len) { static void abAppend(struct abuf *ab, const char *s, int len) {
char *neww = (char*)realloc(ab->b,ab->len+len); char *new = realloc(ab->b,ab->len+len);
if (neww == NULL) return; if (new == NULL) return;
memcpy(neww+ab->len,s,len); memcpy(new+ab->len,s,len);
ab->b = neww; ab->b = new;
ab->len += len; ab->len += len;
} }
@ -460,6 +471,30 @@ static void abFree(struct abuf *ab) {
free(ab->b); free(ab->b);
} }
/* Helper of refreshSingleLine() and refreshMultiLine() to show hints
* to the right of the prompt. */
void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) {
char seq[64];
if (hintsCallback && plen+l->len < l->cols) {
int color = -1, bold = 0;
char *hint = hintsCallback(l->buf,&color,&bold);
if (hint) {
int hintlen = strlen(hint);
int hintmaxlen = l->cols-(plen+l->len);
if (hintlen > hintmaxlen) hintlen = hintmaxlen;
if (bold == 1 && color == -1) color = 37;
if (color != -1 || bold != 0)
snprintf(seq,64,"\033[%d;%d;49m",bold,color);
abAppend(ab,seq,strlen(seq));
abAppend(ab,hint,hintlen);
if (color != -1 || bold != 0)
abAppend(ab,"\033[0m",4);
/* Call the function to free the hint returned. */
if (freeHintsCallback) freeHintsCallback(hint);
}
}
}
/* Single line low level line refresh. /* Single line low level line refresh.
* *
* Rewrite the currently edited line accordingly to the buffer content, * Rewrite the currently edited line accordingly to the buffer content,
@ -489,6 +524,8 @@ static void refreshSingleLine(struct linenoiseState *l) {
/* Write the prompt and the current buffer content */ /* Write the prompt and the current buffer content */
abAppend(&ab,l->prompt,strlen(l->prompt)); abAppend(&ab,l->prompt,strlen(l->prompt));
abAppend(&ab,buf,len); abAppend(&ab,buf,len);
/* Show hits if any. */
refreshShowHints(&ab,l,plen);
/* Erase to right */ /* Erase to right */
snprintf(seq,64,"\x1b[0K"); snprintf(seq,64,"\x1b[0K");
abAppend(&ab,seq,strlen(seq)); abAppend(&ab,seq,strlen(seq));
@ -542,6 +579,9 @@ static void refreshMultiLine(struct linenoiseState *l) {
abAppend(&ab,l->prompt,strlen(l->prompt)); abAppend(&ab,l->prompt,strlen(l->prompt));
abAppend(&ab,l->buf,l->len); abAppend(&ab,l->buf,l->len);
/* Show hits if any. */
refreshShowHints(&ab,l,plen);
/* If we are at the very end of the screen with our prompt, we need to /* 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. */ * emit a newline and move the prompt to the first column. */
if (l->pos && if (l->pos &&
@ -602,7 +642,7 @@ int linenoiseEditInsert(struct linenoiseState *l, char c) {
l->pos++; l->pos++;
l->len++; l->len++;
l->buf[l->len] = '\0'; l->buf[l->len] = '\0';
if ((!mlmode && l->plen+l->len < l->cols) /* || mlmode */) { if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) {
/* Avoid a full update of the line in the /* Avoid a full update of the line in the
* trivial case. */ * trivial case. */
if (write(l->ofd,&c,1) == -1) return -1; if (write(l->ofd,&c,1) == -1) return -1;
@ -725,7 +765,7 @@ void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
* when ctrl+d is typed. * when ctrl+d is typed.
* *
* The function returns the length of the current buffer. */ * The function returns the length of the current buffer. */
static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, char *prompt) static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt)
{ {
struct linenoiseState l; struct linenoiseState l;
@ -776,6 +816,14 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
history_len--; history_len--;
free(history[history_len]); free(history[history_len]);
if (mlmode) linenoiseEditMoveEnd(&l); if (mlmode) linenoiseEditMoveEnd(&l);
if (hintsCallback) {
/* Force a refresh without hints to leave the previous
* line as the user typed it after a newline. */
linenoiseHintsCallback *hc = hintsCallback;
hintsCallback = NULL;
refreshLine(&l);
hintsCallback = hc;
}
return (int)l.len; return (int)l.len;
case CTRL_C: /* ctrl-c */ case CTRL_C: /* ctrl-c */
errno = EAGAIN; errno = EAGAIN;
@ -931,41 +979,71 @@ void linenoisePrintKeyCodes(void) {
/* This function calls the line editing function linenoiseEdit() using /* This function calls the line editing function linenoiseEdit() using
* the STDIN file descriptor set in raw mode. */ * the STDIN file descriptor set in raw mode. */
static int linenoiseRaw(char *buf, size_t buflen, char *prompt) { static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
int count; int count;
if (buflen == 0) { if (buflen == 0) {
errno = EINVAL; errno = EINVAL;
return -1; 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') {
count--;
buf[count] = '\0';
}
} else {
/* Interactive editing. */
if (enableRawMode(STDIN_FILENO) == -1) return -1; if (enableRawMode(STDIN_FILENO) == -1) return -1;
count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt);
disableRawMode(STDIN_FILENO); disableRawMode(STDIN_FILENO);
printf("\n"); printf("\n");
}
return count; return count;
} }
/* This function is called when linenoise() is called with the standard
* input file descriptor not attached to a TTY. So for example when the
* program using linenoise is called in pipe or with a file redirected
* to its standard input. In this case, we want to be able to return the
* line regardless of its length (by default we are limited to 4k). */
static char *linenoiseNoTTY(void) {
char *line = NULL;
size_t len = 0, maxlen = 0;
while(1) {
if (len == maxlen) {
if (maxlen == 0) maxlen = 16;
maxlen *= 2;
char *oldval = line;
line = realloc(line,maxlen);
if (line == NULL) {
if (oldval) free(oldval);
return NULL;
}
}
int c = fgetc(stdin);
if (c == EOF || c == '\n') {
if (c == EOF && len == 0) {
free(line);
return NULL;
} else {
line[len] = '\0';
return line;
}
} else {
line[len] = c;
len++;
}
}
}
/* The high level function that is the main API of the linenoise library. /* The high level function that is the main API of the linenoise library.
* This function checks if the terminal has basic capabilities, just checking * This function checks if the terminal has basic capabilities, just checking
* for a blacklist of stupid terminals, and later either calls the line * for a blacklist of stupid terminals, and later either calls the line
* editing function or uses dummy fgets() so that you will be able to type * editing function or uses dummy fgets() so that you will be able to type
* something even in the most desperate of the conditions. */ * something even in the most desperate of the conditions. */
char *linenoise(char *prompt) { char *linenoise(const char *prompt) {
char buf[LINENOISE_MAX_LINE]; char buf[LINENOISE_MAX_LINE];
int count; int count;
if (isUnsupportedTerm()) { if (!isatty(STDIN_FILENO)) {
/* Not a tty: read from file / pipe. In this mode we don't want any
* limit to the line size, so we call a function to handle that. */
return linenoiseNoTTY();
} else if (isUnsupportedTerm()) {
size_t len; size_t len;
printf("%s",prompt); printf("%s",prompt);
@ -984,6 +1062,14 @@ char *linenoise(char *prompt) {
} }
} }
/* This is just a wrapper the user may want to call in order to make sure
* the linenoise returned buffer is freed with the same allocator it was
* created with. Useful when the main program is using an alternative
* allocator. */
void linenoiseFree(void *ptr) {
free(ptr);
}
/* ================================ History ================================= */ /* ================================ History ================================= */
/* Free the history, but does not reset it. Only used when we have to /* Free the history, but does not reset it. Only used when we have to
@ -1011,14 +1097,14 @@ static void linenoiseAtExit(void) {
* histories, but will work well for a few hundred of entries. * histories, but will work well for a few hundred of entries.
* *
* Using a circular buffer is smarter, but a bit more complex to handle. */ * Using a circular buffer is smarter, but a bit more complex to handle. */
int linenoiseHistoryAdd(char *line) { int linenoiseHistoryAdd(const char *line) {
char *linecopy; char *linecopy;
if (history_max_len == 0) return 0; if (history_max_len == 0) return 0;
/* Initialization on first call. */ /* Initialization on first call. */
if (history == NULL) { if (history == NULL) {
history = (char**)malloc(sizeof(char*)*history_max_len); history = malloc(sizeof(char*)*history_max_len);
if (history == NULL) return 0; if (history == NULL) return 0;
memset(history,0,(sizeof(char*)*history_max_len)); memset(history,0,(sizeof(char*)*history_max_len));
} }
@ -1045,14 +1131,14 @@ int linenoiseHistoryAdd(char *line) {
* just the latest 'len' elements if the new history length value is smaller * just the latest 'len' elements if the new history length value is smaller
* than the amount of items already inside the history. */ * than the amount of items already inside the history. */
int linenoiseHistorySetMaxLen(int len) { int linenoiseHistorySetMaxLen(int len) {
char **neww; char **new;
if (len < 1) return 0; if (len < 1) return 0;
if (history) { if (history) {
int tocopy = history_len; int tocopy = history_len;
neww = (char**)malloc(sizeof(char*)*len); new = malloc(sizeof(char*)*len);
if (neww == NULL) return 0; if (new == NULL) return 0;
/* If we can't copy everything, free the elements we'll not use. */ /* If we can't copy everything, free the elements we'll not use. */
if (len < tocopy) { if (len < tocopy) {
@ -1061,10 +1147,10 @@ int linenoiseHistorySetMaxLen(int len) {
for (j = 0; j < tocopy-len; j++) free(history[j]); for (j = 0; j < tocopy-len; j++) free(history[j]);
tocopy = len; tocopy = len;
} }
memset(neww,0,sizeof(char*)*len); memset(new,0,sizeof(char*)*len);
memcpy(neww,history+(history_len-tocopy), sizeof(char*)*tocopy); memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy);
free(history); free(history);
history = neww; history = new;
} }
history_max_len = len; history_max_len = len;
if (history_len > history_max_len) if (history_len > history_max_len)
@ -1074,11 +1160,15 @@ int linenoiseHistorySetMaxLen(int len) {
/* Save the history in the specified file. On success 0 is returned /* Save the history in the specified file. On success 0 is returned
* otherwise -1 is returned. */ * otherwise -1 is returned. */
int linenoiseHistorySave(char *filename) { int linenoiseHistorySave(const char *filename) {
FILE *fp = fopen(filename,"w"); mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
FILE *fp;
int j; int j;
fp = fopen(filename,"w");
umask(old_umask);
if (fp == NULL) return -1; if (fp == NULL) return -1;
chmod(filename,S_IRUSR|S_IWUSR);
for (j = 0; j < history_len; j++) for (j = 0; j < history_len; j++)
fprintf(fp,"%s\n",history[j]); fprintf(fp,"%s\n",history[j]);
fclose(fp); fclose(fp);
@ -1090,7 +1180,7 @@ int linenoiseHistorySave(char *filename) {
* *
* If the file exists and the operation succeeded 0 is returned, otherwise * If the file exists and the operation succeeded 0 is returned, otherwise
* on error -1 is returned. */ * on error -1 is returned. */
int linenoiseHistoryLoad(char *filename) { int linenoiseHistoryLoad(const char *filename) {
FILE *fp = fopen(filename,"r"); FILE *fp = fopen(filename,"r");
char buf[LINENOISE_MAX_LINE]; char buf[LINENOISE_MAX_LINE];

View file

@ -39,22 +39,35 @@
#ifndef __LINENOISE_H #ifndef __LINENOISE_H
#define __LINENOISE_H #define __LINENOISE_H
#ifdef __cplusplus
extern "C" {
#endif
typedef struct linenoiseCompletions { typedef struct linenoiseCompletions {
size_t len; size_t len;
char **cvec; char **cvec;
} linenoiseCompletions; } linenoiseCompletions;
typedef void(linenoiseCompletionCallback)(char *, linenoiseCompletions *); typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold);
typedef void(linenoiseFreeHintsCallback)(void *);
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
void linenoiseAddCompletion(linenoiseCompletions *, char *); void linenoiseSetHintsCallback(linenoiseHintsCallback *);
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
char *linenoise(char *prompt); char *linenoise(const char *prompt);
int linenoiseHistoryAdd(char *line); void linenoiseFree(void *ptr);
int linenoiseHistoryAdd(const char *line);
int linenoiseHistorySetMaxLen(int len); int linenoiseHistorySetMaxLen(int len);
int linenoiseHistorySave(char *filename); int linenoiseHistorySave(const char *filename);
int linenoiseHistoryLoad(char *filename); int linenoiseHistoryLoad(const char *filename);
void linenoiseClearScreen(void); void linenoiseClearScreen(void);
void linenoiseSetMultiLine(int ml); void linenoiseSetMultiLine(int ml);
void linenoisePrintKeyCodes(void); void linenoisePrintKeyCodes(void);
#ifdef __cplusplus
}
#endif
#endif /* __LINENOISE_H */ #endif /* __LINENOISE_H */

View file

@ -14,8 +14,7 @@ type
CompletionCallback* = proc (a2: cstring; a3: ptr Completions) {.cdecl.} CompletionCallback* = proc (a2: cstring; a3: ptr Completions) {.cdecl.}
{.emit: staticRead"clinenoise.h".} {.compile: "linenoise.c".}
{.emit: staticRead"clinenoise.c".}
proc setCompletionCallback*(a2: ptr CompletionCallback) {. proc setCompletionCallback*(a2: ptr CompletionCallback) {.
importc: "linenoiseSetCompletionCallback".} importc: "linenoiseSetCompletionCallback".}