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