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