1 /** \file   linenoise.c
2  *
3  * \brief   uerrilla line editing library against the idea that a
4  *          line editing lib needs to be 20,000 lines of C code.
5  *
6  * Modified for the VICE project by Fabrizio Gennari,
7  * for use in combination with a terminal
8  * not represented by a couple of file descrptors
9  * (abstracted buy the opaque struct console_private_s)
10  *
11  * You can find the latest source code at:
12  *
13  *   http://github.com/antirez/linenoise
14  *
15  * Does a number of crazy assumptions that happen to be true in 99.9999% of
16  * the 2010 UNIX computers around.
17  *
18  * ------------------------------------------------------------------------
19  *
20  * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
21  * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
22  *
23  * All rights reserved.
24  *
25  * Redistribution and use in source and binary forms, with or without
26  * modification, are permitted provided that the following conditions are
27  * met:
28  *
29  *  *  Redistributions of source code must retain the above copyright
30  *     notice, this list of conditions and the following disclaimer.
31  *
32  *  *  Redistributions in binary form must reproduce the above copyright
33  *     notice, this list of conditions and the following disclaimer in the
34  *     documentation and/or other materials provided with the distribution.
35  *
36  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
37  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
38  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
39  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
40  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
43  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
45  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
46  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47  *
48  * ------------------------------------------------------------------------
49  *
50  * References:
51  * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
52  * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
53  *
54  * Todo list:
55  * - Switch to gets() if $TERM is something we can't support.
56  * - Filter bogus Ctrl+<char> combinations.
57  * - Win32 support
58  *
59  * Bloat:
60  * - Completion?
61  * - History search like Ctrl+r in readline?
62  *
63  * List of escape sequences used by this program, we do everything just
64  * with three sequences. In order to be so cheap we may have some
65  * flickering effect with some slow terminal, but the lesser sequences
66  * the more compatible.
67  *
68  * CHA (Cursor Horizontal Absolute)
69  *    Sequence: ESC [ n G
70  *    Effect: moves cursor to column n
71  *
72  * EL (Erase Line)
73  *    Sequence: ESC [ n K
74  *    Effect: if n is 0 or missing, clear from cursor to end of line
75  *    Effect: if n is 1, clear from beginning of line to cursor
76  *    Effect: if n is 2, clear entire line
77  *
78  * CUF (CUrsor Forward)
79  *    Sequence: ESC [ n C
80  *    Effect: moves cursor forward of n chars
81  *
82  * The following are used to clear the screen: ESC [ H ESC [ 2 J
83  * This is actually composed of two sequences:
84  *
85  * cursorhome
86  *    Sequence: ESC [ H
87  *    Effect: moves the cursor to upper left corner
88  *
89  * ED2 (Clear entire screen)
90  *    Sequence: ESC [ 2 J
91  *    Effect: clear the whole screen
92  *
93  */
94 
95 #include "vice.h"
96 
97 #include <unistd.h>
98 #include <stdlib.h>
99 #include <stdio.h>
100 #include <errno.h>
101 #include <string.h>
102 #include <stdlib.h>
103 #include <sys/types.h>
104 #include <unistd.h>
105 #include <gtk/gtk.h> /* for gtk_main_iteration() */
106 
107 #include "linenoise.h"
108 #include "uimon.h"
109 
110 #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
111 #define LINENOISE_MAX_LINE 4096
112 static linenoiseCompletionCallback *completionCallback = NULL;
113 
114 /* static struct termios orig_termios; */ /* in order to restore at exit */
115 /* static int rawmode = 0; */ /* for atexit() function to check if restore is needed*/
116 /* static int atexit_registered = 0; */ /* register atexit just 1 time */
117 static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
118 static int history_len = 0;
119 char **history = NULL;
120 
121 /* static void linenoiseAtExit(void); */
122 int linenoiseHistoryAdd(const char *line);
123 
124 /* FIXME: unused -> memory leak
125 static void freeHistory(void) {
126     if (history) {
127         int j;
128 
129         for (j = 0; j < history_len; j++) {
130             free(history[j]);
131         }
132         free(history);
133     }
134 }
135 */
136 
137 /* At exit we'll try to fix the terminal to the initial conditions. */
138 /*
139 static void linenoiseAtExit(void) {
140     freeHistory();
141 }
142 */
143 
144 
refreshLine(struct console_private_s * term,const char * prompt,char * buf,size_t len,size_t pos,size_t cols)145 static void refreshLine(struct console_private_s *term, const char *prompt, char *buf, size_t len, size_t pos, size_t cols) {
146     char seq[64];
147     size_t plen = strlen(prompt);
148 
149     while((plen+pos) >= cols) {
150         buf++;
151         len--;
152         pos--;
153     }
154     while (plen+len > cols) {
155         len--;
156     }
157     /* Cursor to left edge */
158     snprintf(seq, 64, "\x1b[0G");
159     uimon_write_to_terminal(term, seq, strlen(seq));
160     /* Write the prompt and the current buffer content */
161     uimon_write_to_terminal(term, prompt, strlen(prompt));
162     uimon_write_to_terminal(term, buf, len);
163     /* Erase to right */
164     snprintf(seq, 64, "\x1b[0K");
165     uimon_write_to_terminal(term, seq, strlen(seq));
166     /* Move cursor to original position. */
167     snprintf(seq, 64, "\x1b[0G\x1b[%dC", (int)(pos+plen));
168     uimon_write_to_terminal(term, seq, strlen(seq));
169 }
170 
beep(struct console_private_s * term)171 static void beep(struct console_private_s *term) {
172     const char beepsequence[] = "\x7";
173     uimon_write_to_terminal(term, beepsequence, strlen(beepsequence));
174 }
175 
freeCompletions(linenoiseCompletions * lc)176 static void freeCompletions(linenoiseCompletions *lc) {
177     size_t i;
178     for (i = 0; i < lc->len; i++) {
179         free(lc->cvec[i]);
180     }
181     if (lc->cvec != NULL) {
182         free(lc->cvec);
183     }
184 }
185 
completeLine(struct console_private_s * term,const char * prompt,char * buf,size_t buflen,size_t * len,size_t * pos,size_t cols)186 static int completeLine(struct console_private_s *term, const char *prompt, char *buf, size_t buflen, size_t *len, size_t *pos, size_t cols) {
187     linenoiseCompletions lc = { 0, NULL };
188     int nread, nwritten;
189     char c = 0;
190 
191     completionCallback(buf,&lc);
192     if (lc.len == 0) {
193         beep(term);
194     } else {
195         size_t stop = 0, i = 0;
196         size_t clen;
197 
198         while(!stop) {
199             /* Show completion or original buffer */
200             if (i < lc.len) {
201                 clen = strlen(lc.cvec[i]);
202                 refreshLine(term,prompt,lc.cvec[i],clen,clen,cols);
203             } else {
204                 refreshLine(term,prompt,buf,*len,*pos,cols);
205             }
206 
207             nread = uimon_get_string(term,&c,1);
208             if (nread <= 0) {
209                 freeCompletions(&lc);
210                 return -1;
211             }
212 
213             switch(c) {
214                 case 9: /* tab */
215                     i = (i+1) % (lc.len+1);
216                     if (i == lc.len) {
217                         beep(term);
218                     }
219                     break;
220                 case 27: /* escape */
221                     /* Re-show original buffer */
222                     if (i < lc.len) {
223                         refreshLine(term,prompt,buf,*len,*pos,cols);
224                     }
225                     stop = 1;
226                     break;
227                 default:
228                     /* Update buffer and return */
229                     if (i < lc.len) {
230                         nwritten = snprintf(buf,buflen,"%s",lc.cvec[i]);
231                         *len = *pos = nwritten;
232                     }
233                     stop = 1;
234                     break;
235             }
236         }
237     }
238 
239     freeCompletions(&lc);
240     return c; /* Return last read character */
241 }
242 
linenoiseClearScreen(struct console_private_s * term)243 void linenoiseClearScreen(struct console_private_s *term) {
244     const char clearseq[] = "\x1b[H\x1b[2J";
245     uimon_write_to_terminal(term, clearseq, strlen(clearseq));
246 }
247 
linenoisePrompt(struct console_private_s * term,char * buf,size_t buflen,const char * prompt)248 static int linenoisePrompt(struct console_private_s *term, char *buf, size_t buflen, const char *prompt) {
249     size_t plen = strlen(prompt);
250     size_t pos = 0;
251     size_t len = 0;
252     size_t cols = uimon_get_columns(term);
253     int history_index = 0;
254     int i;
255 
256     buf[0] = '\0';
257     buflen--; /* Make sure there is always space for the nulterm */
258 
259     /* The latest history entry is always our current buffer, that
260      * initially is just an empty string. */
261     linenoiseHistoryAdd("");
262 
263     /* HACK HACK HACK
264 
265        what we really want to do here is writing the prompt, and then tell VTE
266        to flush its buffers and redraw its terminal. (necessary to make the
267        initial prompt show up reliably, else it may be delayed until a key is
268        pressed, which is confusing and annoying) unfortunately there seems to be
269        no distinct way to do this, however.
270 
271        the following loop seems to do the trick (using about 10 iterations, so
272        i am using 20 to be on the safe side). yes its ugly :(
273     */
274     for(i = 0; i < 20; i++) {
275         uimon_write_to_terminal(term, "\r", 1);
276         uimon_write_to_terminal(term, prompt, plen);
277         gtk_main_iteration();
278     }
279 
280     while(1) {
281         int c;
282         int nread;
283         char seq[2], seq2[2];
284         char tmp[1];
285 
286         nread = uimon_get_string(term, tmp, 1);
287         if (nread <= 0) {
288             return -1;
289         }
290         c = tmp[0];
291 
292         /* Only autocomplete when the callback is set. It returns < 0 when
293          * there was an error reading from fd. Otherwise it will return the
294          * character that should be handled next. */
295         if (c == 9 && completionCallback != NULL) {
296             c = completeLine(term, prompt, buf, buflen, &len, &pos, cols);
297             /* Return on errors */
298             if (c < 0) {
299                 return len;
300             }
301             /* Read next character when 0 */
302             if (c == 0) {
303                 continue;
304             }
305         }
306 
307         switch(c) {
308         case 13:    /* enter */
309             history_len--;
310             free(history[history_len]);
311             return (int)len;
312         case 3:     /* ctrl-c */
313             errno = EAGAIN;
314             return -1;
315         case 127:   /* backspace */
316         case 8:     /* ctrl-h */
317             if (pos > 0 && len > 0) {
318                 memmove(buf+pos-1, buf+pos, len-pos);
319                 pos--;
320                 len--;
321                 buf[len] = '\0';
322                 refreshLine(term, prompt, buf, len, pos, cols);
323             }
324             break;
325         case 4:     /* ctrl-d, remove char at right of cursor */
326             if (len > 1 && pos < (len-1)) {
327                 memmove(buf+pos, buf+pos+1, len-pos);
328                 len--;
329                 buf[len] = '\0';
330                 refreshLine(term, prompt, buf, len, pos, cols);
331             } else if (len == 0) {
332                 history_len--;
333                 free(history[history_len]);
334                 return -1;
335             }
336             break;
337         case 20:    /* ctrl-t */
338             if (pos > 0 && pos < len) {
339                 int aux = buf[pos-1];
340                 buf[pos-1] = buf[pos];
341                 buf[pos] = aux;
342                 if (pos != len-1) pos++;
343                 refreshLine(term, prompt, buf, len, pos, cols);
344             }
345             break;
346         case 2:     /* ctrl-b */
347             goto left_arrow;
348         case 6:     /* ctrl-f */
349             goto right_arrow;
350         case 16:    /* ctrl-p */
351             seq[1] = 65;
352             goto up_down_arrow;
353         case 14:    /* ctrl-n */
354             seq[1] = 66;
355             goto up_down_arrow;
356             break;
357         case 27:    /* escape sequence */
358             if (uimon_get_string(term,seq,2) == -1) {
359                 break;
360             }
361             if (seq[0] == 91 && seq[1] == 68) {
362 left_arrow:
363                 /* left arrow */
364                 if (pos > 0) {
365                     pos--;
366                     refreshLine(term, prompt, buf, len, pos, cols);
367                 }
368             } else if (seq[0] == 91 && seq[1] == 67) {
369 right_arrow:
370                 /* right arrow */
371                 if (pos != len) {
372                     pos++;
373                     refreshLine(term, prompt, buf, len, pos, cols);
374                 }
375             } else if (seq[0] == 91 && (seq[1] == 65 || seq[1] == 66)) {
376 up_down_arrow:
377                 /* up and down arrow: history */
378                 if (history_len > 1) {
379                     /* Update the current history entry before to
380                      * overwrite it with tne next one. */
381                     free(history[history_len-1-history_index]);
382                     history[history_len-1-history_index] = strdup(buf);
383                     /* Show the new entry */
384                     history_index += (seq[1] == 65) ? 1 : -1;
385                     if (history_index < 0) {
386                         history_index = 0;
387                         break;
388                     } else if (history_index >= history_len) {
389                         history_index = history_len-1;
390                         break;
391                     }
392                     strncpy(buf, history[history_len-1-history_index], buflen);
393                     buf[buflen] = '\0';
394                     len = pos = strlen(buf);
395                     refreshLine(term, prompt, buf, len, pos, cols);
396                 }
397             } else if (seq[0] == 91 && seq[1] > 48 && seq[1] < 55) {
398                 /* extended escape */
399                 if (uimon_get_string(term,seq2,2) == -1) {
400                     break;
401                 }
402                 if (seq[1] == 51 && seq2[0] == 126) {
403                     /* delete */
404                     if (len > 0 && pos < len) {
405                         memmove(buf+pos, buf+pos+1, len-pos-1);
406                         len--;
407                         buf[len] = '\0';
408                         refreshLine(term, prompt, buf, len, pos,cols);
409                     }
410                 }
411             }
412             break;
413         default:
414             if (len < buflen) {
415                 if (len == pos) {
416                     buf[pos] = c;
417                     pos++;
418                     len++;
419                     buf[len] = '\0';
420                     refreshLine(term, prompt, buf, len, pos, cols);
421                 } else {
422                     memmove(buf+pos+1, buf+pos, len-pos);
423                     buf[pos] = c;
424                     len++;
425                     pos++;
426                     buf[len] = '\0';
427                     refreshLine(term, prompt, buf, len, pos, cols);
428                 }
429             }
430             break;
431         case 21: /* Ctrl+u, delete the whole line. */
432             buf[0] = '\0';
433             pos = len = 0;
434             refreshLine(term, prompt, buf, len, pos, cols);
435             break;
436         case 11: /* Ctrl+k, delete from current to end of line. */
437             buf[pos] = '\0';
438             len = pos;
439             refreshLine(term, prompt, buf, len, pos, cols);
440             break;
441         case 1: /* Ctrl+a, go to the start of the line */
442             pos = 0;
443             refreshLine(term, prompt, buf, len, pos, cols);
444             break;
445         case 5: /* ctrl+e, go to the end of the line */
446             pos = len;
447             refreshLine(term, prompt, buf, len, pos, cols);
448             break;
449         case 12: /* ctrl+l, clear screen */
450             linenoiseClearScreen(term);
451             refreshLine(term, prompt, buf, len, pos, cols);
452         }
453     }
454 }
455 
linenoise(const char * prompt,struct console_private_s * term)456 char *linenoise(const char *prompt, struct console_private_s *term) {
457     char buf[LINENOISE_MAX_LINE];
458     int count;
459 
460     count = linenoisePrompt(term, buf, LINENOISE_MAX_LINE, prompt);
461     uimon_write_to_terminal(term, "\r\n", 2);
462     if (count == -1) {
463         return NULL;
464     }
465     return strdup(buf);
466 }
467 
468 /* Register a callback function to be called for tab-completion. */
linenoiseSetCompletionCallback(linenoiseCompletionCallback * fn)469 void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
470     completionCallback = fn;
471 }
472 
linenoiseAddCompletion(linenoiseCompletions * lc,char * str)473 void linenoiseAddCompletion(linenoiseCompletions *lc, char *str) {
474     size_t len = strlen(str);
475     char *copy = malloc(len+1);
476     memcpy(copy,str,len+1);
477     lc->cvec = realloc(lc->cvec, sizeof(char*)*(lc->len+1));
478     lc->cvec[lc->len++] = copy;
479 }
480 
481 /* Using a circular buffer is smarter, but a bit more complex to handle. */
linenoiseHistoryAdd(const char * line)482 int linenoiseHistoryAdd(const char *line) {
483     char *linecopy;
484 
485     if (history_max_len == 0) {
486         return 0;
487     }
488     if (history == NULL) {
489         history = malloc(sizeof(char*)*history_max_len);
490         if (history == NULL) {
491             return 0;
492         }
493         memset(history, 0, (sizeof(char*)*history_max_len));
494     }
495     linecopy = strdup(line);
496     if (!linecopy) {
497         return 0;
498     }
499     if (history_len == history_max_len) {
500         free(history[0]);
501         memmove(history, history+1, sizeof(char*)*(history_max_len-1));
502         history_len--;
503     }
504     history[history_len] = linecopy;
505     history_len++;
506     return 1;
507 }
508 
linenoiseHistorySetMaxLen(int len)509 int linenoiseHistorySetMaxLen(int len) {
510     char **new;
511 
512     if (len < 1) {
513         return 0;
514     }
515     if (history) {
516         int tocopy = history_len;
517 
518         new = malloc(sizeof(char*)*len);
519         if (new == NULL) {
520             return 0;
521         }
522         if (len < tocopy) tocopy = len;
523         memcpy(new, history+(history_max_len-tocopy), sizeof(char*)*tocopy);
524         free(history);
525         history = new;
526     }
527     history_max_len = len;
528     if (history_len > history_max_len) {
529         history_len = history_max_len;
530     }
531     return 1;
532 }
533