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