Fix spelling errors.
[oweals/busybox.git] / 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 <sys/ioctl.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
57 /* ED: sparc termios is broken: revert back to old termio handling. */
58 #ifdef BB_FEATURE_USE_TERMIOS
59
60 #if #cpu(sparc)
61 #      include <termio.h>
62 #      define termios termio
63 #      define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
64 #      define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
65 #else
66 #      include <termios.h>
67 #      define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
68 #      define getTermSettings(fd,argp) tcgetattr(fd, argp);
69 #endif
70
71 /* Current termio and the previous termio before starting sh */
72 struct termios initial_settings, new_settings;
73
74
75 #ifndef _POSIX_VDISABLE
76 #define _POSIX_VDISABLE '\0'
77 #endif
78
79 #endif
80
81
82
83 static int cmdedit_termw = 80;  /* actual terminal width */
84 static int cmdedit_scroll = 27; /* width of EOL scrolling region */
85 static int history_counter = 0; /* Number of commands in history list */
86 static int reset_term = 0;              /* Set to true if the terminal needs to be reset upon exit */
87
88 struct history {
89         char *s;
90         struct history *p;
91         struct history *n;
92 };
93
94 #define xwrite write
95
96 void
97 cmdedit_setwidth(int w)
98 {
99         if (w > 20) {
100                 cmdedit_termw = w;
101                 cmdedit_scroll = w / 3;
102         } else {
103                 errorMsg("\n*** Error: minimum screen width is 21\n");
104         }
105 }
106
107
108 void cmdedit_reset_term(void)
109 {
110         if (reset_term)
111                 /* sparc and other have broken termios support: use old termio handling. */
112                 setTermSettings(fileno(stdin), (void*) &initial_settings);
113 }
114
115 void clean_up_and_die(int sig)
116 {
117         cmdedit_reset_term();
118         fprintf(stdout, "\n");
119         if (sig!=SIGINT)
120                 exit(TRUE);
121 }
122
123 /* Go to HOME position */
124 void input_home(int outputFd, int *cursor)
125 {
126         while (*cursor > 0) {
127                 xwrite(outputFd, "\b", 1);
128                 --*cursor;
129         }
130 }
131
132 /* Go to END position */
133 void input_end(int outputFd, int *cursor, int len)
134 {
135         while (*cursor < len) {
136                 xwrite(outputFd, "\033[C", 3);
137                 ++*cursor;
138         }
139 }
140
141 /* Delete the char in back of the cursor */
142 void input_backspace(char* command, int outputFd, int *cursor, int *len)
143 {
144         int j = 0;
145
146         if (*cursor > 0) {
147                 xwrite(outputFd, "\b \b", 3);
148                 --*cursor;
149                 memmove(command + *cursor, command + *cursor + 1,
150                                 BUFSIZ - *cursor + 1);
151
152                 for (j = *cursor; j < (BUFSIZ - 1); j++) {
153                         if (!*(command + j))
154                                 break;
155                         else
156                                 xwrite(outputFd, (command + j), 1);
157                 }
158
159                 xwrite(outputFd, " \b", 2);
160
161                 while (j-- > *cursor)
162                         xwrite(outputFd, "\b", 1);
163
164                 --*len;
165         }
166 }
167
168 /* Delete the char in front of the cursor */
169 void input_delete(char* command, int outputFd, int cursor, int *len)
170 {
171         int j = 0;
172
173         if (cursor == *len)
174                 return;
175         
176         memmove(command + cursor, command + cursor + 1,
177                         BUFSIZ - cursor - 1);
178         for (j = cursor; j < (BUFSIZ - 1); j++) {
179                 if (!*(command + j))
180                         break;
181                 else
182                         xwrite(outputFd, (command + j), 1);
183         }
184
185         xwrite(outputFd, " \b", 2);
186
187         while (j-- > cursor)
188                 xwrite(outputFd, "\b", 1);
189         --*len;
190 }
191
192 /* Move forward one charactor */
193 void input_forward(int outputFd, int *cursor, int len)
194 {
195         if (*cursor < len) {
196                 xwrite(outputFd, "\033[C", 3);
197                 ++*cursor;
198         }
199 }
200
201 /* Move back one charactor */
202 void input_backward(int outputFd, int *cursor)
203 {
204         if (*cursor > 0) {
205                 xwrite(outputFd, "\033[D", 3);
206                 --*cursor;
207         }
208 }
209
210
211
212 #ifdef BB_FEATURE_SH_TAB_COMPLETION
213 char** username_tab_completion(char* command, int *num_matches)
214 {
215         char **matches = (char **) NULL;
216         *num_matches=0;
217         fprintf(stderr, "\nin username_tab_completion\n");
218         return (matches);
219 }
220
221 #include <dirent.h>
222 char** exe_n_cwd_tab_completion(char* command, int *num_matches)
223 {
224         char *dirName;
225         char **matches = (char **) NULL;
226         DIR *dir;
227         struct dirent *next;
228                         
229         matches = malloc( sizeof(char*)*50);
230
231         /* Stick a wildcard onto the command, for later use */
232         strcat( command, "*");
233
234         /* Now wall the current directory */
235         dirName = get_current_dir_name();
236         dir = opendir(dirName);
237         if (!dir) {
238                 /* Don't print an error, just shut up and return */
239                 *num_matches=0;
240                 return (matches);
241         }
242         while ((next = readdir(dir)) != NULL) {
243
244                 /* Some quick sanity checks */
245                 if ((strcmp(next->d_name, "..") == 0)
246                         || (strcmp(next->d_name, ".") == 0)) {
247                         continue;
248                 } 
249                 /* See if this matches */
250                 if (check_wildcard_match(next->d_name, command) == TRUE) {
251                         /* Cool, found a match.  Add it to the list */
252                         matches[*num_matches] = malloc(strlen(next->d_name)+1);
253                         strcpy( matches[*num_matches], next->d_name);
254                         ++*num_matches;
255                         //matches = realloc( matches, sizeof(char*)*(*num_matches));
256                 }
257         }
258
259         return (matches);
260 }
261
262 void input_tab(char* command, char* prompt, int outputFd, int *cursor, int *len)
263 {
264         /* Do TAB completion */
265         static int num_matches=0;
266         static char **matches = (char **) NULL;
267         int pos = cursor;
268
269
270         if (lastWasTab == FALSE) {
271                 char *tmp, *tmp1, *matchBuf;
272
273                 /* For now, we will not bother with trying to distinguish
274                  * whether the cursor is in/at a command extression -- we
275                  * will always try all possable matches.  If you don't like
276                  * that then feel free to fix it.
277                  */
278
279                 /* Make a local copy of the string -- up 
280                  * to the position of the cursor */
281                 matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
282                 strncpy(matchBuf, command, cursor);
283                 tmp=matchBuf;
284
285                 /* skip past any command seperator tokens */
286                 while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
287                         tmp=++tmp1;
288                         /* skip any leading white space */
289                         while (*tmp && isspace(*tmp)) 
290                                 ++tmp;
291                 }
292
293                 /* skip any leading white space */
294                 while (*tmp && isspace(*tmp)) 
295                         ++tmp;
296
297                 /* Free up any memory already allocated */
298                 if (matches) {
299                         free(matches);
300                         matches = (char **) NULL;
301                 }
302
303                 /* If the word starts with `~' and there is no slash in the word, 
304                  * then try completing this word as a username. */
305
306                 /* FIXME -- this check is broken! */
307                 if (*tmp == '~' && !strchr(tmp, '/'))
308                         matches = username_tab_completion(tmp, &num_matches);
309
310                 /* Try to match any executable in our path and everything 
311                  * in the current working directory that matches.  */
312                 if (!matches)
313                         matches = exe_n_cwd_tab_completion(tmp, &num_matches);
314
315                 /* Don't leak memory */
316                 free( matchBuf);
317
318                 /* Did we find exactly one match? */
319                 if (matches && num_matches==1) {
320                         /* write out the matched command */
321                         strncpy(command+pos, matches[0]+pos, strlen(matches[0])-pos);
322                         len=strlen(command);
323                         cursor=len;
324                         xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
325                         break;
326                 }
327         } else {
328                 /* Ok -- the last char was a TAB.  Since they
329                  * just hit TAB again, print a list of all the
330                  * available choices... */
331                 if ( matches && num_matches>0 ) {
332                         int i, col;
333
334                         /* Go to the next line */
335                         xwrite(outputFd, "\n", 1);
336                         /* Print the list of matches */
337                         for (i=0,col=0; i<num_matches; i++) {
338                                 char foo[17];
339                                 sprintf(foo, "%-14s  ", matches[i]);
340                                 col += xwrite(outputFd, foo, strlen(foo));
341                                 if (col > 60 && matches[i+1] != NULL) {
342                                         xwrite(outputFd, "\n", 1);
343                                         col = 0;
344                                 }
345                         }
346                         /* Go to the next line */
347                         xwrite(outputFd, "\n", 1);
348                         /* Rewrite the prompt */
349                         xwrite(outputFd, prompt, strlen(prompt));
350                         /* Rewrite the command */
351                         xwrite(outputFd, command, len);
352                         /* Put the cursor back to where it used to be */
353                         for (cursor=len; cursor > pos; cursor--)
354                                 xwrite(outputFd, "\b", 1);
355                 }
356         }
357 }
358 #endif
359
360 void get_previous_history(struct history **hp, char* command)
361 {
362         free((*hp)->s);
363         (*hp)->s = strdup(command);
364         *hp = (*hp)->p;
365 }
366
367 void get_next_history(struct history **hp, char* command)
368 {
369         free((*hp)->s);
370         (*hp)->s = strdup(command);
371         *hp = (*hp)->n;
372 }
373
374 /*
375  * This function is used to grab a character buffer
376  * from the input file descriptor and allows you to
377  * a string with full command editing (sortof like
378  * a mini readline).
379  *
380  * The following standard commands are not implemented:
381  * ESC-b -- Move back one word
382  * ESC-f -- Move forward one word
383  * ESC-d -- Delete back one word
384  * ESC-h -- Delete forward one word
385  * CTL-t -- Transpose two characters
386  *
387  * Furthermore, the "vi" command editing keys are not implemented.
388  *
389  * TODO: implement TAB command completion. :)
390  */
391 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
392 {
393
394         int inputFd=fileno(stdin);
395         int outputFd=fileno(stdout);
396         int nr = 0;
397         int len = 0;
398         int j = 0;
399         int cursor = 0;
400         int break_out = 0;
401         int ret = 0;
402         int lastWasTab = FALSE;
403         char c = 0;
404         struct history *hp = his_end;
405
406         memset(command, 0, sizeof(command));
407         if (!reset_term) {
408                 
409                 getTermSettings(inputFd, (void*) &initial_settings);
410                 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
411                 new_settings.c_cc[VMIN] = 1;
412                 new_settings.c_cc[VTIME] = 0;
413                 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
414                 new_settings.c_lflag &= ~ICANON;        /* unbuffered input */
415                 new_settings.c_lflag &= ~(ECHO|ECHOCTL|ECHONL); /* Turn off echoing */
416                 reset_term = 1;
417         }
418         setTermSettings(inputFd, (void*) &new_settings);
419
420         memset(command, 0, BUFSIZ);
421
422         while (1) {
423
424                 if ((ret = read(inputFd, &c, 1)) < 1)
425                         return;
426                 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
427
428                 switch (c) {
429                 case '\n':
430                 case '\r':
431                         /* Enter */
432                         *(command + len++ + 1) = c;
433                         xwrite(outputFd, &c, 1);
434                         break_out = 1;
435                         break;
436                 case 1:
437                         /* Control-a -- Beginning of line */
438                         input_home(outputFd, &cursor);
439                 case 2:
440                         /* Control-b -- Move back one character */
441                         input_backward(outputFd, &cursor);
442                         break;
443                 case 3:
444                         /* Control-c -- leave the current line, 
445                          * and start over on the next line */ 
446
447                         /* Go to the next line */
448                         xwrite(outputFd, "\n", 1);
449
450                         /* Rewrite the prompt */
451                         xwrite(outputFd, prompt, strlen(prompt));
452
453                         /* Reset the command string */
454                         memset(command, 0, sizeof(command));
455                         len = cursor = 0;
456
457                         break;
458                 case 4:
459                         /* Control-d -- Delete one character, or exit 
460                          * if the len=0 and no chars to delete */
461                         if (len == 0) {
462                                 xwrite(outputFd, "exit", 4);
463                                 clean_up_and_die(0);
464                         } else {
465                                 input_delete(command, outputFd, cursor, &len);
466                         }
467                         break;
468                 case 5:
469                         /* Control-e -- End of line */
470                         input_end(outputFd, &cursor, len);
471                         break;
472                 case 6:
473                         /* Control-f -- Move forward one character */
474                         input_forward(outputFd, &cursor, len);
475                         break;
476                 case '\b':
477                 case DEL:
478                         /* Control-h and DEL */
479                         input_backspace(command, outputFd, &cursor, &len);
480                         break;
481                 case '\t':
482 #ifdef BB_FEATURE_SH_TAB_COMPLETION
483                         input_tab(command, prompt, outputFd, &cursor, &len);
484 #endif
485                         break;
486                 case 14:
487                         /* Control-n -- Get next command in history */
488                         if (hp && hp->n && hp->n->s) {
489                                 get_next_history(&hp, command);
490                                 goto rewrite_line;
491                         } else {
492                                 xwrite(outputFd, "\007", 1);
493                         }
494                         break;
495                 case 16:
496                         /* Control-p -- Get previous command from history */
497                         if (hp && hp->p) {
498                                 get_previous_history(&hp, command);
499                                 goto rewrite_line;
500                         } else {
501                                 xwrite(outputFd, "\007", 1);
502                         }
503                         break;
504                 case ESC:{
505                                 /* escape sequence follows */
506                                 if ((ret = read(inputFd, &c, 1)) < 1)
507                                         return;
508
509                                 if (c == '[') { /* 91 */
510                                         if ((ret = read(inputFd, &c, 1)) < 1)
511                                                 return;
512
513                                         switch (c) {
514                                         case 'A':
515                                                 /* Up Arrow -- Get previous command from history */
516                                                 if (hp && hp->p) {
517                                                         get_previous_history(&hp, command);
518                                                         goto rewrite_line;
519                                                 } else {
520                                                         xwrite(outputFd, "\007", 1);
521                                                 }
522                                                 break;
523                                         case 'B':
524                                                 /* Down Arrow -- Get next command in history */
525                                                 if (hp && hp->n && hp->n->s) {
526                                                         get_next_history(&hp, command);
527                                                         goto rewrite_line;
528                                                 } else {
529                                                         xwrite(outputFd, "\007", 1);
530                                                 }
531                                                 break;
532
533                                                 /* Rewrite the line with the selected history item */
534                                           rewrite_line:
535                                                 /* erase old command from command line */
536                                                 len = strlen(command)-strlen(hp->s);
537                                                 while (len>0)
538                                                         input_backspace(command, outputFd, &cursor, &len);
539                                                 input_home(outputFd, &cursor);
540                                                 
541                                                 /* write new command */
542                                                 strcpy(command, hp->s);
543                                                 len = strlen(hp->s);
544                                                 xwrite(outputFd, command, len);
545                                                 cursor = len;
546                                                 break;
547                                         case 'C':
548                                                 /* Right Arrow -- Move forward one character */
549                                                 input_forward(outputFd, &cursor, len);
550                                                 break;
551                                         case 'D':
552                                                 /* Left Arrow -- Move back one character */
553                                                 input_backward(outputFd, &cursor);
554                                                 break;
555                                         case '3':
556                                                 /* Delete */
557                                                 input_delete(command, outputFd, cursor, &len);
558                                                 break;
559                                         case '1':
560                                                 /* Home (Ctrl-A) */
561                                                 input_home(outputFd, &cursor);
562                                                 break;
563                                         case '4':
564                                                 /* End (Ctrl-E) */
565                                                 input_end(outputFd, &cursor, len);
566                                                 break;
567                                         default:
568                                                 xwrite(outputFd, "\007", 1);
569                                         }
570                                         if (c == '1' || c == '3' || c == '4')
571                                                 if ((ret = read(inputFd, &c, 1)) < 1)
572                                                         return; /* read 126 (~) */
573                                 }
574                                 if (c == 'O') {
575                                         /* 79 */
576                                         if ((ret = read(inputFd, &c, 1)) < 1)
577                                                 return;
578                                         switch (c) {
579                                         case 'H':
580                                                 /* Home (xterm) */
581                                                 input_home(outputFd, &cursor);
582                                                 break;
583                                         case 'F':
584                                                 /* End (xterm) */
585                                                 input_end(outputFd, &cursor, len);
586                                                 break;
587                                         default:
588                                                 xwrite(outputFd, "\007", 1);
589                                         }
590                                 }
591                                 c = 0;
592                                 break;
593                         }
594
595                 default:                                /* If it's regular input, do the normal thing */
596
597                         if (!isprint(c)) {      /* Skip non-printable characters */
598                                 break;
599                         }
600
601                         if (len >= (BUFSIZ - 2))        /* Need to leave space for enter */
602                                 break;
603
604                         len++;
605
606                         if (cursor == (len - 1)) {      /* Append if at the end of the line */
607                                 *(command + cursor) = c;
608                         } else {                        /* Insert otherwise */
609                                 memmove(command + cursor + 1, command + cursor,
610                                                 len - cursor - 1);
611
612                                 *(command + cursor) = c;
613
614                                 for (j = cursor; j < len; j++)
615                                         xwrite(outputFd, command + j, 1);
616                                 for (; j > cursor; j--)
617                                         xwrite(outputFd, "\033[D", 3);
618                         }
619
620                         cursor++;
621                         xwrite(outputFd, &c, 1);
622                         break;
623                 }
624                 if (c == '\t')
625                         lastWasTab = TRUE;
626                 else
627                         lastWasTab = FALSE;
628
629                 if (break_out)                  /* Enter is the command terminator, no more input. */
630                         break;
631         }
632
633         nr = len + 1;
634         setTermSettings(inputFd, (void *) &initial_settings);
635         reset_term = 0;
636
637
638         /* Handle command history log */
639         if (*(command)) {
640
641                 struct history *h = his_end;
642
643                 if (!h) {
644                         /* No previous history */
645                         h = his_front = malloc(sizeof(struct history));
646                         h->n = malloc(sizeof(struct history));
647
648                         h->p = NULL;
649                         h->s = strdup(command);
650                         h->n->p = h;
651                         h->n->n = NULL;
652                         h->n->s = NULL;
653                         his_end = h->n;
654                         history_counter++;
655                 } else {
656                         /* Add a new history command */
657                         h->n = malloc(sizeof(struct history));
658
659                         h->n->p = h;
660                         h->n->n = NULL;
661                         h->n->s = NULL;
662                         h->s = strdup(command);
663                         his_end = h->n;
664
665                         /* After max history, remove the oldest command */
666                         if (history_counter >= MAX_HISTORY) {
667
668                                 struct history *p = his_front->n;
669
670                                 p->p = NULL;
671                                 free(his_front->s);
672                                 free(his_front);
673                                 his_front = p;
674                         } else {
675                                 history_counter++;
676                         }
677                 }
678         }
679
680         return;
681 }
682
683 extern void cmdedit_init(void)
684 {
685         atexit(cmdedit_reset_term);
686         signal(SIGKILL, clean_up_and_die);
687         signal(SIGINT, clean_up_and_die);
688         signal(SIGQUIT, clean_up_and_die);
689         signal(SIGTERM, clean_up_and_die);
690 }
691 #endif                                                  /* BB_FEATURE_SH_COMMAND_EDITING */