d15c694977ff28f746cf6723b6aae36b9ac04e1f
[oweals/busybox.git] / 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 #ifdef BB_FEATURE_SH_TAB_COMPLETION
181
182 char** username_completion_matches(char* command, int *num_matches)
183 {
184         char **matches = (char **) NULL;
185         *num_matches=0;
186         fprintf(stderr, "\nin username_completion_matches\n");
187         return (matches);
188 }
189
190 #include <dirent.h>
191 char** find_path_executable_n_cwd_matches(char* command, int *num_matches)
192 {
193         char *dirName;
194         char **matches = (char **) NULL;
195         DIR *dir;
196         struct dirent *next;
197                         
198         matches = malloc( sizeof(char*)*50);
199
200         /* Stick a wildcard onto the command, for later use */
201         strcat( command, "*");
202
203         /* Now wall the current directory */
204         dirName = get_current_dir_name();
205         dir = opendir(dirName);
206         if (!dir) {
207                 /* Don't print an error, just shut up and return */
208                 *num_matches=0;
209                 return (matches);
210         }
211         while ((next = readdir(dir)) != NULL) {
212
213                 /* Some quick sanity checks */
214                 if ((strcmp(next->d_name, "..") == 0)
215                         || (strcmp(next->d_name, ".") == 0)) {
216                         continue;
217                 } 
218                 /* See if this matches */
219                 if (check_wildcard_match(next->d_name, command) == TRUE) {
220                         /* Cool, found a match.  Add it to the list */
221                         matches[*num_matches] = malloc(strlen(next->d_name)+1);
222                         strcpy( matches[*num_matches], next->d_name);
223                         ++*num_matches;
224                         //matches = realloc( matches, sizeof(char*)*(*num_matches));
225                 }
226         }
227
228         return (matches);
229 }
230 #endif
231
232 /*
233  * This function is used to grab a character buffer
234  * from the input file descriptor and allows you to
235  * a string with full command editing (sortof like
236  * a mini readline).
237  *
238  * The following standard commands are not implemented:
239  * ESC-b -- Move back one word
240  * ESC-f -- Move forward one word
241  * ESC-d -- Delete back one word
242  * ESC-h -- Delete forward one word
243  * CTL-t -- Transpose two characters
244  *
245  * Furthermore, the "vi" command editing keys are not implemented.
246  *
247  * TODO: implement TAB command completion. :)
248  *
249  */
250 extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd,
251                                                           char command[BUFSIZ])
252 {
253
254         int nr = 0;
255         int len = 0;
256         int j = 0;
257         int cursor = 0;
258         int break_out = 0;
259         int ret = 0;
260         int lastWasTab = FALSE;
261         char c = 0;
262         struct history *hp = his_end;
263
264         memset(command, 0, sizeof(command));
265         parsenextc = command;
266         if (!reset_term) {
267                 xioctl(inputFd, TCGETA, (void *) &old_term);
268                 memcpy(&new_term, &old_term, sizeof(struct termio));
269
270                 new_term.c_cc[VMIN] = 1;
271                 new_term.c_cc[VTIME] = 0;
272                 new_term.c_lflag &= ~ICANON;    /* unbuffered input */
273                 new_term.c_lflag &= ~ECHO;
274                 xioctl(inputFd, TCSETA, (void *) &new_term);
275                 reset_term = 1;
276         } else {
277                 xioctl(inputFd, TCSETA, (void *) &new_term);
278         }
279
280         memset(parsenextc, 0, BUFSIZ);
281
282         while (1) {
283
284                 if ((ret = read(inputFd, &c, 1)) < 1)
285                         return ret;
286
287                 fprintf(stderr, "\n\nkey=%d (%c)\n\n", c, c);
288                 /* Go to the next line */
289                 xwrite(outputFd, "\n", 1);
290                 /* Rewrite the prompt */
291                 xwrite(outputFd, prompt, strlen(prompt));
292                 /* Rewrite the command */
293                 xwrite(outputFd, parsenextc, len);
294
295                 switch (c) {
296                 case 1:
297                         /* Control-a -- Beginning of line */
298                         input_home(outputFd, &cursor);
299                 case 5:
300                         /* Control-e -- End of line */
301                         input_end(outputFd, &cursor, len);
302                         break;
303                 case 2:
304                         /* Control-b -- Move back one character */
305                         if (cursor > 0) {
306                                 xwrite(outputFd, "\033[D", 3);
307                                 cursor--;
308                         }
309                         break;
310                 case 6:
311                         /* Control-f -- Move forward one character */
312                         if (cursor < len) {
313                                 xwrite(outputFd, "\033[C", 3);
314                                 cursor++;
315                         }
316                         break;
317                 case 4:
318                         /* Control-d -- Delete one character */
319                         if (cursor != len) {
320                                 input_delete(outputFd, cursor);
321                                 len--;
322                         } else if (len == 0) {
323                                 prepareToDie(0);
324                                 exit(0);
325                         }
326                         break;
327                 case 14:
328                         /* Control-n -- Get next command */
329                         if (hp && hp->n && hp->n->s) {
330                                 free(hp->s);
331                                 hp->s = strdup(parsenextc);
332                                 hp = hp->n;
333                                 goto hop;
334                         }
335                         break;
336                 case 16:
337                         /* Control-p -- Get previous command */
338                         if (hp && hp->p) {
339                                 free(hp->s);
340                                 hp->s = strdup(parsenextc);
341                                 hp = hp->p;
342                                 goto hop;
343                         }
344                         break;
345                 case '\t':
346 #ifdef BB_FEATURE_SH_TAB_COMPLETION
347                         {
348                                 /* Do TAB completion */
349                                 static int num_matches=0;
350                                 static char **matches = (char **) NULL;
351                                 int pos = cursor;
352                                 
353
354                                 if (lastWasTab == FALSE) {
355                                         char *tmp, *tmp1, *matchBuf;
356
357                                         /* For now, we will not bother with trying to distinguish
358                                          * whether the cursor is in/at a command extression -- we
359                                          * will always try all possable matches.  If you don't like
360                                          * that then feel free to fix it.
361                                          */
362                                         
363                                         /* Make a local copy of the string -- up 
364                                          * to the position of the cursor */
365                                         matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
366                                         strncpy(matchBuf, parsenextc, cursor);
367                                         tmp=matchBuf;
368
369                                         /* skip past any command seperator tokens */
370                                         while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
371                                                 tmp=++tmp1;
372                                                 /* skip any leading white space */
373                                                 while (*tmp && isspace(*tmp)) 
374                                                         ++tmp;
375                                         }
376
377                                         /* skip any leading white space */
378                                         while (*tmp && isspace(*tmp)) 
379                                                 ++tmp;
380
381                                         /* Free up any memory already allocated */
382                                         if (matches) {
383                                                 free(matches);
384                                                 matches = (char **) NULL;
385                                         }
386
387                                         /* If the word starts with `~' and there is no slash in the word, 
388                                          * then try completing this word as a username. */
389
390                                         /* FIXME -- this check is broken! */
391                                         if (*tmp == '~' && !strchr(tmp, '/'))
392                                                 matches = username_completion_matches(tmp, &num_matches);
393
394                                         /* Try to match any executable in our path and everything 
395                                          * in the current working directory that matches.  */
396                                         if (!matches)
397                                                 matches = find_path_executable_n_cwd_matches(tmp, &num_matches);
398
399                                         /* Don't leak memory */
400                                         free( matchBuf);
401
402                                         /* Did we find exactly one match? */
403                                         if (matches && num_matches==1) {
404                                                 /* write out the matched command */
405                                                 strncpy(parsenextc+pos, matches[0]+pos, strlen(matches[0])-pos);
406                                                 len=strlen(parsenextc);
407                                                 cursor=len;
408                                                 xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
409                                                 break;
410                                         }
411                                 } else {
412                                         /* Ok -- the last char was a TAB.  Since they
413                                          * just hit TAB again, print a list of all the
414                                          * available choices... */
415                                         if ( matches && num_matches>0 ) {
416                                                 int i, col;
417                                                 
418                                                 /* Go to the next line */
419                                                 xwrite(outputFd, "\n", 1);
420                                                 /* Print the list of matches */
421                                                 for (i=0,col=0; i<num_matches; i++) {
422                                                         char foo[17];
423                                                         sprintf(foo, "%-14s  ", matches[i]);
424                                                         col += xwrite(outputFd, foo, strlen(foo));
425                                                         if (col > 60 && matches[i+1] != NULL) {
426                                                                 xwrite(outputFd, "\n", 1);
427                                                                 col = 0;
428                                                         }
429                                                 }
430                                                 /* Go to the next line */
431                                                 xwrite(outputFd, "\n", 1);
432                                                 /* Rewrite the prompt */
433                                                 xwrite(outputFd, prompt, strlen(prompt));
434                                                 /* Rewrite the command */
435                                                 xwrite(outputFd, parsenextc, len);
436                                                 /* Put the cursor back to where it used to be */
437                                                 for (cursor=len; cursor > pos; cursor--)
438                                                         xwrite(outputFd, "\b", 1);
439                                         }
440                                 }
441                                 break;
442                         }
443 #else
444                         break;
445 #endif
446                 case '\b':
447                 case DEL:
448                         /* Backspace */
449                         input_backspace(outputFd, &cursor, &len);
450                         break;
451                 case '\n':
452                         /* Enter */
453                         *(parsenextc + len++ + 1) = c;
454                         xwrite(outputFd, &c, 1);
455                         break_out = 1;
456                         break;
457                 case ESC:{
458                                 /* escape sequence follows */
459                                 if ((ret = read(inputFd, &c, 1)) < 1)
460                                         return ret;
461
462                                 if (c == '[') { /* 91 */
463                                         if ((ret = read(inputFd, &c, 1)) < 1)
464                                                 return ret;
465
466                                         switch (c) {
467                                         case 'A':
468                                                 /* Up Arrow -- Get previous command */
469                                                 if (hp && hp->p) {
470                                                         free(hp->s);
471                                                         hp->s = strdup(parsenextc);
472                                                         hp = hp->p;
473                                                         goto hop;
474                                                 }
475                                                 break;
476                                         case 'B':
477                                                 /* Down Arrow -- Get next command */
478                                                 if (hp && hp->n && hp->n->s) {
479                                                         free(hp->s);
480                                                         hp->s = strdup(parsenextc);
481                                                         hp = hp->n;
482                                                         goto hop;
483                                                 }
484                                                 break;
485
486                                                 /* This is where we rewrite the line 
487                                                  * using the selected history item */
488                                           hop:
489                                                 len = strlen(parsenextc);
490
491                                                 /* return to begining of line */
492                                                 for (; cursor > 0; cursor--)
493                                                         xwrite(outputFd, "\b", 1);
494
495                                                 /* erase old command */
496                                                 for (j = 0; j < len; j++)
497                                                         xwrite(outputFd, " ", 1);
498
499                                                 /* return to begining of line */
500                                                 for (j = len; j > 0; j--)
501                                                         xwrite(outputFd, "\b", 1);
502
503                                                 memset(parsenextc, 0, BUFSIZ);
504                                                 len = strlen(parsenextc);
505                                                 /* write new command */
506                                                 strcpy(parsenextc, hp->s);
507                                                 len = strlen(hp->s);
508                                                 xwrite(outputFd, parsenextc, len);
509                                                 cursor = len;
510                                                 break;
511                                         case 'C':
512                                                 /* Right Arrow -- Move forward one character */
513                                                 if (cursor < len) {
514                                                         xwrite(outputFd, "\033[C", 3);
515                                                         cursor++;
516                                                 }
517                                                 break;
518                                         case 'D':
519                                                 /* Left Arrow -- Move back one character */
520                                                 if (cursor > 0) {
521                                                         xwrite(outputFd, "\033[D", 3);
522                                                         cursor--;
523                                                 }
524                                                 break;
525                                         case '3':
526                                                 /* Delete */
527                                                 if (cursor != len) {
528                                                         input_delete(outputFd, cursor);
529                                                         len--;
530                                                 }
531                                                 break;
532
533                                         //case '5': case '6': /* pgup/pgdown */
534
535                                         case '7':
536                                                 /* rxvt home */
537                                         case '1':
538                                                 /* Home (Ctrl-A) */
539                                                 input_home(outputFd, &cursor);
540                                                 break;
541                                         case '8':
542                                                 /* rxvt END */
543                                         case '4':
544                                                 /* End (Ctrl-E) */
545                                                 input_end(outputFd, &cursor, len);
546                                                 break;
547                                         }
548                                         if (c == '1' || c == '3' || c == '4')
549                                                 if ((ret = read(inputFd, &c, 1)) < 1)
550                                                         return ret;     /* read 126 (~) */
551                                 }
552                                 if (c == 'O') {
553                                         /* 79 */
554                                         if ((ret = read(inputFd, &c, 1)) < 1)
555                                                 return ret;
556                                         switch (c) {
557                                         case 'H':
558                                                 /* Home (xterm) */
559                                                 input_home(outputFd, &cursor);
560                                                 break;
561                                         case 'F':
562                                                 /* End (xterm) */
563                                                 input_end(outputFd, &cursor, len);
564                                                 break;
565                                         }
566                                 }
567                                 c = 0;
568                                 break;
569                         }
570
571                 default:                                /* If it's regular input, do the normal thing */
572
573                         if (!isprint(c))        /* Skip non-printable characters */
574                                 break;
575
576                         if (len >= (BUFSIZ - 2))        /* Need to leave space for enter */
577                                 break;
578
579                         len++;
580
581                         if (cursor == (len - 1)) {      /* Append if at the end of the line */
582                                 *(parsenextc + cursor) = c;
583                         } else {                        /* Insert otherwise */
584                                 memmove(parsenextc + cursor + 1, parsenextc + cursor,
585                                                 len - cursor - 1);
586
587                                 *(parsenextc + cursor) = c;
588
589                                 for (j = cursor; j < len; j++)
590                                         xwrite(outputFd, parsenextc + j, 1);
591                                 for (; j > cursor; j--)
592                                         xwrite(outputFd, "\033[D", 3);
593                         }
594
595                         cursor++;
596                         xwrite(outputFd, &c, 1);
597                         break;
598                 }
599                 if (c == '\t')
600                         lastWasTab = TRUE;
601                 else
602                         lastWasTab = FALSE;
603
604                 if (break_out)                  /* Enter is the command terminator, no more input. */
605                         break;
606         }
607
608         nr = len + 1;
609         xioctl(inputFd, TCSETA, (void *) &old_term);
610         reset_term = 0;
611
612
613         /* Handle command history log */
614         if (*(parsenextc)) {
615
616                 struct history *h = his_end;
617
618                 if (!h) {
619                         /* No previous history */
620                         h = his_front = malloc(sizeof(struct history));
621                         h->n = malloc(sizeof(struct history));
622
623                         h->p = NULL;
624                         h->s = strdup(parsenextc);
625                         h->n->p = h;
626                         h->n->n = NULL;
627                         h->n->s = NULL;
628                         his_end = h->n;
629                         history_counter++;
630                 } else {
631                         /* Add a new history command */
632                         h->n = malloc(sizeof(struct history));
633
634                         h->n->p = h;
635                         h->n->n = NULL;
636                         h->n->s = NULL;
637                         h->s = strdup(parsenextc);
638                         his_end = h->n;
639
640                         /* After max history, remove the oldest command */
641                         if (history_counter >= MAX_HISTORY) {
642
643                                 struct history *p = his_front->n;
644
645                                 p->p = NULL;
646                                 free(his_front->s);
647                                 free(his_front);
648                                 his_front = p;
649                         } else {
650                                 history_counter++;
651                         }
652                 }
653         }
654
655         return nr;
656 }
657
658 extern void cmdedit_init(void)
659 {
660         atexit(cmdedit_reset_term);
661         signal(SIGINT, prepareToDie);
662         signal(SIGQUIT, prepareToDie);
663         signal(SIGTERM, prepareToDie);
664 }
665 #endif                                                  /* BB_FEATURE_SH_COMMAND_EDITING */