2c16276db624505d2d208d2c2c17e6d0c74309f4
[oweals/busybox.git] / shell / cmdedit.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Termios command line History and Editting for NetBSD sh (ash)
4  * Copyright (c) 1999
5  *      Main code:            Adam Rogoyski <rogoyski@cs.utexas.edu> 
6  *      Etc:                  Dave Cinege <dcinege@psychosis.com>
7  *      Adjusted for busybox: Erik Andersen <andersee@debian.org>
8  *
9  * You may use this code as you wish, so long as the original author(s)
10  * are attributed in any redistributions of the source code.
11  * This code is 'as is' with no warranty.
12  * This code may safely be consumed by a BSD or GPL license.
13  *
14  * v 0.5  19990328      Initial release 
15  *
16  * Future plans: Simple file and path name completion. (like BASH)
17  *
18  */
19
20 /*
21    Usage and Known bugs:
22    Terminal key codes are not extensive, and more will probably
23    need to be added. This version was created on Debian GNU/Linux 2.x.
24    Delete, Backspace, Home, End, and the arrow keys were tested
25    to work in an Xterm and console. Ctrl-A also works as Home.
26    Ctrl-E also works as End. The binary size increase is <3K.
27
28    Editting will not display correctly for lines greater then the 
29    terminal width. (more then one line.) However, history will.
30  */
31
32 #include "internal.h"
33 #ifdef BB_FEATURE_SH_COMMAND_EDITING
34
35 #include <stdio.h>
36 #include <errno.h>
37 #include <unistd.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <termio.h>
41 #include <ctype.h>
42 #include <signal.h>
43
44
45 #define  MAX_HISTORY   15               /* Maximum length of the linked list for the command line history */
46
47 #define ESC     27
48 #define DEL     127
49 #define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0)
50 #define whitespace(c) (((c) == ' ') || ((c) == '\t'))
51
52 static struct history *his_front = NULL;        /* First element in command line list */
53 static struct history *his_end = NULL;  /* Last element in command line list */
54 static struct termio old_term, new_term;        /* Current termio and the previous termio before starting ash */
55
56 static int history_counter = 0; /* Number of commands in history list */
57 static int reset_term = 0;              /* Set to true if the terminal needs to be reset upon exit */
58 char *parsenextc;                               /* copy of parsefile->nextc */
59
60 struct history {
61         char *s;
62         struct history *p;
63         struct history *n;
64 };
65
66
67 /* Version of write which resumes after a signal is caught.  */
68 int xwrite(int fd, char *buf, int nbytes)
69 {
70         int ntry;
71         int i;
72         int n;
73
74         n = nbytes;
75         ntry = 0;
76         for (;;) {
77                 i = write(fd, buf, n);
78                 if (i > 0) {
79                         if ((n -= i) <= 0)
80                                 return nbytes;
81                         buf += i;
82                         ntry = 0;
83                 } else if (i == 0) {
84                         if (++ntry > 10)
85                                 return nbytes - n;
86                 } else if (errno != EINTR) {
87                         return -1;
88                 }
89         }
90 }
91
92
93 /* Version of ioctl that retries after a signal is caught.  */
94 int xioctl(int fd, unsigned long request, char *arg)
95 {
96         int i;
97
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* command, int *num_matches)
181 {
182         char **matches = (char **) NULL;
183         *num_matches=0;
184         fprintf(stderr, "\nin username_completion_matches\n");
185         return (matches);
186 }
187
188 #include <dirent.h>
189 char** find_path_executable_n_cwd_matches(char* command, int *num_matches)
190 {
191         char *dirName;
192         char **matches = (char **) NULL;
193         DIR *dir;
194         struct dirent *next;
195                         
196         matches = malloc( sizeof(char*)*50);
197
198         /* Stick a wildcard onto the command, for later use */
199         strcat( command, "*");
200
201         /* Now wall the current directory */
202         dirName = get_current_dir_name();
203         dir = opendir(dirName);
204         if (!dir) {
205                 /* Don't print an error, just shut up and return */
206                 *num_matches=0;
207                 return (matches);
208         }
209         while ((next = readdir(dir)) != NULL) {
210
211                 /* Some quick sanity checks */
212                 if ((strcmp(next->d_name, "..") == 0)
213                         || (strcmp(next->d_name, ".") == 0)) {
214                         continue;
215                 } 
216                 /* See if this matches */
217                 if (check_wildcard_match(next->d_name, command) == TRUE) {
218                         /* Cool, found a match.  Add it to the list */
219                         matches[*num_matches] = malloc(strlen(next->d_name)+1);
220                         strcpy( matches[*num_matches], next->d_name);
221                         ++*num_matches;
222                         //matches = realloc( matches, sizeof(char*)*(*num_matches));
223                 }
224         }
225
226         return (matches);
227 }
228
229 /*
230  * This function is used to grab a character buffer
231  * from the input file descriptor and allows you to
232  * a string with full command editing (sortof like
233  * a mini readline).
234  *
235  * The following standard commands are not implemented:
236  * ESC-b -- Move back one word
237  * ESC-f -- Move forward one word
238  * ESC-d -- Delete back one word
239  * ESC-h -- Delete forward one word
240  * CTL-t -- Transpose two characters
241  *
242  * Furthermore, the "vi" command editing keys are not implemented.
243  *
244  * TODO: implement TAB command completion. :)
245  *
246  */
247 extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd,
248                                                           char command[BUFSIZ])
249 {
250
251         int nr = 0;
252         int len = 0;
253         int j = 0;
254         int cursor = 0;
255         int break_out = 0;
256         int ret = 0;
257         int lastWasTab = FALSE;
258         char c = 0;
259         struct history *hp = his_end;
260
261         memset(command, 0, sizeof(command));
262         parsenextc = command;
263         if (!reset_term) {
264                 xioctl(inputFd, TCGETA, (void *) &old_term);
265                 memcpy(&new_term, &old_term, sizeof(struct termio));
266
267                 new_term.c_cc[VMIN] = 1;
268                 new_term.c_cc[VTIME] = 0;
269                 new_term.c_lflag &= ~ICANON;    /* unbuffered input */
270                 new_term.c_lflag &= ~ECHO;
271                 xioctl(inputFd, TCSETA, (void *) &new_term);
272                 reset_term = 1;
273         } else {
274                 xioctl(inputFd, TCSETA, (void *) &new_term);
275         }
276
277         memset(parsenextc, 0, BUFSIZ);
278
279         while (1) {
280
281                 if ((ret = read(inputFd, &c, 1)) < 1)
282                         return ret;
283
284                 fprintf(stderr, "\n\nkey=%d (%c)\n\n", c, c);
285                 /* Go to the next line */
286                 xwrite(outputFd, "\n", 1);
287                 /* Rewrite the prompt */
288                 xwrite(outputFd, prompt, strlen(prompt));
289                 /* Rewrite the command */
290                 xwrite(outputFd, parsenextc, len);
291
292                 switch (c) {
293                 case 1:
294                         /* Control-a -- Beginning of line */
295                         input_home(outputFd, &cursor);
296                 case 5:
297                         /* Control-e -- End of line */
298                         input_end(outputFd, &cursor, len);
299                         break;
300                 case 2:
301                         /* Control-b -- Move back one character */
302                         if (cursor > 0) {
303                                 xwrite(outputFd, "\033[D", 3);
304                                 cursor--;
305                         }
306                         break;
307                 case 6:
308                         /* Control-f -- Move forward one character */
309                         if (cursor < len) {
310                                 xwrite(outputFd, "\033[C", 3);
311                                 cursor++;
312                         }
313                         break;
314                 case 4:
315                         /* Control-d -- Delete one character */
316                         if (cursor != len) {
317                                 input_delete(outputFd, cursor);
318                                 len--;
319                         } else if (len == 0) {
320                                 prepareToDie(0);
321                                 exit(0);
322                         }
323                         break;
324                 case 14:
325                         /* Control-n -- Get next command */
326                         if (hp && hp->n && hp->n->s) {
327                                 free(hp->s);
328                                 hp->s = strdup(parsenextc);
329                                 hp = hp->n;
330                                 goto hop;
331                         }
332                         break;
333                 case 16:
334                         /* Control-p -- Get previous command */
335                         if (hp && hp->p) {
336                                 free(hp->s);
337                                 hp->s = strdup(parsenextc);
338                                 hp = hp->p;
339                                 goto hop;
340                         }
341                         break;
342                 case '\t':
343                         {
344                                 /* Do TAB completion */
345                                 static int num_matches=0;
346                                 static char **matches = (char **) NULL;
347                                 int pos = cursor;
348                                 
349
350                                 if (lastWasTab == FALSE) {
351                                         char *tmp, *tmp1, *matchBuf;
352
353                                         /* For now, we will not bother with trying to distinguish
354                                          * whether the cursor is in/at a command extression -- we
355                                          * will always try all possable matches.  If you don't like
356                                          * that then feel free to fix it.
357                                          */
358                                         
359                                         /* Make a local copy of the string -- up 
360                                          * to the position of the cursor */
361                                         matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
362                                         strncpy(matchBuf, parsenextc, cursor);
363                                         tmp=matchBuf;
364
365                                         /* skip past any command seperator tokens */
366                                         while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
367                                                 tmp=++tmp1;
368                                                 /* skip any leading white space */
369                                                 while (*tmp && isspace(*tmp)) 
370                                                         ++tmp;
371                                         }
372
373                                         /* skip any leading white space */
374                                         while (*tmp && isspace(*tmp)) 
375                                                 ++tmp;
376
377                                         /* Free up any memory already allocated */
378                                         if (matches) {
379                                                 free(matches);
380                                                 matches = (char **) NULL;
381                                         }
382
383                                         /* If the word starts with `~' and there is no slash in the word, 
384                                          * then try completing this word as a username. */
385
386                                         /* FIXME -- this check is broken! */
387                                         if (*tmp == '~' && !strchr(tmp, '/'))
388                                                 matches = username_completion_matches(tmp, &num_matches);
389
390                                         /* Try to match any executable in our path and everything 
391                                          * in the current working directory that matches.  */
392                                         if (!matches)
393                                                 matches = find_path_executable_n_cwd_matches(tmp, &num_matches);
394
395                                         /* Don't leak memory */
396                                         free( matchBuf);
397
398                                         /* Did we find exactly one match? */
399                                         if (matches && num_matches==1) {
400                                                 /* write out the matched command */
401                                                 strncpy(parsenextc+pos, matches[0]+pos, strlen(matches[0])-pos);
402                                                 len=strlen(parsenextc);
403                                                 cursor=len;
404                                                 xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
405                                                 break;
406                                         }
407                                 } else {
408                                         /* Ok -- the last char was a TAB.  Since they
409                                          * just hit TAB again, print a list of all the
410                                          * available choices... */
411                                         if ( matches && num_matches>0 ) {
412                                                 int i, col;
413                                                 
414                                                 /* Go to the next line */
415                                                 xwrite(outputFd, "\n", 1);
416                                                 /* Print the list of matches */
417                                                 for (i=0,col=0; i<num_matches; i++) {
418                                                         char foo[17];
419                                                         sprintf(foo, "%-14s  ", matches[i]);
420                                                         col += xwrite(outputFd, foo, strlen(foo));
421                                                         if (col > 60 && matches[i+1] != NULL) {
422                                                                 xwrite(outputFd, "\n", 1);
423                                                                 col = 0;
424                                                         }
425                                                 }
426                                                 /* Go to the next line */
427                                                 xwrite(outputFd, "\n", 1);
428                                                 /* Rewrite the prompt */
429                                                 xwrite(outputFd, prompt, strlen(prompt));
430                                                 /* Rewrite the command */
431                                                 xwrite(outputFd, parsenextc, len);
432                                                 /* Put the cursor back to where it used to be */
433                                                 for (cursor=len; cursor > pos; cursor--)
434                                                         xwrite(outputFd, "\b", 1);
435                                         }
436                                 }
437                                 break;
438                         }
439                 case '\b':
440                 case DEL:
441                         /* Backspace */
442                         input_backspace(outputFd, &cursor, &len);
443                         break;
444                 case '\n':
445                         /* Enter */
446                         *(parsenextc + len++ + 1) = c;
447                         xwrite(outputFd, &c, 1);
448                         break_out = 1;
449                         break;
450                 case ESC:{
451                                 /* escape sequence follows */
452                                 if ((ret = read(inputFd, &c, 1)) < 1)
453                                         return ret;
454
455                                 if (c == '[') { /* 91 */
456                                         if ((ret = read(inputFd, &c, 1)) < 1)
457                                                 return ret;
458
459                                         switch (c) {
460                                         case 'A':
461                                                 /* Up Arrow -- Get previous command */
462                                                 if (hp && hp->p) {
463                                                         free(hp->s);
464                                                         hp->s = strdup(parsenextc);
465                                                         hp = hp->p;
466                                                         goto hop;
467                                                 }
468                                                 break;
469                                         case 'B':
470                                                 /* Down Arrow -- Get next command */
471                                                 if (hp && hp->n && hp->n->s) {
472                                                         free(hp->s);
473                                                         hp->s = strdup(parsenextc);
474                                                         hp = hp->n;
475                                                         goto hop;
476                                                 }
477                                                 break;
478
479                                                 /* This is where we rewrite the line 
480                                                  * using the selected history item */
481                                           hop:
482                                                 len = strlen(parsenextc);
483
484                                                 /* return to begining of line */
485                                                 for (; cursor > 0; cursor--)
486                                                         xwrite(outputFd, "\b", 1);
487
488                                                 /* erase old command */
489                                                 for (j = 0; j < len; j++)
490                                                         xwrite(outputFd, " ", 1);
491
492                                                 /* return to begining of line */
493                                                 for (j = len; j > 0; j--)
494                                                         xwrite(outputFd, "\b", 1);
495
496                                                 memset(parsenextc, 0, BUFSIZ);
497                                                 len = strlen(parsenextc);
498                                                 /* write new command */
499                                                 strcpy(parsenextc, hp->s);
500                                                 len = strlen(hp->s);
501                                                 xwrite(outputFd, parsenextc, len);
502                                                 cursor = len;
503                                                 break;
504                                         case 'C':
505                                                 /* Right Arrow -- Move forward one character */
506                                                 if (cursor < len) {
507                                                         xwrite(outputFd, "\033[C", 3);
508                                                         cursor++;
509                                                 }
510                                                 break;
511                                         case 'D':
512                                                 /* Left Arrow -- Move back one character */
513                                                 if (cursor > 0) {
514                                                         xwrite(outputFd, "\033[D", 3);
515                                                         cursor--;
516                                                 }
517                                                 break;
518                                         case '3':
519                                                 /* Delete */
520                                                 if (cursor != len) {
521                                                         input_delete(outputFd, cursor);
522                                                         len--;
523                                                 }
524                                                 break;
525
526                                         //case '5': case '6': /* pgup/pgdown */
527
528                                         case '7':
529                                                 /* rxvt home */
530                                         case '1':
531                                                 /* Home (Ctrl-A) */
532                                                 input_home(outputFd, &cursor);
533                                                 break;
534                                         case '8':
535                                                 /* rxvt END */
536                                         case '4':
537                                                 /* End (Ctrl-E) */
538                                                 input_end(outputFd, &cursor, len);
539                                                 break;
540                                         }
541                                         if (c == '1' || c == '3' || c == '4')
542                                                 if ((ret = read(inputFd, &c, 1)) < 1)
543                                                         return ret;     /* read 126 (~) */
544                                 }
545                                 if (c == 'O') {
546                                         /* 79 */
547                                         if ((ret = read(inputFd, &c, 1)) < 1)
548                                                 return ret;
549                                         switch (c) {
550                                         case 'H':
551                                                 /* Home (xterm) */
552                                                 input_home(outputFd, &cursor);
553                                                 break;
554                                         case 'F':
555                                                 /* End (xterm) */
556                                                 input_end(outputFd, &cursor, len);
557                                                 break;
558                                         }
559                                 }
560                                 c = 0;
561                                 break;
562                         }
563
564                 default:                                /* If it's regular input, do the normal thing */
565
566                         if (!isprint(c))        /* Skip non-printable characters */
567                                 break;
568
569                         if (len >= (BUFSIZ - 2))        /* Need to leave space for enter */
570                                 break;
571
572                         len++;
573
574                         if (cursor == (len - 1)) {      /* Append if at the end of the line */
575                                 *(parsenextc + cursor) = c;
576                         } else {                        /* Insert otherwise */
577                                 memmove(parsenextc + cursor + 1, parsenextc + cursor,
578                                                 len - cursor - 1);
579
580                                 *(parsenextc + cursor) = c;
581
582                                 for (j = cursor; j < len; j++)
583                                         xwrite(outputFd, parsenextc + j, 1);
584                                 for (; j > cursor; j--)
585                                         xwrite(outputFd, "\033[D", 3);
586                         }
587
588                         cursor++;
589                         xwrite(outputFd, &c, 1);
590                         break;
591                 }
592                 if (c == '\t')
593                         lastWasTab = TRUE;
594                 else
595                         lastWasTab = FALSE;
596
597                 if (break_out)                  /* Enter is the command terminator, no more input. */
598                         break;
599         }
600
601         nr = len + 1;
602         xioctl(inputFd, TCSETA, (void *) &old_term);
603         reset_term = 0;
604
605
606         /* Handle command history log */
607         if (*(parsenextc)) {
608
609                 struct history *h = his_end;
610
611                 if (!h) {
612                         /* No previous history */
613                         h = his_front = malloc(sizeof(struct history));
614                         h->n = malloc(sizeof(struct history));
615
616                         h->p = NULL;
617                         h->s = strdup(parsenextc);
618                         h->n->p = h;
619                         h->n->n = NULL;
620                         h->n->s = NULL;
621                         his_end = h->n;
622                         history_counter++;
623                 } else {
624                         /* Add a new history command */
625                         h->n = malloc(sizeof(struct history));
626
627                         h->n->p = h;
628                         h->n->n = NULL;
629                         h->n->s = NULL;
630                         h->s = strdup(parsenextc);
631                         his_end = h->n;
632
633                         /* After max history, remove the oldest command */
634                         if (history_counter >= MAX_HISTORY) {
635
636                                 struct history *p = his_front->n;
637
638                                 p->p = NULL;
639                                 free(his_front->s);
640                                 free(his_front);
641                                 his_front = p;
642                         } else {
643                                 history_counter++;
644                         }
645                 }
646         }
647
648         return nr;
649 }
650
651 extern void cmdedit_init(void)
652 {
653         atexit(cmdedit_reset_term);
654         signal(SIGINT, prepareToDie);
655         signal(SIGQUIT, prepareToDie);
656         signal(SIGTERM, prepareToDie);
657 }
658 #endif                                                  /* BB_FEATURE_SH_COMMAND_EDITING */