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