fix
[libpgclient.git] / util / linenoise.c
blob2856560925423b21b622ede672448023c6468f66
1 /* linenoise.c -- guerrilla line editing library against the idea that a
2 * line editing lib needs to be 20,000 lines of C code.
4 * You can find the latest source code at:
6 * http://github.com/antirez/linenoise
8 * Does a number of crazy assumptions that happen to be true in 99.9999% of
9 * the 2010 UNIX computers around.
11 * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
12 * All rights reserved.
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions are met:
17 * * Redistributions of source code must retain the above copyright notice,
18 * this list of conditions and the following disclaimer.
19 * * Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 * * Neither the name of Redis nor the names of its contributors may be used
23 * to endorse or promote products derived from this software without
24 * specific prior written permission.
26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
30 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
38 * References:
39 * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
40 * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
42 * Todo list:
43 * - Switch to gets() if $TERM is something we can't support.
44 * - Filter bogus Ctrl+<char> combinations.
45 * - Win32 support
47 * Bloat:
48 * - Completion?
49 * - History search like Ctrl+r in readline?
51 * List of escape sequences used by this program, we do everything just
52 * with three sequences. In order to be so cheap we may have some
53 * flickering effect with some slow terminal, but the lesser sequences
54 * the more compatible.
56 * CHA (Cursor Horizontal Absolute)
57 * Sequence: ESC [ n G
58 * Effect: moves cursor to column n
60 * EL (Erase Line)
61 * Sequence: ESC [ n K
62 * Effect: if n is 0 or missing, clear from cursor to end of line
63 * Effect: if n is 1, clear from beginning of line to cursor
64 * Effect: if n is 2, clear entire line
66 * CUF (CUrsor Forward)
67 * Sequence: ESC [ n C
68 * Effect: moves cursor forward of n chars
70 * Changes by Ketmar // Vampire Avalon:
71 * [*] reformatted
72 * [+] ignore unknown control codes
73 * [+] understands DEL, HOME, END in my mrxvt
74 * [+] Alt+w, Alt+BS: delete previous s-expression
75 * [+] ^w: delete previous word (shitty for now)
76 * [+] autocomplete hook
77 * [+] save/load history
78 * [+] LISP shit
79 * [+] yanking
80 * [+] many more shit
82 #include <ctype.h>
83 #include <termios.h>
84 #include <unistd.h>
85 #include <stdlib.h>
86 #include <stdio.h>
87 #include <errno.h>
88 #include <string.h>
89 #include <stdlib.h>
90 #include <stdint.h>
91 #include <sys/types.h>
92 #include <sys/ioctl.h>
93 #include <unistd.h>
95 #include "linenoise.h"
98 ////////////////////////////////////////////////////////////////////////////////
99 static char *idioticTerms[] = {"dumb", "cons25", NULL};
102 ////////////////////////////////////////////////////////////////////////////////
103 static struct termios origTIOS; /* in order to restore at exit */
104 static int rawmode = 0; /* for atexit() function to check if restore is needed*/
105 static int atexitInited = 0; /* register atexit just 1 time */
106 static int historyMaxLen = 256;
107 static int historyLen = 0;
108 linenoiseACHookFn linenoiseACHook = NULL;
109 int linenoiseOptHilightBrackets = 1;
110 const char *linenoiseBrcHiStr = "\x1b[7m";
111 char **history = NULL;
114 ////////////////////////////////////////////////////////////////////////////////
115 static char yankBuf[LINENOISE_MAX_LINE_LEN] = {0};
116 typedef struct {
117 int levelP;
118 int levelB;
119 } BrcInfo;
120 static BrcInfo brcBuf[LINENOISE_MAX_LINE_LEN];
123 ////////////////////////////////////////////////////////////////////////////////
124 static void linenoiseAtExit (void);
125 static int linenoiseHistoryAddInternal (const char *line, int noProcessing);
128 ////////////////////////////////////////////////////////////////////////////////
129 static int isUnsupportedTerm (void) {
130 const char *term = getenv("TERM");
132 if (term == NULL) return 0;
133 for (int f = 0; idioticTerms[f]; ++f) if (!strcasecmp(term, idioticTerms[f])) return 1;
134 return 0;
138 static int enableRawMode (int fd) {
139 struct termios raw;
141 if (!isatty(STDIN_FILENO)) goto fatal;
142 if (!atexitInited) {
143 atexit(linenoiseAtExit);
144 atexitInited = 1;
146 if (tcgetattr(fd, &origTIOS) == -1) goto fatal;
147 raw = origTIOS; /* modify the original mode */
148 /* input modes: no break, no CR to NL, no parity check, no strip char,
149 * no start/stop output control. */
150 raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
151 /* output modes - disable post processing */
152 raw.c_oflag &= ~(OPOST);
153 /* control modes - set 8 bit chars */
154 raw.c_cflag |= (CS8);
155 /* local modes - choing off, canonical off, no extended functions,
156 * no signal chars (^Z,^C) */
157 raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
158 /* control chars - set return condition: min number of bytes and timer;
159 * we want read to return every single byte, without timeout */
160 raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
161 /* put terminal in raw mode after flushing */
162 if (tcsetattr(fd, TCSAFLUSH, &raw) < 0) goto fatal;
163 rawmode = 1;
164 return 0;
165 fatal:
166 errno = ENOTTY;
167 return -1;
171 static void disableRawMode (int fd) {
172 /* don't even check the return value as it's too late */
173 if (rawmode && tcsetattr(fd, TCSAFLUSH, &origTIOS) != -1) rawmode = 0;
177 ////////////////////////////////////////////////////////////////////////////////
178 /* at exit we'll try to fix the terminal to the initial conditions */
179 static void linenoiseAtExit (void) {
180 disableRawMode(STDIN_FILENO);
181 linenoiseClearHistory();
185 static int getColumns (void) {
186 struct winsize ws;
188 if (ioctl(1, TIOCGWINSZ, &ws) == -1) return 80;
189 return ws.ws_col;
193 ////////////////////////////////////////////////////////////////////////////////
194 static void buildBrcBuf (const char *buf) {
195 int levelP = 0, levelB = 0, inStr = 0, ignore = 0;
197 for (int f = 0; buf[f]; ++f) {
198 brcBuf[f].levelP = 0;
199 brcBuf[f].levelB = 0;
200 if (ignore) { --ignore; continue; }
201 if (inStr) {
202 if (inStr == 1 && buf[f] == '\\' && buf[f+1] == '"') ++ignore;
203 else if (buf[f] == '"') {
204 if (inStr == 1) inStr = 0;
205 else if (buf[f+1] != '"') inStr = 0;
206 else ++ignore;
208 } else {
209 switch (buf[f]) {
210 case '"': inStr = 1; break;
211 case '(': brcBuf[f].levelP = ++levelP; break;
212 case ')': brcBuf[f].levelP = levelP--; break;
213 case '[': brcBuf[f].levelB = ++levelB; break;
214 case ']': brcBuf[f].levelB = levelB--; break;
215 case ';': for (; buf[f]; ++f) brcBuf[f].levelP = brcBuf[f].levelB = 0; return;
216 case '#':
217 switch (buf[f+1]) {
218 case ';': ++ignore; break;
219 case '\\': ignore += 2; break;
220 case '"': inStr = 2; ++ignore; break;
222 break;
229 static int refreshLine (int fd, const char *prompt, size_t plen, const char *buf, size_t len, size_t pos, size_t cols) {
230 char seq[64];
231 //size_t plen = strlen(prompt);
232 size_t hipos = len;
234 if (linenoiseOptHilightBrackets && pos < len && strchr("()[]", buf[pos])) {
235 buildBrcBuf(buf);
236 if (brcBuf[pos].levelP || brcBuf[pos].levelB) {
237 int h = (int)pos;
239 switch (buf[pos]) {
240 case '(':
241 for (++h; h < len; ++h) if (brcBuf[h].levelP == brcBuf[pos].levelP) break;
242 break;
243 case '[':
244 for (++h; h < len; ++h) if (brcBuf[h].levelB == brcBuf[pos].levelB) break;
245 break;
246 case ')':
247 for (--h; h >= 0; --h) if (brcBuf[h].levelP == brcBuf[pos].levelP) break;
248 break;
249 case ']':
250 for (--h; h >= 0; --h) if (brcBuf[h].levelB == brcBuf[pos].levelB) break;
251 break;
253 hipos = h<0?len:(size_t)h;
257 while (plen+pos >= cols) {
258 ++buf;
259 --len;
260 --pos;
261 if (hipos > 0) --hipos; else hipos = len;
263 while (plen+len > cols) --len;
264 /* cursor to left edge */
265 snprintf(seq, 64, linenoiseOptHilightBrackets?"\x1b[0m\x1b[0G":"\x1b[0G");
266 if (write(fd, seq, strlen(seq)) == -1) return -1;
267 /* write the prompt and the current buffer content */
268 if (write(fd, prompt, strlen(prompt)) == -1) return -1;
269 if (hipos < len) {
270 /* rewrite this to output string in three parts */
271 while (*buf) {
272 if (!hipos) {
273 hipos = len+1;
274 if (write(fd, linenoiseBrcHiStr, strlen(linenoiseBrcHiStr)) == -1) return -1;
275 if (write(fd, buf, 1) == -1) return -1;
276 if (write(fd, "\x1b[0m", 4) == -1) return -1;
277 } else {
278 --hipos;
279 if (write(fd, buf, 1) == -1) return -1;
281 ++buf;
283 } else if (write(fd, buf, len) == -1) return -1;
284 /* erase to right */
285 snprintf(seq, 64, "\x1b[0K");
286 if (write(fd, seq, strlen(seq)) == -1) return -1;
287 /* move cursor to original position */
288 snprintf(seq, 64, "\x1b[0G\x1b[%dC", (int)(pos+plen));
289 if (write(fd, seq, strlen(seq)) == -1) return -1;
290 return 0;
294 static void autoCompletion (char *buf, size_t *posp, size_t *lenp, size_t cols) {
295 if (linenoiseACHook) {
296 //HACK!
297 int ipos = (int)(*posp), ilen = (int)(*lenp);
299 linenoiseACHook(buf, &ipos, ilen, cols);
300 ilen = strlen(buf);
301 if (ipos < 0) posp = 0; else if (ipos > ilen) ipos = ilen;
302 *posp = (size_t)ipos;
303 *lenp = (size_t)ilen;
308 /* move to the start of just finished sexpr */
309 static void leftSExpr (const char *buf, size_t *posp, size_t *lenp) {
310 size_t pos = *posp;
311 int level = 1;
313 if (pos <= 0) return;
314 if (buf[pos-1] == ')') --level;
315 while (pos > 0) {
316 --pos;
317 switch (buf[pos]) {
318 case ')': ++level; break;
319 case '(': if (--level < 1) goto done; break;
322 done:
323 *posp = pos;
327 static void leftWord (const char *buf, size_t *posp, size_t *lenp) {
328 size_t pos = *posp;
330 if (pos <= 0) return;
331 if ((unsigned char)(buf[pos-1]) <= ' ') {
332 /* spaces */
333 while (pos > 0) if ((unsigned char)(buf[pos-1]) <= ' ') --pos; else break;
335 if (pos > 0) {
336 if (isalnum(buf[pos-1])) {
337 /* delete word */
338 while (pos > 0) if (isalnum(buf[pos-1])) --pos; else break;
339 } else {
340 --pos;
343 *posp = pos;
347 static void rightSExpr (const char *buf, size_t *posp, size_t *lenp) {
348 size_t pos = *posp;
349 size_t len = *lenp;
350 int level = 1;
352 if (pos >= len) return;
353 if (buf[pos+1] == '(') --level;
354 while (pos < len) {
355 ++pos;
356 switch (buf[pos]) {
357 case '\0': goto done;
358 case '(': ++level; break;
359 case ')':
360 if (--level < 1) {
361 if (pos < len) ++pos;
362 goto done;
364 break;
367 done:
368 *posp = pos;
372 static void rightWord (const char *buf, size_t *posp, size_t *lenp) {
373 size_t pos = *posp;
374 size_t len = *lenp;
376 if (pos >= len) return;
377 if (isalnum(buf[pos])) {
378 /* skip word */
379 while (pos < len) if (isalnum(buf[pos])) ++pos; else break;
380 } else {
381 ++pos;
383 if (pos < len && (unsigned char)(buf[pos]) <= ' ') {
384 /* spaces; go to the beginning of the word */
385 while (pos < len) if ((unsigned char)(buf[pos]) <= ' ') ++pos; else break;
387 *posp = pos;
391 typedef void (*MoveFn) (const char *buf, size_t *posp, size_t *lenp);
393 static void yankIt (MoveFn fn, char *buf, size_t *posp, size_t *lenp) {
394 size_t opos = *posp, len = *lenp, pos;
396 fn(buf, posp, lenp);
397 pos = *posp;
398 if (pos > opos) {
399 size_t tmp = pos;
400 pos = opos;
401 opos = tmp;
403 if (pos < opos) {
404 memcpy(yankBuf, buf+pos, opos-pos);
405 yankBuf[opos-pos] = '\0';
406 memmove(buf+pos, buf+opos, len-opos+1);
407 len = strlen(buf); /* i'm soooo lazy */
409 if (pos > len) pos = len;
410 *posp = pos;
411 *lenp = len;
415 static void insertText (const char *text, char *buf, size_t *posp, size_t *lenp) {
416 size_t pos = *posp, len = *lenp;
418 if (text == NULL) return;
419 for (int f = 0; text[f] && len < LINENOISE_MAX_LINE_LEN-1; ++f, ++len, ++pos) {
420 if (pos < len) memmove(buf+pos+1, buf+pos, len-pos);
421 buf[pos] = text[f];
423 buf[len] = '\0';
424 *posp = pos;
425 *lenp = len;
429 /* action:
430 * 1: upcase
431 * 2: downcase
432 * 3: capitalize */
433 static void doWordWork (char *buf, size_t *posp, size_t *lenp, int action) {
434 size_t pos = *posp;
436 if (!buf[pos]) return;
437 if (!isalnum(buf[pos])) return; /* not in the word */
438 /* find word start */
439 while (pos > 0) if (isalnum(buf[pos-1])) --pos; else break;
440 /* process word */
441 while (buf[pos] && isalnum(buf[pos])) {
442 buf[pos] = (action != 2) ? toupper(buf[pos]) : tolower(buf[pos]);
443 if (action == 3) action = 2;
444 ++pos;
448 static size_t prompt_len (const char *prompt, size_t *nlen) {
449 const char *p = prompt;
450 size_t len = 0;
451 *nlen = 0;
452 while (*p) {
453 if (*p == 27) {
454 const char *q = p+1;
455 ++len;
456 if (*q++ == '[') {
457 while (*q && isdigit(*q)) {
458 ++q; ++len;
460 *nlen += (uintptr_t)(++q) - (uintptr_t)p;
461 p = q;
462 len += 2;
463 continue;
465 ++len;
467 ++p;
468 ++len;
470 return len;
473 #define clear() printf("\033[H\033[J")
474 #define gotoxy(x,y) printf("\033[%d;%dH", (x), (y))
476 static int linenoisePrompt (int fd, char *buf, const char *prompt, const char *defval) {
477 //size_t plen = strlen(prompt), pos = 0, len = 0, cols = getColumns(), tmp_len;
478 size_t nlen = 0;
479 size_t plen = prompt_len(prompt, &nlen), pos = 0, len = 0, cols = getColumns(), tmp_len;
480 size_t buflen = LINENOISE_MAX_LINE_LEN-1; /* make sure there is always space for the nulterm */
481 plen -= nlen;
482 int historyIndex = 0;
484 buf[0] = '\0';
485 if (defval && (tmp_len = strlen(defval) < LINENOISE_MAX_LINE_LEN-1)) {
486 insertText(defval, buf, &pos, &len);
488 /* the latest history entry is always our current buffer, that initially is just an empty string */
489 linenoiseHistoryAdd("");
490 if (write(fd, prompt, plen) == -1) return -1;
491 for (;;) {
492 char c, seq[2];
493 int nread;
495 if (refreshLine(fd, prompt, plen, buf, len, pos, cols)) return len;
496 nread = read(fd, &c, 1);
497 if (nread <= 0) return len;
498 switch (c) {
499 case 13: /* enter */
500 pos = len;
501 refreshLine(fd, prompt, plen, buf, len, pos, cols);
502 free(history[--historyLen]);
503 return len;
504 case 4: /* ctrl-d */
505 pos = len;
506 refreshLine(fd, prompt, plen, buf, len, pos, cols);
507 free(history[--historyLen]);
508 return (len == 0) ? -1 : (int)len;
509 case 3: /* ctrl-c */
510 pos = len;
511 refreshLine(fd, prompt, plen, buf, len, pos, cols);
512 free(history[--historyLen]);
513 errno = EAGAIN;
514 return -1;
515 case 127: /* backspace */
516 case 8: /* ctrl-h */
517 if (pos > 0 && len > 0) {
518 memmove(buf+pos-1, buf+pos, len-pos);
519 --pos;
520 buf[--len] = '\0';
522 break;
523 case 20: /* ctrl-t */
524 if (pos > 0 && pos < len) {
525 int aux = buf[pos-1];
526 buf[pos-1] = buf[pos];
527 buf[pos] = aux;
528 if (pos != len-1) ++pos;
530 break;
531 case 2: /* ctrl-b */
532 if (pos > 0) --pos;
533 break;
534 case 6: /* ctrl-f */
535 if (pos != len) ++pos;
536 break;
537 case 16: /* ctrl-p */
538 c = 'A';
539 goto up_down_arrow;
540 case 14: /* ctrl-n */
541 c = 'B';
542 goto up_down_arrow;
543 case 27: /* escape sequence */
544 if (read(fd, &c, 1) == -1) break;
545 switch (c) {
546 case '[': /* special char */
547 if (read(fd, &c, 1) == -1) break;
548 switch (c) {
549 case 'A': case 'B': /* up and down arrow: history */
550 up_down_arrow: if (historyLen > 1) {
551 /* Update the current history entry before to
552 * overwrite it with tne next one. */
553 free(history[historyLen-1-historyIndex]);
554 history[historyLen-1-historyIndex] = strdup(buf);
555 /* Show the new entry */
556 historyIndex += (c == 65) ? 1 : -1;
557 if (historyIndex < 0) {
558 historyIndex = 0;
559 break;
560 } else if (historyIndex >= historyLen) {
561 historyIndex = historyLen-1;
562 break;
564 strncpy(buf, history[historyLen-1-historyIndex], buflen);
565 buf[buflen] = '\0';
566 len = pos = strlen(buf);
568 break;
569 case 'C': /* right arrow */
570 if (pos != len) ++pos;
571 break;
572 case 'D': /* left arrow */
573 if (pos > 0) --pos;
574 break;
575 case '1': /* Fx, mods+arrows, etc */
576 case '2': /* here we wants Ctrl+Ins */
577 seq[0] = c;
578 if (read(fd, &c, 1) == -1) break;
579 if (c == ';') {
580 /* with mods */
581 if (read(fd, &c, 1) == -1) break;
582 if (isdigit(c)) {
583 int modT = 0; /* bit 0: alt; bit 1: ctrl; bit 2: shift */
584 switch (c) {
585 /*case '2': modT = 0x04; break;*/
586 case '3': modT = 0x01; break;
587 /*case '4': modT = 0x05; break;*/
588 case '5': modT = 0x02; break;
589 default: goto skip_special;
591 if (read(fd, &c, 1) == -1) break;
592 switch (seq[0]) {
593 case '1': /* arrows */
594 switch (c) {
595 case 'C': /* right */
596 ((modT == 1) ? rightSExpr : rightWord)(buf, &pos, &len);
597 break;
598 case 'D': /* left */
599 ((modT == 1) ? leftSExpr : leftWord)(buf, &pos, &len);
600 break;
602 break;
603 case '2': /* ins */
604 switch (c) {
605 case '~':
606 insertText(yankBuf, buf, &pos, &len);
607 break;
609 break;
612 if (c == ';') goto skip_special;
613 } else if (isdigit(c) || c == ';') goto skip_special;
614 break;
615 case '3': /* DEL */
616 do { if (read(fd, &c, 1) == -1) break; } while (isdigit(c) || c == ';');
617 if (pos < len) {
618 memmove(buf+pos, buf+pos+1, len-pos);
619 buf[--len] = '\0';
621 break;
622 case '7': /* HOME */
623 if (read(fd, &c, 1) == -1) break;
624 if (c == '~') pos = 0;
625 else if (isdigit(c) || c == ';') goto skip_special;
626 break;
627 case '8': /* END */
628 if (read(fd, &c, 1) == -1) break;
629 if (c == '~') pos = len;
630 else if (isdigit(c) || c == ';') goto skip_special;
631 break;
632 case '0': case '4': case '5': case '6': case '9':
633 /* skip special */
634 skip_special: do { if (read(fd, &c, 1) == -1) break; } while (isdigit(c) || c == ';');
635 break;
637 break;
638 case '\x7f': /* alt+BS */
639 /* delete previous sexpr */
640 yankIt(leftSExpr, buf, &pos, &len);
641 break;
642 case 'u': case 'U':
643 doWordWork(buf, &pos, &len, 1);
644 break;
645 case 'l': case 'L':
646 doWordWork(buf, &pos, &len, 2);
647 break;
648 case 'c': case 'C':
649 doWordWork(buf, &pos, &len, 3);
650 break;
651 case 'd': case 'D':
652 /* delete next word */
653 yankIt(rightWord, buf, &pos, &len);
654 break;
655 case 's': case 'S':
656 /* delete next sexpr */
657 yankIt(rightSExpr, buf, &pos, &len);
658 break;
659 case 'n': case 'N':
660 /* delete the whole line. */
661 strcpy(yankBuf, buf);
662 buf[0] = '\0';
663 pos = len = 0;
664 break;
666 break;
667 case '\t':
668 autoCompletion(buf, &pos, &len, cols);
669 break;
670 case 11: /* Ctrl+k, delete from current to end of line. */
671 strcpy(yankBuf, buf+pos);
672 buf[pos] = '\0';
673 len = pos;
674 break;
675 case '\x17': /* Ctrl+w */
676 /* delete previous word */
677 yankIt(leftWord, buf, &pos, &len);
678 break;
679 case '\x13': /* Ctrl+s */
680 /* delete previous sexpr */
681 yankIt(leftSExpr, buf, &pos, &len);
682 break;
683 case 25: /* Ctrl+y, yank */
684 insertText(yankBuf, buf, &pos, &len);
685 break;
686 case 1: /* Ctrl+a, go to the start of the line */
687 pos = 0;
688 break;
689 case 5: /* Ctrl+e, go to the end of the line */
690 pos = len;
691 break;
692 default:
693 if ((unsigned char)c >= 32) {
694 seq[0] = c;
695 seq[1] = '\0';
696 insertText(seq, buf, &pos, &len);
698 break;
701 free(history[--historyLen]);
702 return len;
706 static int linenoiseRaw (char *buf, const char *prompt, const char *defval) {
707 int count;
709 /* if (buflen == 0) { errno = EINVAL; return -1; } */
710 if (enableRawMode(STDIN_FILENO) == -1) return -1;
711 count = linenoisePrompt(STDIN_FILENO, buf, prompt, defval);
712 disableRawMode(STDIN_FILENO);
713 fprintf(stdout, "\n"); fflush(stdout);
714 return count;
718 char *linenoise (const char *prompt, const char *defval) {
719 static char buf[LINENOISE_MAX_LINE_LEN];
721 if (isUnsupportedTerm() && !isatty(STDIN_FILENO)) {
722 int count;
724 fprintf(stdout, "%s", prompt); fflush(stdout);
725 if (defval && strlen(defval) < LINENOISE_MAX_LINE_LEN) {
726 strcpy(buf, defval);
727 fprintf(stdout, "%s", buf); fflush(stdout);
729 if (fgets(buf, LINENOISE_MAX_LINE_LEN, stdin) == NULL) return NULL;
730 count = strlen(buf)-1;
731 while (count >= 0 && (buf[count] == '\n' || buf[count] == '\r')) buf[count--] = '\0';
732 } else {
733 if (linenoiseRaw(buf, prompt, defval) < 0) return NULL;
735 return buf;
739 /* Using a circular buffer is smarter, but a bit more complex to handle. */
740 static int linenoiseHistoryAddInternal (const char *line, int noProcessing) {
741 char *linecopy;
743 if (historyMaxLen <= 0) return -1;
744 if (history == NULL) {
745 history = malloc(sizeof(char *)*historyMaxLen);
746 if (history == NULL) return -1;
747 memset(history, 0, (sizeof(char *)*historyMaxLen));
749 if (!noProcessing) {
750 // find duplicate, move it if found
751 for (int f = 0; f < historyLen-1; ++f) {
752 if (strcmp(line, history[f]) == 0) {
753 // got it!
754 char *tmp = history[f];
756 for (++f; f < historyLen; ++f) history[f-1] = history[f];
757 history[historyLen-1] = tmp;
758 return 0;
762 linecopy = strdup(line);
763 if (linecopy == NULL) return -1;
764 if (historyLen == historyMaxLen) {
765 memmove(history, history+1, sizeof(char *)*(historyMaxLen-1));
766 --historyLen;
768 history[historyLen++] = linecopy;
769 return 0;
773 int linenoiseHistoryAdd (const char *line) {
774 if (line != NULL) return linenoiseHistoryAddInternal(line, 0);
775 return -1;
779 int linenoiseHistorySetMaxLen (int len) {
780 char **new;
782 if (len < 1) return -1;
783 if (history) {
784 int tocopy = historyLen;
786 new = malloc(sizeof(char *)*len);
787 if (new == NULL) return -1;
788 if (len < tocopy) tocopy = len;
789 memcpy(new, history+(historyMaxLen-tocopy), sizeof(char *)*tocopy);
790 free(history);
791 history = new;
793 historyMaxLen = len;
794 if (historyLen > historyMaxLen) historyLen = historyMaxLen;
795 return 0;
799 void linenoiseClearHistory (void) {
800 if (history != NULL) {
801 for (int f = historyLen-1; f >= 0; --f) if (history[f] != NULL) free(history[f]);
802 historyLen = 0;
803 free(history);
804 history = NULL;
809 int linenoiseHistorySave (FILE *fl) {
810 if (!history) return 0;
811 for (int f = 0; f < historyLen; ++f) {
812 if (history[f] != NULL) {
813 if (fprintf(fl, "%s\n", history[f]) < 0) return -1;
816 return 0;
820 int linenoiseHistoryLoad (FILE *fl) {
821 char *line = malloc((LINENOISE_MAX_LINE_LEN+16)*sizeof(char));
823 if (!line) return -1;
824 linenoiseClearHistory();
825 while (fgets(line, LINENOISE_MAX_LINE_LEN+1, fl)) {
826 while (line[0] && (strchr("\n\r", line[strlen(line)-1]) != NULL)) line[strlen(line)-1] = '\0';
827 linenoiseHistoryAdd(line);
829 free(line);
830 return 0;
834 int linenoiseHistorySaveFile (const char *fname) {
835 int res;
836 FILE *fl = fopen(fname, "w");
838 if (!fl) return -1;
839 res = linenoiseHistorySave(fl);
840 if (fclose(fl) < 0) res = -1;
841 return res;
845 int linenoiseHistoryLoadFile (const char *fname) {
846 int res;
847 FILE *fl = fopen(fname, "r");
849 if (!fl) return -1;
850 res = linenoiseHistoryLoad(fl);
851 if (fclose(fl) < 0) res = -1;
852 return res;