| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #include <termios.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <ctype.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/ioctl.h> |
| #include <unistd.h> |
| #include <stdint.h> |
| #include "linenoise.h" |
|
|
| #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 |
| #define LINENOISE_MAX_LINE (1024*1024) |
| #define LINENOISE_INITIAL_BUFLEN 4096 |
| #define PASTE_FOLD_THRESHOLD 200 |
| #define PASTE_FOLD_CONTEXT 8 |
| #define HISTORY_FOLD_THRESHOLD 4096 |
| #define HISTORY_FOLD_MULTILINE_LINES 16 |
| #define HISTORY_FOLD_CONTEXT 96 |
| #define PASTE_MAX_BYTES LINENOISE_MAX_LINE |
| static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; |
| static linenoiseCompletionCallback *completionCallback = NULL; |
| static linenoiseHintsCallback *hintsCallback = NULL; |
| static linenoiseFreeHintsCallback *freeHintsCallback = NULL; |
| static char *linenoiseReadLine(FILE *fp, int *err); |
| static char *linenoiseNoTTY(void); |
| static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags); |
| static void refreshLineWithFlags(struct linenoiseState *l, int flags); |
| static void linenoiseFoldClear(struct linenoiseState *l); |
|
|
| static struct termios orig_termios; |
| static int maskmode = 0; |
| static int rawmode = 0; |
| static int rawmode_output = STDOUT_FILENO; |
| static int mlmode = 0; |
| static int atexit_registered = 0; |
| static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; |
| static int history_len = 0; |
| static char **history = NULL; |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| static int utf8ByteLen(char c) { |
| unsigned char uc = (unsigned char)c; |
| if ((uc & 0x80) == 0) return 1; |
| if ((uc & 0xE0) == 0xC0) return 2; |
| if ((uc & 0xF0) == 0xE0) return 3; |
| if ((uc & 0xF8) == 0xF0) return 4; |
| return 1; |
| } |
|
|
| |
| |
| static uint32_t utf8DecodeChar(const char *s, size_t *len) { |
| unsigned char *p = (unsigned char *)s; |
| uint32_t cp; |
|
|
| if ((*p & 0x80) == 0) { |
| *len = 1; |
| return *p; |
| } else if ((*p & 0xE0) == 0xC0) { |
| *len = 2; |
| cp = (*p & 0x1F) << 6; |
| cp |= (p[1] & 0x3F); |
| return cp; |
| } else if ((*p & 0xF0) == 0xE0) { |
| *len = 3; |
| cp = (*p & 0x0F) << 12; |
| cp |= (p[1] & 0x3F) << 6; |
| cp |= (p[2] & 0x3F); |
| return cp; |
| } else if ((*p & 0xF8) == 0xF0) { |
| *len = 4; |
| cp = (*p & 0x07) << 18; |
| cp |= (p[1] & 0x3F) << 12; |
| cp |= (p[2] & 0x3F) << 6; |
| cp |= (p[3] & 0x3F); |
| return cp; |
| } |
| *len = 1; |
| return *p; |
| } |
|
|
| |
| static int isVariationSelector(uint32_t cp) { |
| return cp == 0xFE0E || cp == 0xFE0F; |
| } |
|
|
| |
| static int isSkinToneModifier(uint32_t cp) { |
| return cp >= 0x1F3FB && cp <= 0x1F3FF; |
| } |
|
|
| |
| static int isZWJ(uint32_t cp) { |
| return cp == 0x200D; |
| } |
|
|
| |
| static int isRegionalIndicator(uint32_t cp) { |
| return cp >= 0x1F1E6 && cp <= 0x1F1FF; |
| } |
|
|
| |
| static int isCombiningMark(uint32_t cp) { |
| return (cp >= 0x0300 && cp <= 0x036F) || |
| (cp >= 0x1AB0 && cp <= 0x1AFF) || |
| (cp >= 0x1DC0 && cp <= 0x1DFF) || |
| (cp >= 0x20D0 && cp <= 0x20FF) || |
| (cp >= 0xFE20 && cp <= 0xFE2F); |
| } |
|
|
| |
| static int isGraphemeExtend(uint32_t cp) { |
| return isVariationSelector(cp) || isSkinToneModifier(cp) || |
| isZWJ(cp) || isCombiningMark(cp); |
| } |
|
|
| |
| |
| static uint32_t utf8DecodePrev(const char *buf, size_t pos, size_t *cplen) { |
| if (pos == 0) { |
| *cplen = 0; |
| return 0; |
| } |
| |
| size_t i = pos; |
| do { |
| i--; |
| } while (i > 0 && (pos - i) < 4 && ((unsigned char)buf[i] & 0xC0) == 0x80); |
| *cplen = pos - i; |
| size_t dummy; |
| return utf8DecodeChar(buf + i, &dummy); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static size_t utf8PrevCharLen(const char *buf, size_t pos) { |
| if (pos == 0) return 0; |
|
|
| size_t total = 0; |
| size_t curpos = pos; |
|
|
| |
| size_t cplen; |
| uint32_t cp = utf8DecodePrev(buf, curpos, &cplen); |
| if (cplen == 0) return 0; |
| total += cplen; |
| curpos -= cplen; |
|
|
| |
| |
| while (curpos > 0) { |
| size_t prevlen; |
| uint32_t prevcp = utf8DecodePrev(buf, curpos, &prevlen); |
| if (prevlen == 0) break; |
|
|
| if (isZWJ(prevcp)) { |
| |
| |
| total += prevlen; |
| curpos -= prevlen; |
| |
| prevcp = utf8DecodePrev(buf, curpos, &prevlen); |
| if (prevlen == 0) break; |
| total += prevlen; |
| curpos -= prevlen; |
| cp = prevcp; |
| continue; |
| } else if (isGraphemeExtend(cp)) { |
| |
| total += prevlen; |
| curpos -= prevlen; |
| cp = prevcp; |
| continue; |
| } else if (isRegionalIndicator(cp) && isRegionalIndicator(prevcp)) { |
| |
| |
| |
| total += prevlen; |
| curpos -= prevlen; |
| break; |
| } else { |
| |
| break; |
| } |
| } |
|
|
| return total; |
| } |
|
|
| |
| |
| static size_t utf8NextCharLen(const char *buf, size_t pos, size_t len) { |
| if (pos >= len) return 0; |
|
|
| size_t total = 0; |
| size_t curpos = pos; |
|
|
| |
| size_t cplen; |
| uint32_t cp = utf8DecodeChar(buf + curpos, &cplen); |
| total += cplen; |
| curpos += cplen; |
|
|
| int isRI = isRegionalIndicator(cp); |
|
|
| |
| while (curpos < len) { |
| size_t nextlen; |
| uint32_t nextcp = utf8DecodeChar(buf + curpos, &nextlen); |
|
|
| if (isZWJ(nextcp) && curpos + nextlen < len) { |
| |
| total += nextlen; |
| curpos += nextlen; |
| |
| nextcp = utf8DecodeChar(buf + curpos, &nextlen); |
| total += nextlen; |
| curpos += nextlen; |
| continue; |
| } else if (isGraphemeExtend(nextcp)) { |
| |
| total += nextlen; |
| curpos += nextlen; |
| continue; |
| } else if (isRI && isRegionalIndicator(nextcp)) { |
| |
| total += nextlen; |
| curpos += nextlen; |
| isRI = 0; |
| continue; |
| } else { |
| break; |
| } |
| } |
|
|
| return total; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static int utf8CharWidth(uint32_t cp) { |
| |
| if (cp < 32 || (cp >= 0x7F && cp < 0xA0)) return 0; |
| if (isCombiningMark(cp)) return 0; |
|
|
| |
| |
| if (isVariationSelector(cp)) return 0; |
| if (isSkinToneModifier(cp)) return 0; |
| if (isZWJ(cp)) return 0; |
|
|
| |
| |
| |
| |
| if (cp >= 0x1100 && |
| (cp <= 0x115F || |
| cp == 0x2329 || cp == 0x232A || |
| (cp >= 0x231A && cp <= 0x231B) || |
| (cp >= 0x23E9 && cp <= 0x23F3) || |
| (cp >= 0x23F8 && cp <= 0x23FA) || |
| (cp >= 0x25AA && cp <= 0x25AB) || |
| (cp >= 0x25FB && cp <= 0x25FE) || |
| (cp >= 0x2600 && cp <= 0x26FF) || |
| (cp >= 0x2700 && cp <= 0x27BF) || |
| (cp >= 0x2934 && cp <= 0x2935) || |
| (cp >= 0x2B05 && cp <= 0x2B07) || |
| (cp >= 0x2B1B && cp <= 0x2B1C) || |
| cp == 0x2B50 || cp == 0x2B55 || |
| (cp >= 0x2E80 && cp <= 0xA4CF && |
| cp != 0x303F) || |
| (cp >= 0xAC00 && cp <= 0xD7A3) || |
| (cp >= 0xF900 && cp <= 0xFAFF) || |
| (cp >= 0xFE10 && cp <= 0xFE1F) || |
| (cp >= 0xFE30 && cp <= 0xFE6F) || |
| (cp >= 0xFF00 && cp <= 0xFF60) || |
| (cp >= 0xFFE0 && cp <= 0xFFE6) || |
| (cp >= 0x1F1E6 && cp <= 0x1F1FF) || |
| (cp >= 0x1F300 && cp <= 0x1F64F) || |
| (cp >= 0x1F680 && cp <= 0x1F6FF) || |
| (cp >= 0x1F900 && cp <= 0x1F9FF) || |
| (cp >= 0x1FA00 && cp <= 0x1FAFF) || |
| (cp >= 0x20000 && cp <= 0x2FFFF))) |
| return 2; |
|
|
| return 1; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static size_t ansiEscapeLen(const char *s, size_t len) { |
| size_t i; |
| if (len < 2 || s[1] != '[') return 0; |
| i = 2; |
| while (i < len && (unsigned char)s[i] >= 0x30 && (unsigned char)s[i] <= 0x3f) i++; |
| while (i < len && (unsigned char)s[i] >= 0x20 && (unsigned char)s[i] <= 0x2f) i++; |
| if (i >= len || (unsigned char)s[i] < 0x40 || (unsigned char)s[i] > 0x7e) return 0; |
| return i + 1; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static size_t utf8StrWidth(const char *s, size_t len) { |
| size_t width = 0; |
| size_t i = 0; |
| int after_zwj = 0; |
|
|
| while (i < len) { |
| size_t clen; |
| uint32_t cp = utf8DecodeChar(s + i, &clen); |
|
|
| |
| |
| |
| |
| if (cp == 0x1b) { |
| size_t skip = ansiEscapeLen(s + i, len - i); |
| if (skip > 0) { |
| i += skip; |
| continue; |
| } |
| } |
|
|
| if (after_zwj) { |
| |
| |
| after_zwj = 0; |
| } else { |
| width += utf8CharWidth(cp); |
| } |
|
|
| |
| if (isZWJ(cp)) { |
| after_zwj = 1; |
| } |
|
|
| i += clen; |
| } |
| return width; |
| } |
|
|
| |
| static int utf8SingleCharWidth(const char *s, size_t len) { |
| if (len == 0) return 0; |
| size_t clen; |
| uint32_t cp = utf8DecodeChar(s, &clen); |
| return utf8CharWidth(cp); |
| } |
|
|
| enum KEY_ACTION{ |
| KEY_NULL = 0, |
| CTRL_A = 1, |
| CTRL_B = 2, |
| CTRL_C = 3, |
| CTRL_D = 4, |
| CTRL_E = 5, |
| CTRL_F = 6, |
| CTRL_H = 8, |
| TAB = 9, |
| CTRL_K = 11, |
| CTRL_L = 12, |
| ENTER = 13, |
| CTRL_N = 14, |
| CTRL_P = 16, |
| CTRL_T = 20, |
| CTRL_U = 21, |
| CTRL_W = 23, |
| ESC = 27, |
| BACKSPACE = 127 |
| }; |
|
|
| static void linenoiseAtExit(void); |
| int linenoiseHistoryAdd(const char *line); |
| #define REFRESH_CLEAN (1<<0) |
| #define REFRESH_WRITE (1<<1) |
| #define REFRESH_ALL (REFRESH_CLEAN|REFRESH_WRITE) |
| static void refreshLine(struct linenoiseState *l); |
|
|
| |
| #if 0 |
| 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->oldrows,old_rows); \ |
| } \ |
| fprintf(lndebug_fp, ", " __VA_ARGS__); \ |
| fflush(lndebug_fp); \ |
| } while (0) |
| #else |
| #define lndebug(fmt, ...) |
| #endif |
|
|
| |
|
|
| |
| |
| |
| |
| void linenoiseMaskModeEnable(void) { |
| maskmode = 1; |
| } |
|
|
| |
| void linenoiseMaskModeDisable(void) { |
| maskmode = 0; |
| } |
|
|
| |
| void linenoiseSetMultiLine(int ml) { |
| mlmode = ml; |
| } |
|
|
| |
| |
| 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 linenoiseMakeRawMode(struct termios *raw) { |
| raw->c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); |
| raw->c_oflag &= ~(OPOST); |
| raw->c_cflag |= (CS8); |
| raw->c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); |
| raw->c_cc[VMIN] = 1; |
| raw->c_cc[VTIME] = 0; |
| } |
|
|
| static int linenoiseTermiosRawFieldsEqual(const struct termios *a, |
| const struct termios *b) { |
| return a->c_iflag == b->c_iflag && |
| a->c_oflag == b->c_oflag && |
| a->c_cflag == b->c_cflag && |
| a->c_lflag == b->c_lflag && |
| a->c_cc[VMIN] == b->c_cc[VMIN] && |
| a->c_cc[VTIME] == b->c_cc[VTIME]; |
| } |
|
|
| |
| static int enableRawMode(int fd) { |
| struct termios raw; |
|
|
| |
| |
| if (getenv("LINENOISE_ASSUME_TTY")) { |
| rawmode = 1; |
| return 0; |
| } |
|
|
| 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; |
| linenoiseMakeRawMode(&raw); |
|
|
| |
| if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; |
| rawmode = 1; |
| |
| if (write(rawmode_output, "\x1b[?2004h", 8) == -1) {} |
| return 0; |
|
|
| fatal: |
| errno = ENOTTY; |
| return -1; |
| } |
|
|
| static void disableRawMode(int fd) { |
| |
| if (getenv("LINENOISE_ASSUME_TTY")) { |
| rawmode = 0; |
| return; |
| } |
| |
| if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) { |
| |
| if (write(rawmode_output, "\x1b[?2004l", 8) == -1) {} |
| rawmode = 0; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| void linenoiseRestoreRawMode(void) { |
| if (getenv("LINENOISE_ASSUME_TTY")) return; |
| if (!rawmode) return; |
| if (!isatty(STDIN_FILENO)) return; |
| struct termios current, raw = orig_termios; |
| linenoiseMakeRawMode(&raw); |
| if (tcgetattr(STDIN_FILENO, ¤t) == -1) return; |
| if (!linenoiseTermiosRawFieldsEqual(¤t, &raw)) |
| tcsetattr(STDIN_FILENO, TCSANOW, &raw); |
| |
| if (write(rawmode_output, "\x1b[?2004h", 8) == -1) {} |
| } |
|
|
| |
| |
| |
| static int getCursorPosition(int ifd, int ofd) { |
| char buf[32]; |
| int cols, rows; |
| unsigned int i = 0; |
|
|
| |
| if (write(ofd, "\x1b[6n", 4) != 4) return -1; |
|
|
| |
| while (i < sizeof(buf)-1) { |
| if (read(ifd,buf+i,1) != 1) break; |
| if (buf[i] == 'R') break; |
| i++; |
| } |
| buf[i] = '\0'; |
|
|
| |
| if (buf[0] != ESC || buf[1] != '[') return -1; |
| if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; |
| return cols; |
| } |
|
|
| |
| |
| static int getColumns(int ifd, int ofd) { |
| struct winsize ws; |
|
|
| |
| char *cols_env = getenv("LINENOISE_COLS"); |
| if (cols_env) return atoi(cols_env); |
|
|
| if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { |
| |
| int start, cols; |
|
|
| |
| start = getCursorPosition(ifd,ofd); |
| if (start == -1) goto failed; |
|
|
| |
| if (write(ofd,"\x1b[999C",6) != 6) goto failed; |
| cols = getCursorPosition(ifd,ofd); |
| if (cols == -1) goto failed; |
|
|
| |
| if (cols > start) { |
| char seq[32]; |
| snprintf(seq,32,"\x1b[%dD",cols-start); |
| if (write(ofd,seq,strlen(seq)) == -1) { |
| |
| } |
| } |
| return cols; |
| } else { |
| return ws.ws_col; |
| } |
|
|
| failed: |
| return 80; |
| } |
|
|
| |
| void linenoiseClearScreen(void) { |
| if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { |
| |
| } |
| } |
|
|
| |
| |
| static void linenoiseBeep(void) { |
| fprintf(stderr, "\x7"); |
| fflush(stderr); |
| } |
|
|
| |
|
|
| |
| static void freeCompletions(linenoiseCompletions *lc) { |
| size_t i; |
| for (i = 0; i < lc->len; i++) |
| free(lc->cvec[i]); |
| if (lc->cvec != NULL) |
| free(lc->cvec); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags) { |
| |
| linenoiseCompletions ctable = { 0, NULL }; |
| if (lc == NULL) { |
| completionCallback(ls->buf,&ctable); |
| lc = &ctable; |
| } |
|
|
| |
| if (ls->completion_idx < lc->len) { |
| struct linenoiseState saved = *ls; |
| ls->len = ls->pos = strlen(lc->cvec[ls->completion_idx]); |
| ls->buf = lc->cvec[ls->completion_idx]; |
| ls->fold_count = 0; |
| refreshLineWithFlags(ls,flags); |
| ls->len = saved.len; |
| ls->pos = saved.pos; |
| ls->buf = saved.buf; |
| ls->fold_count = saved.fold_count; |
| } else { |
| refreshLineWithFlags(ls,flags); |
| } |
|
|
| |
| if (lc != &ctable) freeCompletions(&ctable); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static int completeLine(struct linenoiseState *ls, int keypressed) { |
| linenoiseCompletions lc = { 0, NULL }; |
| int nwritten; |
| char c = keypressed; |
|
|
| completionCallback(ls->buf,&lc); |
| if (lc.len == 0) { |
| linenoiseBeep(); |
| ls->in_completion = 0; |
| c = 0; |
| } else { |
| switch(c) { |
| case 9: |
| if (ls->in_completion == 0) { |
| ls->in_completion = 1; |
| ls->completion_idx = 0; |
| } else { |
| ls->completion_idx = (ls->completion_idx+1) % (lc.len+1); |
| if (ls->completion_idx == lc.len) linenoiseBeep(); |
| } |
| c = 0; |
| break; |
| case 27: |
| |
| if (ls->completion_idx < lc.len) refreshLine(ls); |
| ls->in_completion = 0; |
| c = 0; |
| break; |
| default: |
| |
| if (ls->completion_idx < lc.len) { |
| nwritten = snprintf(ls->buf,ls->buflen,"%s", |
| lc.cvec[ls->completion_idx]); |
| ls->len = ls->pos = nwritten; |
| linenoiseFoldClear(ls); |
| } |
| ls->in_completion = 0; |
| break; |
| } |
|
|
| |
| if (ls->in_completion && ls->completion_idx < lc.len) { |
| refreshLineWithCompletion(ls,&lc,REFRESH_ALL); |
| } else { |
| refreshLine(ls); |
| } |
| } |
|
|
| freeCompletions(&lc); |
| return c; |
| } |
|
|
| |
| void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { |
| completionCallback = fn; |
| } |
|
|
| |
| |
| void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { |
| hintsCallback = fn; |
| } |
|
|
| |
| |
| void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { |
| freeHintsCallback = fn; |
| } |
|
|
| |
| |
| |
| |
| void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { |
| size_t len = strlen(str); |
| char *copy, **cvec; |
|
|
| copy = malloc(len+1); |
| if (copy == NULL) return; |
| memcpy(copy,str,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; |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| 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); |
| } |
|
|
| static int linenoiseStatusActive(struct linenoiseState *l) { |
| return l->status && l->status[0]; |
| } |
|
|
| static int linenoiseStatusRows(struct linenoiseState *l) { |
| if (!linenoiseStatusActive(l)) return 0; |
| int rows = 1; |
| for (char *p = l->status; *p; p++) { |
| if (*p == '\n') rows++; |
| } |
| return rows; |
| } |
|
|
| |
| |
| |
| |
| static size_t abAppendUtf8Clipped(struct abuf *ab, const char *s, size_t len, |
| size_t maxcols) { |
| size_t i = 0, width = 0; |
| int after_zwj = 0; |
| while (i < len) { |
| size_t clen; |
| uint32_t cp = utf8DecodeChar(s + i, &clen); |
| if (cp == 0x1b) { |
| size_t skip = ansiEscapeLen(s + i, len - i); |
| if (skip > 0) { |
| abAppend(ab, s + i, (int)skip); |
| i += skip; |
| continue; |
| } |
| } |
|
|
| int cwidth = after_zwj ? 0 : utf8CharWidth(cp); |
| if (width + (size_t)cwidth > maxcols) break; |
| abAppend(ab, s + i, (int)clen); |
| width += cwidth; |
| after_zwj = isZWJ(cp); |
| if (!isZWJ(cp) && after_zwj) after_zwj = 0; |
| i += clen; |
| } |
| return width; |
| } |
|
|
| static void refreshStatusLinePart(struct abuf *ab, struct linenoiseState *l, |
| const char *s, size_t len) { |
| size_t width = 0; |
| size_t cols = l->cols ? l->cols : 80; |
| abAppend(ab, "\r\n", 2); |
| |
| |
| |
| |
| |
| abAppend(ab, "\x1b[?7l", 5); |
| if (l->status_start) abAppend(ab, l->status_start, (int)strlen(l->status_start)); |
| width = abAppendUtf8Clipped(ab, s, len, cols); |
| while (width < cols) { |
| abAppend(ab, " ", 1); |
| width++; |
| } |
| if (l->status_end) abAppend(ab, l->status_end, (int)strlen(l->status_end)); |
| abAppend(ab, "\x1b[?7h", 5); |
| } |
|
|
| static void refreshStatusLine(struct abuf *ab, struct linenoiseState *l) { |
| const char *p = l->status; |
| while (p && *p) { |
| const char *nl = strchr(p, '\n'); |
| size_t len = nl ? (size_t)(nl - p) : strlen(p); |
| refreshStatusLinePart(ab, l, p, len); |
| if (!nl) break; |
| p = nl + 1; |
| } |
| } |
|
|
| |
| |
| |
| struct linenoiseFold { |
| size_t start; |
| size_t end; |
| char display[64]; |
| size_t displaylen; |
| }; |
|
|
| struct linenoiseFolds { |
| int count; |
| struct linenoiseFold fold[LINENOISE_MAX_FOLDS]; |
| }; |
|
|
| |
| static size_t foldCountLines(const char *buf, size_t len) { |
| size_t lines = 1, j; |
| for (j = 0; j < len; j++) { |
| if (buf[j] == '\n') lines++; |
| } |
| return lines; |
| } |
|
|
| |
| |
| static int shouldFoldText(const char *buf, size_t len) { |
| return memchr(buf, '\n', len) != NULL || len >= PASTE_FOLD_THRESHOLD; |
| } |
|
|
| |
| |
| |
| static int shouldFoldHistoryText(const char *buf, size_t len) { |
| size_t lines = foldCountLines(buf,len); |
| if (lines <= 1) return len >= HISTORY_FOLD_THRESHOLD; |
| return len >= HISTORY_FOLD_THRESHOLD || |
| lines >= HISTORY_FOLD_MULTILINE_LINES; |
| } |
|
|
| |
| static void foldSetRenderedText(struct linenoiseFold *f, const char *buf) { |
| size_t hidden = f->end - f->start; |
| size_t lines = foldCountLines(buf + f->start, hidden); |
| int n; |
|
|
| if (lines > 1) |
| n = snprintf(f->display,sizeof(f->display),"[... %zu pasted lines ...]",lines); |
| else |
| n = snprintf(f->display,sizeof(f->display),"[... %zu pasted chars ...]",hidden); |
| if (n < 0) n = 0; |
| f->displaylen = (size_t)n; |
| } |
|
|
| |
| |
| |
| |
| static int linenoiseBuildHistoryFold(struct linenoiseState *l, struct linenoiseFold *f) { |
| f->start = f->end = f->displaylen = 0; |
| if (l->len == 0 || maskmode) return 0; |
| if (!shouldFoldHistoryText(l->buf,l->len)) return 0; |
|
|
| f->start = 0; |
| f->end = l->len; |
| if (l->len > HISTORY_FOLD_CONTEXT*2) { |
| size_t pos = 0, chars = 0; |
| int nl = 0; |
|
|
| |
| |
| while (pos < l->len && chars < HISTORY_FOLD_CONTEXT) { |
| size_t step = utf8NextCharLen(l->buf,pos,l->len); |
| if (step == 0 || pos + step > l->len) break; |
| if (l->buf[pos] == '\n') nl = 1; |
| pos += step; |
| chars++; |
| } |
| f->start = nl ? 0 : pos; |
|
|
| |
| pos = l->len; |
| chars = 0; |
| nl = 0; |
| while (pos > 0 && chars < HISTORY_FOLD_CONTEXT) { |
| size_t step = utf8PrevCharLen(l->buf,pos); |
| if (step == 0 || step > pos) break; |
| pos -= step; |
| if (l->buf[pos] == '\n') nl = 1; |
| chars++; |
| } |
| f->end = nl ? l->len : pos; |
| if (f->start >= f->end) { |
| f->start = 0; |
| f->end = l->len; |
| } |
| } |
| foldSetRenderedText(f,l->buf); |
| return 1; |
| } |
|
|
| |
| |
| |
| static int linenoiseGetRenderFolds(struct linenoiseState *l, struct linenoiseFolds *fs) { |
| int j; |
|
|
| fs->count = 0; |
| if (l->len == 0 || maskmode) return 0; |
|
|
| for (j = 0; j < l->fold_count; j++) { |
| struct linenoiseFold *f; |
| size_t start = l->fold_start[j]; |
| size_t end = l->fold_end[j]; |
|
|
| if (start >= end || end > l->len) continue; |
| f = fs->fold + fs->count++; |
| f->start = start; |
| f->end = end; |
| foldSetRenderedText(f,l->buf); |
| } |
| return fs->count != 0; |
| } |
|
|
| |
| |
| |
| |
| |
| static int linenoiseRenderBuffer(struct linenoiseState *l, char **out, size_t *outlen, size_t *outpos) { |
| struct linenoiseFolds fs; |
| size_t len, pos, src, dst; |
| char *r; |
| int j, pos_set = 0; |
|
|
| if (!linenoiseGetRenderFolds(l,&fs)) { |
| |
| |
| r = malloc(l->len+1); |
| if (r == NULL) return -1; |
| memcpy(r,l->buf,l->len); |
| r[l->len] = '\0'; |
| *out = r; |
| *outlen = l->len; |
| *outpos = l->pos; |
| return 0; |
| } |
|
|
| |
| |
| |
| len = l->len; |
| for (j = 0; j < fs.count; j++) { |
| struct linenoiseFold *f = fs.fold+j; |
| len -= f->end - f->start; |
| len += f->displaylen; |
| } |
| r = malloc(len+1); |
| if (r == NULL) return -1; |
|
|
| src = dst = 0; |
| pos = 0; |
| for (j = 0; j < fs.count; j++) { |
| struct linenoiseFold *f = fs.fold+j; |
| size_t gap = f->start - src; |
|
|
| if (!pos_set && l->pos <= f->start) { |
| pos = dst + (l->pos - src); |
| pos_set = 1; |
| } |
| memcpy(r+dst,l->buf+src,gap); |
| dst += gap; |
|
|
| if (!pos_set && l->pos < f->end) { |
| pos = dst + f->displaylen; |
| pos_set = 1; |
| } |
| memcpy(r+dst,f->display,f->displaylen); |
| dst += f->displaylen; |
| if (!pos_set && l->pos == f->end) { |
| pos = dst; |
| pos_set = 1; |
| } |
| src = f->end; |
| } |
| if (!pos_set) pos = dst + (l->pos - src); |
| memcpy(r+dst,l->buf+src,l->len-src); |
| r[len] = '\0'; |
|
|
| *out = r; |
| *outlen = len; |
| *outpos = pos; |
| return 0; |
| } |
|
|
| |
| |
| static size_t linenoiseEditNextLen(struct linenoiseState *l, size_t pos) { |
| struct linenoiseFolds fs; |
| int j; |
|
|
| if (linenoiseGetRenderFolds(l,&fs)) { |
| for (j = 0; j < fs.count; j++) { |
| if (pos == fs.fold[j].start) |
| return fs.fold[j].end - fs.fold[j].start; |
| } |
| } |
| return utf8NextCharLen(l->buf,pos,l->len); |
| } |
|
|
| |
| |
| static size_t linenoiseEditPrevLen(struct linenoiseState *l, size_t pos) { |
| struct linenoiseFolds fs; |
| int j; |
|
|
| if (linenoiseGetRenderFolds(l,&fs)) { |
| for (j = 0; j < fs.count; j++) { |
| if (pos == fs.fold[j].end) |
| return fs.fold[j].end - fs.fold[j].start; |
| } |
| } |
| return utf8PrevCharLen(l->buf,pos); |
| } |
|
|
| |
| static void linenoiseFoldAdd(struct linenoiseState *l, size_t start, size_t end) { |
| int j; |
|
|
| if (start >= end || l->fold_count == LINENOISE_MAX_FOLDS) return; |
| j = l->fold_count; |
| while (j > 0 && start < l->fold_start[j-1]) { |
| l->fold_start[j] = l->fold_start[j-1]; |
| l->fold_end[j] = l->fold_end[j-1]; |
| j--; |
| } |
| l->fold_start[j] = start; |
| l->fold_end[j] = end; |
| l->fold_count++; |
| } |
|
|
| |
| static void linenoiseFoldClear(struct linenoiseState *l) { |
| l->fold_count = 0; |
| } |
|
|
| |
| static void linenoiseFoldRemove(struct linenoiseState *l, int j) { |
| memmove(l->fold_start+j,l->fold_start+j+1, |
| sizeof(size_t)*(l->fold_count-j-1)); |
| memmove(l->fold_end+j,l->fold_end+j+1, |
| sizeof(size_t)*(l->fold_count-j-1)); |
| l->fold_count--; |
| } |
|
|
| |
| static int linenoiseRangeOverlapsFold(struct linenoiseState *l, size_t pos, size_t len) { |
| size_t end = pos + len; |
| int j; |
|
|
| for (j = 0; j < l->fold_count; j++) { |
| if (end > l->fold_start[j] && pos < l->fold_end[j]) |
| return 1; |
| } |
| return 0; |
| } |
|
|
| |
| |
| static void linenoiseAdjustFoldsAfterInsert(struct linenoiseState *l, size_t pos, size_t len) { |
| int j = 0; |
|
|
| while (j < l->fold_count) { |
| if (pos <= l->fold_start[j]) { |
| l->fold_start[j] += len; |
| l->fold_end[j] += len; |
| j++; |
| } else if (pos < l->fold_end[j]) { |
| linenoiseFoldRemove(l,j); |
| } else { |
| j++; |
| } |
| } |
| } |
|
|
| |
| |
| static void linenoiseAdjustFoldsAfterDelete(struct linenoiseState *l, size_t pos, size_t len) { |
| size_t end = pos + len; |
| int j = 0; |
|
|
| while (j < l->fold_count) { |
| if (end <= l->fold_start[j]) { |
| l->fold_start[j] -= len; |
| l->fold_end[j] -= len; |
| j++; |
| } else if (pos >= l->fold_end[j]) { |
| j++; |
| } else { |
| linenoiseFoldRemove(l,j); |
| } |
| } |
| } |
|
|
| |
| |
| void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int pwidth, size_t bufwidth) { |
| if (hintsCallback) { |
| char seq[64]; |
| int color = -1, bold = 0; |
| char *hint; |
|
|
| if (pwidth + bufwidth >= l->cols) return; |
| hint = hintsCallback(l->buf,&color,&bold); |
| if (hint) { |
| size_t hintlen = strlen(hint); |
| size_t hintwidth = utf8StrWidth(hint, hintlen); |
| size_t hintmaxwidth = l->cols - (pwidth + bufwidth); |
| |
| if (hintwidth > hintmaxwidth) { |
| size_t i = 0, w = 0; |
| while (i < hintlen) { |
| size_t clen = utf8NextCharLen(hint, i, hintlen); |
| int cwidth = utf8SingleCharWidth(hint + i, clen); |
| if (w + cwidth > hintmaxwidth) break; |
| w += cwidth; |
| i += clen; |
| } |
| hintlen = i; |
| } |
| if (bold == 1 && color == -1) color = 37; |
| if (color != -1 || bold != 0) |
| snprintf(seq,64,"\033[%d;%d;49m",bold,color); |
| else |
| seq[0] = '\0'; |
| abAppend(ab,seq,strlen(seq)); |
| abAppend(ab,hint,hintlen); |
| if (color != -1 || bold != 0) |
| abAppend(ab,"\033[0m",4); |
| |
| if (freeHintsCallback) freeHintsCallback(hint); |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static void refreshSingleLine(struct linenoiseState *l, int flags) { |
| char seq[64]; |
| size_t pwidth = utf8StrWidth(l->prompt, l->plen); |
| int fd = l->ofd; |
| char *render = NULL; |
| char *buf; |
| size_t len; |
| size_t pos; |
| size_t poscol; |
| size_t lencol; |
| size_t fullwidth; |
| struct abuf ab; |
|
|
| if (linenoiseRenderBuffer(l,&render,&len,&pos) == -1) return; |
| buf = render; |
|
|
| |
| poscol = utf8StrWidth(buf, pos); |
| lencol = utf8StrWidth(buf, len); |
| fullwidth = lencol; |
|
|
| |
| |
| |
| while (pwidth + poscol >= l->cols) { |
| size_t clen = utf8NextCharLen(buf, 0, len); |
| int cwidth = utf8SingleCharWidth(buf, clen); |
| buf += clen; |
| len -= clen; |
| pos -= clen; |
| poscol -= cwidth; |
| lencol -= cwidth; |
| } |
|
|
| |
| while (pwidth + lencol > l->cols) { |
| size_t clen = utf8PrevCharLen(buf, len); |
| int cwidth = utf8SingleCharWidth(buf + len - clen, clen); |
| len -= clen; |
| lencol -= cwidth; |
| } |
|
|
| abInit(&ab); |
| |
| snprintf(seq,sizeof(seq),"\r"); |
| abAppend(&ab,seq,strlen(seq)); |
|
|
| if (flags & REFRESH_WRITE) { |
| |
| abAppend(&ab,l->prompt,l->plen); |
| if (maskmode == 1) { |
| |
| size_t i = 0; |
| while (i < len) { |
| abAppend(&ab,"*",1); |
| i += utf8NextCharLen(buf, i, len); |
| } |
| } else { |
| abAppend(&ab,buf,len); |
| } |
| |
| refreshShowHints(&ab,l,pwidth,fullwidth); |
| } |
|
|
| |
| snprintf(seq,sizeof(seq),"\x1b[0K"); |
| abAppend(&ab,seq,strlen(seq)); |
|
|
| if (flags & REFRESH_WRITE) { |
| |
| snprintf(seq,sizeof(seq),"\r\x1b[%dC", (int)(poscol+pwidth)); |
| abAppend(&ab,seq,strlen(seq)); |
| } |
|
|
| if (write(fd,ab.b,ab.len) == -1) {} |
| abFree(&ab); |
| free(render); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static void refreshMultiLine(struct linenoiseState *l, int flags) { |
| char seq[64]; |
| size_t pwidth = utf8StrWidth(l->prompt, l->plen); |
| char *render = NULL; |
| size_t render_len, render_pos; |
| size_t bufwidth; |
| size_t poswidth; |
| int rows; |
| int rpos = l->oldrpos; |
| int rpos2; |
| int layout_prompt_row = 0; |
| int col; |
| int old_rows = l->oldrows; |
| int old_status_rows = l->oldstatusrows; |
| int old_total_rows = old_rows + old_status_rows; |
| int fd = l->ofd, j; |
| struct abuf ab; |
|
|
| if (linenoiseRenderBuffer(l,&render,&render_len,&render_pos) == -1) return; |
| bufwidth = utf8StrWidth(render, render_len); |
| poswidth = utf8StrWidth(render, render_pos); |
| rows = (pwidth+bufwidth+l->cols-1)/l->cols; |
| int cursor_wrap_row = l->pos && |
| render_pos == render_len && |
| (poswidth+pwidth) % l->cols == 0; |
| if (cursor_wrap_row) rows++; |
|
|
| |
| |
| abInit(&ab); |
|
|
| if (flags & REFRESH_CLEAN) { |
| if (old_total_rows-rpos > 0) { |
| lndebug("go down %d", old_total_rows-rpos); |
| snprintf(seq,64,"\x1b[%dB", old_total_rows-rpos); |
| abAppend(&ab,seq,strlen(seq)); |
| } |
|
|
| |
| for (j = 0; j < old_total_rows-1; j++) { |
| lndebug("clear+up"); |
| snprintf(seq,64,"\r\x1b[0K\x1b[1A"); |
| abAppend(&ab,seq,strlen(seq)); |
| } |
|
|
| |
| |
| |
| |
| if (old_total_rows > 0) { |
| lndebug("clear top"); |
| snprintf(seq,64,"\r\x1b[0K"); |
| abAppend(&ab,seq,strlen(seq)); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| if ((flags & REFRESH_WRITE) && l->layout_callback) { |
| if (write(fd,ab.b,ab.len) == -1) {} |
| abFree(&ab); |
| abInit(&ab); |
| layout_prompt_row = |
| l->layout_callback(l, (size_t)rows, |
| (size_t)linenoiseStatusRows(l), |
| l->layout_privdata); |
| } |
|
|
| if (flags & REFRESH_ALL) { |
| |
| lndebug("clear"); |
| snprintf(seq,64,"\r\x1b[0K"); |
| abAppend(&ab,seq,strlen(seq)); |
| } |
|
|
| if (flags & REFRESH_WRITE) { |
| |
| abAppend(&ab,l->prompt,l->plen); |
| if (maskmode == 1) { |
| |
| size_t i = 0; |
| while (i < render_len) { |
| abAppend(&ab,"*",1); |
| i += utf8NextCharLen(render, i, render_len); |
| } |
| } else { |
| abAppend(&ab,render,render_len); |
| } |
|
|
| |
| refreshShowHints(&ab,l,pwidth,bufwidth); |
|
|
| |
| |
| if (cursor_wrap_row) { |
| lndebug("<newline>"); |
| abAppend(&ab,"\n",1); |
| snprintf(seq,64,"\r"); |
| abAppend(&ab,seq,strlen(seq)); |
| } |
|
|
| |
| rpos2 = (pwidth+poswidth+l->cols)/l->cols; |
| lndebug("rpos2 %d", rpos2); |
|
|
| if (linenoiseStatusActive(l)) { |
| refreshStatusLine(&ab, l); |
| } |
|
|
| |
| col = (pwidth+poswidth) % l->cols; |
| if (layout_prompt_row > 0) { |
| |
| |
| |
| |
| |
| l->screen_cursor_row = layout_prompt_row + rpos2 - 1; |
| l->screen_cursor_col = 1 + col; |
| snprintf(seq,64,"\x1b[%d;%dH", layout_prompt_row + rpos2 - 1, |
| 1 + col); |
| } else { |
| l->screen_cursor_row = 0; |
| l->screen_cursor_col = 0; |
| |
| int rows_below_cursor = rows - rpos2 + linenoiseStatusRows(l); |
| if (rows_below_cursor > 0) { |
| lndebug("go-up %d", rows_below_cursor); |
| snprintf(seq,64,"\x1b[%dA", rows_below_cursor); |
| abAppend(&ab,seq,strlen(seq)); |
| } |
|
|
| 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)); |
| } |
|
|
| lndebug("\n"); |
| l->oldpos = l->pos; |
| if (flags & REFRESH_WRITE) { |
| l->oldrows = rows; |
| l->oldstatusrows = (size_t)linenoiseStatusRows(l); |
| l->oldrpos = rpos2; |
| } else if (flags & REFRESH_CLEAN) { |
| l->oldrows = 0; |
| l->oldstatusrows = 0; |
| l->oldrpos = 1; |
| l->screen_cursor_row = 0; |
| l->screen_cursor_col = 0; |
| } |
|
|
| if (write(fd,ab.b,ab.len) == -1) {} |
| abFree(&ab); |
| free(render); |
| } |
|
|
| |
| |
| static void refreshLineWithFlags(struct linenoiseState *l, int flags) { |
| if (mlmode || linenoiseStatusActive(l) || l->oldstatusrows) |
| refreshMultiLine(l,flags); |
| else |
| refreshSingleLine(l,flags); |
| } |
|
|
| |
| static void refreshLine(struct linenoiseState *l) { |
| refreshLineWithFlags(l,REFRESH_ALL); |
| } |
|
|
| |
| void linenoiseHide(struct linenoiseState *l) { |
| refreshLineWithFlags(l,REFRESH_CLEAN); |
| } |
|
|
| |
| void linenoiseShow(struct linenoiseState *l) { |
| if (l->in_completion) { |
| refreshLineWithCompletion(l,NULL,REFRESH_WRITE); |
| } else { |
| refreshLineWithFlags(l,REFRESH_WRITE); |
| } |
| } |
|
|
| |
| |
| |
| void linenoiseEditClear(struct linenoiseState *l) { |
| l->buf[0] = '\0'; |
| l->len = 0; |
| l->pos = 0; |
| l->oldpos = 0; |
| l->in_completion = 0; |
| linenoiseFoldClear(l); |
| refreshLine(l); |
| } |
|
|
| |
| |
| |
| static int linenoiseEditGrow(struct linenoiseState *l, size_t needed) { |
| size_t newlen; |
| char *newbuf; |
|
|
| if (needed <= l->buflen) return 0; |
|
|
| |
| |
| if (l->buflen_max == 0 || needed > l->buflen_max) return -1; |
|
|
| |
| |
| newlen = l->buflen ? l->buflen : 16; |
| while (newlen < needed) { |
| if (newlen > l->buflen_max/2) { |
| newlen = l->buflen_max; |
| break; |
| } |
| newlen *= 2; |
| } |
| if (newlen < needed || newlen == SIZE_MAX) return -1; |
|
|
| |
| newbuf = realloc(l->buf,newlen+1); |
| if (newbuf == NULL) return -1; |
| l->buf = newbuf; |
| l->buflen = newlen; |
| return 0; |
| } |
|
|
| |
| |
| |
| static int linenoiseEditInsertNoRefresh(struct linenoiseState *l, const char *c, size_t clen) { |
| size_t insert_pos = l->pos; |
|
|
| if (clen > SIZE_MAX-l->len || linenoiseEditGrow(l,l->len+clen) == -1) |
| return -1; |
|
|
| if (l->len == l->pos) { |
| memcpy(l->buf+l->pos,c,clen); |
| } else { |
| memmove(l->buf+l->pos+clen,l->buf+l->pos,l->len-l->pos); |
| memcpy(l->buf+l->pos,c,clen); |
| } |
| l->pos += clen; |
| l->len += clen; |
| l->buf[l->len] = '\0'; |
| linenoiseAdjustFoldsAfterInsert(l,insert_pos,clen); |
| return 0; |
| } |
|
|
| |
| |
| |
| |
| int linenoiseEditInsert(struct linenoiseState *l, const char *c, size_t clen) { |
| if (l->len == l->pos) { |
| int needs_refresh = memchr(c, '\n', clen) != NULL || |
| memchr(c, '\r', clen) != NULL; |
|
|
| if (linenoiseEditInsertNoRefresh(l,c,clen) == -1) return 0; |
| if (!needs_refresh && !mlmode && !linenoiseStatusActive(l) && |
| !l->oldstatusrows && !hintsCallback && |
| (maskmode || l->fold_count == 0)) |
| { |
| size_t bufwidth = utf8StrWidth(l->buf,l->len); |
| if (utf8StrWidth(l->prompt,l->plen)+bufwidth < l->cols) { |
| |
| |
| if (maskmode == 1) { |
| if (write(l->ofd,"*",1) == -1) return -1; |
| } else { |
| if (write(l->ofd,c,clen) == -1) return -1; |
| } |
| return 0; |
| } |
| } |
| refreshLine(l); |
| } else { |
| if (linenoiseEditInsertNoRefresh(l,c,clen) == -1) return 0; |
| refreshLine(l); |
| } |
| return 0; |
| } |
|
|
| size_t linenoiseEditQueuedInput(struct linenoiseState *l) { |
| return l->queued_input_len - l->queued_input_pos; |
| } |
|
|
| int linenoiseEditQueueInput(struct linenoiseState *l, const char *buf, size_t len) { |
| if (len == 0) return 0; |
| if (l->queued_input_pos) { |
| size_t rem = l->queued_input_len - l->queued_input_pos; |
| memmove(l->queued_input, l->queued_input + l->queued_input_pos, rem); |
| l->queued_input_len = rem; |
| l->queued_input_pos = 0; |
| } |
| if (l->queued_input_len + len > l->queued_input_cap) { |
| size_t cap = l->queued_input_cap ? l->queued_input_cap * 2 : 128; |
| while (cap < l->queued_input_len + len) { |
| size_t next = cap * 2; |
| if (next <= cap) return -1; |
| cap = next; |
| } |
| char *p = realloc(l->queued_input, cap); |
| if (!p) return -1; |
| l->queued_input = p; |
| l->queued_input_cap = cap; |
| } |
| memcpy(l->queued_input + l->queued_input_len, buf, len); |
| l->queued_input_len += len; |
| return 0; |
| } |
|
|
| |
| |
| |
| |
| |
| int linenoiseEditSetStatus(struct linenoiseState *l, const char *status, |
| const char *start_escape, const char *end_escape) { |
| char *ns = NULL, *nstart = NULL, *nend = NULL; |
| if (status && status[0]) { |
| ns = strdup(status); |
| if (!ns) goto oom; |
| } |
| if (start_escape && start_escape[0]) { |
| nstart = strdup(start_escape); |
| if (!nstart) goto oom; |
| } |
| if (end_escape && end_escape[0]) { |
| nend = strdup(end_escape); |
| if (!nend) goto oom; |
| } |
|
|
| free(l->status); |
| free(l->status_start); |
| free(l->status_end); |
| l->status = ns; |
| l->status_start = nstart; |
| l->status_end = nend; |
| return 0; |
|
|
| oom: |
| free(ns); |
| free(nstart); |
| free(nend); |
| return -1; |
| } |
|
|
| void linenoiseEditSetLayoutCallback(struct linenoiseState *l, |
| linenoiseLayoutCallback *fn, |
| void *privdata) { |
| l->layout_callback = fn; |
| l->layout_privdata = privdata; |
| } |
|
|
| static int linenoiseReadByte(struct linenoiseState *l, char *c) { |
| if (l->queued_input_pos < l->queued_input_len) { |
| *c = l->queued_input[l->queued_input_pos++]; |
| if (l->queued_input_pos == l->queued_input_len) { |
| l->queued_input_pos = 0; |
| l->queued_input_len = 0; |
| } |
| return 1; |
| } |
| return (int)read(l->ifd, c, 1); |
| } |
|
|
| |
| void linenoiseEditMoveLeft(struct linenoiseState *l) { |
| if (l->pos > 0) { |
| l->pos -= linenoiseEditPrevLen(l, l->pos); |
| refreshLine(l); |
| } |
| } |
|
|
| |
| void linenoiseEditMoveRight(struct linenoiseState *l) { |
| if (l->pos != l->len) { |
| l->pos += linenoiseEditNextLen(l, l->pos); |
| refreshLine(l); |
| } |
| } |
|
|
| |
| void linenoiseEditMoveHome(struct linenoiseState *l) { |
| if (l->pos != 0) { |
| l->pos = 0; |
| refreshLine(l); |
| } |
| } |
|
|
| |
| void linenoiseEditMoveEnd(struct linenoiseState *l) { |
| if (l->pos != l->len) { |
| l->pos = l->len; |
| refreshLine(l); |
| } |
| } |
|
|
| |
| |
| #define LINENOISE_HISTORY_NEXT 0 |
| #define LINENOISE_HISTORY_PREV 1 |
| void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { |
| if (history_len > 1) { |
| const char *src; |
| size_t len; |
| struct linenoiseFold f; |
|
|
| |
| |
| free(history[history_len - 1 - l->history_index]); |
| history[history_len - 1 - l->history_index] = strdup(l->buf); |
| |
| l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; |
| if (l->history_index < 0) { |
| l->history_index = 0; |
| return; |
| } else if (l->history_index >= history_len) { |
| l->history_index = history_len-1; |
| return; |
| } |
|
|
| |
| |
| src = history[history_len - 1 - l->history_index]; |
| len = strlen(src); |
| if (linenoiseEditGrow(l,len) == -1 && len > l->buflen) |
| len = l->buflen; |
| memcpy(l->buf,src,len); |
| l->buf[len] = '\0'; |
| l->len = l->pos = len; |
| linenoiseFoldClear(l); |
|
|
| |
| |
| |
| if (linenoiseBuildHistoryFold(l,&f)) |
| linenoiseFoldAdd(l,f.start,f.end); |
| refreshLine(l); |
| } |
| } |
|
|
| |
| |
| |
| void linenoiseEditDelete(struct linenoiseState *l) { |
| if (l->len > 0 && l->pos < l->len) { |
| size_t clen = linenoiseEditNextLen(l, l->pos); |
| linenoiseAdjustFoldsAfterDelete(l,l->pos,clen); |
| memmove(l->buf+l->pos, l->buf+l->pos+clen, l->len-l->pos-clen); |
| l->len -= clen; |
| l->buf[l->len] = '\0'; |
| refreshLine(l); |
| } |
| } |
|
|
| |
| void linenoiseEditBackspace(struct linenoiseState *l) { |
| if (l->pos > 0 && l->len > 0) { |
| size_t clen = linenoiseEditPrevLen(l, l->pos); |
| linenoiseAdjustFoldsAfterDelete(l,l->pos-clen,clen); |
| memmove(l->buf+l->pos-clen, l->buf+l->pos, l->len-l->pos); |
| l->pos -= clen; |
| l->len -= clen; |
| l->buf[l->len] = '\0'; |
| refreshLine(l); |
| } |
| } |
|
|
| |
| |
| void linenoiseEditDeletePrevWord(struct linenoiseState *l) { |
| size_t old_pos = l->pos; |
| size_t diff; |
|
|
| |
| while (l->pos > 0 && l->buf[l->pos-1] == ' ') |
| l->pos -= linenoiseEditPrevLen(l, l->pos); |
| |
| while (l->pos > 0 && l->buf[l->pos-1] != ' ') |
| l->pos -= linenoiseEditPrevLen(l, l->pos); |
| diff = old_pos - l->pos; |
| linenoiseAdjustFoldsAfterDelete(l,l->pos,diff); |
| memmove(l->buf+l->pos, l->buf+old_pos, l->len-old_pos+1); |
| l->len -= diff; |
| refreshLine(l); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) { |
| |
| |
| l->in_completion = 0; |
| l->ifd = stdin_fd != -1 ? stdin_fd : STDIN_FILENO; |
| l->ofd = stdout_fd != -1 ? stdout_fd : STDOUT_FILENO; |
| l->buf = buf; |
| l->buflen = buflen; |
| l->buflen_max = 0; |
| l->prompt = prompt; |
| l->plen = strlen(prompt); |
| l->oldpos = l->pos = 0; |
| l->len = 0; |
| l->status = NULL; |
| l->status_start = NULL; |
| l->status_end = NULL; |
| l->layout_callback = NULL; |
| l->layout_privdata = NULL; |
| l->screen_cursor_row = 0; |
| l->screen_cursor_col = 0; |
| l->queued_input = NULL; |
| l->queued_input_len = 0; |
| l->queued_input_pos = 0; |
| l->queued_input_cap = 0; |
| linenoiseFoldClear(l); |
|
|
| |
| rawmode_output = l->ofd; |
| if (enableRawMode(l->ifd) == -1) return -1; |
|
|
| l->cols = getColumns(stdin_fd, stdout_fd); |
| l->oldrows = 0; |
| l->oldstatusrows = 0; |
| l->oldrpos = 1; |
| l->history_index = 0; |
|
|
| |
| l->buf[0] = '\0'; |
| l->buflen--; |
|
|
| |
| |
| |
| if (!isatty(l->ifd) && !getenv("LINENOISE_ASSUME_TTY")) return 0; |
|
|
| |
| |
| linenoiseHistoryAdd(""); |
|
|
| if (write(l->ofd,prompt,l->plen) == -1) return -1; |
| l->oldrows = 1; |
| return 0; |
| } |
|
|
| |
| |
| static int pasteBufferReserve(char **buf, size_t *cap, size_t len, size_t need) { |
| size_t want; |
| char *nb; |
|
|
| |
| |
| if (*cap >= len + need) return 0; |
|
|
| |
| |
| want = *cap ? *cap : 64; |
| while (want < len + need) { |
| size_t doubled = want*2; |
| if (doubled <= want || doubled > PASTE_MAX_BYTES) { |
| want = PASTE_MAX_BYTES; |
| break; |
| } |
| want = doubled; |
| } |
| if (want < len + need) return -1; |
|
|
| |
| nb = realloc(*buf, want); |
| if (nb == NULL) return -1; |
| *buf = nb; |
| *cap = want; |
| return 0; |
| } |
|
|
| |
| |
| static int pasteBufferAppend(struct linenoiseState *l, char **buf, size_t *cap, |
| size_t *len, const char *s, size_t slen, size_t maxlen) { |
| size_t needed; |
|
|
| if (*len > maxlen || slen > maxlen-*len) return -1; |
| if (*len > SIZE_MAX-slen) return -1; |
| needed = *len+slen; |
| if (l->len > SIZE_MAX-needed) return -1; |
| if (linenoiseEditGrow(l,l->len+needed) == -1) return -1; |
| if (pasteBufferReserve(buf,cap,*len,slen) == -1) return -1; |
| memcpy(*buf+*len,s,slen); |
| *len = needed; |
| return 0; |
| } |
|
|
| |
| |
| static void linenoiseEditPaste(struct linenoiseState *l) { |
| static const char END[] = "\x1b[201~"; |
| const size_t ENDLEN = sizeof(END)-1; |
| char *buf = NULL; |
| size_t cap = 0, len = 0, match = 0; |
| size_t maxlen = l->buflen_max ? l->buflen_max : l->buflen; |
| int overflowed = 0; |
|
|
| maxlen = maxlen > l->len ? maxlen - l->len : 0; |
| if (maxlen > PASTE_MAX_BYTES) maxlen = PASTE_MAX_BYTES; |
| |
| if (l->fold_count == LINENOISE_MAX_FOLDS) maxlen = 0; |
|
|
| while (1) { |
| char c; |
| if (linenoiseReadByte(l, &c) != 1) break; |
|
|
| |
| |
| |
| if (c == END[match]) { |
| match++; |
| if (match == ENDLEN) break; |
| continue; |
| } |
|
|
| if (match > 0) { |
| if (!overflowed && |
| pasteBufferAppend(l,&buf,&cap,&len,END,match,maxlen) == -1) |
| overflowed = 1; |
| match = 0; |
| if (c == END[0]) { |
| match = 1; |
| continue; |
| } |
| } |
|
|
| if (!overflowed && |
| pasteBufferAppend(l,&buf,&cap,&len,&c,1,maxlen) == -1) |
| overflowed = 1; |
| } |
|
|
| if (overflowed) { |
| free(buf); |
| linenoiseBeep(); |
| return; |
| } |
| if (buf == NULL) return; |
|
|
| { |
| |
| |
| size_t r = 0, w = 0; |
| while (r < len) { |
| if (buf[r] == '\r') { |
| buf[w++] = '\n'; |
| r += (r+1 < len && buf[r+1] == '\n') ? 2 : 1; |
| } else { |
| buf[w++] = buf[r++]; |
| } |
| } |
| len = w; |
| } |
|
|
| if (!maskmode && shouldFoldText(buf,len)) { |
| size_t start = l->pos; |
| if (linenoiseEditInsertNoRefresh(l,buf,len) == -1) { |
| free(buf); |
| linenoiseBeep(); |
| return; |
| } |
| linenoiseFoldAdd(l,start,start+len); |
| refreshLine(l); |
| } else { |
| linenoiseEditInsert(l,buf,len); |
| } |
| free(buf); |
| } |
|
|
| char *linenoiseEditMore = "If you see this, you are misusing the API: when linenoiseEditFeed() is called, if it returns linenoiseEditMore the user is yet editing the line. See the README file for more information."; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| char *linenoiseEditFeed(struct linenoiseState *l) { |
| |
| |
| if (!isatty(l->ifd) && !getenv("LINENOISE_ASSUME_TTY")) return linenoiseNoTTY(); |
|
|
| char c; |
| int nread; |
| char seq[3]; |
|
|
| nread = linenoiseReadByte(l, &c); |
| if (nread < 0) { |
| return (errno == EAGAIN || errno == EWOULDBLOCK) ? linenoiseEditMore : NULL; |
| } else if (nread == 0) { |
| return NULL; |
| } |
|
|
| |
| |
| |
| if ((l->in_completion || c == 9 ) && completionCallback != NULL) { |
| int retval = completeLine(l,c); |
| |
| if (retval == 0) return linenoiseEditMore; |
| c = retval; |
| } |
|
|
| switch(c) { |
| case ENTER: |
| history_len--; |
| free(history[history_len]); |
| if (mlmode) linenoiseEditMoveEnd(l); |
| if (hintsCallback) { |
| |
| |
| linenoiseHintsCallback *hc = hintsCallback; |
| hintsCallback = NULL; |
| refreshLine(l); |
| hintsCallback = hc; |
| } |
| return strdup(l->buf); |
| case CTRL_C: |
| errno = EAGAIN; |
| return NULL; |
| case BACKSPACE: |
| case 8: |
| linenoiseEditBackspace(l); |
| break; |
| case CTRL_D: |
| |
| if (l->len > 0) { |
| linenoiseEditDelete(l); |
| } else { |
| history_len--; |
| free(history[history_len]); |
| errno = ENOENT; |
| return NULL; |
| } |
| break; |
| case CTRL_T: |
| |
| if (l->pos > 0 && l->pos < l->len) { |
| char tmp[32]; |
| size_t prevlen = linenoiseEditPrevLen(l, l->pos); |
| size_t currlen = linenoiseEditNextLen(l, l->pos); |
| size_t prevstart = l->pos - prevlen; |
| if (prevlen > sizeof(tmp) || currlen > sizeof(tmp)) break; |
| if (linenoiseRangeOverlapsFold(l,prevstart,prevlen+currlen)) { |
| linenoiseBeep(); |
| break; |
| } |
| |
| memcpy(tmp, l->buf + l->pos, currlen); |
| memmove(l->buf + prevstart + currlen, l->buf + prevstart, prevlen); |
| memcpy(l->buf + prevstart, tmp, currlen); |
| if (l->pos + currlen <= l->len) l->pos += currlen; |
| refreshLine(l); |
| } |
| break; |
| case CTRL_B: |
| linenoiseEditMoveLeft(l); |
| break; |
| case CTRL_F: |
| linenoiseEditMoveRight(l); |
| break; |
| case CTRL_P: |
| linenoiseEditHistoryNext(l, LINENOISE_HISTORY_PREV); |
| break; |
| case CTRL_N: |
| linenoiseEditHistoryNext(l, LINENOISE_HISTORY_NEXT); |
| break; |
| case ESC: |
| |
| |
| |
| if (linenoiseReadByte(l, seq) != 1) break; |
| if (linenoiseReadByte(l, seq + 1) != 1) break; |
|
|
| |
| if (seq[0] == '[') { |
| if (seq[1] >= '0' && seq[1] <= '9') { |
| char param[8]; |
| size_t plen = 1; |
| char final = 0; |
|
|
| param[0] = seq[1]; |
| while (plen < sizeof(param)) { |
| char p; |
| if (linenoiseReadByte(l, &p) != 1) break; |
| if (p >= '0' && p <= '9') { |
| param[plen++] = p; |
| } else { |
| final = p; |
| break; |
| } |
| } |
| if (final == '~') { |
| if (plen == 1 && param[0] == '3') { |
| linenoiseEditDelete(l); |
| } else if (plen == 3 && memcmp(param,"200",3) == 0) { |
| linenoiseEditPaste(l); |
| } |
| } |
| } else { |
| switch(seq[1]) { |
| case 'A': |
| linenoiseEditHistoryNext(l, LINENOISE_HISTORY_PREV); |
| break; |
| case 'B': |
| linenoiseEditHistoryNext(l, LINENOISE_HISTORY_NEXT); |
| break; |
| case 'C': |
| linenoiseEditMoveRight(l); |
| break; |
| case 'D': |
| linenoiseEditMoveLeft(l); |
| break; |
| case 'H': |
| linenoiseEditMoveHome(l); |
| break; |
| case 'F': |
| linenoiseEditMoveEnd(l); |
| break; |
| } |
| } |
| } |
|
|
| |
| else if (seq[0] == 'O') { |
| switch(seq[1]) { |
| case 'H': |
| linenoiseEditMoveHome(l); |
| break; |
| case 'F': |
| linenoiseEditMoveEnd(l); |
| break; |
| } |
| } |
| break; |
| default: |
| |
| |
| |
| { |
| char utf8[4]; |
| int utf8len = utf8ByteLen(c); |
| utf8[0] = c; |
| if (utf8len > 1) { |
| |
| int i; |
| for (i = 1; i < utf8len; i++) { |
| if (linenoiseReadByte(l, utf8+i) != 1) break; |
| } |
| } |
| if (linenoiseEditInsert(l, utf8, utf8len)) return NULL; |
| } |
| break; |
| case CTRL_U: |
| l->buf[0] = '\0'; |
| l->pos = l->len = 0; |
| linenoiseFoldClear(l); |
| refreshLine(l); |
| break; |
| case CTRL_K: |
| linenoiseAdjustFoldsAfterDelete(l,l->pos,l->len-l->pos); |
| l->buf[l->pos] = '\0'; |
| l->len = l->pos; |
| refreshLine(l); |
| break; |
| case CTRL_A: |
| linenoiseEditMoveHome(l); |
| break; |
| case CTRL_E: |
| linenoiseEditMoveEnd(l); |
| break; |
| case CTRL_L: |
| linenoiseClearScreen(); |
| refreshLine(l); |
| break; |
| case CTRL_W: |
| linenoiseEditDeletePrevWord(l); |
| break; |
| } |
| return linenoiseEditMore; |
| } |
|
|
| char *linenoiseEditFeedByte(struct linenoiseState *l, char c) { |
| if (linenoiseEditQueueInput(l, &c, 1) == -1) { |
| errno = ENOMEM; |
| return NULL; |
| } |
| return linenoiseEditFeed(l); |
| } |
|
|
| |
| |
| |
| |
| void linenoiseEditStop(struct linenoiseState *l) { |
| int had_status = l->oldstatusrows != 0; |
| int had_prompt = l->oldrows != 0 || l->oldstatusrows != 0; |
| if ((isatty(l->ifd) || getenv("LINENOISE_ASSUME_TTY")) && had_status) { |
| char seq[64]; |
| int down = (int)(l->oldrows + l->oldstatusrows) - l->oldrpos; |
| if (down > 0) { |
| int n = snprintf(seq, sizeof(seq), "\x1b[%dB", down); |
| if (n > 0 && (size_t)n < sizeof(seq)) { |
| if (write(l->ofd, seq, (size_t)n) == -1) {} |
| } |
| } |
| if (write(l->ofd, "\r\x1b[0K", 5) == -1) {} |
| } |
| free(l->queued_input); |
| l->queued_input = NULL; |
| l->queued_input_len = 0; |
| l->queued_input_pos = 0; |
| l->queued_input_cap = 0; |
| free(l->status); |
| free(l->status_start); |
| free(l->status_end); |
| l->status = NULL; |
| l->status_start = NULL; |
| l->status_end = NULL; |
| l->oldstatusrows = 0; |
| if (!isatty(l->ifd) && !getenv("LINENOISE_ASSUME_TTY")) return; |
| disableRawMode(l->ifd); |
| if (!had_status && had_prompt) printf("\n"); |
| } |
|
|
| |
| |
| |
| |
| |
| static char *linenoiseBlockingEdit(int stdin_fd, int stdout_fd, const char *prompt) |
| { |
| struct linenoiseState l; |
| char *buf = malloc(LINENOISE_INITIAL_BUFLEN); |
| char *res; |
|
|
| if (buf == NULL) { |
| errno = ENOMEM; |
| return NULL; |
| } |
|
|
| if (linenoiseEditStart(&l,stdin_fd,stdout_fd,buf, |
| LINENOISE_INITIAL_BUFLEN,prompt) == -1) |
| { |
| free(buf); |
| return NULL; |
| } |
| l.buflen_max = LINENOISE_MAX_LINE; |
| while((res = linenoiseEditFeed(&l)) == linenoiseEditMore); |
| linenoiseEditStop(&l); |
| free(l.buf); |
| return res; |
| } |
|
|
| |
| |
| |
| 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"); |
| rawmode_output = STDOUT_FILENO; |
| 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); |
| quit[sizeof(quit)-1] = c; |
| 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"); |
| fflush(stdout); |
| } |
| disableRawMode(STDIN_FILENO); |
| } |
|
|
| |
| |
| static char *linenoiseReadLine(FILE *fp, int *err) { |
| char *line = NULL; |
| size_t len = 0, cap = 0; |
|
|
| if (err) *err = 0; |
|
|
| while(1) { |
| if (len+1 >= cap) { |
| size_t newcap = cap ? cap*2 : 16; |
| char *oldval = line; |
| if (newcap <= cap) { |
| free(line); |
| if (err) *err = 1; |
| errno = ENOMEM; |
| return NULL; |
| } |
| line = realloc(line,newcap); |
| if (line == NULL) { |
| if (oldval) free(oldval); |
| if (err) *err = 1; |
| return NULL; |
| } |
| cap = newcap; |
| } |
| int c = fgetc(fp); |
| 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++; |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| static char *linenoiseNoTTY(void) { |
| return linenoiseReadLine(stdin,NULL); |
| } |
|
|
| |
| |
| |
| |
| |
| char *linenoise(const char *prompt) { |
| if (!isatty(STDIN_FILENO) && !getenv("LINENOISE_ASSUME_TTY")) { |
| |
| |
| return linenoiseNoTTY(); |
| } |
|
|
| if (isUnsupportedTerm()) { |
| char *retval; |
| size_t len; |
|
|
| printf("%s",prompt); |
| fflush(stdout); |
| retval = linenoiseNoTTY(); |
| if (retval == NULL) return NULL; |
| len = strlen(retval); |
| while(len && retval[len-1] == '\r') { |
| len--; |
| retval[len] = '\0'; |
| } |
| return retval; |
| } else { |
| return linenoiseBlockingEdit(STDIN_FILENO,STDOUT_FILENO,prompt); |
| } |
| } |
|
|
| |
| |
| |
| |
| void linenoiseFree(void *ptr) { |
| if (ptr == linenoiseEditMore) return; |
| free(ptr); |
| } |
|
|
| |
|
|
| |
| |
| static void freeHistory(void) { |
| if (history) { |
| int j; |
|
|
| for (j = 0; j < history_len; j++) |
| free(history[j]); |
| free(history); |
| } |
| } |
|
|
| |
| static void linenoiseAtExit(void) { |
| disableRawMode(STDIN_FILENO); |
| freeHistory(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| int linenoiseHistoryAdd(const char *line) { |
| char *linecopy; |
|
|
| if (history_max_len == 0) return 0; |
|
|
| |
| if (history == NULL) { |
| history = malloc(sizeof(char*)*history_max_len); |
| if (history == NULL) return 0; |
| memset(history,0,(sizeof(char*)*history_max_len)); |
| } |
|
|
| |
| if (history_len && !strcmp(history[history_len-1], line)) return 0; |
|
|
| |
| |
| linecopy = strdup(line); |
| if (!linecopy) return 0; |
| if (history_len == history_max_len) { |
| free(history[0]); |
| 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) { |
| int j; |
|
|
| for (j = 0; j < tocopy-len; j++) free(history[j]); |
| tocopy = len; |
| } |
| memset(new,0,sizeof(char*)*len); |
| memcpy(new,history+(history_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; |
| } |
|
|
| |
| |
| int linenoiseHistorySave(const char *filename) { |
| mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); |
| FILE *fp; |
| int j; |
|
|
| fp = fopen(filename,"w"); |
| umask(old_umask); |
| if (fp == NULL) return -1; |
| fchmod(fileno(fp),S_IRUSR|S_IWUSR); |
| for (j = 0; j < history_len; j++) { |
| char *p = history[j]; |
| |
| |
| while (*p) { |
| fputc(*p == '\n' ? '\r' : *p, fp); |
| p++; |
| } |
| fputc('\n', fp); |
| } |
| fclose(fp); |
| return 0; |
| } |
|
|
| |
| |
| |
| |
| |
| int linenoiseHistoryLoad(const char *filename) { |
| FILE *fp = fopen(filename,"r"); |
| char *buf; |
| int err = 0; |
|
|
| if (fp == NULL) return -1; |
|
|
| while ((buf = linenoiseReadLine(fp,&err)) != NULL) { |
| size_t j; |
|
|
| |
| for (j = 0; buf[j]; j++) { |
| if (buf[j] == '\r') buf[j] = '\n'; |
| } |
| linenoiseHistoryAdd(buf); |
| free(buf); |
| } |
| if (err || ferror(fp)) { |
| fclose(fp); |
| return -1; |
| } |
| fclose(fp); |
| return 0; |
| } |
|
|