more minor fixes
[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                 switch (c) {
285                 case 1:
286                         /* Control-a -- Beginning of line */
287                         input_home(outputFd, &cursor);
288                 case 5:
289                         /* Control-e -- End of line */
290                         input_end(outputFd, &cursor, len);
291                         break;
292                 case 2:
293                         /* Control-b -- Move back one character */
294                         if (cursor > 0) {
295                                 xwrite(outputFd, "\033[D", 3);
296                                 cursor--;
297                         }
298                         break;
299                 case 6:
300                         /* Control-f -- Move forward one character */
301                         if (cursor < len) {
302                                 xwrite(outputFd, "\033[C", 3);
303                                 cursor++;
304                         }
305                         break;
306                 case 4:
307                         /* Control-d -- Delete one character */
308                         if (cursor != len) {
309                                 input_delete(outputFd, cursor);
310                                 len--;
311                         } else if (len == 0) {
312                                 prepareToDie(0);
313                                 exit(0);
314                         }
315                         break;
316                 case 14:
317                         /* Control-n -- Get next command */
318                         if (hp && hp->n && hp->n->s) {
319                                 free(hp->s);
320                                 hp->s = strdup(parsenextc);
321                                 hp = hp->n;
322                                 goto hop;
323                         }
324                         break;
325                 case 16:
326                         /* Control-p -- Get previous command */
327                         if (hp && hp->p) {
328                                 free(hp->s);
329                                 hp->s = strdup(parsenextc);
330                                 hp = hp->p;
331                                 goto hop;
332                         }
333                         break;
334                 case '\t':
335                         {
336                                 /* Do TAB completion */
337                                 static int num_matches=0;
338                                 static char **matches = (char **) NULL;
339                                 int pos = cursor;
340                                 
341
342                                 if (lastWasTab == FALSE) {
343                                         char *tmp, *tmp1, *matchBuf;
344
345                                         /* For now, we will not bother with trying to distinguish
346                                          * whether the cursor is in/at a command extression -- we
347                                          * will always try all possable matches.  If you don't like
348                                          * that then feel free to fix it.
349                                          */
350                                         
351                                         /* Make a local copy of the string -- up 
352                                          * to the position of the cursor */
353                                         matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
354                                         strncpy(matchBuf, parsenextc, cursor);
355                                         tmp=matchBuf;
356
357                                         /* skip past any command seperator tokens */
358                                         while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
359                                                 tmp=++tmp1;
360                                                 /* skip any leading white space */
361                                                 while (*tmp && isspace(*tmp)) 
362                                                         ++tmp;
363                                         }
364
365                                         /* skip any leading white space */
366                                         while (*tmp && isspace(*tmp)) 
367                                                 ++tmp;
368
369                                         /* Free up any memory already allocated */
370                                         if (matches) {
371                                                 free(matches);
372                                                 matches = (char **) NULL;
373                                         }
374
375                                         /* If the word starts with `~' and there is no slash in the word, 
376                                          * then try completing this word as a username. */
377
378                                         /* FIXME -- this check is broken! */
379                                         if (*tmp == '~' && !strchr(tmp, '/'))
380                                                 matches = username_completion_matches(tmp, &num_matches);
381
382                                         /* Try to match any executable in our path and everything 
383                                          * in the current working directory that matches.  */
384                                         if (!matches)
385                                                 matches = find_path_executable_n_cwd_matches(tmp, &num_matches);
386
387                                         /* Don't leak memory */
388                                         free( matchBuf);
389
390                                         /* Did we find exactly one match? */
391                                         if (matches && num_matches==1) {
392                                                 /* write out the matched command */
393                                                 strncpy(parsenextc+pos, matches[0]+pos, strlen(matches[0])-pos);
394                                                 len=strlen(parsenextc);
395                                                 cursor=len;
396                                                 xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
397                                                 break;
398                                         }
399                                 } else {
400                                         /* Ok -- the last char was a TAB.  Since they
401                                          * just hit TAB again, print a list of all the
402                                          * available choices... */
403                                         if ( matches && num_matches>0 ) {
404                                                 int i, col;
405                                                 
406                                                 /* Go to the next line */
407                                                 xwrite(outputFd, "\n", 1);
408                                                 /* Print the list of matches */
409                                                 for (i=0,col=0; i<num_matches; i++) {
410                                                         char foo[17];
411                                                         sprintf(foo, "%-14s  ", matches[i]);
412                                                         col += xwrite(outputFd, foo, strlen(foo));
413                                                         if (col > 60 && matches[i+1] != NULL) {
414                                                                 xwrite(outputFd, "\n", 1);
415                                                                 col = 0;
416                                                         }
417                                                 }
418                                                 /* Go to the next line */
419                                                 xwrite(outputFd, "\n", 1);
420                                                 /* Rewrite the prompt */
421                                                 xwrite(outputFd, prompt, strlen(prompt));
422                                                 /* Rewrite the command */
423                                                 xwrite(outputFd, parsenextc, len);
424                                                 /* Put the cursor back to where it used to be */
425                                                 for (cursor=len; cursor > pos; cursor--)
426                                                         xwrite(outputFd, "\b", 1);
427                                         }
428                                 }
429                                 break;
430                         }
431                 case '\b':
432                 case DEL:
433                         /* Backspace */
434                         input_backspace(outputFd, &cursor, &len);
435                         break;
436                 case '\n':
437                         /* Enter */
438                         *(parsenextc + len++ + 1) = c;
439                         xwrite(outputFd, &c, 1);
440                         break_out = 1;
441                         break;
442                 case ESC:{
443                                 /* escape sequence follows */
444                                 if ((ret = read(inputFd, &c, 1)) < 1)
445                                         return ret;
446
447                                 if (c == '[') { /* 91 */
448                                         if ((ret = read(inputFd, &c, 1)) < 1)
449                                                 return ret;
450
451                                         switch (c) {
452                                         case 'A':
453                                                 /* Up Arrow -- Get previous command */
454                                                 if (hp && hp->p) {
455                                                         free(hp->s);
456                                                         hp->s = strdup(parsenextc);
457                                                         hp = hp->p;
458                                                         goto hop;
459                                                 }
460                                                 break;
461                                         case 'B':
462                                                 /* Down Arrow -- Get next command */
463                                                 if (hp && hp->n && hp->n->s) {
464                                                         free(hp->s);
465                                                         hp->s = strdup(parsenextc);
466                                                         hp = hp->n;
467                                                         goto hop;
468                                                 }
469                                                 break;
470
471                                                 /* This is where we rewrite the line 
472                                                  * using the selected history item */
473                                           hop:
474                                                 len = strlen(parsenextc);
475
476                                                 /* return to begining of line */
477                                                 for (; cursor > 0; cursor--)
478                                                         xwrite(outputFd, "\b", 1);
479
480                                                 /* erase old command */
481                                                 for (j = 0; j < len; j++)
482                                                         xwrite(outputFd, " ", 1);
483
484                                                 /* return to begining of line */
485                                                 for (j = len; j > 0; j--)
486                                                         xwrite(outputFd, "\b", 1);
487
488                                                 memset(parsenextc, 0, BUFSIZ);
489                                                 len = strlen(parsenextc);
490                                                 /* write new command */
491                                                 strcpy(parsenextc, hp->s);
492                                                 len = strlen(hp->s);
493                                                 xwrite(outputFd, parsenextc, len);
494                                                 cursor = len;
495                                                 break;
496                                         case 'C':
497                                                 /* Right Arrow -- Move forward one character */
498                                                 if (cursor < len) {
499                                                         xwrite(outputFd, "\033[C", 3);
500                                                         cursor++;
501                                                 }
502                                                 break;
503                                         case 'D':
504                                                 /* Left Arrow -- Move back one character */
505                                                 if (cursor > 0) {
506                                                         xwrite(outputFd, "\033[D", 3);
507                                                         cursor--;
508                                                 }
509                                                 break;
510                                         case '3':
511                                                 /* Delete */
512                                                 if (cursor != len) {
513                                                         input_delete(outputFd, cursor);
514                                                         len--;
515                                                 }
516                                                 break;
517                                         case '1':
518                                                 /* Home (Ctrl-A) */
519                                                 input_home(outputFd, &cursor);
520                                                 break;
521                                         case '4':
522                                                 /* End (Ctrl-E) */
523                                                 input_end(outputFd, &cursor, len);
524                                                 break;
525                                         }
526                                         if (c == '1' || c == '3' || c == '4')
527                                                 if ((ret = read(inputFd, &c, 1)) < 1)
528                                                         return ret;     /* read 126 (~) */
529                                 }
530                                 if (c == 'O') {
531                                         /* 79 */
532                                         if ((ret = read(inputFd, &c, 1)) < 1)
533                                                 return ret;
534                                         switch (c) {
535                                         case 'H':
536                                                 /* Home (xterm) */
537                                                 input_home(outputFd, &cursor);
538                                                 break;
539                                         case 'F':
540                                                 /* End (xterm) */
541                                                 input_end(outputFd, &cursor, len);
542                                                 break;
543                                         }
544                                 }
545                                 c = 0;
546                                 break;
547                         }
548
549                 default:                                /* If it's regular input, do the normal thing */
550
551                         if (!isprint(c))        /* Skip non-printable characters */
552                                 break;
553
554                         if (len >= (BUFSIZ - 2))        /* Need to leave space for enter */
555                                 break;
556
557                         len++;
558
559                         if (cursor == (len - 1)) {      /* Append if at the end of the line */
560                                 *(parsenextc + cursor) = c;
561                         } else {                        /* Insert otherwise */
562                                 memmove(parsenextc + cursor + 1, parsenextc + cursor,
563                                                 len - cursor - 1);
564
565                                 *(parsenextc + cursor) = c;
566
567                                 for (j = cursor; j < len; j++)
568                                         xwrite(outputFd, parsenextc + j, 1);
569                                 for (; j > cursor; j--)
570                                         xwrite(outputFd, "\033[D", 3);
571                         }
572
573                         cursor++;
574                         xwrite(outputFd, &c, 1);
575                         break;
576                 }
577                 if (c == '\t')
578                         lastWasTab = TRUE;
579                 else
580                         lastWasTab = FALSE;
581
582                 if (break_out)                  /* Enter is the command terminator, no more input. */
583                         break;
584         }
585
586         nr = len + 1;
587         xioctl(inputFd, TCSETA, (void *) &old_term);
588         reset_term = 0;
589
590
591         /* Handle command history log */
592         if (*(parsenextc)) {
593
594                 struct history *h = his_end;
595
596                 if (!h) {
597                         /* No previous history */
598                         h = his_front = malloc(sizeof(struct history));
599                         h->n = malloc(sizeof(struct history));
600
601                         h->p = NULL;
602                         h->s = strdup(parsenextc);
603                         h->n->p = h;
604                         h->n->n = NULL;
605                         h->n->s = NULL;
606                         his_end = h->n;
607                         history_counter++;
608                 } else {
609                         /* Add a new history command */
610                         h->n = malloc(sizeof(struct history));
611
612                         h->n->p = h;
613                         h->n->n = NULL;
614                         h->n->s = NULL;
615                         h->s = strdup(parsenextc);
616                         his_end = h->n;
617
618                         /* After max history, remove the oldest command */
619                         if (history_counter >= MAX_HISTORY) {
620
621                                 struct history *p = his_front->n;
622
623                                 p->p = NULL;
624                                 free(his_front->s);
625                                 free(his_front);
626                                 his_front = p;
627                         } else {
628                                 history_counter++;
629                         }
630                 }
631         }
632
633         return nr;
634 }
635
636 extern void cmdedit_init(void)
637 {
638         atexit(cmdedit_reset_term);
639         signal(SIGINT, prepareToDie);
640         signal(SIGQUIT, prepareToDie);
641         signal(SIGTERM, prepareToDie);
642 }
643 #endif                                                  /* BB_FEATURE_SH_COMMAND_EDITING */