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 */
98 * TODO: Someday we want to implement 'horizontal scrolling' of the
99 * command-line when the user has typed more than the current width. This
100 * would allow the user to see a 'window' of what he has typed.
103 cmdedit_setwidth(int w)
107 cmdedit_scroll = w / 3;
109 error_msg("\n*** Error: minimum screen width is 21\n");
114 void cmdedit_reset_term(void)
117 /* sparc and other have broken termios support: use old termio handling. */
118 setTermSettings(fileno(stdin), (void*) &initial_settings);
119 #ifdef BB_FEATURE_CLEAN_UP
122 //while(his_front!=his_end) {
123 while(his_front!=his_end) {
133 void clean_up_and_die(int sig)
135 cmdedit_reset_term();
136 fprintf(stdout, "\n");
141 /* Go to HOME position */
142 void input_home(int outputFd, int *cursor)
144 while (*cursor > 0) {
145 xwrite(outputFd, "\b", 1);
150 /* Go to END position */
151 void input_end(int outputFd, int *cursor, int len)
153 while (*cursor < len) {
154 xwrite(outputFd, "\033[C", 3);
159 /* Delete the char in back of the cursor */
160 void input_backspace(char* command, int outputFd, int *cursor, int *len)
165 //fprintf(stderr, "\nerik: len=%d, cursor=%d, strlen(command)='%d'\n", *len, *cursor, strlen(command));
166 //xwrite(outputFd, command, *len);
171 xwrite(outputFd, "\b \b", 3);
173 memmove(command + *cursor, command + *cursor + 1,
174 BUFSIZ - *cursor + 1);
176 for (j = *cursor; j < (BUFSIZ - 1); j++) {
180 xwrite(outputFd, (command + j), 1);
183 xwrite(outputFd, " \b", 2);
185 while (j-- > *cursor)
186 xwrite(outputFd, "\b", 1);
192 /* Delete the char in front of the cursor */
193 void input_delete(char* command, int outputFd, int cursor, int *len)
200 memmove(command + cursor, command + cursor + 1,
201 BUFSIZ - cursor - 1);
202 for (j = cursor; j < (BUFSIZ - 1); j++) {
206 xwrite(outputFd, (command + j), 1);
209 xwrite(outputFd, " \b", 2);
212 xwrite(outputFd, "\b", 1);
216 /* Move forward one charactor */
217 void input_forward(int outputFd, int *cursor, int len)
220 xwrite(outputFd, "\033[C", 3);
225 /* Move back one charactor */
226 void input_backward(int outputFd, int *cursor)
229 xwrite(outputFd, "\033[D", 3);
236 #ifdef BB_FEATURE_SH_TAB_COMPLETION
237 char** username_tab_completion(char* command, int *num_matches)
239 char **matches = (char **) NULL;
241 fprintf(stderr, "\nin username_tab_completion\n");
246 char** exe_n_cwd_tab_completion(char* command, int *num_matches)
253 matches = xmalloc( sizeof(char*)*50);
255 /* Stick a wildcard onto the command, for later use */
256 strcat( command, "*");
258 /* Now wall the current directory */
259 dirName = get_current_dir_name();
260 dir = opendir(dirName);
262 /* Don't print an error, just shut up and return */
266 while ((next = readdir(dir)) != NULL) {
268 /* Some quick sanity checks */
269 if ((strcmp(next->d_name, "..") == 0)
270 || (strcmp(next->d_name, ".") == 0)) {
273 /* See if this matches */
274 if (check_wildcard_match(next->d_name, command) == TRUE) {
275 /* Cool, found a match. Add it to the list */
276 matches[*num_matches] = xmalloc(strlen(next->d_name)+1);
277 strcpy( matches[*num_matches], next->d_name);
279 //matches = realloc( matches, sizeof(char*)*(*num_matches));
286 void input_tab(char* command, char* prompt, int outputFd, int *cursor, int *len, int lastWasTab)
288 /* Do TAB completion */
289 static int num_matches=0;
290 static char **matches = (char **) NULL;
294 if (lastWasTab == FALSE) {
295 char *tmp, *tmp1, *matchBuf;
297 /* For now, we will not bother with trying to distinguish
298 * whether the cursor is in/at a command extression -- we
299 * will always try all possible matches. If you don't like
300 * that then feel free to fix it.
303 /* Make a local copy of the string -- up
304 * to the position of the cursor */
305 matchBuf = (char *) xcalloc(BUFSIZ, sizeof(char));
306 strncpy(matchBuf, command, *cursor);
309 /* skip past any command seperator tokens */
310 while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
312 /* skip any leading white space */
313 while (*tmp && isspace(*tmp))
317 /* skip any leading white space */
318 while (*tmp && isspace(*tmp))
321 /* Free up any memory already allocated */
324 matches = (char **) NULL;
327 /* If the word starts with `~' and there is no slash in the word,
328 * then try completing this word as a username. */
330 /* FIXME -- this check is broken! */
331 if (*tmp == '~' && !strchr(tmp, '/'))
332 matches = username_tab_completion(tmp, &num_matches);
334 /* Try to match any executable in our path and everything
335 * in the current working directory that matches. */
337 matches = exe_n_cwd_tab_completion(tmp, &num_matches);
339 /* Don't leak memory */
342 /* Did we find exactly one match? */
343 if (matches && num_matches==1) {
344 /* write out the matched command */
345 strncpy(command+pos, matches[0]+pos, strlen(matches[0])-pos);
346 *len=strlen(command);
348 xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
352 /* Ok -- the last char was a TAB. Since they
353 * just hit TAB again, print a list of all the
354 * available choices... */
355 if ( matches && num_matches>0 ) {
358 /* Go to the next line */
359 xwrite(outputFd, "\n", 1);
360 /* Print the list of matches */
361 for (i=0,col=0; i<num_matches; i++) {
363 sprintf(foo, "%-14s ", matches[i]);
364 col += xwrite(outputFd, foo, strlen(foo));
365 if (col > 60 && matches[i+1] != NULL) {
366 xwrite(outputFd, "\n", 1);
370 /* Go to the next line */
371 xwrite(outputFd, "\n", 1);
372 /* Rewrite the prompt */
373 xwrite(outputFd, prompt, strlen(prompt));
374 /* Rewrite the command */
375 xwrite(outputFd, command, *len);
376 /* Put the cursor back to where it used to be */
377 for (cursor=len; *cursor > pos; cursor--)
378 xwrite(outputFd, "\b", 1);
384 void get_previous_history(struct history **hp, char* command)
388 (*hp)->s = strdup(command);
392 void get_next_history(struct history **hp, char* command)
396 (*hp)->s = strdup(command);
401 * This function is used to grab a character buffer
402 * from the input file descriptor and allows you to
403 * a string with full command editing (sortof like
406 * The following standard commands are not implemented:
407 * ESC-b -- Move back one word
408 * ESC-f -- Move forward one word
409 * ESC-d -- Delete back one word
410 * ESC-h -- Delete forward one word
411 * CTL-t -- Transpose two characters
413 * Furthermore, the "vi" command editing keys are not implemented.
415 * TODO: implement TAB command completion. :)
417 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
420 int inputFd=fileno(stdin);
421 int outputFd=fileno(stdout);
428 int lastWasTab = FALSE;
430 struct history *hp = his_end;
434 getTermSettings(inputFd, (void*) &initial_settings);
435 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
436 new_settings.c_cc[VMIN] = 1;
437 new_settings.c_cc[VTIME] = 0;
438 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
439 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
440 new_settings.c_lflag &= ~(ECHO|ECHOCTL|ECHONL); /* Turn off echoing */
443 setTermSettings(inputFd, (void*) &new_settings);
445 memset(command, 0, BUFSIZ);
449 if ((ret = read(inputFd, &c, 1)) < 1)
451 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
457 *(command + len++ + 1) = c;
458 xwrite(outputFd, &c, 1);
462 /* Control-a -- Beginning of line */
463 input_home(outputFd, &cursor);
465 /* Control-b -- Move back one character */
466 input_backward(outputFd, &cursor);
469 /* Control-c -- leave the current line,
470 * and start over on the next line */
472 /* Go to the next line */
473 xwrite(outputFd, "\n", 1);
475 /* Rewrite the prompt */
476 xwrite(outputFd, prompt, strlen(prompt));
478 /* Reset the command string */
479 memset(command, 0, BUFSIZ);
484 /* Control-d -- Delete one character, or exit
485 * if the len=0 and no chars to delete */
487 xwrite(outputFd, "exit", 4);
490 input_delete(command, outputFd, cursor, &len);
494 /* Control-e -- End of line */
495 input_end(outputFd, &cursor, len);
498 /* Control-f -- Move forward one character */
499 input_forward(outputFd, &cursor, len);
503 /* Control-h and DEL */
504 input_backspace(command, outputFd, &cursor, &len);
507 #ifdef BB_FEATURE_SH_TAB_COMPLETION
508 input_tab(command, prompt, outputFd, &cursor,
513 /* Control-n -- Get next command in history */
514 if (hp && hp->n && hp->n->s) {
515 get_next_history(&hp, command);
518 xwrite(outputFd, "\007", 1);
522 /* Control-p -- Get previous command from history */
524 get_previous_history(&hp, command);
527 xwrite(outputFd, "\007", 1);
531 /* escape sequence follows */
532 if ((ret = read(inputFd, &c, 1)) < 1)
535 if (c == '[') { /* 91 */
536 if ((ret = read(inputFd, &c, 1)) < 1)
541 /* Up Arrow -- Get previous command from history */
543 get_previous_history(&hp, command);
546 xwrite(outputFd, "\007", 1);
550 /* Down Arrow -- Get next command in history */
551 if (hp && hp->n && hp->n->s) {
552 get_next_history(&hp, command);
555 xwrite(outputFd, "\007", 1);
559 /* Rewrite the line with the selected history item */
561 /* erase old command from command line */
562 len = strlen(command)-strlen(hp->s);
565 input_delete(command, outputFd, cursor, &len);
567 input_backspace(command, outputFd, &cursor, &len);
568 input_home(outputFd, &cursor);
570 /* write new command */
571 strcpy(command, hp->s);
573 xwrite(outputFd, command, len);
577 /* Right Arrow -- Move forward one character */
578 input_forward(outputFd, &cursor, len);
581 /* Left Arrow -- Move back one character */
582 input_backward(outputFd, &cursor);
586 input_delete(command, outputFd, cursor, &len);
590 input_home(outputFd, &cursor);
594 input_end(outputFd, &cursor, len);
597 xwrite(outputFd, "\007", 1);
599 if (c == '1' || c == '3' || c == '4')
600 if ((ret = read(inputFd, &c, 1)) < 1)
601 return; /* read 126 (~) */
605 if ((ret = read(inputFd, &c, 1)) < 1)
610 input_home(outputFd, &cursor);
614 input_end(outputFd, &cursor, len);
617 xwrite(outputFd, "\007", 1);
624 default: /* If it's regular input, do the normal thing */
626 if (!isprint(c)) { /* Skip non-printable characters */
630 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
635 if (cursor == (len - 1)) { /* Append if at the end of the line */
636 *(command + cursor) = c;
637 } else { /* Insert otherwise */
638 memmove(command + cursor + 1, command + cursor,
641 *(command + cursor) = c;
643 for (j = cursor; j < len; j++)
644 xwrite(outputFd, command + j, 1);
645 for (; j > cursor; j--)
646 xwrite(outputFd, "\033[D", 3);
650 xwrite(outputFd, &c, 1);
658 if (break_out) /* Enter is the command terminator, no more input. */
663 setTermSettings(inputFd, (void *) &initial_settings);
667 /* Handle command history log */
670 struct history *h = his_end;
673 /* No previous history -- this memory is never freed */
674 h = his_front = xmalloc(sizeof(struct history));
675 h->n = xmalloc(sizeof(struct history));
678 h->s = strdup(command);
685 /* Add a new history command -- this memory is never freed */
686 h->n = xmalloc(sizeof(struct history));
691 h->s = strdup(command);
694 /* After max history, remove the oldest command */
695 if (history_counter >= MAX_HISTORY) {
697 struct history *p = his_front->n;
712 extern void cmdedit_init(void)
714 if(exithandler_set == 0) {
715 atexit(cmdedit_reset_term); /* be sure to do this only once */
718 signal(SIGKILL, clean_up_and_die);
719 signal(SIGINT, clean_up_and_die);
720 signal(SIGQUIT, clean_up_and_die);
721 signal(SIGTERM, clean_up_and_die);
725 ** Undo the effects of cmdedit_init() as good as we can:
726 ** I am not aware of a way to revoke an atexit() handler,
727 ** but, fortunately, our particular handler can be made
728 ** a no-op by setting reset_term = 0.
730 extern void cmdedit_terminate(void)
732 cmdedit_reset_term();
734 signal(SIGKILL, SIG_DFL);
735 signal(SIGINT, SIG_DFL);
736 signal(SIGQUIT, SIG_DFL);
737 signal(SIGTERM, SIG_DFL);
742 #endif /* BB_FEATURE_SH_COMMAND_EDITING */