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