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 #include "pwd_grp/pwd.h"
60 static const int MAX_HISTORY = 15; /* Maximum length of the linked list for the command line history */
67 #define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0)
68 #define whitespace(c) (((c) == ' ') || ((c) == '\t'))
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 */
73 /* ED: sparc termios is broken: revert back to old termio handling. */
77 # define termios termio
78 # define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
79 # define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
82 # define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
83 # define getTermSettings(fd,argp) tcgetattr(fd, argp);
86 /* Current termio and the previous termio before starting sh */
87 static struct termios initial_settings, new_settings;
90 #ifndef _POSIX_VDISABLE
91 #define _POSIX_VDISABLE '\0'
96 volatile int cmdedit_termw; /* actual terminal width */
97 static int history_counter = 0; /* Number of commands in history list */
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
108 SET_TERM_HANDLERS = 2,
109 SET_WCHG_HANDLERS = 4,
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 */
119 static int cursor; /* required global for signal handler */
120 static int len; /* --- "" - - "" - -"- --""-- --""--- */
121 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
122 static const char *cmdedit_prompt;/* --- "" - - "" - -"- --""-- --""--- */
124 /* Link into lash to reset context to 0
126 extern unsigned int shell_context;
135 static void cmdedit_setwidth(int w, int redraw_flg);
137 static void win_changed(int nsig)
139 struct winsize win = { 0, 0, 0, 0 };
140 static __sighandler_t previous_SIGWINCH_handler; /* for reset */
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 );
148 /* Default to 79 if their console doesn't want to share */
149 cmdedit_setwidth( 79, nsig == SIGWINCH );
153 /* Unix not all standart in recall signal */
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 */
163 static void cmdedit_reset_term(void)
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;
170 if((handlers_sets & SET_WCHG_HANDLERS)!=0) {
171 /* reset SIGWINCH handler to previous (default) */
173 handlers_sets &= ~SET_WCHG_HANDLERS;
176 #ifdef BB_FEATURE_CLEAN_UP
179 //while(his_front!=his_end) {
180 while(his_front!=his_end) {
192 /* special for recount position for scroll and remove terminal margin effect */
193 static void cmdedit_set_out_char(int c, int next_char) {
195 if(++cmdedit_x>=cmdedit_termw) {
196 /* terminal is scrolled down */
202 /* destroy "(auto)margin" */
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) {
213 cmdedit_set_out_char(command_ps[cursor], 0);
216 /* Go to the next line */
217 static void goto_new_line(void) {
219 cmdedit_set_out_char('\n', 0);
223 static inline void out1str(const char *s) { fputs (s, stdout); }
224 static inline void beep (void) { putchar('\007'); }
226 /* Go to HOME position */
227 static void input_home(void)
229 while(cmdedit_y>0) { /* up to start y */
235 out1str(cmdedit_prompt);
236 cmdedit_x = cmdedit_prmt_len;
240 /* Move back one charactor */
241 static void input_backward(void) {
244 if(cmdedit_x!=0) { /* no first position in terminal line */
249 out1str("\033[A"); /* up */
252 /* to end in current terminal line */
253 while(cmdedit_x<(cmdedit_termw-1)) {
261 /* Delete the char in front of the cursor */
262 static void input_delete(void)
269 memmove (command_ps + j, command_ps + j + 1, BUFSIZ - j - 1);
271 input_end(); /* rewtite new line */
272 cmdedit_set_out_char(' ', 0); /* destroy end char */
274 input_backward(); /* back to old pos cursor */
277 /* Delete the char in back of the cursor */
278 static void input_backspace(void)
287 /* Move forward one charactor */
288 static void input_forward(void)
291 cmdedit_set_out_char(command_ps[cursor], command_ps[cursor + 1]);
295 static void clean_up_and_die(int sig)
299 exit(EXIT_SUCCESS); /* cmdedit_reset_term() called in atexit */
300 cmdedit_reset_term();
303 static void cmdedit_setwidth(int w, int redraw_flg)
305 cmdedit_termw = cmdedit_prmt_len+2;
306 if (w > cmdedit_termw) {
311 int sav_cursor = cursor;
313 /* set variables for new terminal size */
314 cmdedit_y = sav_cursor/w;
315 cmdedit_x = sav_cursor-cmdedit_y*w;
320 while(sav_cursor<cursor)
324 error_msg("\n*** Error: minimum screen width is %d", cmdedit_termw);
328 extern void cmdedit_init(void)
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;
336 if((handlers_sets & SET_ATEXIT)==0) {
337 atexit(cmdedit_reset_term); /* be sure to do this only once */
338 handlers_sets |= SET_ATEXIT;
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;
349 #ifdef BB_FEATURE_SH_TAB_COMPLETION
351 #ifdef BB_FEATURE_USERNAME_COMPLETION
352 static char** username_tab_completion(char *ud, int *num_matches)
354 static struct passwd *entry;
356 char **matches = (char **) NULL;
361 userlen = strlen (ud + 1);
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)) {
367 temp = xmalloc (3 + strlen (entry->pw_name));
368 sprintf(temp, "~%s/", entry->pw_name);
370 matches = xrealloc(matches, (nm+1)*sizeof(char *));
371 matches[nm++] = temp;
389 static int path_parse(char ***p, int flags)
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(".");
406 npth++; /* count words is + 1 count ':' */
407 tmp = strchr(tmp, ':');
414 *p = xmalloc(npth*sizeof(char *));
417 (*p)[0] = xstrdup(tmp);
418 npth=1; /* count words is + 1 count ':' */
421 tmp = strchr(tmp, ':');
423 (*p)[0][(tmp-pth)]=0; /* ':' -> '\0'*/
427 (*p)[npth++] = &(*p)[0][(tmp-pth)]; /* p[next]=p[0][&'\0'+1] */
433 static char** exe_n_cwd_tab_completion(char* command, int *num_matches, int type)
441 char found [BUFSIZ+4];
442 int nm = *num_matches;
447 char full_pth[BUFSIZ+4+PATH_MAX];
450 strcpy(cmd, command); /* save for change (last '/' to '\0') */
452 dirName = strrchr(cmd, '/');
454 /* no dir, if flags==EXE_ONLY - get paths, else "." */
455 npaths = path_parse(&paths, type);
462 dirbuf = xstrdup(cmd);
463 /* set only dirname */
464 dirbuf[(dirName-cmd)+1]=0;
466 /* strip dirname in cmd */
467 strcpy(cmd, dirName+1);
469 paths = xmalloc(sizeof(char*));
471 npaths = 1; /* only 1 dir */
474 for(i=0; i < npaths; i++) {
476 dir = opendir(paths[i]);
478 /* Don't print an error, just shut up and return */
481 while ((next = readdir(dir)) != NULL) {
483 if(strncmp(next->d_name, cmd, strlen(cmd)))
485 /* not see .name without .match */
486 if(*next->d_name == '.' && *cmd != '.')
488 sprintf(full_pth, "%s/%s", paths[i], next->d_name);
489 /* hmm, remover in progress? */
490 if(stat(full_pth, &st)<0)
492 /* Cool, found a match. */
493 if (S_ISDIR(st.st_mode)) {
494 /* name is directory */
495 strcpy(found, next->d_name);
497 if(type==FIND_DIR_ONLY)
500 /* not put found file if search only dirs for cd */
501 if(type==FIND_DIR_ONLY)
503 strcpy(found, next->d_name);
506 /* Add it to the list */
507 matches = xrealloc(matches, (nm+1)*sizeof(char *));
508 matches[nm++] = xstrdup(found);
511 free(paths[0]); /* allocate memory only in first member */
517 static void input_tab(int lastWasTab)
519 /* Do TAB completion */
520 static int num_matches;
521 static char **matches;
523 char matchBuf[BUFSIZ];
526 int find_type=FIND_FILE_ONLY;
529 if (lastWasTab == FALSE) {
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.
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);
544 /* skip past any command seperator tokens */
545 while ( (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
549 /* skip any leading white space */
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;
558 /* find begin curent word */
559 if( (tmp1=strrchr(tmp, ' ')) != NULL) {
562 strcpy(matchBuf, tmp);
564 /* Free up any memory already allocated */
567 free(matches[--num_matches]);
569 matches = (char **) NULL;
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. */
576 if (matchBuf[0]=='~' && strchr(matchBuf, '/')==0) {
577 matches = username_tab_completion(matchBuf, &num_matches);
580 /* Try to match any executable in our path and everything
581 * in the current working directory that matches. */
583 matches = exe_n_cwd_tab_completion(matchBuf, &num_matches, find_type);
585 /* Did we find exactly one match? */
586 if(!matches || num_matches>1) {
591 len_found = strlen(matches[0]);
593 /* have space to placed match? */
594 if ( (len_found-strlen(matchBuf)+len) < BUFSIZ ) {
596 int recalc_pos = len;
598 /* before word for match */
599 command_ps[pos-strlen(matchBuf)]=0;
602 strcpy(matchBuf, command_ps+pos);
605 strcat(command_ps, matches[0]);
607 strcat(command_ps, matchBuf);
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)
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 ) {
623 int sav_cursor = cursor;
625 /* Go to the next 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) {
636 /* Go to the next line and rewrite the prompt */
637 printf("\n%s", cmdedit_prompt);
638 cmdedit_x = cmdedit_prmt_len;
641 input_end(); /* Rewrite the command */
642 /* Put the cursor back to where it used to be */
643 while (sav_cursor < cursor)
650 static void get_previous_history(struct history **hp, char* command)
654 (*hp)->s = strdup(command);
658 static void get_next_history(struct history **hp, char* command)
662 (*hp)->s = strdup(command);
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
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
679 * Furthermore, the "vi" command editing keys are not implemented.
681 * TODO: implement TAB command completion. :)
683 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
686 int inputFd=fileno(stdin);
691 int lastWasTab = FALSE;
693 struct history *hp = his_end;
697 command_ps = command;
699 if (new_settings.c_cc[VMIN]==0) {
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 */
709 setTermSettings(inputFd, (void*) &new_settings);
710 handlers_sets |= SET_RESET_TERM;
712 memset(command, 0, BUFSIZ);
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 */
726 fflush(stdout); /* buffered out to fast */
728 if ((ret = read(inputFd, &c, 1)) < 1)
730 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
736 *(command + len) = c;
742 /* Control-a -- Beginning of line */
746 /* Control-b -- Move back one character */
750 /* Control-c -- stop gathering input */
752 /* Link into lash to reset context to 0 on ^C and such */
755 /* Go to the next line */
759 /* Rewrite the prompt */
760 printf("%s", prompt);
762 /* Reset the command string */
763 memset(command, 0, BUFSIZ);
769 /* Control-d -- Delete one character, or exit
770 * if the len=0 and no chars to delete */
779 /* Control-e -- End of line */
783 /* Control-f -- Move forward one character */
788 /* Control-h and DEL */
792 #ifdef BB_FEATURE_SH_TAB_COMPLETION
793 input_tab(lastWasTab);
797 /* Control-n -- Get next command in history */
798 if (hp && hp->n && hp->n->s) {
799 get_next_history(&hp, command);
806 /* Control-p -- Get previous command from history */
808 get_previous_history(&hp, command);
815 /* escape sequence follows */
816 if ((ret = read(inputFd, &c, 1)) < 1)
819 if (c == '[') { /* 91 */
820 if ((ret = read(inputFd, &c, 1)) < 1)
825 /* Up Arrow -- Get previous command from history */
827 get_previous_history(&hp, command);
834 /* Down Arrow -- Get next command in history */
835 if (hp && hp->n && hp->n->s) {
836 get_next_history(&hp, command);
843 /* Rewrite the line with the selected history item */
845 /* return to begin of line */
847 /* for next memmoves without set '\0' */
848 memset (command, 0, BUFSIZ);
850 strcpy (command, hp->s);
851 /* write new command */
852 for (j=0; command[j]; j++)
853 cmdedit_set_out_char(command[j], 0);
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++)
861 len = cursor; /* set new len */
864 /* Right Arrow -- Move forward one character */
868 /* Left Arrow -- Move back one character */
886 if (c == '1' || c == '3' || c == '4')
887 if ((ret = read(inputFd, &c, 1)) < 1)
888 return; /* read 126 (~) */
892 if ((ret = read(inputFd, &c, 1)) < 1)
911 default: /* If it's regular input, do the normal thing */
913 if (!isprint(c)) { /* Skip non-printable characters */
917 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
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,
929 *(command + cursor) = c;
931 /* rewrite from cursor */
933 /* to prev x pos + 1 */
945 if (break_out) /* Enter is the command terminator, no more input. */
949 setTermSettings (inputFd, (void *) &initial_settings);
950 handlers_sets &= ~SET_RESET_TERM;
952 /* Handle command history log */
953 if (len>1) { /* no put empty line (only '\n') */
955 struct history *h = his_end;
958 command[len-1] = 0; /* destroy end '\n' */
959 ss = strdup(command); /* duplicate without '\n' */
960 command[len-1] = '\n'; /* restore '\n' */
963 /* No previous history -- this memory is never freed */
964 h = his_front = xmalloc(sizeof(struct history));
965 h->n = xmalloc(sizeof(struct history));
975 /* Add a new history command -- this memory is never freed */
976 h->n = xmalloc(sizeof(struct history));
984 /* After max history, remove the oldest command */
985 if (history_counter >= MAX_HISTORY) {
987 struct history *p = his_front->n;
1003 /* Undo the effects of cmdedit_init(). */
1004 extern void cmdedit_terminate(void)
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;
1019 #endif /* BB_FEATURE_SH_COMMAND_EDITING */