9e162c6eea769453f43077feca3a41ac0e8505a5
[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                     fprintf(stderr, "matchBuf='%s'\n", matchBuf);
323
324                     /* skip leading white space */
325                     tmp = matchBuf;
326                     while (*tmp && isspace(*tmp)) {
327                         (tmp)++;
328                         ti++;
329                     }
330
331                     /* Determine if this is a command word or not */
332                     //while ((ti > -1) && (whitespace (matchBuf[ti]))) {
333 //printf("\nti=%d\n", ti);
334                 //      ti--;
335                  //   }
336 printf("\nti=%d\n", ti);
337
338                     if (ti < 0) {
339                           in_command_position++;
340                     } else if (member(matchBuf[ti], ";|&{(`")) {
341                         int this_char, prev_char;
342                         in_command_position++;
343                         /* Handle the two character tokens `>&', `<&', and `>|'.
344                          We are not in a command position after one of these. */
345                         this_char = matchBuf[ti];
346                         prev_char = matchBuf[ti - 1];
347
348                         if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) ||
349                                 (this_char == '|' && prev_char == '>')) {
350                             in_command_position = 0;
351                         } 
352                         /* For now, do not bother with catching quoted
353                          * expressions and marking them as not in command
354                          * positions.  Some other day.  Or not.
355                          */
356                         //else if (char_is_quoted (matchBuf, ti)) {
357                         //    in_command_position = 0;
358                         //}
359                     }
360 printf("\nin_command_position=%d\n", in_command_position);
361                     /* If the word starts in `~', and there is no slash in the word, 
362                      * then try completing this word as a username. */
363                     if (*matchBuf == '~' && !strchr (matchBuf, '/'))
364                         matches = username_completion_matches(matchBuf);
365
366                     /* If this word is in a command position, then complete over possible 
367                      * command names, including aliases, built-ins, and executables. */
368                     if (!matches && in_command_position) {
369                         matches = command_completion_matches(matchBuf);
370
371                     /* If we are attempting command completion and nothing matches, 
372                      * then try and match directories as a last resort... */
373                     if (!matches)
374                         matches = directory_completion_matches(matchBuf);
375                     }
376                 } else {
377                     printf("\nprinting match list\n");
378                 }
379                 /* Rewrite the whole line (for debugging) */
380                 for (; cursor > 0; cursor--)    
381                     xwrite(outputFd, "\b", 1);
382                 len = strlen(parsenextc);
383                 xwrite(outputFd, parsenextc, len);
384                 cursor = len;
385                 break;
386             }
387         case '\b':              
388         case DEL:
389             /* Backspace */
390             input_backspace(outputFd, &cursor, &len);
391             break;
392         case '\n':              
393             /* Enter */
394             *(parsenextc + len++ + 1) = c;
395             xwrite(outputFd, &c, 1);
396             break_out = 1;
397             break;
398         case ESC:               {
399             /* escape sequence follows */
400             if ((ret = read(inputFd, &c, 1)) < 1)
401                 return ret;
402
403             if (c == '[') {     /* 91 */
404                 if ((ret = read(inputFd, &c, 1)) < 1)
405                     return ret;
406                 
407                 switch (c) {
408                 case 'A':
409                     /* Up Arrow -- Get previous command */
410                     if (hp && hp->p) {  
411                         free( hp->s);
412                         hp->s = strdup(parsenextc);
413                         hp = hp->p;
414                         goto hop;
415                     }
416                     break;
417                 case 'B':
418                     /* Down Arrow -- Get next command */
419                     if (hp && hp->n && hp->n->s) {      
420                         free( hp->s);
421                         hp->s = strdup(parsenextc);
422                         hp = hp->n;
423                         goto hop;
424                     }
425                     break;
426
427                     /* This is where we rewrite the line 
428                      * using the selected history item */
429                   hop:          
430                     len = strlen(parsenextc);
431
432                     /* return to begining of line */
433                     for (; cursor > 0; cursor--)        
434                         xwrite(outputFd, "\b", 1);
435                     xwrite(outputFd, parsenextc, len);
436
437                     /* erase old command */
438                     for (j = 0; j < len; j++)   
439                         xwrite(outputFd, " ", 1);
440
441                     /* return to begining of line */
442                     for (j = len; j > 0; j--)   
443                         xwrite(outputFd, "\b", 1);
444
445                     memset(parsenextc, 0, BUFSIZ);
446                     /* write new command */
447                     strcpy(parsenextc, hp->s);  
448                     len = strlen(hp->s);
449                     xwrite(outputFd, parsenextc, len);
450                     cursor = len;
451                     break;
452                 case 'C':       
453                     /* Right Arrow -- Move forward one character */
454                     if (cursor < len) {
455                         xwrite(outputFd, "\033[C", 3);
456                         cursor++;
457                     }
458                     break;
459                 case 'D':       
460                     /* Left Arrow -- Move back one character */
461                     if (cursor > 0) {
462                         xwrite(outputFd, "\033[D", 3);
463                         cursor--;
464                     }
465                     break;
466                 case '3':       
467                     /* Delete */
468                     if (cursor != len) {
469                         input_delete(outputFd, cursor);
470                         len--;
471                     }
472                     break;
473                 case '1':       
474                     /* Home (Ctrl-A) */
475                     input_home(outputFd, &cursor);
476                     break;
477                 case '4':       
478                     /* End (Ctrl-E) */
479                     input_end(outputFd, &cursor, len);
480                     break;
481                 }
482                 if (c == '1' || c == '3' || c == '4')
483                     if ((ret = read(inputFd, &c, 1)) < 1)
484                         return ret;     /* read 126 (~) */
485             }
486             if (c == 'O') {     
487                 /* 79 */
488                 if ((ret = read(inputFd, &c, 1)) < 1)
489                     return ret;
490                 switch (c) {
491                 case 'H':       
492                     /* Home (xterm) */
493                     input_home(outputFd, &cursor);
494                     break;
495                 case 'F':       
496                     /* End (xterm) */
497                     input_end(outputFd, &cursor, len);
498                     break;
499                 }
500             }
501             c = 0;
502             break;
503         }
504
505         default:                /* If it's regular input, do the normal thing */
506
507             if (!isprint(c))    /* Skip non-printable characters */
508                 break;
509
510             if (len >= (BUFSIZ - 2))    /* Need to leave space for enter */
511                 break;
512
513             len++;
514
515             if (cursor == (len - 1)) {  /* Append if at the end of the line */
516                 *(parsenextc + cursor) = c;
517             } else {            /* Insert otherwise */
518                 memmove(parsenextc + cursor + 1, parsenextc + cursor,
519                         len - cursor - 1);
520
521                 *(parsenextc + cursor) = c;
522
523                 for (j = cursor; j < len; j++)
524                     xwrite(outputFd, parsenextc + j, 1);
525                 for (; j > cursor; j--)
526                     xwrite(outputFd, "\033[D", 3);
527             }
528
529             cursor++;
530             xwrite(outputFd, &c, 1);
531             break;
532         }
533         if (c=='\t')
534             lastWasTab = TRUE;
535         else
536             lastWasTab = FALSE;
537
538         if (break_out)          /* Enter is the command terminator, no more input. */
539             break;
540     }
541
542     nr = len + 1;
543     xioctl(inputFd, TCSETA, (void *) &old_term);
544     reset_term = 0;
545
546
547     /* Handle command history log */
548     if (*(parsenextc)) {        
549
550         struct history *h = his_end;
551
552         if (!h) {               
553             /* No previous history */
554             h = his_front = malloc(sizeof(struct history));
555             h->n = malloc(sizeof(struct history));
556             h->p = NULL;
557             h->s = strdup(parsenextc);
558             h->n->p = h;
559             h->n->n = NULL;
560             h->n->s = NULL;
561             his_end = h->n;
562             history_counter++;
563         } else {                
564             /* Add a new history command */
565             h->n = malloc(sizeof(struct history));
566             h->n->p = h;
567             h->n->n = NULL;
568             h->n->s = NULL;
569             h->s = strdup(parsenextc);
570             his_end = h->n;
571
572             /* After max history, remove the oldest command */
573             if (history_counter >= MAX_HISTORY) {       
574
575                 struct history *p = his_front->n;
576
577                 p->p = NULL;
578                 free(his_front->s);
579                 free(his_front);
580                 his_front = p;
581             } else {
582                 history_counter++;
583             }
584         }
585     }
586
587     return nr;
588 }
589
590 extern void cmdedit_init(void)
591 {
592     atexit(cmdedit_reset_term);
593     signal(SIGINT, prepareToDie);
594     signal(SIGQUIT, prepareToDie);
595     signal(SIGTERM, prepareToDie);
596 }
597 #endif                          /* BB_FEATURE_SH_COMMAND_EDITING */