Fix header file usage -- there were many unnecessary header files included in
[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.
29
30
31    Editor with vertical scrolling and completion by
32    Vladimir Oleynik. vodz@usa.net (c) 2001
33
34    Small bug: not true work if terminal size (x*y symbols) less
35               size (prompt + editor`s line + 2 symbols)
36  */
37
38
39
40 #include "busybox.h"
41
42 #ifdef BB_FEATURE_SH_COMMAND_EDITING
43
44 #include <stdio.h>
45 #include <errno.h>
46 #include <unistd.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <sys/ioctl.h>
50 #include <ctype.h>
51 #include <signal.h>
52
53 #ifdef BB_FEATURE_SH_TAB_COMPLETION
54 #include <sys/stat.h>
55 #endif
56
57 static const int MAX_HISTORY = 15;              /* Maximum length of the linked list for the command line history */
58
59 enum {
60         ESC = 27,
61         DEL = 127,
62 };
63
64 #define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0)
65 #define whitespace(c) (((c) == ' ') || ((c) == '\t'))
66
67 static struct history *his_front = NULL;        /* First element in command line list */
68 static struct history *his_end = NULL;  /* Last element in command line list */
69
70 /* ED: sparc termios is broken: revert back to old termio handling. */
71
72 #if #cpu(sparc)
73 #      include <termio.h>
74 #      define termios termio
75 #      define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
76 #      define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
77 #else
78 #      include <termios.h>
79 #      define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
80 #      define getTermSettings(fd,argp) tcgetattr(fd, argp);
81 #endif
82
83 /* Current termio and the previous termio before starting sh */
84 static struct termios initial_settings, new_settings;
85
86
87 #ifndef _POSIX_VDISABLE
88 #define _POSIX_VDISABLE '\0'
89 #endif
90
91
92 static
93 volatile int cmdedit_termw;       /* actual terminal width */
94 static   int history_counter = 0; /* Number of commands in history list */
95
96 static
97 volatile int handlers_sets = 0;    /* Set next bites
98                                      when atexit() has been called
99                                      and set many "terminates" signal handlers
100                                      and winchg signal handler
101                                      and if the terminal needs to be reset upon exit
102                                      */
103 enum {
104         SET_ATEXIT        = 1,
105         SET_TERM_HANDLERS = 2,
106         SET_WCHG_HANDLERS = 4,
107         SET_RESET_TERM    = 8,
108 };
109
110         
111 static   int cmdedit_x;           /* real x terminal position,
112                                    require put prompt in start x position */
113 static   int cmdedit_y;           /* pseudoreal y terminal position */
114 static   int cmdedit_prmt_len;    /* for fast running, without duplicate calculate */
115
116 static   int cursor;              /* required global for signal handler */
117 static   int len;                 /* --- "" - - "" - -"- --""-- --""--- */
118 static  char *command_ps;         /* --- "" - - "" - -"- --""-- --""--- */
119 static const char *cmdedit_prompt;/* --- "" - - "" - -"- --""-- --""--- */
120
121 /* Link into lash to reset context to 0
122  * on ^C and such */
123 extern unsigned int shell_context;
124
125
126 struct history {
127         char *s;
128         struct history *p;
129         struct history *n;
130 };
131
132 static void cmdedit_setwidth(int w, int redraw_flg);
133
134 static void win_changed(int nsig)
135 {
136         struct winsize win = { 0, 0, 0, 0 };
137         static __sighandler_t previous_SIGWINCH_handler; /* for reset */
138
139            /*   emulate      || signal call */
140         if(nsig == -SIGWINCH || nsig == SIGWINCH) {
141                 ioctl(0, TIOCGWINSZ, &win);
142                 if (win.ws_col > 0) {
143                         cmdedit_setwidth( win.ws_col, nsig == SIGWINCH );
144                         }
145         }
146                 /* Unix not all standart in recall signal */
147
148         if(nsig == -SIGWINCH)                   /* save previous handler */
149                 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
150         else if(nsig == SIGWINCH)              /* signaled called handler */
151                 signal(SIGWINCH, win_changed);  /* set for next call */
152         else                                   /* set previous handler */
153                 signal(SIGWINCH, previous_SIGWINCH_handler);   /* reset */
154 }
155
156 static void cmdedit_reset_term(void)
157 {
158         if((handlers_sets & SET_RESET_TERM)!=0) {
159                 /* sparc and other have broken termios support: use old termio handling. */
160                 setTermSettings(fileno(stdin), (void*) &initial_settings);
161                 handlers_sets &= ~SET_RESET_TERM;
162         }
163         if((handlers_sets & SET_WCHG_HANDLERS)!=0) {
164                 /* reset SIGWINCH handler to previous (default) */
165                 win_changed(0);
166                 handlers_sets &= ~SET_WCHG_HANDLERS;
167         }
168         fflush(stdout);
169 #ifdef BB_FEATURE_CLEAN_UP
170         if (his_front) {
171                 struct history *n;
172                 //while(his_front!=his_end) {
173                 while(his_front!=his_end) {
174                         n = his_front->n;
175                         free(his_front->s);
176                         free(his_front);
177                         his_front=n;
178                 }
179         }
180 #endif
181 }
182
183
184
185 /* special for recount position for scroll and remove terminal margin effect */
186 static void cmdedit_set_out_char(int c, int next_char) {
187         putchar(c);
188         if(++cmdedit_x>=cmdedit_termw) {
189                 /* terminal is scrolled down */
190                 cmdedit_y++;
191                 cmdedit_x=0;
192
193                 if(!next_char)
194                         next_char = ' ';
195                 /* destroy "(auto)margin" */
196                 putchar(next_char);
197                 putchar('\b');
198         }
199         cursor++;
200 }
201
202 /* Move to end line. Bonus: rewrite line from cursor without use
203    special control terminal strings, also saved size and speed! */
204 static void input_end (void) {
205         while(cursor < len)
206                 cmdedit_set_out_char(command_ps[cursor], 0);
207 }
208
209 /* Go to the next line */
210 static void goto_new_line(void) {
211         input_end();
212         cmdedit_set_out_char('\n', 0);
213 }
214
215
216 static inline void out1str(const char *s) { fputs  (s, stdout); }
217 static inline void beep   (void)          { putchar('\007');    }
218
219 /* Go to HOME position */
220 static void input_home(void)
221 {
222         while(cmdedit_y>0) {            /* up to start y */
223                 out1str("\033[A");
224                 cmdedit_y--;
225         }
226         putchar('\r');
227         cursor = 0;
228         out1str(cmdedit_prompt);
229         cmdedit_x = cmdedit_prmt_len;
230
231 }
232
233 /* Move back one charactor */
234 static void input_backward(void) {
235         if (cursor > 0) {
236                 cursor--;
237                 if(cmdedit_x!=0) {   /* no first position in terminal line */
238                         putchar('\b');
239                         cmdedit_x--;
240                         }
241                  else {
242                         out1str("\033[A");      /* up */
243                         cmdedit_y--;
244
245                         /* to end in current terminal line */
246                         while(cmdedit_x<(cmdedit_termw-1)) {
247                                 out1str("\033[C");
248                                 cmdedit_x++;
249                                 }
250                         }
251         }
252 }
253
254 /* Delete the char in front of the cursor */
255 static void input_delete(void)
256 {
257         int j = cursor;
258
259         if (j == len)
260                 return;
261         
262         memmove (command_ps + j, command_ps + j + 1, BUFSIZ - j - 1);
263         len--;
264         input_end();                            /* rewtite new line */
265         cmdedit_set_out_char(' ', 0);           /* destroy end char */
266         while (j < cursor)
267                 input_backward();               /* back to old pos cursor */
268 }
269
270 /* Delete the char in back of the cursor */
271 static void input_backspace(void)
272 {
273         if (cursor > 0) {
274                 input_backward();
275                 input_delete  ();
276         }
277 }
278
279
280 /* Move forward one charactor */
281 static void input_forward(void)
282 {
283     if (cursor < len)
284         cmdedit_set_out_char(command_ps[cursor], command_ps[cursor + 1]);
285 }
286
287
288 static void clean_up_and_die(int sig)
289 {
290         goto_new_line();
291         if (sig!=SIGINT)
292                 exit(EXIT_SUCCESS);  /* cmdedit_reset_term() called in atexit */
293         cmdedit_reset_term();
294 }
295
296 static void cmdedit_setwidth(int w, int redraw_flg)
297 {
298         cmdedit_termw = cmdedit_prmt_len+2;
299         if (w > cmdedit_termw) {
300
301                 cmdedit_termw = w;
302
303                 if(redraw_flg) {
304                         int sav_cursor = cursor;
305
306                         /* set variables for new terminal size */
307                         cmdedit_y = sav_cursor/w;
308                         cmdedit_x = sav_cursor-cmdedit_y*w;
309
310                         /* redraw */
311                         input_home();
312                         input_end();
313                         while(sav_cursor<cursor)
314                                 input_backward();
315                 }
316         } else {
317                 error_msg("\n*** Error: minimum screen width is %d\n", cmdedit_termw);
318         }
319 }
320
321 extern void cmdedit_init(void)
322 {
323         if((handlers_sets & SET_WCHG_HANDLERS)==0) {
324         /* emulate usage handler to set handler and call yours work */
325                 win_changed(-SIGWINCH);
326                 handlers_sets |= SET_WCHG_HANDLERS;
327         }
328
329         if((handlers_sets & SET_ATEXIT)==0) {
330                 atexit(cmdedit_reset_term);     /* be sure to do this only once */
331                 handlers_sets |= SET_ATEXIT;
332         }
333         if((handlers_sets & SET_TERM_HANDLERS)==0) {
334                 signal(SIGKILL, clean_up_and_die);
335                 signal(SIGINT,  clean_up_and_die);
336                 signal(SIGQUIT, clean_up_and_die);
337                 signal(SIGTERM, clean_up_and_die);
338                 handlers_sets |= SET_TERM_HANDLERS;
339         }
340 }
341
342 #ifdef BB_FEATURE_SH_TAB_COMPLETION
343
344 #ifdef BB_FEATURE_USERNAME_COMPLETION
345 static char** username_tab_completion(char *ud, int *num_matches)
346 {
347     static struct passwd *entry;
348     int                   userlen;
349         char **matches = (char **) NULL;
350     char                 *temp;
351     int                   nm = 0;
352
353     bb_setpwent ();
354     userlen = strlen (ud + 1);
355
356     while ((entry = bb_getpwent ()) != NULL) {
357         /* Null usernames should result in all users as possible completions. */
358         if (!userlen || !strncmp (ud + 1, entry->pw_name, userlen)) {
359
360                 temp = xmalloc (3 + strlen (entry->pw_name));
361                 sprintf(temp, "~%s/", entry->pw_name);
362
363                 matches = xrealloc(matches, (nm+1)*sizeof(char *));
364                 matches[nm++] = temp;
365         }
366     }
367
368     bb_endpwent ();
369     (*num_matches) = nm;
370         return (matches);
371 }
372 #endif
373
374 enum {
375         FIND_EXE_ONLY  = 0,
376         FIND_DIR_ONLY  = 1,
377         FIND_FILE_ONLY = 2,
378 };
379
380 #include <dirent.h>
381
382 static int path_parse(char ***p, int flags)
383 {
384         int  npth;
385         char *tmp;
386         char *pth;
387
388         if(flags!=FIND_EXE_ONLY || (pth=getenv("PATH"))==0) {
389         /* if not setenv PATH variable, to search cur dir "." */
390                 (*p) = xmalloc(sizeof(char *));
391                 (*p)[0] = xstrdup(".");
392                 return 1;
393         }
394
395         tmp = pth;
396         npth=0;
397
398         for(;;) {
399                 npth++; /* count words is + 1 count ':' */
400                 tmp = strchr(tmp, ':');
401                 if(tmp)
402                         tmp++;
403                 else
404                         break;
405         }
406
407         *p = xmalloc(npth*sizeof(char *));
408
409         tmp = pth;
410         (*p)[0] = xstrdup(tmp);
411         npth=1;                 /* count words is + 1 count ':' */
412
413         for(;;) {
414                 tmp = strchr(tmp, ':');
415                 if(tmp) {
416                         (*p)[0][(tmp-pth)]=0; /* ':' -> '\0'*/
417                         tmp++;
418                 } else
419                         break;
420                 (*p)[npth++] = &(*p)[0][(tmp-pth)]; /* p[next]=p[0][&'\0'+1] */
421         }
422
423         return npth;
424 }
425
426 static char** exe_n_cwd_tab_completion(char* command, int *num_matches, int type)
427 {
428         char *dirName;
429         char             **matches = 0;
430         DIR *dir;
431         struct dirent *next;
432         char               cmd   [BUFSIZ+4];
433         char              *dirbuf;
434         char               found [BUFSIZ+4];
435         int                nm = *num_matches;
436         struct stat        st;
437         char             **paths;
438         int                npaths;
439         int                i;
440         char               full_pth[BUFSIZ+4+PATH_MAX];
441
442
443         strcpy(cmd, command); /* save for change (last '/' to '\0') */
444
445         dirName = strrchr(cmd, '/');
446         if(dirName==NULL) {
447                 /* no dir, if flags==EXE_ONLY - get paths, else "." */
448                 npaths = path_parse(&paths, type);
449                 if(npaths==0)
450                         return 0;
451         } else {
452                 /* with dir */
453
454                 /* save dir */
455                 dirbuf = xstrdup(cmd);
456                 /* set only dirname */
457                 dirbuf[(dirName-cmd)+1]=0;
458
459                 /* strip dirname in cmd */
460                 strcpy(cmd, dirName+1);
461                         
462                 paths = xmalloc(sizeof(char*));
463                 paths[0] = dirbuf;
464                 npaths = 1;      /* only 1 dir */
465         }
466
467         for(i=0; i < npaths; i++) {
468
469                 dir = opendir(paths[i]);
470         if (!dir) {
471                 /* Don't print an error, just shut up and return */
472                 return (matches);
473         }
474         while ((next = readdir(dir)) != NULL) {
475                         /* matched ? */
476                         if(strncmp(next->d_name, cmd, strlen(cmd)))
477                                 continue;
478                         /* not see .name without .match */
479                         if(*next->d_name == '.' && *cmd != '.')
480                                 continue;
481                         sprintf(full_pth, "%s/%s", paths[i], next->d_name);
482                         /* hmm, remover in progress? */
483                         if(stat(full_pth, &st)<0)
484                                         continue;
485                         /* Cool, found a match. */
486                         if (S_ISDIR(st.st_mode)) {
487                                 /* name is directory */
488                                 strcpy(found, next->d_name);
489                                 strcat(found, "/");
490                                 if(type==FIND_DIR_ONLY)
491                                         strcat(found, " ");
492                         } else {
493                           /* not put found file if search only dirs for cd */
494                                 if(type==FIND_DIR_ONLY)
495                         continue;
496                                 strcpy(found, next->d_name);
497                                 strcat(found, " ");
498                 } 
499                         /* Add it to the list */
500                         matches = xrealloc(matches, (nm+1)*sizeof(char *));
501                         matches[nm++] = xstrdup(found);
502                 }
503         }
504         free(paths[0]); /* allocate memory only in first member */
505         free(paths);
506         *num_matches = nm;
507         return (matches);
508 }
509
510 static void input_tab(int lastWasTab)
511 {
512         /* Do TAB completion */
513         static int    num_matches;
514         static char **matches;
515
516         char          matchBuf[BUFSIZ];
517
518         int           pos = cursor;
519         int           find_type=FIND_FILE_ONLY;
520
521
522         if (lastWasTab == FALSE) {
523                 char *tmp, *tmp1;
524                 int len_found;
525
526                 /* For now, we will not bother with trying to distinguish
527                  * whether the cursor is in/at a command extression -- we
528                  * will always try all possible matches.  If you don't like
529                  * that then feel free to fix it.
530                  */
531
532                 /* Make a local copy of the string -- up 
533                  * to the position of the cursor */
534                 memset(matchBuf, 0, BUFSIZ);
535                 tmp = strncpy(matchBuf, command_ps, cursor);
536
537                 /* skip past any command seperator tokens */
538                 while ( (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
539                         tmp = ++tmp1;
540                 }
541
542                 /* skip any leading white space */
543                 while (*tmp == ' ')
544                         tmp++;
545
546                 if(strncmp(tmp, "cd ", 3)==0)
547                         find_type = FIND_DIR_ONLY;
548                  else if(strchr(tmp, ' ')==NULL)
549                         find_type = FIND_EXE_ONLY;
550
551                 /* find begin curent word */
552                 if( (tmp1=strrchr(tmp, ' ')) != NULL) {
553                         tmp = ++tmp1;
554                 }
555                 strcpy(matchBuf, tmp);
556
557                 /* Free up any memory already allocated */
558                 if (matches) {
559                         while(num_matches>0)
560                                 free(matches[--num_matches]);
561                         free(matches);
562                         matches = (char **) NULL;
563                 }
564
565 #ifdef BB_FEATURE_USERNAME_COMPLETION
566                 /* If the word starts with `~' and there is no slash in the word, 
567                  * then try completing this word as a username. */
568
569                 if (matchBuf[0]=='~' && strchr(matchBuf, '/')==0) {
570                         matches = username_tab_completion(matchBuf, &num_matches);
571                 }
572 #endif
573                 /* Try to match any executable in our path and everything 
574                  * in the current working directory that matches.  */
575                 if (!matches)
576                         matches = exe_n_cwd_tab_completion(matchBuf, &num_matches, find_type);
577
578                 /* Did we find exactly one match? */
579                 if(!matches || num_matches>1) {
580                         beep();
581                         return;
582                 }
583
584                 len_found = strlen(matches[0]);
585
586                 /* have space to placed match? */
587                 if ( (len_found-strlen(matchBuf)+len) < BUFSIZ ) {
588
589                         int recalc_pos = len;
590
591                         /* before word for match */
592                         command_ps[pos-strlen(matchBuf)]=0;
593
594                         /* tail line */
595                         strcpy(matchBuf, command_ps+pos);
596
597                         /* add match */
598                         strcat(command_ps, matches[0]);
599                         /* add tail */
600                         strcat(command_ps, matchBuf);
601
602                         /* write out the matched command */
603                         len=strlen(command_ps);
604                         recalc_pos = len-recalc_pos+pos;
605                         input_end();    /* write */
606                         while(recalc_pos<cursor)
607                                 input_backward();
608                         return;
609                 }
610         } else {
611                 /* Ok -- the last char was a TAB.  Since they
612                  * just hit TAB again, print a list of all the
613                  * available choices... */
614                 if ( matches && num_matches>0 ) {
615                         int i, col;
616                         int sav_cursor = cursor;
617
618                         /* Go to the next line */
619                         goto_new_line();
620                         for (i=0,col=0; i<num_matches; i++) {
621                                 printf("%s  ", matches[i]);
622                                 col += strlen(matches[i])+2;
623                                 col -= (col/cmdedit_termw)*cmdedit_termw;
624                                 if (col > 60 && matches[i+1] != NULL) {
625                                         putchar('\n');
626                                         col = 0;
627                                 }
628                         }
629                         /* Go to the next line and rewrite the prompt */
630                         printf("\n%s", cmdedit_prompt);
631                         cmdedit_x = cmdedit_prmt_len;
632                         cmdedit_y = 0;
633                         cursor    = 0;
634                         input_end();    /* Rewrite the command */
635                         /* Put the cursor back to where it used to be */
636                         while (sav_cursor < cursor)
637                                 input_backward();
638                 }
639         }
640 }
641 #endif
642
643 static void get_previous_history(struct history **hp, char* command)
644 {
645         if ((*hp)->s)
646                 free((*hp)->s);
647         (*hp)->s = strdup(command);
648         *hp = (*hp)->p;
649 }
650
651 static void get_next_history(struct history **hp, char* command)
652 {
653         if ((*hp)->s)
654                 free((*hp)->s);
655         (*hp)->s = strdup(command);
656         *hp = (*hp)->n;
657 }
658
659 /*
660  * This function is used to grab a character buffer
661  * from the input file descriptor and allows you to
662  * a string with full command editing (sortof like
663  * a mini readline).
664  *
665  * The following standard commands are not implemented:
666  * ESC-b -- Move back one word
667  * ESC-f -- Move forward one word
668  * ESC-d -- Delete back one word
669  * ESC-h -- Delete forward one word
670  * CTL-t -- Transpose two characters
671  *
672  * Furthermore, the "vi" command editing keys are not implemented.
673  *
674  * TODO: implement TAB command completion. :)
675  */
676 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
677 {
678
679         int inputFd=fileno(stdin);
680
681         int j = 0;
682         int break_out = 0;
683         int ret = 0;
684         int lastWasTab = FALSE;
685         char c = 0;
686         struct history *hp = his_end;
687
688         len = 0;
689         cursor = 0;
690         command_ps = command;
691
692         if (new_settings.c_cc[VMIN]==0) {
693                 
694                 getTermSettings(inputFd, (void*) &initial_settings);
695                 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
696                 new_settings.c_cc[VMIN] = 1;
697                 new_settings.c_cc[VTIME] = 0;
698                 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
699                 new_settings.c_lflag &= ~ICANON;        /* unbuffered input */
700                 new_settings.c_lflag &= ~(ECHO|ECHOCTL|ECHONL); /* Turn off echoing */
701         }
702         setTermSettings(inputFd, (void*) &new_settings);
703         handlers_sets |= SET_RESET_TERM;
704
705         memset(command, 0, BUFSIZ);
706
707         cmdedit_init();
708
709         /* Print out the command prompt */
710         cmdedit_prompt = prompt;
711         cmdedit_prmt_len = strlen(prompt);
712         printf("%s", prompt);
713         cmdedit_x = cmdedit_prmt_len;   /* count real x terminal position */
714         cmdedit_y = 0;                  /* quasireal y, not true work if line > xt*yt */
715
716
717         while (1) {
718
719                 fflush(stdout);         /* buffered out to fast */
720
721                 if ((ret = read(inputFd, &c, 1)) < 1)
722                         return;
723                 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
724
725                 switch (c) {
726                 case '\n':
727                 case '\r':
728                         /* Enter */
729                         *(command + len) = c;
730                         len++;
731                         input_end ();
732                         break_out = 1;
733                         break;
734                 case 1:
735                         /* Control-a -- Beginning of line */
736                         input_home();
737                         break;
738                 case 2:
739                         /* Control-b -- Move back one character */
740                         input_backward();
741                         break;
742                 case 3:
743                         /* Control-c -- stop gathering input */
744                         
745                         /* Link into lash to reset context to 0 on ^C and such */
746                         shell_context = 0;
747
748                         /* Go to the next line */
749                         goto_new_line();
750
751 #if 0
752                         /* Rewrite the prompt */
753                         printf("%s", prompt);
754
755                         /* Reset the command string */
756                         memset(command, 0, BUFSIZ);
757                         len = cursor = 0;
758 #endif
759                         return;
760
761                 case 4:
762                         /* Control-d -- Delete one character, or exit 
763                          * if the len=0 and no chars to delete */
764                         if (len == 0) {
765                                 printf("exit");
766                                 clean_up_and_die(0);
767                         } else {
768                                 input_delete();
769                         }
770                         break;
771                 case 5:
772                         /* Control-e -- End of line */
773                         input_end();
774                         break;
775                 case 6:
776                         /* Control-f -- Move forward one character */
777                         input_forward();
778                         break;
779                 case '\b':
780                 case DEL:
781                         /* Control-h and DEL */
782                         input_backspace();
783                         break;
784                 case '\t':
785 #ifdef BB_FEATURE_SH_TAB_COMPLETION
786                         input_tab(lastWasTab);
787 #endif
788                         break;
789                 case 14:
790                         /* Control-n -- Get next command in history */
791                         if (hp && hp->n && hp->n->s) {
792                                 get_next_history(&hp, command);
793                                 goto rewrite_line;
794                         } else {
795                                 beep();
796                         }
797                         break;
798                 case 16:
799                         /* Control-p -- Get previous command from history */
800                         if (hp && hp->p) {
801                                 get_previous_history(&hp, command);
802                                 goto rewrite_line;
803                         } else {
804                                 beep();
805                         }
806                         break;
807                 case ESC:{
808                                 /* escape sequence follows */
809                                 if ((ret = read(inputFd, &c, 1)) < 1)
810                                         return;
811
812                                 if (c == '[') { /* 91 */
813                                         if ((ret = read(inputFd, &c, 1)) < 1)
814                                                 return;
815
816                                         switch (c) {
817                                         case 'A':
818                                                 /* Up Arrow -- Get previous command from history */
819                                                 if (hp && hp->p) {
820                                                         get_previous_history(&hp, command);
821                                                         goto rewrite_line;
822                                                 } else {
823                                                         beep();
824                                                 }
825                                                 break;
826                                         case 'B':
827                                                 /* Down Arrow -- Get next command in history */
828                                                 if (hp && hp->n && hp->n->s) {
829                                                         get_next_history(&hp, command);
830                                                         goto rewrite_line;
831                                                 } else {
832                                                         beep();
833                                                 }
834                                                 break;
835
836                                                 /* Rewrite the line with the selected history item */
837                                           rewrite_line:
838                                                 /* return to begin of line */
839                                                 input_home ();
840                                                 /* for next memmoves without set '\0' */
841                                                 memset (command, 0, BUFSIZ);
842                                                 /* change command */
843                                                 strcpy (command, hp->s);
844                                                 /* write new command */
845                                                 for (j=0; command[j]; j++)
846                                                         cmdedit_set_out_char(command[j], 0);
847                                                 ret = cursor;
848                                                 /* erase tail if required */
849                                                 for (j = ret; j < len; j++)
850                                                         cmdedit_set_out_char(' ', 0);
851                                                 /* and backward cursor */
852                                                 for (j = ret; j < len; j++)
853                                                         input_backward();
854                                                 len = cursor;                           /* set new len */
855                                                 break;
856                                         case 'C':
857                                                 /* Right Arrow -- Move forward one character */
858                                                 input_forward();
859                                                 break;
860                                         case 'D':
861                                                 /* Left Arrow -- Move back one character */
862                                                 input_backward();
863                                                 break;
864                                         case '3':
865                                                 /* Delete */
866                                                 input_delete();
867                                                 break;
868                                         case '1':
869                                                 /* Home (Ctrl-A) */
870                                                 input_home();
871                                                 break;
872                                         case '4':
873                                                 /* End (Ctrl-E) */
874                                                 input_end();
875                                                 break;
876                                         default:
877                                                 beep();
878                                         }
879                                         if (c == '1' || c == '3' || c == '4')
880                                                 if ((ret = read(inputFd, &c, 1)) < 1)
881                                                         return; /* read 126 (~) */
882                                 }
883                                 if (c == 'O') {
884                                         /* 79 */
885                                         if ((ret = read(inputFd, &c, 1)) < 1)
886                                                 return;
887                                         switch (c) {
888                                         case 'H':
889                                                 /* Home (xterm) */
890                                                 input_home();
891                                                 break;
892                                         case 'F':
893                                                 /* End (xterm) */
894                                                 input_end();
895                                                 break;
896                                         default:
897                                                 beep();
898                                         }
899                                 }
900                                 c = 0;
901                                 break;
902                         }
903
904                 default:                                /* If it's regular input, do the normal thing */
905
906                         if (!isprint(c)) {      /* Skip non-printable characters */
907                                 break;
908                         }
909
910                         if (len >= (BUFSIZ - 2))        /* Need to leave space for enter */
911                                 break;
912
913                         len++;
914
915                         if (cursor == (len - 1)) {      /* Append if at the end of the line */
916                                 *(command + cursor) = c;
917                                 cmdedit_set_out_char(c, command[cursor+1]);
918                         } else {                        /* Insert otherwise */
919                                 memmove(command + cursor + 1, command + cursor,
920                                                 len - cursor - 1);
921
922                                 *(command + cursor) = c;
923                                 j = cursor+1;
924                                 /* rewrite from cursor */
925                                 input_end ();
926                                 /* to prev x pos + 1 */
927                                 while(cursor > j)
928                                         input_backward();
929                         }
930
931                         break;
932                 }
933                 if (c == '\t')
934                         lastWasTab = TRUE;
935                 else
936                         lastWasTab = FALSE;
937
938                 if (break_out)                  /* Enter is the command terminator, no more input. */
939                         break;
940         }
941
942         setTermSettings (inputFd, (void *) &initial_settings);
943         handlers_sets &= ~SET_RESET_TERM;
944
945         /* Handle command history log */
946         if (len>1) {    /* no put empty line (only '\n') */
947
948                 struct history *h = his_end;
949                 char           *ss;
950
951                 command[len-1] = 0;     /* destroy end '\n' */
952                 ss = strdup(command);   /* duplicate without '\n' */
953                 command[len-1] = '\n';  /* restore '\n' */
954
955                 if (!h) {
956                         /* No previous history -- this memory is never freed */
957                         h = his_front = xmalloc(sizeof(struct history));
958                         h->n = xmalloc(sizeof(struct history));
959
960                         h->p = NULL;
961                         h->s = ss;
962                         h->n->p = h;
963                         h->n->n = NULL;
964                         h->n->s = NULL;
965                         his_end = h->n;
966                         history_counter++;
967                 } else {
968                         /* Add a new history command -- this memory is never freed */
969                         h->n = xmalloc(sizeof(struct history));
970
971                         h->n->p = h;
972                         h->n->n = NULL;
973                         h->n->s = NULL;
974                         h->s = ss;
975                         his_end = h->n;
976
977                         /* After max history, remove the oldest command */
978                         if (history_counter >= MAX_HISTORY) {
979
980                                 struct history *p = his_front->n;
981
982                                 p->p = NULL;
983                                 free(his_front->s);
984                                 free(his_front);
985                                 his_front = p;
986                         } else {
987                                 history_counter++;
988                         }
989                 }
990         }
991
992         return;
993 }
994
995
996 /* Undo the effects of cmdedit_init(). */
997 extern void cmdedit_terminate(void)
998 {
999         cmdedit_reset_term();
1000         if((handlers_sets & SET_TERM_HANDLERS)!=0) {
1001                 signal(SIGKILL, SIG_DFL);
1002                 signal(SIGINT, SIG_DFL);
1003                 signal(SIGQUIT, SIG_DFL);
1004                 signal(SIGTERM, SIG_DFL);
1005                 signal(SIGWINCH, SIG_DFL);
1006                 handlers_sets &= ~SET_TERM_HANDLERS;
1007         }
1008 }
1009
1010
1011
1012 #endif  /* BB_FEATURE_SH_COMMAND_EDITING */