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.
39 * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
40 * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
43 * - Switch to gets() if $TERM is something we can't support.
44 * - Filter bogus Ctrl+<char> combinations.
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)
58 * Effect: moves cursor to column n
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)
68 * Effect: moves cursor forward of n chars
70 * Changes by Ketmar // Vampire Avalon:
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
91 #include <sys/types.h>
92 #include <sys/ioctl.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};
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;
138 static int enableRawMode (int fd
) {
141 if (!isatty(STDIN_FILENO
)) goto fatal
;
143 atexit(linenoiseAtExit
);
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
;
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) {
188 if (ioctl(1, TIOCGWINSZ
, &ws
) == -1) return 80;
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; }
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;
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;
218 case ';': ++ignore
; break;
219 case '\\': ignore
+= 2; break;
220 case '"': inStr
= 2; ++ignore
; 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
) {
231 //size_t plen = strlen(prompt);
234 if (linenoiseOptHilightBrackets
&& pos
< len
&& strchr("()[]", buf
[pos
])) {
236 if (brcBuf
[pos
].levelP
|| brcBuf
[pos
].levelB
) {
241 for (++h
; h
< len
; ++h
) if (brcBuf
[h
].levelP
== brcBuf
[pos
].levelP
) break;
244 for (++h
; h
< len
; ++h
) if (brcBuf
[h
].levelB
== brcBuf
[pos
].levelB
) break;
247 for (--h
; h
>= 0; --h
) if (brcBuf
[h
].levelP
== brcBuf
[pos
].levelP
) break;
250 for (--h
; h
>= 0; --h
) if (brcBuf
[h
].levelB
== brcBuf
[pos
].levelB
) break;
253 hipos
= h
<0?len
:(size_t)h
;
257 while (plen
+pos
>= cols
) {
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;
270 /* rewrite this to output string in three parts */
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;
279 if (write(fd
, buf
, 1) == -1) return -1;
283 } else if (write(fd
, buf
, len
) == -1) return -1;
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;
294 static void autoCompletion (char *buf
, size_t *posp
, size_t *lenp
, size_t cols
) {
295 if (linenoiseACHook
) {
297 int ipos
= (int)(*posp
), ilen
= (int)(*lenp
);
299 linenoiseACHook(buf
, &ipos
, ilen
, cols
);
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
) {
313 if (pos
<= 0) return;
314 if (buf
[pos
-1] == ')') --level
;
318 case ')': ++level
; break;
319 case '(': if (--level
< 1) goto done
; break;
327 static void leftWord (const char *buf
, size_t *posp
, size_t *lenp
) {
330 if (pos
<= 0) return;
331 if ((unsigned char)(buf
[pos
-1]) <= ' ') {
333 while (pos
> 0) if ((unsigned char)(buf
[pos
-1]) <= ' ') --pos
; else break;
336 if (isalnum(buf
[pos
-1])) {
338 while (pos
> 0) if (isalnum(buf
[pos
-1])) --pos
; else break;
347 static void rightSExpr (const char *buf
, size_t *posp
, size_t *lenp
) {
352 if (pos
>= len
) return;
353 if (buf
[pos
+1] == '(') --level
;
357 case '\0': goto done
;
358 case '(': ++level
; break;
361 if (pos
< len
) ++pos
;
372 static void rightWord (const char *buf
, size_t *posp
, size_t *lenp
) {
376 if (pos
>= len
) return;
377 if (isalnum(buf
[pos
])) {
379 while (pos
< len
) if (isalnum(buf
[pos
])) ++pos
; else break;
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;
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
;
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
;
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
);
433 static void doWordWork (char *buf
, size_t *posp
, size_t *lenp
, int action
) {
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;
441 while (buf
[pos
] && isalnum(buf
[pos
])) {
442 buf
[pos
] = (action
!= 2) ? toupper(buf
[pos
]) : tolower(buf
[pos
]);
443 if (action
== 3) action
= 2;
448 static size_t prompt_len (const char *prompt
, size_t *nlen
) {
449 const char *p
= prompt
;
457 while (*q
&& isdigit(*q
)) {
460 *nlen
+= (uintptr_t)(++q
) - (uintptr_t)p
;
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;
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 */
482 int historyIndex
= 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;
495 if (refreshLine(fd
, prompt
, plen
, buf
, len
, pos
, cols
)) return len
;
496 nread
= read(fd
, &c
, 1);
497 if (nread
<= 0) return len
;
501 refreshLine(fd
, prompt
, plen
, buf
, len
, pos
, cols
);
502 free(history
[--historyLen
]);
506 refreshLine(fd
, prompt
, plen
, buf
, len
, pos
, cols
);
507 free(history
[--historyLen
]);
508 return (len
== 0) ? -1 : (int)len
;
511 refreshLine(fd
, prompt
, plen
, buf
, len
, pos
, cols
);
512 free(history
[--historyLen
]);
515 case 127: /* backspace */
517 if (pos
> 0 && len
> 0) {
518 memmove(buf
+pos
-1, buf
+pos
, len
-pos
);
523 case 20: /* ctrl-t */
524 if (pos
> 0 && pos
< len
) {
525 int aux
= buf
[pos
-1];
526 buf
[pos
-1] = buf
[pos
];
528 if (pos
!= len
-1) ++pos
;
535 if (pos
!= len
) ++pos
;
537 case 16: /* ctrl-p */
540 case 14: /* ctrl-n */
543 case 27: /* escape sequence */
544 if (read(fd
, &c
, 1) == -1) break;
546 case '[': /* special char */
547 if (read(fd
, &c
, 1) == -1) break;
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) {
560 } else if (historyIndex
>= historyLen
) {
561 historyIndex
= historyLen
-1;
564 strncpy(buf
, history
[historyLen
-1-historyIndex
], buflen
);
566 len
= pos
= strlen(buf
);
569 case 'C': /* right arrow */
570 if (pos
!= len
) ++pos
;
572 case 'D': /* left arrow */
575 case '1': /* Fx, mods+arrows, etc */
576 case '2': /* here we wants Ctrl+Ins */
578 if (read(fd
, &c
, 1) == -1) break;
581 if (read(fd
, &c
, 1) == -1) break;
583 int modT
= 0; /* bit 0: alt; bit 1: ctrl; bit 2: shift */
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;
593 case '1': /* arrows */
595 case 'C': /* right */
596 ((modT
== 1) ? rightSExpr
: rightWord
)(buf
, &pos
, &len
);
599 ((modT
== 1) ? leftSExpr
: leftWord
)(buf
, &pos
, &len
);
606 insertText(yankBuf
, buf
, &pos
, &len
);
612 if (c
== ';') goto skip_special
;
613 } else if (isdigit(c
) || c
== ';') goto skip_special
;
616 do { if (read(fd
, &c
, 1) == -1) break; } while (isdigit(c
) || c
== ';');
618 memmove(buf
+pos
, buf
+pos
+1, len
-pos
);
623 if (read(fd
, &c
, 1) == -1) break;
624 if (c
== '~') pos
= 0;
625 else if (isdigit(c
) || c
== ';') goto skip_special
;
628 if (read(fd
, &c
, 1) == -1) break;
629 if (c
== '~') pos
= len
;
630 else if (isdigit(c
) || c
== ';') goto skip_special
;
632 case '0': case '4': case '5': case '6': case '9':
634 skip_special
: do { if (read(fd
, &c
, 1) == -1) break; } while (isdigit(c
) || c
== ';');
638 case '\x7f': /* alt+BS */
639 /* delete previous sexpr */
640 yankIt(leftSExpr
, buf
, &pos
, &len
);
643 doWordWork(buf
, &pos
, &len
, 1);
646 doWordWork(buf
, &pos
, &len
, 2);
649 doWordWork(buf
, &pos
, &len
, 3);
652 /* delete next word */
653 yankIt(rightWord
, buf
, &pos
, &len
);
656 /* delete next sexpr */
657 yankIt(rightSExpr
, buf
, &pos
, &len
);
660 /* delete the whole line. */
661 strcpy(yankBuf
, buf
);
668 autoCompletion(buf
, &pos
, &len
, cols
);
670 case 11: /* Ctrl+k, delete from current to end of line. */
671 strcpy(yankBuf
, buf
+pos
);
675 case '\x17': /* Ctrl+w */
676 /* delete previous word */
677 yankIt(leftWord
, buf
, &pos
, &len
);
679 case '\x13': /* Ctrl+s */
680 /* delete previous sexpr */
681 yankIt(leftSExpr
, buf
, &pos
, &len
);
683 case 25: /* Ctrl+y, yank */
684 insertText(yankBuf
, buf
, &pos
, &len
);
686 case 1: /* Ctrl+a, go to the start of the line */
689 case 5: /* Ctrl+e, go to the end of the line */
693 if ((unsigned char)c
>= 32) {
696 insertText(seq
, buf
, &pos
, &len
);
701 free(history
[--historyLen
]);
706 static int linenoiseRaw (char *buf
, const char *prompt
, const char *defval
) {
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
);
718 char *linenoise (const char *prompt
, const char *defval
) {
719 static char buf
[LINENOISE_MAX_LINE_LEN
];
721 if (isUnsupportedTerm() && !isatty(STDIN_FILENO
)) {
724 fprintf(stdout
, "%s", prompt
); fflush(stdout
);
725 if (defval
&& strlen(defval
) < LINENOISE_MAX_LINE_LEN
) {
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';
733 if (linenoiseRaw(buf
, prompt
, defval
) < 0) return NULL
;
739 /* Using a circular buffer is smarter, but a bit more complex to handle. */
740 static int linenoiseHistoryAddInternal (const char *line
, int noProcessing
) {
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
));
750 // find duplicate, move it if found
751 for (int f
= 0; f
< historyLen
-1; ++f
) {
752 if (strcmp(line
, history
[f
]) == 0) {
754 char *tmp
= history
[f
];
756 for (++f
; f
< historyLen
; ++f
) history
[f
-1] = history
[f
];
757 history
[historyLen
-1] = tmp
;
762 linecopy
= strdup(line
);
763 if (linecopy
== NULL
) return -1;
764 if (historyLen
== historyMaxLen
) {
765 memmove(history
, history
+1, sizeof(char *)*(historyMaxLen
-1));
768 history
[historyLen
++] = linecopy
;
773 int linenoiseHistoryAdd (const char *line
) {
774 if (line
!= NULL
) return linenoiseHistoryAddInternal(line
, 0);
779 int linenoiseHistorySetMaxLen (int len
) {
782 if (len
< 1) return -1;
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
);
794 if (historyLen
> historyMaxLen
) historyLen
= historyMaxLen
;
799 void linenoiseClearHistory (void) {
800 if (history
!= NULL
) {
801 for (int f
= historyLen
-1; f
>= 0; --f
) if (history
[f
] != NULL
) free(history
[f
]);
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;
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
);
834 int linenoiseHistorySaveFile (const char *fname
) {
836 FILE *fl
= fopen(fname
, "w");
839 res
= linenoiseHistorySave(fl
);
840 if (fclose(fl
) < 0) res
= -1;
845 int linenoiseHistoryLoadFile (const char *fname
) {
847 FILE *fl
= fopen(fname
, "r");
850 res
= linenoiseHistoryLoad(fl
);
851 if (fclose(fl
) < 0) res
= -1;