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. The binary size increase is <3K.
30 Editting will not display correctly for lines greater then the
31 terminal width. (more then one line.) However, history will.
35 #ifdef BB_FEATURE_SH_COMMAND_EDITING
42 #include <sys/ioctl.h>
47 static const int MAX_HISTORY = 15; /* Maximum length of the linked list for the command line history */
54 #define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0)
55 #define whitespace(c) (((c) == ' ') || ((c) == '\t'))
57 static struct history *his_front = NULL; /* First element in command line list */
58 static struct history *his_end = NULL; /* Last element in command line list */
60 /* ED: sparc termios is broken: revert back to old termio handling. */
61 #ifdef BB_FEATURE_USE_TERMIOS
65 # define termios termio
66 # define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
67 # define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
70 # define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
71 # define getTermSettings(fd,argp) tcgetattr(fd, argp);
74 /* Current termio and the previous termio before starting sh */
75 static struct termios initial_settings, new_settings;
78 #ifndef _POSIX_VDISABLE
79 #define _POSIX_VDISABLE '\0'
86 static int cmdedit_termw = 80; /* actual terminal width */
87 static int cmdedit_scroll = 27; /* width of EOL scrolling region */
88 static int history_counter = 0; /* Number of commands in history list */
89 static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */
90 static int exithandler_set = 0; /* Set to true when atexit() has been called */
93 /* Link into lash to reset context to 0
95 extern unsigned int shell_context;
107 * TODO: Someday we want to implement 'horizontal scrolling' of the
108 * command-line when the user has typed more than the current width. This
109 * would allow the user to see a 'window' of what he has typed.
111 static void cmdedit_setwidth(int w)
115 cmdedit_scroll = w / 3;
117 error_msg("\n*** Error: minimum screen width is 21\n");
121 static void win_changed(int junk)
123 struct winsize win = { 0, 0, 0, 0 };
124 ioctl(0, TIOCGWINSZ, &win);
125 if (win.ws_col > 0) {
126 cmdedit_setwidth( win.ws_col - 1);
131 static void cmdedit_reset_term(void)
134 /* sparc and other have broken termios support: use old termio handling. */
135 setTermSettings(fileno(stdin), (void*) &initial_settings);
136 #ifdef BB_FEATURE_CLEAN_UP
139 //while(his_front!=his_end) {
140 while(his_front!=his_end) {
150 static void clean_up_and_die(int sig)
152 cmdedit_reset_term();
158 /* Go to HOME position */
159 static void input_home(int outputFd, int *cursor)
161 while (*cursor > 0) {
162 xwrite(outputFd, "\b", 1);
167 /* Go to END position */
168 static void input_end(int outputFd, int *cursor, int len)
170 while (*cursor < len) {
171 xwrite(outputFd, "\033[C", 3);
176 /* Delete the char in back of the cursor */
177 static void input_backspace(char* command, int outputFd, int *cursor, int *len)
182 //fprintf(stderr, "\nerik: len=%d, cursor=%d, strlen(command)='%d'\n", *len, *cursor, strlen(command));
183 //xwrite(outputFd, command, *len);
188 xwrite(outputFd, "\b \b", 3);
190 memmove(command + *cursor, command + *cursor + 1,
191 BUFSIZ - *cursor + 1);
193 for (j = *cursor; j < (BUFSIZ - 1); j++) {
197 xwrite(outputFd, (command + j), 1);
200 xwrite(outputFd, " \b", 2);
202 while (j-- > *cursor)
203 xwrite(outputFd, "\b", 1);
209 /* Delete the char in front of the cursor */
210 static void input_delete(char* command, int outputFd, int cursor, int *len)
217 memmove(command + cursor, command + cursor + 1,
218 BUFSIZ - cursor - 1);
219 for (j = cursor; j < (BUFSIZ - 1); j++) {
223 xwrite(outputFd, (command + j), 1);
226 xwrite(outputFd, " \b", 2);
229 xwrite(outputFd, "\b", 1);
233 /* Move forward one charactor */
234 static void input_forward(int outputFd, int *cursor, int len)
237 xwrite(outputFd, "\033[C", 3);
242 /* Move back one charactor */
243 static void input_backward(int outputFd, int *cursor)
246 xwrite(outputFd, "\033[D", 3);
253 #ifdef BB_FEATURE_SH_TAB_COMPLETION
254 static char** username_tab_completion(char* command, int *num_matches)
256 char **matches = (char **) NULL;
258 fprintf(stderr, "\nin username_tab_completion\n");
263 static char** exe_n_cwd_tab_completion(char* command, int *num_matches)
270 matches = xmalloc( sizeof(char*)*50);
272 /* Stick a wildcard onto the command, for later use */
273 strcat( command, "*");
275 /* Now wall the current directory */
276 dirName = get_current_dir_name();
277 dir = opendir(dirName);
279 /* Don't print an error, just shut up and return */
283 while ((next = readdir(dir)) != NULL) {
285 /* Some quick sanity checks */
286 if ((strcmp(next->d_name, "..") == 0)
287 || (strcmp(next->d_name, ".") == 0)) {
290 /* See if this matches */
291 if (check_wildcard_match(next->d_name, command) == TRUE) {
292 /* Cool, found a match. Add it to the list */
293 matches[*num_matches] = xmalloc(strlen(next->d_name)+1);
294 strcpy( matches[*num_matches], next->d_name);
296 //matches = realloc( matches, sizeof(char*)*(*num_matches));
303 static void input_tab(char* command, char* prompt, int outputFd, int *cursor, int *len, int lastWasTab)
305 /* Do TAB completion */
306 static int num_matches=0;
307 static char **matches = (char **) NULL;
311 if (lastWasTab == FALSE) {
312 char *tmp, *tmp1, *matchBuf;
314 /* For now, we will not bother with trying to distinguish
315 * whether the cursor is in/at a command extression -- we
316 * will always try all possible matches. If you don't like
317 * that then feel free to fix it.
320 /* Make a local copy of the string -- up
321 * to the position of the cursor */
322 matchBuf = (char *) xcalloc(BUFSIZ, sizeof(char));
323 strncpy(matchBuf, command, *cursor);
326 /* skip past any command seperator tokens */
327 while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
329 /* skip any leading white space */
330 while (*tmp && isspace(*tmp))
334 /* skip any leading white space */
335 while (*tmp && isspace(*tmp))
338 /* Free up any memory already allocated */
341 matches = (char **) NULL;
344 /* If the word starts with `~' and there is no slash in the word,
345 * then try completing this word as a username. */
347 /* FIXME -- this check is broken! */
348 if (*tmp == '~' && !strchr(tmp, '/'))
349 matches = username_tab_completion(tmp, &num_matches);
351 /* Try to match any executable in our path and everything
352 * in the current working directory that matches. */
354 matches = exe_n_cwd_tab_completion(tmp, &num_matches);
356 /* Don't leak memory */
359 /* Did we find exactly one match? */
360 if (matches && num_matches==1) {
361 /* write out the matched command */
362 strncpy(command+pos, matches[0]+pos, strlen(matches[0])-pos);
363 *len=strlen(command);
365 xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
369 /* Ok -- the last char was a TAB. Since they
370 * just hit TAB again, print a list of all the
371 * available choices... */
372 if ( matches && num_matches>0 ) {
375 /* Go to the next line */
376 xwrite(outputFd, "\n", 1);
377 /* Print the list of matches */
378 for (i=0,col=0; i<num_matches; i++) {
380 sprintf(foo, "%-14s ", matches[i]);
381 col += xwrite(outputFd, foo, strlen(foo));
382 if (col > 60 && matches[i+1] != NULL) {
383 xwrite(outputFd, "\n", 1);
387 /* Go to the next line */
388 xwrite(outputFd, "\n", 1);
389 /* Rewrite the prompt */
390 xwrite(outputFd, prompt, strlen(prompt));
391 /* Rewrite the command */
392 xwrite(outputFd, command, *len);
393 /* Put the cursor back to where it used to be */
394 for (cursor=len; *cursor > pos; cursor--)
395 xwrite(outputFd, "\b", 1);
401 static void get_previous_history(struct history **hp, char* command)
405 (*hp)->s = strdup(command);
409 static void get_next_history(struct history **hp, char* command)
413 (*hp)->s = strdup(command);
418 * This function is used to grab a character buffer
419 * from the input file descriptor and allows you to
420 * a string with full command editing (sortof like
423 * The following standard commands are not implemented:
424 * ESC-b -- Move back one word
425 * ESC-f -- Move forward one word
426 * ESC-d -- Delete back one word
427 * ESC-h -- Delete forward one word
428 * CTL-t -- Transpose two characters
430 * Furthermore, the "vi" command editing keys are not implemented.
432 * TODO: implement TAB command completion. :)
434 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
437 int inputFd=fileno(stdin);
438 int outputFd=fileno(stdout);
445 int lastWasTab = FALSE;
447 struct history *hp = his_end;
451 getTermSettings(inputFd, (void*) &initial_settings);
452 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
453 new_settings.c_cc[VMIN] = 1;
454 new_settings.c_cc[VTIME] = 0;
455 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
456 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
457 new_settings.c_lflag &= ~(ECHO|ECHOCTL|ECHONL); /* Turn off echoing */
460 setTermSettings(inputFd, (void*) &new_settings);
462 memset(command, 0, BUFSIZ);
464 /* Print out the command prompt */
465 xwrite(outputFd, prompt, strlen(prompt));
469 if ((ret = read(inputFd, &c, 1)) < 1)
471 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
477 *(command + len++ + 1) = c;
478 xwrite(outputFd, &c, 1);
482 /* Control-a -- Beginning of line */
483 input_home(outputFd, &cursor);
485 /* Control-b -- Move back one character */
486 input_backward(outputFd, &cursor);
489 /* Control-c -- stop gathering input */
491 /* Link into lash to reset context to 0 on ^C and such */
494 /* Go to the next line */
495 xwrite(outputFd, "\n", 1);
498 /* Rewrite the prompt */
499 xwrite(outputFd, prompt, strlen(prompt));
501 /* Reset the command string */
502 memset(command, 0, BUFSIZ);
508 /* Control-d -- Delete one character, or exit
509 * if the len=0 and no chars to delete */
511 xwrite(outputFd, "exit", 4);
514 input_delete(command, outputFd, cursor, &len);
518 /* Control-e -- End of line */
519 input_end(outputFd, &cursor, len);
522 /* Control-f -- Move forward one character */
523 input_forward(outputFd, &cursor, len);
527 /* Control-h and DEL */
528 input_backspace(command, outputFd, &cursor, &len);
531 #ifdef BB_FEATURE_SH_TAB_COMPLETION
532 input_tab(command, prompt, outputFd, &cursor,
537 /* Control-n -- Get next command in history */
538 if (hp && hp->n && hp->n->s) {
539 get_next_history(&hp, command);
542 xwrite(outputFd, "\007", 1);
546 /* Control-p -- Get previous command from history */
548 get_previous_history(&hp, command);
551 xwrite(outputFd, "\007", 1);
555 /* escape sequence follows */
556 if ((ret = read(inputFd, &c, 1)) < 1)
559 if (c == '[') { /* 91 */
560 if ((ret = read(inputFd, &c, 1)) < 1)
565 /* Up Arrow -- Get previous command from history */
567 get_previous_history(&hp, command);
570 xwrite(outputFd, "\007", 1);
574 /* Down Arrow -- Get next command in history */
575 if (hp && hp->n && hp->n->s) {
576 get_next_history(&hp, command);
579 xwrite(outputFd, "\007", 1);
583 /* Rewrite the line with the selected history item */
585 /* erase old command from command line */
586 len = strlen(command)-strlen(hp->s);
589 input_delete(command, outputFd, cursor, &len);
591 input_backspace(command, outputFd, &cursor, &len);
592 input_home(outputFd, &cursor);
594 /* write new command */
595 strcpy(command, hp->s);
597 xwrite(outputFd, command, len);
601 /* Right Arrow -- Move forward one character */
602 input_forward(outputFd, &cursor, len);
605 /* Left Arrow -- Move back one character */
606 input_backward(outputFd, &cursor);
610 input_delete(command, outputFd, cursor, &len);
614 input_home(outputFd, &cursor);
618 input_end(outputFd, &cursor, len);
621 xwrite(outputFd, "\007", 1);
623 if (c == '1' || c == '3' || c == '4')
624 if ((ret = read(inputFd, &c, 1)) < 1)
625 return; /* read 126 (~) */
629 if ((ret = read(inputFd, &c, 1)) < 1)
634 input_home(outputFd, &cursor);
638 input_end(outputFd, &cursor, len);
641 xwrite(outputFd, "\007", 1);
648 default: /* If it's regular input, do the normal thing */
650 if (!isprint(c)) { /* Skip non-printable characters */
654 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
659 if (cursor == (len - 1)) { /* Append if at the end of the line */
660 *(command + cursor) = c;
661 } else { /* Insert otherwise */
662 memmove(command + cursor + 1, command + cursor,
665 *(command + cursor) = c;
667 for (j = cursor; j < len; j++)
668 xwrite(outputFd, command + j, 1);
669 for (; j > cursor; j--)
670 xwrite(outputFd, "\033[D", 3);
674 xwrite(outputFd, &c, 1);
682 if (break_out) /* Enter is the command terminator, no more input. */
687 setTermSettings(inputFd, (void *) &initial_settings);
691 /* Handle command history log */
694 struct history *h = his_end;
697 /* No previous history -- this memory is never freed */
698 h = his_front = xmalloc(sizeof(struct history));
699 h->n = xmalloc(sizeof(struct history));
702 h->s = strdup(command);
709 /* Add a new history command -- this memory is never freed */
710 h->n = xmalloc(sizeof(struct history));
715 h->s = strdup(command);
718 /* After max history, remove the oldest command */
719 if (history_counter >= MAX_HISTORY) {
721 struct history *p = his_front->n;
736 extern void cmdedit_init(void)
739 signal(SIGWINCH, win_changed);
741 if(exithandler_set == 0) {
742 atexit(cmdedit_reset_term); /* be sure to do this only once */
745 signal(SIGKILL, clean_up_and_die);
746 signal(SIGINT, clean_up_and_die);
747 signal(SIGQUIT, clean_up_and_die);
748 signal(SIGTERM, clean_up_and_die);
752 ** Undo the effects of cmdedit_init() as good as we can:
753 ** I am not aware of a way to revoke an atexit() handler,
754 ** but, fortunately, our particular handler can be made
755 ** a no-op by setting reset_term = 0.
757 extern void cmdedit_terminate(void)
759 cmdedit_reset_term();
761 signal(SIGKILL, SIG_DFL);
762 signal(SIGINT, SIG_DFL);
763 signal(SIGQUIT, SIG_DFL);
764 signal(SIGTERM, SIG_DFL);
765 signal(SIGWINCH, SIG_DFL);
770 #endif /* BB_FEATURE_SH_COMMAND_EDITING */