Several fixes.
[oweals/busybox.git] / shell / cmdedit.c
1 /*
2  * Termios command line History and Editting for NetBSD sh (ash)
3  * Copyright (c) 1999
4  *      Main code:            Adam Rogoyski <rogoyski@cs.utexas.edu> 
5  *      Etc:                  Dave Cinege <dcinege@psychosis.com>
6  *      Adjusted for busybox: Erik Andersen <andersee@debian.org>
7  *
8  * You may use this code as you wish, so long as the original author(s)
9  * are attributed in any redistributions of the source code.
10  * This code is 'as is' with no warranty.
11  * This code may safely be consumed by a BSD or GPL license.
12  *
13  * v 0.5  19990328      Initial release 
14  *
15  * Future plans: Simple file and path name completion. (like BASH)
16  *
17  */
18
19 /*
20    Usage and Known bugs:
21    Terminal key codes are not extensive, and more will probably
22    need to be added. This version was created on Debian GNU/Linux 2.x.
23    Delete, Backspace, Home, End, and the arrow keys were tested
24    to work in an Xterm and console. Ctrl-A also works as Home.
25    Ctrl-E also works as End. The binary size increase is <3K.
26
27    Editting will not display correctly for lines greater then the 
28    terminal width. (more then one line.) However, history will.
29  */
30
31 #include "internal.h"
32 #ifdef BB_FEATURE_SH_COMMAND_EDITING
33
34 #include <stdio.h>
35 #include <errno.h>
36 #include <unistd.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <termio.h>
40 #include <ctype.h>
41 #include <signal.h>
42
43 #include "cmdedit.h"
44
45
46 #define  MAX_HISTORY   15       /* Maximum length of the linked list for the command line history */
47
48 #define ESC     27
49 #define DEL     127
50 #define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0)
51 #define whitespace(c) (((c) == ' ') || ((c) == '\t'))
52
53 static struct history *his_front = NULL;        /* First element in command line list */
54 static struct history *his_end = NULL;  /* Last element in command line list */
55 static struct termio old_term, new_term;        /* Current termio and the previous termio before starting ash */
56
57 static int history_counter = 0; /* Number of commands in history list */
58 static int reset_term = 0;      /* Set to true if the terminal needs to be reset upon exit */
59 char *parsenextc;               /* copy of parsefile->nextc */
60
61 struct history {
62     char *s;
63     struct history *p;
64     struct history *n;
65 };
66
67
68 /* Version of write which resumes after a signal is caught.  */
69 int xwrite(int fd, char *buf, int nbytes)
70 {
71     int ntry;
72     int i;
73     int n;
74
75     n = nbytes;
76     ntry = 0;
77     for (;;) {
78         i = write(fd, buf, n);
79         if (i > 0) {
80             if ((n -= i) <= 0)
81                 return nbytes;
82             buf += i;
83             ntry = 0;
84         } else if (i == 0) {
85             if (++ntry > 10)
86                 return nbytes - n;
87         } else if (errno != EINTR) {
88             return -1;
89         }
90     }
91 }
92
93
94 /* Version of ioctl that retries after a signal is caught.  */
95 int xioctl(int fd, unsigned long request, char *arg)
96 {
97     int i;
98     while ((i = ioctl(fd, request, arg)) == -1 && errno == EINTR);
99     return i;
100 }
101
102
103 void cmdedit_reset_term(void)
104 {
105     if (reset_term)
106         xioctl(fileno(stdin), TCSETA, (void *) &old_term);
107 }
108
109 void prepareToDie(int sig)
110 {
111     cmdedit_reset_term();
112     fprintf(stdout, "\n");
113     exit(TRUE);
114 }
115
116 void input_home(int outputFd, int *cursor)
117 {                               /* Command line input routines */
118     while (*cursor > 0) {
119         xwrite(outputFd, "\b", 1);
120         --*cursor;
121     }
122 }
123
124
125 void input_delete(int outputFd, int cursor)
126 {
127     int j = 0;
128
129     memmove(parsenextc + cursor, parsenextc + cursor + 1,
130             BUFSIZ - cursor - 1);
131     for (j = cursor; j < (BUFSIZ - 1); j++) {
132         if (!*(parsenextc + j))
133             break;
134         else
135             xwrite(outputFd, (parsenextc + j), 1);
136     }
137
138     xwrite(outputFd, " \b", 2);
139
140     while (j-- > cursor)
141         xwrite(outputFd, "\b", 1);
142 }
143
144
145 void input_end(int outputFd, int *cursor, int len)
146 {
147     while (*cursor < len) {
148         xwrite(outputFd, "\033[C", 3);
149         ++*cursor;
150     }
151 }
152
153
154 void input_backspace(int outputFd, int *cursor, int *len)
155 {
156     int j = 0;
157
158     if (*cursor > 0) {
159         xwrite(outputFd, "\b \b", 3);
160         --*cursor;
161         memmove(parsenextc + *cursor, parsenextc + *cursor + 1,
162                 BUFSIZ - *cursor + 1);
163
164         for (j = *cursor; j < (BUFSIZ - 1); j++) {
165             if (!*(parsenextc + j))
166                 break;
167             else
168                 xwrite(outputFd, (parsenextc + j), 1);
169         }
170
171         xwrite(outputFd, " \b", 2);
172
173         while (j-- > *cursor)
174             xwrite(outputFd, "\b", 1);
175
176         --*len;
177     }
178 }
179
180 char **username_completion_matches( char* matchBuf)
181 {
182     fprintf(stderr, "\nin username_completion_matches\n");
183     return ( (char**) NULL);
184 }
185 char **command_completion_matches( char* matchBuf)
186 {
187     fprintf(stderr, "\nin command_completion_matches\n");
188     return ( (char**) NULL);
189 }
190 char **directory_completion_matches( char* matchBuf)
191 {
192     fprintf(stderr, "\nin directory_completion_matches\n");
193     return ( (char**) NULL);
194 }
195
196 /*
197  * This function is used to grab a character buffer
198  * from the input file descriptor and allows you to
199  * a string with full command editing (sortof like
200  * a mini readline).
201  *
202  * The following standard commands are not implemented:
203  * ESC-b -- Move back one word
204  * ESC-f -- Move forward one word
205  * ESC-d -- Delete back one word
206  * ESC-h -- Delete forward one word
207  * CTL-t -- Transpose two characters
208  *
209  * Furthermore, the "vi" command editing keys are not implemented.
210  *
211  * TODO: implement TAB command completion. :)
212  *
213  */
214 extern int cmdedit_read_input(int inputFd, int outputFd,
215                             char command[BUFSIZ])
216 {
217
218     int nr = 0;
219     int len = 0;
220     int j = 0;
221     int cursor = 0;
222     int break_out = 0;
223     int ret = 0;
224     int lastWasTab = FALSE;
225     char **matches = (char **)NULL;
226     char c = 0;
227     struct history *hp = his_end;
228
229     memset(command, 0, sizeof(command));
230     parsenextc = command;
231     if (!reset_term) {
232         xioctl(inputFd, TCGETA, (void *) &old_term);
233         memcpy(&new_term, &old_term, sizeof(struct termio));
234         new_term.c_cc[VMIN] = 1;
235         new_term.c_cc[VTIME] = 0;
236         new_term.c_lflag &= ~ICANON;    /* unbuffered input */
237         new_term.c_lflag &= ~ECHO;
238         xioctl(inputFd, TCSETA, (void *) &new_term);
239         reset_term = 1;
240     } else {
241         xioctl(inputFd, TCSETA, (void *) &new_term);
242     }
243
244     memset(parsenextc, 0, BUFSIZ);
245
246     while (1) {
247
248         if ((ret = read(inputFd, &c, 1)) < 1)
249             return ret;
250         
251         switch (c) {
252         case 1:         
253             /* Control-a -- Beginning of line */
254             input_home(outputFd, &cursor);
255         case 5:         
256             /* Control-e -- End of line */
257             input_end(outputFd, &cursor, len);
258             break;
259         case 2:
260             /* Control-b -- Move back one character */
261             if (cursor > 0) {
262                 xwrite(outputFd, "\033[D", 3);
263                 cursor--;
264             }
265             break;
266         case 6: 
267             /* Control-f -- Move forward one character */
268             if (cursor < len) {
269                 xwrite(outputFd, "\033[C", 3);
270                 cursor++;
271             }
272             break;
273         case 4:
274             /* Control-d -- Delete one character */
275             if (cursor != len) {
276                 input_delete(outputFd, cursor);
277                 len--;
278             } else if (len == 0) {
279                 prepareToDie(0);
280                 exit(0);
281             }
282             break;
283         case 14:
284             /* Control-n -- Get next command */
285             if (hp && hp->n && hp->n->s) {      
286                 free( hp->s);
287                 hp->s = strdup(parsenextc);
288                 hp = hp->n;
289                 goto hop;
290             }
291             break;
292         case 16:
293             /* Control-p -- Get previous command */
294             if (hp && hp->p) {  
295                 free( hp->s);
296                 hp->s = strdup(parsenextc);
297                 hp = hp->p;
298                 goto hop;
299             }
300             break;
301         case '\t':
302             {
303             /* Do TAB completion */
304                 int in_command_position=0, ti=len-1;
305
306                 if (lastWasTab == FALSE) {
307                     char *tmp;
308                     char *matchBuf;
309
310                     if (matches) {
311                         free(matches);
312                         matches = (char **)NULL;
313                     }
314
315                     matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
316
317                     /* Make a local copy of the string -- up 
318                      * to the the position of the cursor */
319                     strcpy( matchBuf, parsenextc); 
320                     matchBuf[cursor+1] = '\0';
321
322                     /* skip leading white space */
323                     tmp = matchBuf;
324                     while (*tmp && isspace(*tmp)) {
325                         (tmp)++;
326                         ti++;
327                     }
328
329                     /* Determine if this is a command word or not */
330                     //while ((ti > -1) && (whitespace (matchBuf[ti]))) {
331 //printf("\nti=%d\n", ti);
332                 //      ti--;
333                  //   }
334 printf("\nti=%d\n", ti);
335
336                     if (ti < 0) {
337                           in_command_position++;
338                     } else if (member(matchBuf[ti], ";|&{(`")) {
339                         int this_char, prev_char;
340                         in_command_position++;
341                         /* Handle the two character tokens `>&', `<&', and `>|'.
342                          We are not in a command position after one of these. */
343                         this_char = matchBuf[ti];
344                         prev_char = matchBuf[ti - 1];
345
346                         if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) ||
347                                 (this_char == '|' && prev_char == '>')) {
348                             in_command_position = 0;
349                         } 
350                         /* For now, do not bother with catching quoted
351                          * expressions and marking them as not in command
352                          * positions.  Some other day.  Or not.
353                          */
354                         //else if (char_is_quoted (matchBuf, ti)) {
355                         //    in_command_position = 0;
356                         //}
357                     }
358 printf("\nin_command_position=%d\n", in_command_position);
359                     /* If the word starts in `~', and there is no slash in the word, 
360                      * then try completing this word as a username. */
361                     if (*matchBuf == '~' && !strchr (matchBuf, '/'))
362                         matches = username_completion_matches(matchBuf);
363
364                     /* If this word is in a command position, then complete over possible 
365                      * command names, including aliases, built-ins, and executables. */
366                     if (!matches && in_command_position) {
367                         matches = command_completion_matches(matchBuf);
368
369                     /* If we are attempting command completion and nothing matches, 
370                      * then try and match directories as a last resort... */
371                     if (!matches)
372                         matches = directory_completion_matches(matchBuf);
373                     }
374                 } else {
375                     printf("\nprinting match list\n");
376                 }
377                 /* Rewrite the whole line (for debugging) */
378                 for (; cursor > 0; cursor--)    
379                     xwrite(outputFd, "\b", 1);
380                 len = strlen(parsenextc);
381                 xwrite(outputFd, parsenextc, len);
382                 cursor = len;
383                 break;
384             }
385         case '\b':              
386         case DEL:
387             /* Backspace */
388             input_backspace(outputFd, &cursor, &len);
389             break;
390         case '\n':              
391             /* Enter */
392             *(parsenextc + len++ + 1) = c;
393             xwrite(outputFd, &c, 1);
394             break_out = 1;
395             break;
396         case ESC:               {
397             /* escape sequence follows */
398             if ((ret = read(inputFd, &c, 1)) < 1)
399                 return ret;
400
401             if (c == '[') {     /* 91 */
402                 if ((ret = read(inputFd, &c, 1)) < 1)
403                     return ret;
404                 
405                 switch (c) {
406                 case 'A':
407                     /* Up Arrow -- Get previous command */
408                     if (hp && hp->p) {  
409                         free( hp->s);
410                         hp->s = strdup(parsenextc);
411                         hp = hp->p;
412                         goto hop;
413                     }
414                     break;
415                 case 'B':
416                     /* Down Arrow -- Get next command */
417                     if (hp && hp->n && hp->n->s) {      
418                         free( hp->s);
419                         hp->s = strdup(parsenextc);
420                         hp = hp->n;
421                         goto hop;
422                     }
423                     break;
424
425                     /* This is where we rewrite the line 
426                      * using the selected history item */
427                   hop:          
428                     len = strlen(parsenextc);
429
430                     /* return to begining of line */
431                     for (; cursor > 0; cursor--)        
432                         xwrite(outputFd, "\b", 1);
433                     xwrite(outputFd, parsenextc, len);
434
435                     /* erase old command */
436                     for (j = 0; j < len; j++)   
437                         xwrite(outputFd, " ", 1);
438
439                     /* return to begining of line */
440                     for (j = len; j > 0; j--)   
441                         xwrite(outputFd, "\b", 1);
442
443                     memset(parsenextc, 0, BUFSIZ);
444                     /* write new command */
445                     strcpy(parsenextc, hp->s);  
446                     len = strlen(hp->s);
447                     xwrite(outputFd, parsenextc, len);
448                     cursor = len;
449                     break;
450                 case 'C':       
451                     /* Right Arrow -- Move forward one character */
452                     if (cursor < len) {
453                         xwrite(outputFd, "\033[C", 3);
454                         cursor++;
455                     }
456                     break;
457                 case 'D':       
458                     /* Left Arrow -- Move back one character */
459                     if (cursor > 0) {
460                         xwrite(outputFd, "\033[D", 3);
461                         cursor--;
462                     }
463                     break;
464                 case '3':       
465                     /* Delete */
466                     if (cursor != len) {
467                         input_delete(outputFd, cursor);
468                         len--;
469                     }
470                     break;
471                 case '1':       
472                     /* Home (Ctrl-A) */
473                     input_home(outputFd, &cursor);
474                     break;
475                 case '4':       
476                     /* End (Ctrl-E) */
477                     input_end(outputFd, &cursor, len);
478                     break;
479                 }
480                 if (c == '1' || c == '3' || c == '4')
481                     if ((ret = read(inputFd, &c, 1)) < 1)
482                         return ret;     /* read 126 (~) */
483             }
484             if (c == 'O') {     
485                 /* 79 */
486                 if ((ret = read(inputFd, &c, 1)) < 1)
487                     return ret;
488                 switch (c) {
489                 case 'H':       
490                     /* Home (xterm) */
491                     input_home(outputFd, &cursor);
492                     break;
493                 case 'F':       
494                     /* End (xterm) */
495                     input_end(outputFd, &cursor, len);
496                     break;
497                 }
498             }
499             c = 0;
500             break;
501         }
502
503         default:                /* If it's regular input, do the normal thing */
504
505             if (!isprint(c))    /* Skip non-printable characters */
506                 break;
507
508             if (len >= (BUFSIZ - 2))    /* Need to leave space for enter */
509                 break;
510
511             len++;
512
513             if (cursor == (len - 1)) {  /* Append if at the end of the line */
514                 *(parsenextc + cursor) = c;
515             } else {            /* Insert otherwise */
516                 memmove(parsenextc + cursor + 1, parsenextc + cursor,
517                         len - cursor - 1);
518
519                 *(parsenextc + cursor) = c;
520
521                 for (j = cursor; j < len; j++)
522                     xwrite(outputFd, parsenextc + j, 1);
523                 for (; j > cursor; j--)
524                     xwrite(outputFd, "\033[D", 3);
525             }
526
527             cursor++;
528             xwrite(outputFd, &c, 1);
529             break;
530         }
531         if (c=='\t')
532             lastWasTab = TRUE;
533         else
534             lastWasTab = FALSE;
535
536         if (break_out)          /* Enter is the command terminator, no more input. */
537             break;
538     }
539
540     nr = len + 1;
541     xioctl(inputFd, TCSETA, (void *) &old_term);
542     reset_term = 0;
543
544
545     /* Handle command history log */
546     if (*(parsenextc)) {        
547
548         struct history *h = his_end;
549
550         if (!h) {               
551             /* No previous history */
552             h = his_front = malloc(sizeof(struct history));
553             h->n = malloc(sizeof(struct history));
554             h->p = NULL;
555             h->s = strdup(parsenextc);
556             h->n->p = h;
557             h->n->n = NULL;
558             h->n->s = NULL;
559             his_end = h->n;
560             history_counter++;
561         } else {                
562             /* Add a new history command */
563             h->n = malloc(sizeof(struct history));
564             h->n->p = h;
565             h->n->n = NULL;
566             h->n->s = NULL;
567             h->s = strdup(parsenextc);
568             his_end = h->n;
569
570             /* After max history, remove the oldest command */
571             if (history_counter >= MAX_HISTORY) {       
572
573                 struct history *p = his_front->n;
574
575                 p->p = NULL;
576                 free(his_front->s);
577                 free(his_front);
578                 his_front = p;
579             } else {
580                 history_counter++;
581             }
582         }
583     }
584
585     return nr;
586 }
587
588 extern void cmdedit_init(void)
589 {
590     atexit(cmdedit_reset_term);
591     signal(SIGINT, prepareToDie);
592     signal(SIGQUIT, prepareToDie);
593     signal(SIGTERM, prepareToDie);
594 }
595 #endif                          /* BB_FEATURE_SH_COMMAND_EDITING */