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