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 static const int MAX_HISTORY = 15; /* Maximum length of the linked list for the command line history */
64 #define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0)
65 #define whitespace(c) (((c) == ' ') || ((c) == '\t'))
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 */
70 /* ED: sparc termios is broken: revert back to old termio handling. */
74 # define termios termio
75 # define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
76 # define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
79 # define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
80 # define getTermSettings(fd,argp) tcgetattr(fd, argp);
83 /* Current termio and the previous termio before starting sh */
84 static struct termios initial_settings, new_settings;
87 #ifndef _POSIX_VDISABLE
88 #define _POSIX_VDISABLE '\0'
93 volatile int cmdedit_termw; /* actual terminal width */
94 static int history_counter = 0; /* Number of commands in history list */
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
105 SET_TERM_HANDLERS = 2,
106 SET_WCHG_HANDLERS = 4,
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 */
116 static int cursor; /* required global for signal handler */
117 static int len; /* --- "" - - "" - -"- --""-- --""--- */
118 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
119 static const char *cmdedit_prompt;/* --- "" - - "" - -"- --""-- --""--- */
121 /* Link into lash to reset context to 0
123 extern unsigned int shell_context;
132 static void cmdedit_setwidth(int w, int redraw_flg);
134 static void win_changed(int nsig)
136 struct winsize win = { 0, 0, 0, 0 };
137 static __sighandler_t previous_SIGWINCH_handler; /* for reset */
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 );
147 /* Unix not all standart in recall signal */
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 */
157 static void cmdedit_reset_term(void)
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;
164 if((handlers_sets & SET_WCHG_HANDLERS)!=0) {
165 /* reset SIGWINCH handler to previous (default) */
167 handlers_sets &= ~SET_WCHG_HANDLERS;
170 #ifdef BB_FEATURE_CLEAN_UP
173 //while(his_front!=his_end) {
174 while(his_front!=his_end) {
186 /* special for recount position for scroll and remove terminal margin effect */
187 static void cmdedit_set_out_char(int c, int next_char) {
189 if(++cmdedit_x>=cmdedit_termw) {
190 /* terminal is scrolled down */
196 /* destroy "(auto)margin" */
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) {
207 cmdedit_set_out_char(command_ps[cursor], 0);
210 /* Go to the next line */
211 static void goto_new_line(void) {
213 cmdedit_set_out_char('\n', 0);
217 static inline void out1str(const char *s) { fputs (s, stdout); }
218 static inline void beep (void) { putchar('\007'); }
220 /* Go to HOME position */
221 static void input_home(void)
223 while(cmdedit_y>0) { /* up to start y */
229 out1str(cmdedit_prompt);
230 cmdedit_x = cmdedit_prmt_len;
234 /* Move back one charactor */
235 static void input_backward(void) {
238 if(cmdedit_x!=0) { /* no first position in terminal line */
243 out1str("\033[A"); /* up */
246 /* to end in current terminal line */
247 while(cmdedit_x<(cmdedit_termw-1)) {
255 /* Delete the char in front of the cursor */
256 static void input_delete(void)
263 memmove (command_ps + j, command_ps + j + 1, BUFSIZ - j - 1);
265 input_end(); /* rewtite new line */
266 cmdedit_set_out_char(' ', 0); /* destroy end char */
268 input_backward(); /* back to old pos cursor */
271 /* Delete the char in back of the cursor */
272 static void input_backspace(void)
281 /* Move forward one charactor */
282 static void input_forward(void)
285 cmdedit_set_out_char(command_ps[cursor], command_ps[cursor + 1]);
289 static void clean_up_and_die(int sig)
293 exit(EXIT_SUCCESS); /* cmdedit_reset_term() called in atexit */
294 cmdedit_reset_term();
297 static void cmdedit_setwidth(int w, int redraw_flg)
299 cmdedit_termw = cmdedit_prmt_len+2;
300 if (w > cmdedit_termw) {
305 int sav_cursor = cursor;
307 /* set variables for new terminal size */
308 cmdedit_y = sav_cursor/w;
309 cmdedit_x = sav_cursor-cmdedit_y*w;
314 while(sav_cursor<cursor)
318 error_msg("\n*** Error: minimum screen width is %d\n", cmdedit_termw);
322 extern void cmdedit_init(void)
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;
330 if((handlers_sets & SET_ATEXIT)==0) {
331 atexit(cmdedit_reset_term); /* be sure to do this only once */
332 handlers_sets |= SET_ATEXIT;
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;
343 #ifdef BB_FEATURE_SH_TAB_COMPLETION
345 #ifdef BB_FEATURE_USERNAME_COMPLETION
346 static char** username_tab_completion(char *ud, int *num_matches)
348 static struct passwd *entry;
350 char **matches = (char **) NULL;
355 userlen = strlen (ud + 1);
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)) {
361 temp = xmalloc (3 + strlen (entry->pw_name));
362 sprintf(temp, "~%s/", entry->pw_name);
364 matches = xrealloc(matches, (nm+1)*sizeof(char *));
365 matches[nm++] = temp;
383 static int path_parse(char ***p, int flags)
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(".");
400 npth++; /* count words is + 1 count ':' */
401 tmp = strchr(tmp, ':');
408 *p = xmalloc(npth*sizeof(char *));
411 (*p)[0] = xstrdup(tmp);
412 npth=1; /* count words is + 1 count ':' */
415 tmp = strchr(tmp, ':');
417 (*p)[0][(tmp-pth)]=0; /* ':' -> '\0'*/
421 (*p)[npth++] = &(*p)[0][(tmp-pth)]; /* p[next]=p[0][&'\0'+1] */
427 static char** exe_n_cwd_tab_completion(char* command, int *num_matches, int type)
435 char found [BUFSIZ+4];
436 int nm = *num_matches;
441 char full_pth[BUFSIZ+4+PATH_MAX];
444 strcpy(cmd, command); /* save for change (last '/' to '\0') */
446 dirName = strrchr(cmd, '/');
448 /* no dir, if flags==EXE_ONLY - get paths, else "." */
449 npaths = path_parse(&paths, type);
456 dirbuf = xstrdup(cmd);
457 /* set only dirname */
458 dirbuf[(dirName-cmd)+1]=0;
460 /* strip dirname in cmd */
461 strcpy(cmd, dirName+1);
463 paths = xmalloc(sizeof(char*));
465 npaths = 1; /* only 1 dir */
468 for(i=0; i < npaths; i++) {
470 dir = opendir(paths[i]);
472 /* Don't print an error, just shut up and return */
475 while ((next = readdir(dir)) != NULL) {
477 if(strncmp(next->d_name, cmd, strlen(cmd)))
479 /* not see .name without .match */
480 if(*next->d_name == '.' && *cmd != '.')
482 sprintf(full_pth, "%s/%s", paths[i], next->d_name);
483 /* hmm, remover in progress? */
484 if(stat(full_pth, &st)<0)
486 /* Cool, found a match. */
487 if (S_ISDIR(st.st_mode)) {
488 /* name is directory */
489 strcpy(found, next->d_name);
491 if(type==FIND_DIR_ONLY)
494 /* not put found file if search only dirs for cd */
495 if(type==FIND_DIR_ONLY)
497 strcpy(found, next->d_name);
500 /* Add it to the list */
501 matches = xrealloc(matches, (nm+1)*sizeof(char *));
502 matches[nm++] = xstrdup(found);
505 free(paths[0]); /* allocate memory only in first member */
511 static void input_tab(int lastWasTab)
513 /* Do TAB completion */
514 static int num_matches;
515 static char **matches;
517 char matchBuf[BUFSIZ];
520 int find_type=FIND_FILE_ONLY;
523 if (lastWasTab == FALSE) {
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.
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);
538 /* skip past any command seperator tokens */
539 while ( (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
543 /* skip any leading white space */
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;
552 /* find begin curent word */
553 if( (tmp1=strrchr(tmp, ' ')) != NULL) {
556 strcpy(matchBuf, tmp);
558 /* Free up any memory already allocated */
561 free(matches[--num_matches]);
563 matches = (char **) NULL;
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. */
570 if (matchBuf[0]=='~' && strchr(matchBuf, '/')==0) {
571 matches = username_tab_completion(matchBuf, &num_matches);
574 /* Try to match any executable in our path and everything
575 * in the current working directory that matches. */
577 matches = exe_n_cwd_tab_completion(matchBuf, &num_matches, find_type);
579 /* Did we find exactly one match? */
580 if(!matches || num_matches>1) {
585 len_found = strlen(matches[0]);
587 /* have space to placed match? */
588 if ( (len_found-strlen(matchBuf)+len) < BUFSIZ ) {
590 int recalc_pos = len;
592 /* before word for match */
593 command_ps[pos-strlen(matchBuf)]=0;
596 strcpy(matchBuf, command_ps+pos);
599 strcat(command_ps, matches[0]);
601 strcat(command_ps, matchBuf);
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)
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 ) {
617 int sav_cursor = cursor;
619 /* Go to the next 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) {
630 /* Go to the next line and rewrite the prompt */
631 printf("\n%s", cmdedit_prompt);
632 cmdedit_x = cmdedit_prmt_len;
635 input_end(); /* Rewrite the command */
636 /* Put the cursor back to where it used to be */
637 while (sav_cursor < cursor)
644 static void get_previous_history(struct history **hp, char* command)
648 (*hp)->s = strdup(command);
652 static void get_next_history(struct history **hp, char* command)
656 (*hp)->s = strdup(command);
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
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
673 * Furthermore, the "vi" command editing keys are not implemented.
675 * TODO: implement TAB command completion. :)
677 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
680 int inputFd=fileno(stdin);
685 int lastWasTab = FALSE;
687 struct history *hp = his_end;
691 command_ps = command;
693 if (new_settings.c_cc[VMIN]==0) {
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 */
703 setTermSettings(inputFd, (void*) &new_settings);
704 handlers_sets |= SET_RESET_TERM;
706 memset(command, 0, BUFSIZ);
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 */
720 fflush(stdout); /* buffered out to fast */
722 if ((ret = read(inputFd, &c, 1)) < 1)
724 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
730 *(command + len) = c;
736 /* Control-a -- Beginning of line */
740 /* Control-b -- Move back one character */
744 /* Control-c -- stop gathering input */
746 /* Link into lash to reset context to 0 on ^C and such */
749 /* Go to the next line */
753 /* Rewrite the prompt */
754 printf("%s", prompt);
756 /* Reset the command string */
757 memset(command, 0, BUFSIZ);
763 /* Control-d -- Delete one character, or exit
764 * if the len=0 and no chars to delete */
773 /* Control-e -- End of line */
777 /* Control-f -- Move forward one character */
782 /* Control-h and DEL */
786 #ifdef BB_FEATURE_SH_TAB_COMPLETION
787 input_tab(lastWasTab);
791 /* Control-n -- Get next command in history */
792 if (hp && hp->n && hp->n->s) {
793 get_next_history(&hp, command);
800 /* Control-p -- Get previous command from history */
802 get_previous_history(&hp, command);
809 /* escape sequence follows */
810 if ((ret = read(inputFd, &c, 1)) < 1)
813 if (c == '[') { /* 91 */
814 if ((ret = read(inputFd, &c, 1)) < 1)
819 /* Up Arrow -- Get previous command from history */
821 get_previous_history(&hp, command);
828 /* Down Arrow -- Get next command in history */
829 if (hp && hp->n && hp->n->s) {
830 get_next_history(&hp, command);
837 /* Rewrite the line with the selected history item */
839 /* return to begin of line */
841 /* for next memmoves without set '\0' */
842 memset (command, 0, BUFSIZ);
844 strcpy (command, hp->s);
845 /* write new command */
846 for (j=0; command[j]; j++)
847 cmdedit_set_out_char(command[j], 0);
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++)
855 len = cursor; /* set new len */
858 /* Right Arrow -- Move forward one character */
862 /* Left Arrow -- Move back one character */
880 if (c == '1' || c == '3' || c == '4')
881 if ((ret = read(inputFd, &c, 1)) < 1)
882 return; /* read 126 (~) */
886 if ((ret = read(inputFd, &c, 1)) < 1)
905 default: /* If it's regular input, do the normal thing */
907 if (!isprint(c)) { /* Skip non-printable characters */
911 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
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,
923 *(command + cursor) = c;
925 /* rewrite from cursor */
927 /* to prev x pos + 1 */
939 if (break_out) /* Enter is the command terminator, no more input. */
943 setTermSettings (inputFd, (void *) &initial_settings);
944 handlers_sets &= ~SET_RESET_TERM;
946 /* Handle command history log */
947 if (len>1) { /* no put empty line (only '\n') */
949 struct history *h = his_end;
952 command[len-1] = 0; /* destroy end '\n' */
953 ss = strdup(command); /* duplicate without '\n' */
954 command[len-1] = '\n'; /* restore '\n' */
957 /* No previous history -- this memory is never freed */
958 h = his_front = xmalloc(sizeof(struct history));
959 h->n = xmalloc(sizeof(struct history));
969 /* Add a new history command -- this memory is never freed */
970 h->n = xmalloc(sizeof(struct history));
978 /* After max history, remove the oldest command */
979 if (history_counter >= MAX_HISTORY) {
981 struct history *p = his_front->n;
997 /* Undo the effects of cmdedit_init(). */
998 extern void cmdedit_terminate(void)
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;
1013 #endif /* BB_FEATURE_SH_COMMAND_EDITING */