1 /* vi: set sw=4 ts=4: */
3 * Termios command line History and Editting, originally
4 * intended for NetBSD sh (ash)
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>
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.
16 * v 0.5 19990328 Initial release
18 * Future plans: Simple file and path name completion. (like BASH)
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.
31 Editor with vertical scrolling and completion by
32 Vladimir Oleynik. vodz@usa.net (c) 2001
34 Small bug: not true work if terminal size (x*y symbols) less
35 size (prompt + editor`s line + 2 symbols)
42 #ifdef BB_FEATURE_SH_COMMAND_EDITING
49 #include <sys/ioctl.h>
53 #ifdef BB_FEATURE_SH_TAB_COMPLETION
57 #ifdef BB_FEATURE_USERNAME_COMPLETION
61 static const int MAX_HISTORY = 15; /* Maximum length of the linked list for the command line history */
68 #define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0)
69 #define whitespace(c) (((c) == ' ') || ((c) == '\t'))
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 */
74 /* ED: sparc termios is broken: revert back to old termio handling. */
78 # define termios termio
79 # define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
80 # define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
83 # define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
84 # define getTermSettings(fd,argp) tcgetattr(fd, argp);
87 /* Current termio and the previous termio before starting sh */
88 static struct termios initial_settings, new_settings;
91 #ifndef _POSIX_VDISABLE
92 #define _POSIX_VDISABLE '\0'
97 volatile int cmdedit_termw; /* actual terminal width */
98 static int history_counter = 0; /* Number of commands in history list */
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
109 SET_TERM_HANDLERS = 2,
110 SET_WCHG_HANDLERS = 4,
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 */
120 static int cursor; /* required global for signal handler */
121 static int len; /* --- "" - - "" - -"- --""-- --""--- */
122 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
123 static const char *cmdedit_prompt;/* --- "" - - "" - -"- --""-- --""--- */
125 /* Link into lash to reset context to 0
127 extern unsigned int shell_context;
136 static void cmdedit_setwidth(int w, int redraw_flg);
138 static void win_changed(int nsig)
140 struct winsize win = { 0, 0, 0, 0 };
141 static __sighandler_t previous_SIGWINCH_handler; /* for reset */
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 );
150 /* Unix not all standart in recall signal */
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 */
160 static void cmdedit_reset_term(void)
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;
167 if((handlers_sets & SET_WCHG_HANDLERS)!=0) {
168 /* reset SIGWINCH handler to previous (default) */
170 handlers_sets &= ~SET_WCHG_HANDLERS;
173 #ifdef BB_FEATURE_CLEAN_UP
176 //while(his_front!=his_end) {
177 while(his_front!=his_end) {
189 /* special for recount position for scroll and remove terminal margin effect */
190 static void cmdedit_set_out_char(int c, int next_char) {
192 if(++cmdedit_x>=cmdedit_termw) {
193 /* terminal is scrolled down */
199 /* destroy "(auto)margin" */
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) {
210 cmdedit_set_out_char(command_ps[cursor], 0);
213 /* Go to the next line */
214 static void goto_new_line(void) {
216 cmdedit_set_out_char('\n', 0);
220 static inline void out1str(const char *s) { fputs (s, stdout); }
221 static inline void beep (void) { putchar('\007'); }
223 /* Go to HOME position */
224 static void input_home(void)
226 while(cmdedit_y>0) { /* up to start y */
232 out1str(cmdedit_prompt);
233 cmdedit_x = cmdedit_prmt_len;
237 /* Move back one charactor */
238 static void input_backward(void) {
241 if(cmdedit_x!=0) { /* no first position in terminal line */
246 out1str("\033[A"); /* up */
249 /* to end in current terminal line */
250 while(cmdedit_x<(cmdedit_termw-1)) {
258 /* Delete the char in front of the cursor */
259 static void input_delete(void)
266 memmove (command_ps + j, command_ps + j + 1, BUFSIZ - j - 1);
268 input_end(); /* rewtite new line */
269 cmdedit_set_out_char(' ', 0); /* destroy end char */
271 input_backward(); /* back to old pos cursor */
274 /* Delete the char in back of the cursor */
275 static void input_backspace(void)
284 /* Move forward one charactor */
285 static void input_forward(void)
288 cmdedit_set_out_char(command_ps[cursor], command_ps[cursor + 1]);
292 static void clean_up_and_die(int sig)
296 exit(EXIT_SUCCESS); /* cmdedit_reset_term() called in atexit */
297 cmdedit_reset_term();
300 static void cmdedit_setwidth(int w, int redraw_flg)
302 cmdedit_termw = cmdedit_prmt_len+2;
303 if (w > cmdedit_termw) {
308 int sav_cursor = cursor;
310 /* set variables for new terminal size */
311 cmdedit_y = sav_cursor/w;
312 cmdedit_x = sav_cursor-cmdedit_y*w;
317 while(sav_cursor<cursor)
321 error_msg("\n*** Error: minimum screen width is %d\n", cmdedit_termw);
325 extern void cmdedit_init(void)
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;
333 if((handlers_sets & SET_ATEXIT)==0) {
334 atexit(cmdedit_reset_term); /* be sure to do this only once */
335 handlers_sets |= SET_ATEXIT;
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;
346 #ifdef BB_FEATURE_SH_TAB_COMPLETION
348 #ifdef BB_FEATURE_USERNAME_COMPLETION
349 static char** username_tab_completion(char *ud, int *num_matches)
351 static struct passwd *entry;
353 char **matches = (char **) NULL;
358 userlen = strlen (ud + 1);
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)) {
364 temp = xmalloc (3 + strlen (entry->pw_name));
365 sprintf(temp, "~%s/", entry->pw_name);
367 matches = xrealloc(matches, (nm+1)*sizeof(char *));
368 matches[nm++] = temp;
386 static int path_parse(char ***p, int flags)
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(".");
403 npth++; /* count words is + 1 count ':' */
404 tmp = strchr(tmp, ':');
411 *p = xmalloc(npth*sizeof(char *));
414 (*p)[0] = xstrdup(tmp);
415 npth=1; /* count words is + 1 count ':' */
418 tmp = strchr(tmp, ':');
420 (*p)[0][(tmp-pth)]=0; /* ':' -> '\0'*/
424 (*p)[npth++] = &(*p)[0][(tmp-pth)]; /* p[next]=p[0][&'\0'+1] */
430 static char** exe_n_cwd_tab_completion(char* command, int *num_matches, int type)
438 char found [BUFSIZ+4];
439 int nm = *num_matches;
444 char full_pth[BUFSIZ+4+PATH_MAX];
447 strcpy(cmd, command); /* save for change (last '/' to '\0') */
449 dirName = strrchr(cmd, '/');
451 /* no dir, if flags==EXE_ONLY - get paths, else "." */
452 npaths = path_parse(&paths, type);
459 dirbuf = xstrdup(cmd);
460 /* set only dirname */
461 dirbuf[(dirName-cmd)+1]=0;
463 /* strip dirname in cmd */
464 strcpy(cmd, dirName+1);
466 paths = xmalloc(sizeof(char*));
468 npaths = 1; /* only 1 dir */
471 for(i=0; i < npaths; i++) {
473 dir = opendir(paths[i]);
475 /* Don't print an error, just shut up and return */
478 while ((next = readdir(dir)) != NULL) {
480 if(strncmp(next->d_name, cmd, strlen(cmd)))
482 /* not see .name without .match */
483 if(*next->d_name == '.' && *cmd != '.')
485 sprintf(full_pth, "%s/%s", paths[i], next->d_name);
486 /* hmm, remover in progress? */
487 if(stat(full_pth, &st)<0)
489 /* Cool, found a match. */
490 if (S_ISDIR(st.st_mode)) {
491 /* name is directory */
492 strcpy(found, next->d_name);
494 if(type==FIND_DIR_ONLY)
497 /* not put found file if search only dirs for cd */
498 if(type==FIND_DIR_ONLY)
500 strcpy(found, next->d_name);
503 /* Add it to the list */
504 matches = xrealloc(matches, (nm+1)*sizeof(char *));
505 matches[nm++] = xstrdup(found);
508 free(paths[0]); /* allocate memory only in first member */
514 static void input_tab(int lastWasTab)
516 /* Do TAB completion */
517 static int num_matches;
518 static char **matches;
520 char matchBuf[BUFSIZ];
523 int find_type=FIND_FILE_ONLY;
526 if (lastWasTab == FALSE) {
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.
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);
541 /* skip past any command seperator tokens */
542 while ( (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
546 /* skip any leading white space */
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;
555 /* find begin curent word */
556 if( (tmp1=strrchr(tmp, ' ')) != NULL) {
559 strcpy(matchBuf, tmp);
561 /* Free up any memory already allocated */
564 free(matches[--num_matches]);
566 matches = (char **) NULL;
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. */
573 if (matchBuf[0]=='~' && strchr(matchBuf, '/')==0) {
574 matches = username_tab_completion(matchBuf, &num_matches);
577 /* Try to match any executable in our path and everything
578 * in the current working directory that matches. */
580 matches = exe_n_cwd_tab_completion(matchBuf, &num_matches, find_type);
582 /* Did we find exactly one match? */
583 if(!matches || num_matches>1) {
588 len_found = strlen(matches[0]);
590 /* have space to placed match? */
591 if ( (len_found-strlen(matchBuf)+len) < BUFSIZ ) {
593 int recalc_pos = len;
595 /* before word for match */
596 command_ps[pos-strlen(matchBuf)]=0;
599 strcpy(matchBuf, command_ps+pos);
602 strcat(command_ps, matches[0]);
604 strcat(command_ps, matchBuf);
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)
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 ) {
620 int sav_cursor = cursor;
622 /* Go to the next 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) {
633 /* Go to the next line and rewrite the prompt */
634 printf("\n%s", cmdedit_prompt);
635 cmdedit_x = cmdedit_prmt_len;
638 input_end(); /* Rewrite the command */
639 /* Put the cursor back to where it used to be */
640 while (sav_cursor < cursor)
647 static void get_previous_history(struct history **hp, char* command)
651 (*hp)->s = strdup(command);
655 static void get_next_history(struct history **hp, char* command)
659 (*hp)->s = strdup(command);
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
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
676 * Furthermore, the "vi" command editing keys are not implemented.
678 * TODO: implement TAB command completion. :)
680 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
683 int inputFd=fileno(stdin);
688 int lastWasTab = FALSE;
690 struct history *hp = his_end;
694 command_ps = command;
696 if (new_settings.c_cc[VMIN]==0) {
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 */
706 setTermSettings(inputFd, (void*) &new_settings);
707 handlers_sets |= SET_RESET_TERM;
709 memset(command, 0, BUFSIZ);
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 */
723 fflush(stdout); /* buffered out to fast */
725 if ((ret = read(inputFd, &c, 1)) < 1)
727 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
733 *(command + len) = c;
739 /* Control-a -- Beginning of line */
743 /* Control-b -- Move back one character */
747 /* Control-c -- stop gathering input */
749 /* Link into lash to reset context to 0 on ^C and such */
752 /* Go to the next line */
756 /* Rewrite the prompt */
757 printf("%s", prompt);
759 /* Reset the command string */
760 memset(command, 0, BUFSIZ);
766 /* Control-d -- Delete one character, or exit
767 * if the len=0 and no chars to delete */
776 /* Control-e -- End of line */
780 /* Control-f -- Move forward one character */
785 /* Control-h and DEL */
789 #ifdef BB_FEATURE_SH_TAB_COMPLETION
790 input_tab(lastWasTab);
794 /* Control-n -- Get next command in history */
795 if (hp && hp->n && hp->n->s) {
796 get_next_history(&hp, command);
803 /* Control-p -- Get previous command from history */
805 get_previous_history(&hp, command);
812 /* escape sequence follows */
813 if ((ret = read(inputFd, &c, 1)) < 1)
816 if (c == '[') { /* 91 */
817 if ((ret = read(inputFd, &c, 1)) < 1)
822 /* Up Arrow -- Get previous command from history */
824 get_previous_history(&hp, command);
831 /* Down Arrow -- Get next command in history */
832 if (hp && hp->n && hp->n->s) {
833 get_next_history(&hp, command);
840 /* Rewrite the line with the selected history item */
842 /* return to begin of line */
844 /* for next memmoves without set '\0' */
845 memset (command, 0, BUFSIZ);
847 strcpy (command, hp->s);
848 /* write new command */
849 for (j=0; command[j]; j++)
850 cmdedit_set_out_char(command[j], 0);
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++)
858 len = cursor; /* set new len */
861 /* Right Arrow -- Move forward one character */
865 /* Left Arrow -- Move back one character */
883 if (c == '1' || c == '3' || c == '4')
884 if ((ret = read(inputFd, &c, 1)) < 1)
885 return; /* read 126 (~) */
889 if ((ret = read(inputFd, &c, 1)) < 1)
908 default: /* If it's regular input, do the normal thing */
910 if (!isprint(c)) { /* Skip non-printable characters */
914 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
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,
926 *(command + cursor) = c;
928 /* rewrite from cursor */
930 /* to prev x pos + 1 */
942 if (break_out) /* Enter is the command terminator, no more input. */
946 setTermSettings (inputFd, (void *) &initial_settings);
947 handlers_sets &= ~SET_RESET_TERM;
949 /* Handle command history log */
950 if (len>1) { /* no put empty line (only '\n') */
952 struct history *h = his_end;
955 command[len-1] = 0; /* destroy end '\n' */
956 ss = strdup(command); /* duplicate without '\n' */
957 command[len-1] = '\n'; /* restore '\n' */
960 /* No previous history -- this memory is never freed */
961 h = his_front = xmalloc(sizeof(struct history));
962 h->n = xmalloc(sizeof(struct history));
972 /* Add a new history command -- this memory is never freed */
973 h->n = xmalloc(sizeof(struct history));
981 /* After max history, remove the oldest command */
982 if (history_counter >= MAX_HISTORY) {
984 struct history *p = his_front->n;
1000 /* Undo the effects of cmdedit_init(). */
1001 extern void cmdedit_terminate(void)
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;
1016 #endif /* BB_FEATURE_SH_COMMAND_EDITING */