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 */
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 );
146 /* Unix not all standart in recall signal */
148 if(nsig == -SIGWINCH) /* save previous handler */
149 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
150 else if(nsig == SIGWINCH) /* signaled called handler */
151 signal(SIGWINCH, win_changed); /* set for next call */
152 else /* set previous handler */
153 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
156 static void cmdedit_reset_term(void)
158 if((handlers_sets & SET_RESET_TERM)!=0) {
159 /* sparc and other have broken termios support: use old termio handling. */
160 setTermSettings(fileno(stdin), (void*) &initial_settings);
161 handlers_sets &= ~SET_RESET_TERM;
163 if((handlers_sets & SET_WCHG_HANDLERS)!=0) {
164 /* reset SIGWINCH handler to previous (default) */
166 handlers_sets &= ~SET_WCHG_HANDLERS;
169 #ifdef BB_FEATURE_CLEAN_UP
172 //while(his_front!=his_end) {
173 while(his_front!=his_end) {
185 /* special for recount position for scroll and remove terminal margin effect */
186 static void cmdedit_set_out_char(int c, int next_char) {
188 if(++cmdedit_x>=cmdedit_termw) {
189 /* terminal is scrolled down */
195 /* destroy "(auto)margin" */
202 /* Move to end line. Bonus: rewrite line from cursor without use
203 special control terminal strings, also saved size and speed! */
204 static void input_end (void) {
206 cmdedit_set_out_char(command_ps[cursor], 0);
209 /* Go to the next line */
210 static void goto_new_line(void) {
212 cmdedit_set_out_char('\n', 0);
216 static inline void out1str(const char *s) { fputs (s, stdout); }
217 static inline void beep (void) { putchar('\007'); }
219 /* Go to HOME position */
220 static void input_home(void)
222 while(cmdedit_y>0) { /* up to start y */
228 out1str(cmdedit_prompt);
229 cmdedit_x = cmdedit_prmt_len;
233 /* Move back one charactor */
234 static void input_backward(void) {
237 if(cmdedit_x!=0) { /* no first position in terminal line */
242 out1str("\033[A"); /* up */
245 /* to end in current terminal line */
246 while(cmdedit_x<(cmdedit_termw-1)) {
254 /* Delete the char in front of the cursor */
255 static void input_delete(void)
262 memmove (command_ps + j, command_ps + j + 1, BUFSIZ - j - 1);
264 input_end(); /* rewtite new line */
265 cmdedit_set_out_char(' ', 0); /* destroy end char */
267 input_backward(); /* back to old pos cursor */
270 /* Delete the char in back of the cursor */
271 static void input_backspace(void)
280 /* Move forward one charactor */
281 static void input_forward(void)
284 cmdedit_set_out_char(command_ps[cursor], command_ps[cursor + 1]);
288 static void clean_up_and_die(int sig)
292 exit(EXIT_SUCCESS); /* cmdedit_reset_term() called in atexit */
293 cmdedit_reset_term();
296 static void cmdedit_setwidth(int w, int redraw_flg)
298 cmdedit_termw = cmdedit_prmt_len+2;
299 if (w > cmdedit_termw) {
304 int sav_cursor = cursor;
306 /* set variables for new terminal size */
307 cmdedit_y = sav_cursor/w;
308 cmdedit_x = sav_cursor-cmdedit_y*w;
313 while(sav_cursor<cursor)
317 error_msg("\n*** Error: minimum screen width is %d\n", cmdedit_termw);
321 extern void cmdedit_init(void)
323 if((handlers_sets & SET_WCHG_HANDLERS)==0) {
324 /* emulate usage handler to set handler and call yours work */
325 win_changed(-SIGWINCH);
326 handlers_sets |= SET_WCHG_HANDLERS;
329 if((handlers_sets & SET_ATEXIT)==0) {
330 atexit(cmdedit_reset_term); /* be sure to do this only once */
331 handlers_sets |= SET_ATEXIT;
333 if((handlers_sets & SET_TERM_HANDLERS)==0) {
334 signal(SIGKILL, clean_up_and_die);
335 signal(SIGINT, clean_up_and_die);
336 signal(SIGQUIT, clean_up_and_die);
337 signal(SIGTERM, clean_up_and_die);
338 handlers_sets |= SET_TERM_HANDLERS;
342 #ifdef BB_FEATURE_SH_TAB_COMPLETION
344 #ifdef BB_FEATURE_USERNAME_COMPLETION
345 static char** username_tab_completion(char *ud, int *num_matches)
347 static struct passwd *entry;
349 char **matches = (char **) NULL;
354 userlen = strlen (ud + 1);
356 while ((entry = bb_getpwent ()) != NULL) {
357 /* Null usernames should result in all users as possible completions. */
358 if (!userlen || !strncmp (ud + 1, entry->pw_name, userlen)) {
360 temp = xmalloc (3 + strlen (entry->pw_name));
361 sprintf(temp, "~%s/", entry->pw_name);
363 matches = xrealloc(matches, (nm+1)*sizeof(char *));
364 matches[nm++] = temp;
382 static int path_parse(char ***p, int flags)
388 if(flags!=FIND_EXE_ONLY || (pth=getenv("PATH"))==0) {
389 /* if not setenv PATH variable, to search cur dir "." */
390 (*p) = xmalloc(sizeof(char *));
391 (*p)[0] = xstrdup(".");
399 npth++; /* count words is + 1 count ':' */
400 tmp = strchr(tmp, ':');
407 *p = xmalloc(npth*sizeof(char *));
410 (*p)[0] = xstrdup(tmp);
411 npth=1; /* count words is + 1 count ':' */
414 tmp = strchr(tmp, ':');
416 (*p)[0][(tmp-pth)]=0; /* ':' -> '\0'*/
420 (*p)[npth++] = &(*p)[0][(tmp-pth)]; /* p[next]=p[0][&'\0'+1] */
426 static char** exe_n_cwd_tab_completion(char* command, int *num_matches, int type)
434 char found [BUFSIZ+4];
435 int nm = *num_matches;
440 char full_pth[BUFSIZ+4+PATH_MAX];
443 strcpy(cmd, command); /* save for change (last '/' to '\0') */
445 dirName = strrchr(cmd, '/');
447 /* no dir, if flags==EXE_ONLY - get paths, else "." */
448 npaths = path_parse(&paths, type);
455 dirbuf = xstrdup(cmd);
456 /* set only dirname */
457 dirbuf[(dirName-cmd)+1]=0;
459 /* strip dirname in cmd */
460 strcpy(cmd, dirName+1);
462 paths = xmalloc(sizeof(char*));
464 npaths = 1; /* only 1 dir */
467 for(i=0; i < npaths; i++) {
469 dir = opendir(paths[i]);
471 /* Don't print an error, just shut up and return */
474 while ((next = readdir(dir)) != NULL) {
476 if(strncmp(next->d_name, cmd, strlen(cmd)))
478 /* not see .name without .match */
479 if(*next->d_name == '.' && *cmd != '.')
481 sprintf(full_pth, "%s/%s", paths[i], next->d_name);
482 /* hmm, remover in progress? */
483 if(stat(full_pth, &st)<0)
485 /* Cool, found a match. */
486 if (S_ISDIR(st.st_mode)) {
487 /* name is directory */
488 strcpy(found, next->d_name);
490 if(type==FIND_DIR_ONLY)
493 /* not put found file if search only dirs for cd */
494 if(type==FIND_DIR_ONLY)
496 strcpy(found, next->d_name);
499 /* Add it to the list */
500 matches = xrealloc(matches, (nm+1)*sizeof(char *));
501 matches[nm++] = xstrdup(found);
504 free(paths[0]); /* allocate memory only in first member */
510 static void input_tab(int lastWasTab)
512 /* Do TAB completion */
513 static int num_matches;
514 static char **matches;
516 char matchBuf[BUFSIZ];
519 int find_type=FIND_FILE_ONLY;
522 if (lastWasTab == FALSE) {
526 /* For now, we will not bother with trying to distinguish
527 * whether the cursor is in/at a command extression -- we
528 * will always try all possible matches. If you don't like
529 * that then feel free to fix it.
532 /* Make a local copy of the string -- up
533 * to the position of the cursor */
534 memset(matchBuf, 0, BUFSIZ);
535 tmp = strncpy(matchBuf, command_ps, cursor);
537 /* skip past any command seperator tokens */
538 while ( (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
542 /* skip any leading white space */
546 if(strncmp(tmp, "cd ", 3)==0)
547 find_type = FIND_DIR_ONLY;
548 else if(strchr(tmp, ' ')==NULL)
549 find_type = FIND_EXE_ONLY;
551 /* find begin curent word */
552 if( (tmp1=strrchr(tmp, ' ')) != NULL) {
555 strcpy(matchBuf, tmp);
557 /* Free up any memory already allocated */
560 free(matches[--num_matches]);
562 matches = (char **) NULL;
565 #ifdef BB_FEATURE_USERNAME_COMPLETION
566 /* If the word starts with `~' and there is no slash in the word,
567 * then try completing this word as a username. */
569 if (matchBuf[0]=='~' && strchr(matchBuf, '/')==0) {
570 matches = username_tab_completion(matchBuf, &num_matches);
573 /* Try to match any executable in our path and everything
574 * in the current working directory that matches. */
576 matches = exe_n_cwd_tab_completion(matchBuf, &num_matches, find_type);
578 /* Did we find exactly one match? */
579 if(!matches || num_matches>1) {
584 len_found = strlen(matches[0]);
586 /* have space to placed match? */
587 if ( (len_found-strlen(matchBuf)+len) < BUFSIZ ) {
589 int recalc_pos = len;
591 /* before word for match */
592 command_ps[pos-strlen(matchBuf)]=0;
595 strcpy(matchBuf, command_ps+pos);
598 strcat(command_ps, matches[0]);
600 strcat(command_ps, matchBuf);
602 /* write out the matched command */
603 len=strlen(command_ps);
604 recalc_pos = len-recalc_pos+pos;
605 input_end(); /* write */
606 while(recalc_pos<cursor)
611 /* Ok -- the last char was a TAB. Since they
612 * just hit TAB again, print a list of all the
613 * available choices... */
614 if ( matches && num_matches>0 ) {
616 int sav_cursor = cursor;
618 /* Go to the next line */
620 for (i=0,col=0; i<num_matches; i++) {
621 printf("%s ", matches[i]);
622 col += strlen(matches[i])+2;
623 col -= (col/cmdedit_termw)*cmdedit_termw;
624 if (col > 60 && matches[i+1] != NULL) {
629 /* Go to the next line and rewrite the prompt */
630 printf("\n%s", cmdedit_prompt);
631 cmdedit_x = cmdedit_prmt_len;
634 input_end(); /* Rewrite the command */
635 /* Put the cursor back to where it used to be */
636 while (sav_cursor < cursor)
643 static void get_previous_history(struct history **hp, char* command)
647 (*hp)->s = strdup(command);
651 static void get_next_history(struct history **hp, char* command)
655 (*hp)->s = strdup(command);
660 * This function is used to grab a character buffer
661 * from the input file descriptor and allows you to
662 * a string with full command editing (sortof like
665 * The following standard commands are not implemented:
666 * ESC-b -- Move back one word
667 * ESC-f -- Move forward one word
668 * ESC-d -- Delete back one word
669 * ESC-h -- Delete forward one word
670 * CTL-t -- Transpose two characters
672 * Furthermore, the "vi" command editing keys are not implemented.
674 * TODO: implement TAB command completion. :)
676 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
679 int inputFd=fileno(stdin);
684 int lastWasTab = FALSE;
686 struct history *hp = his_end;
690 command_ps = command;
692 if (new_settings.c_cc[VMIN]==0) {
694 getTermSettings(inputFd, (void*) &initial_settings);
695 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
696 new_settings.c_cc[VMIN] = 1;
697 new_settings.c_cc[VTIME] = 0;
698 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
699 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
700 new_settings.c_lflag &= ~(ECHO|ECHOCTL|ECHONL); /* Turn off echoing */
702 setTermSettings(inputFd, (void*) &new_settings);
703 handlers_sets |= SET_RESET_TERM;
705 memset(command, 0, BUFSIZ);
709 /* Print out the command prompt */
710 cmdedit_prompt = prompt;
711 cmdedit_prmt_len = strlen(prompt);
712 printf("%s", prompt);
713 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
714 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
719 fflush(stdout); /* buffered out to fast */
721 if ((ret = read(inputFd, &c, 1)) < 1)
723 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
729 *(command + len) = c;
735 /* Control-a -- Beginning of line */
739 /* Control-b -- Move back one character */
743 /* Control-c -- stop gathering input */
745 /* Link into lash to reset context to 0 on ^C and such */
748 /* Go to the next line */
752 /* Rewrite the prompt */
753 printf("%s", prompt);
755 /* Reset the command string */
756 memset(command, 0, BUFSIZ);
762 /* Control-d -- Delete one character, or exit
763 * if the len=0 and no chars to delete */
772 /* Control-e -- End of line */
776 /* Control-f -- Move forward one character */
781 /* Control-h and DEL */
785 #ifdef BB_FEATURE_SH_TAB_COMPLETION
786 input_tab(lastWasTab);
790 /* Control-n -- Get next command in history */
791 if (hp && hp->n && hp->n->s) {
792 get_next_history(&hp, command);
799 /* Control-p -- Get previous command from history */
801 get_previous_history(&hp, command);
808 /* escape sequence follows */
809 if ((ret = read(inputFd, &c, 1)) < 1)
812 if (c == '[') { /* 91 */
813 if ((ret = read(inputFd, &c, 1)) < 1)
818 /* Up Arrow -- Get previous command from history */
820 get_previous_history(&hp, command);
827 /* Down Arrow -- Get next command in history */
828 if (hp && hp->n && hp->n->s) {
829 get_next_history(&hp, command);
836 /* Rewrite the line with the selected history item */
838 /* return to begin of line */
840 /* for next memmoves without set '\0' */
841 memset (command, 0, BUFSIZ);
843 strcpy (command, hp->s);
844 /* write new command */
845 for (j=0; command[j]; j++)
846 cmdedit_set_out_char(command[j], 0);
848 /* erase tail if required */
849 for (j = ret; j < len; j++)
850 cmdedit_set_out_char(' ', 0);
851 /* and backward cursor */
852 for (j = ret; j < len; j++)
854 len = cursor; /* set new len */
857 /* Right Arrow -- Move forward one character */
861 /* Left Arrow -- Move back one character */
879 if (c == '1' || c == '3' || c == '4')
880 if ((ret = read(inputFd, &c, 1)) < 1)
881 return; /* read 126 (~) */
885 if ((ret = read(inputFd, &c, 1)) < 1)
904 default: /* If it's regular input, do the normal thing */
906 if (!isprint(c)) { /* Skip non-printable characters */
910 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
915 if (cursor == (len - 1)) { /* Append if at the end of the line */
916 *(command + cursor) = c;
917 cmdedit_set_out_char(c, command[cursor+1]);
918 } else { /* Insert otherwise */
919 memmove(command + cursor + 1, command + cursor,
922 *(command + cursor) = c;
924 /* rewrite from cursor */
926 /* to prev x pos + 1 */
938 if (break_out) /* Enter is the command terminator, no more input. */
942 setTermSettings (inputFd, (void *) &initial_settings);
943 handlers_sets &= ~SET_RESET_TERM;
945 /* Handle command history log */
946 if (len>1) { /* no put empty line (only '\n') */
948 struct history *h = his_end;
951 command[len-1] = 0; /* destroy end '\n' */
952 ss = strdup(command); /* duplicate without '\n' */
953 command[len-1] = '\n'; /* restore '\n' */
956 /* No previous history -- this memory is never freed */
957 h = his_front = xmalloc(sizeof(struct history));
958 h->n = xmalloc(sizeof(struct history));
968 /* Add a new history command -- this memory is never freed */
969 h->n = xmalloc(sizeof(struct history));
977 /* After max history, remove the oldest command */
978 if (history_counter >= MAX_HISTORY) {
980 struct history *p = his_front->n;
996 /* Undo the effects of cmdedit_init(). */
997 extern void cmdedit_terminate(void)
999 cmdedit_reset_term();
1000 if((handlers_sets & SET_TERM_HANDLERS)!=0) {
1001 signal(SIGKILL, SIG_DFL);
1002 signal(SIGINT, SIG_DFL);
1003 signal(SIGQUIT, SIG_DFL);
1004 signal(SIGTERM, SIG_DFL);
1005 signal(SIGWINCH, SIG_DFL);
1006 handlers_sets &= ~SET_TERM_HANDLERS;
1012 #endif /* BB_FEATURE_SH_COMMAND_EDITING */