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 #define MAX_HISTORY 15 /* Maximum length of the linked list for the command line history */
51 #define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0)
52 #define whitespace(c) (((c) == ' ') || ((c) == '\t'))
54 static struct history *his_front = NULL; /* First element in command line list */
55 static struct history *his_end = NULL; /* Last element in command line list */
57 /* ED: sparc termios is broken: revert back to old termio handling. */
58 #ifdef BB_FEATURE_USE_TERMIOS
62 # define termios termio
63 # define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
64 # define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
67 # define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
68 # define getTermSettings(fd,argp) tcgetattr(fd, argp);
71 /* Current termio and the previous termio before starting sh */
72 static struct termios initial_settings, new_settings;
75 #ifndef _POSIX_VDISABLE
76 #define _POSIX_VDISABLE '\0'
83 static int cmdedit_termw = 80; /* actual terminal width */
84 static int cmdedit_scroll = 27; /* width of EOL scrolling region */
85 static int history_counter = 0; /* Number of commands in history list */
86 static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */
87 static int exithandler_set = 0; /* Set to true when atexit() has been called */
90 /* Link into lash to reset context to 0
92 extern unsigned int shell_context;
104 * TODO: Someday we want to implement 'horizontal scrolling' of the
105 * command-line when the user has typed more than the current width. This
106 * would allow the user to see a 'window' of what he has typed.
109 cmdedit_setwidth(int w)
113 cmdedit_scroll = w / 3;
115 error_msg("\n*** Error: minimum screen width is 21\n");
120 void cmdedit_reset_term(void)
123 /* sparc and other have broken termios support: use old termio handling. */
124 setTermSettings(fileno(stdin), (void*) &initial_settings);
125 #ifdef BB_FEATURE_CLEAN_UP
128 //while(his_front!=his_end) {
129 while(his_front!=his_end) {
139 void clean_up_and_die(int sig)
141 cmdedit_reset_term();
142 fprintf(stdout, "\n");
147 /* Go to HOME position */
148 void input_home(int outputFd, int *cursor)
150 while (*cursor > 0) {
151 xwrite(outputFd, "\b", 1);
156 /* Go to END position */
157 void input_end(int outputFd, int *cursor, int len)
159 while (*cursor < len) {
160 xwrite(outputFd, "\033[C", 3);
165 /* Delete the char in back of the cursor */
166 void input_backspace(char* command, int outputFd, int *cursor, int *len)
171 //fprintf(stderr, "\nerik: len=%d, cursor=%d, strlen(command)='%d'\n", *len, *cursor, strlen(command));
172 //xwrite(outputFd, command, *len);
177 xwrite(outputFd, "\b \b", 3);
179 memmove(command + *cursor, command + *cursor + 1,
180 BUFSIZ - *cursor + 1);
182 for (j = *cursor; j < (BUFSIZ - 1); j++) {
186 xwrite(outputFd, (command + j), 1);
189 xwrite(outputFd, " \b", 2);
191 while (j-- > *cursor)
192 xwrite(outputFd, "\b", 1);
198 /* Delete the char in front of the cursor */
199 void input_delete(char* command, int outputFd, int cursor, int *len)
206 memmove(command + cursor, command + cursor + 1,
207 BUFSIZ - cursor - 1);
208 for (j = cursor; j < (BUFSIZ - 1); j++) {
212 xwrite(outputFd, (command + j), 1);
215 xwrite(outputFd, " \b", 2);
218 xwrite(outputFd, "\b", 1);
222 /* Move forward one charactor */
223 void input_forward(int outputFd, int *cursor, int len)
226 xwrite(outputFd, "\033[C", 3);
231 /* Move back one charactor */
232 void input_backward(int outputFd, int *cursor)
235 xwrite(outputFd, "\033[D", 3);
242 #ifdef BB_FEATURE_SH_TAB_COMPLETION
243 char** username_tab_completion(char* command, int *num_matches)
245 char **matches = (char **) NULL;
247 fprintf(stderr, "\nin username_tab_completion\n");
252 char** exe_n_cwd_tab_completion(char* command, int *num_matches)
259 matches = xmalloc( sizeof(char*)*50);
261 /* Stick a wildcard onto the command, for later use */
262 strcat( command, "*");
264 /* Now wall the current directory */
265 dirName = get_current_dir_name();
266 dir = opendir(dirName);
268 /* Don't print an error, just shut up and return */
272 while ((next = readdir(dir)) != NULL) {
274 /* Some quick sanity checks */
275 if ((strcmp(next->d_name, "..") == 0)
276 || (strcmp(next->d_name, ".") == 0)) {
279 /* See if this matches */
280 if (check_wildcard_match(next->d_name, command) == TRUE) {
281 /* Cool, found a match. Add it to the list */
282 matches[*num_matches] = xmalloc(strlen(next->d_name)+1);
283 strcpy( matches[*num_matches], next->d_name);
285 //matches = realloc( matches, sizeof(char*)*(*num_matches));
292 void input_tab(char* command, char* prompt, int outputFd, int *cursor, int *len, int lastWasTab)
294 /* Do TAB completion */
295 static int num_matches=0;
296 static char **matches = (char **) NULL;
300 if (lastWasTab == FALSE) {
301 char *tmp, *tmp1, *matchBuf;
303 /* For now, we will not bother with trying to distinguish
304 * whether the cursor is in/at a command extression -- we
305 * will always try all possible matches. If you don't like
306 * that then feel free to fix it.
309 /* Make a local copy of the string -- up
310 * to the position of the cursor */
311 matchBuf = (char *) xcalloc(BUFSIZ, sizeof(char));
312 strncpy(matchBuf, command, *cursor);
315 /* skip past any command seperator tokens */
316 while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
318 /* skip any leading white space */
319 while (*tmp && isspace(*tmp))
323 /* skip any leading white space */
324 while (*tmp && isspace(*tmp))
327 /* Free up any memory already allocated */
330 matches = (char **) NULL;
333 /* If the word starts with `~' and there is no slash in the word,
334 * then try completing this word as a username. */
336 /* FIXME -- this check is broken! */
337 if (*tmp == '~' && !strchr(tmp, '/'))
338 matches = username_tab_completion(tmp, &num_matches);
340 /* Try to match any executable in our path and everything
341 * in the current working directory that matches. */
343 matches = exe_n_cwd_tab_completion(tmp, &num_matches);
345 /* Don't leak memory */
348 /* Did we find exactly one match? */
349 if (matches && num_matches==1) {
350 /* write out the matched command */
351 strncpy(command+pos, matches[0]+pos, strlen(matches[0])-pos);
352 *len=strlen(command);
354 xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
358 /* Ok -- the last char was a TAB. Since they
359 * just hit TAB again, print a list of all the
360 * available choices... */
361 if ( matches && num_matches>0 ) {
364 /* Go to the next line */
365 xwrite(outputFd, "\n", 1);
366 /* Print the list of matches */
367 for (i=0,col=0; i<num_matches; i++) {
369 sprintf(foo, "%-14s ", matches[i]);
370 col += xwrite(outputFd, foo, strlen(foo));
371 if (col > 60 && matches[i+1] != NULL) {
372 xwrite(outputFd, "\n", 1);
376 /* Go to the next line */
377 xwrite(outputFd, "\n", 1);
378 /* Rewrite the prompt */
379 xwrite(outputFd, prompt, strlen(prompt));
380 /* Rewrite the command */
381 xwrite(outputFd, command, *len);
382 /* Put the cursor back to where it used to be */
383 for (cursor=len; *cursor > pos; cursor--)
384 xwrite(outputFd, "\b", 1);
390 void get_previous_history(struct history **hp, char* command)
394 (*hp)->s = strdup(command);
398 void get_next_history(struct history **hp, char* command)
402 (*hp)->s = strdup(command);
407 * This function is used to grab a character buffer
408 * from the input file descriptor and allows you to
409 * a string with full command editing (sortof like
412 * The following standard commands are not implemented:
413 * ESC-b -- Move back one word
414 * ESC-f -- Move forward one word
415 * ESC-d -- Delete back one word
416 * ESC-h -- Delete forward one word
417 * CTL-t -- Transpose two characters
419 * Furthermore, the "vi" command editing keys are not implemented.
421 * TODO: implement TAB command completion. :)
423 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
426 int inputFd=fileno(stdin);
427 int outputFd=fileno(stdout);
434 int lastWasTab = FALSE;
436 struct history *hp = his_end;
440 getTermSettings(inputFd, (void*) &initial_settings);
441 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
442 new_settings.c_cc[VMIN] = 1;
443 new_settings.c_cc[VTIME] = 0;
444 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
445 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
446 new_settings.c_lflag &= ~(ECHO|ECHOCTL|ECHONL); /* Turn off echoing */
449 setTermSettings(inputFd, (void*) &new_settings);
451 memset(command, 0, BUFSIZ);
455 if ((ret = read(inputFd, &c, 1)) < 1)
457 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
463 *(command + len++ + 1) = c;
464 xwrite(outputFd, &c, 1);
468 /* Control-a -- Beginning of line */
469 input_home(outputFd, &cursor);
471 /* Control-b -- Move back one character */
472 input_backward(outputFd, &cursor);
475 /* Control-c -- stop gathering input */
477 /* Link into lash to reset context to 0 on ^C and such */
480 /* Go to the next line */
481 xwrite(outputFd, "\n", 1);
484 /* Rewrite the prompt */
485 xwrite(outputFd, prompt, strlen(prompt));
487 /* Reset the command string */
488 memset(command, 0, BUFSIZ);
494 /* Control-d -- Delete one character, or exit
495 * if the len=0 and no chars to delete */
497 xwrite(outputFd, "exit", 4);
500 input_delete(command, outputFd, cursor, &len);
504 /* Control-e -- End of line */
505 input_end(outputFd, &cursor, len);
508 /* Control-f -- Move forward one character */
509 input_forward(outputFd, &cursor, len);
513 /* Control-h and DEL */
514 input_backspace(command, outputFd, &cursor, &len);
517 #ifdef BB_FEATURE_SH_TAB_COMPLETION
518 input_tab(command, prompt, outputFd, &cursor,
523 /* Control-n -- Get next command in history */
524 if (hp && hp->n && hp->n->s) {
525 get_next_history(&hp, command);
528 xwrite(outputFd, "\007", 1);
532 /* Control-p -- Get previous command from history */
534 get_previous_history(&hp, command);
537 xwrite(outputFd, "\007", 1);
541 /* escape sequence follows */
542 if ((ret = read(inputFd, &c, 1)) < 1)
545 if (c == '[') { /* 91 */
546 if ((ret = read(inputFd, &c, 1)) < 1)
551 /* Up Arrow -- Get previous command from history */
553 get_previous_history(&hp, command);
556 xwrite(outputFd, "\007", 1);
560 /* Down Arrow -- Get next command in history */
561 if (hp && hp->n && hp->n->s) {
562 get_next_history(&hp, command);
565 xwrite(outputFd, "\007", 1);
569 /* Rewrite the line with the selected history item */
571 /* erase old command from command line */
572 len = strlen(command)-strlen(hp->s);
575 input_delete(command, outputFd, cursor, &len);
577 input_backspace(command, outputFd, &cursor, &len);
578 input_home(outputFd, &cursor);
580 /* write new command */
581 strcpy(command, hp->s);
583 xwrite(outputFd, command, len);
587 /* Right Arrow -- Move forward one character */
588 input_forward(outputFd, &cursor, len);
591 /* Left Arrow -- Move back one character */
592 input_backward(outputFd, &cursor);
596 input_delete(command, outputFd, cursor, &len);
600 input_home(outputFd, &cursor);
604 input_end(outputFd, &cursor, len);
607 xwrite(outputFd, "\007", 1);
609 if (c == '1' || c == '3' || c == '4')
610 if ((ret = read(inputFd, &c, 1)) < 1)
611 return; /* read 126 (~) */
615 if ((ret = read(inputFd, &c, 1)) < 1)
620 input_home(outputFd, &cursor);
624 input_end(outputFd, &cursor, len);
627 xwrite(outputFd, "\007", 1);
634 default: /* If it's regular input, do the normal thing */
636 if (!isprint(c)) { /* Skip non-printable characters */
640 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
645 if (cursor == (len - 1)) { /* Append if at the end of the line */
646 *(command + cursor) = c;
647 } else { /* Insert otherwise */
648 memmove(command + cursor + 1, command + cursor,
651 *(command + cursor) = c;
653 for (j = cursor; j < len; j++)
654 xwrite(outputFd, command + j, 1);
655 for (; j > cursor; j--)
656 xwrite(outputFd, "\033[D", 3);
660 xwrite(outputFd, &c, 1);
668 if (break_out) /* Enter is the command terminator, no more input. */
673 setTermSettings(inputFd, (void *) &initial_settings);
677 /* Handle command history log */
680 struct history *h = his_end;
683 /* No previous history -- this memory is never freed */
684 h = his_front = xmalloc(sizeof(struct history));
685 h->n = xmalloc(sizeof(struct history));
688 h->s = strdup(command);
695 /* Add a new history command -- this memory is never freed */
696 h->n = xmalloc(sizeof(struct history));
701 h->s = strdup(command);
704 /* After max history, remove the oldest command */
705 if (history_counter >= MAX_HISTORY) {
707 struct history *p = his_front->n;
722 extern void cmdedit_init(void)
724 if(exithandler_set == 0) {
725 atexit(cmdedit_reset_term); /* be sure to do this only once */
728 signal(SIGKILL, clean_up_and_die);
729 signal(SIGINT, clean_up_and_die);
730 signal(SIGQUIT, clean_up_and_die);
731 signal(SIGTERM, clean_up_and_die);
735 ** Undo the effects of cmdedit_init() as good as we can:
736 ** I am not aware of a way to revoke an atexit() handler,
737 ** but, fortunately, our particular handler can be made
738 ** a no-op by setting reset_term = 0.
740 extern void cmdedit_terminate(void)
742 cmdedit_reset_term();
744 signal(SIGKILL, SIG_DFL);
745 signal(SIGINT, SIG_DFL);
746 signal(SIGQUIT, SIG_DFL);
747 signal(SIGTERM, SIG_DFL);
752 #endif /* BB_FEATURE_SH_COMMAND_EDITING */