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 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 */
97 * TODO: Someday we want to implement 'horizontal scrolling' of the
98 * command-line when the user has typed more than the current width. This
99 * would allow the user to see a 'window' of what he has typed.
102 cmdedit_setwidth(int w)
106 cmdedit_scroll = w / 3;
108 errorMsg("\n*** Error: minimum screen width is 21\n");
113 void cmdedit_reset_term(void)
116 /* sparc and other have broken termios support: use old termio handling. */
117 setTermSettings(fileno(stdin), (void*) &initial_settings);
120 void clean_up_and_die(int sig)
122 cmdedit_reset_term();
123 fprintf(stdout, "\n");
128 /* Go to HOME position */
129 void input_home(int outputFd, int *cursor)
131 while (*cursor > 0) {
132 xwrite(outputFd, "\b", 1);
137 /* Go to END position */
138 void input_end(int outputFd, int *cursor, int len)
140 while (*cursor < len) {
141 xwrite(outputFd, "\033[C", 3);
146 /* Delete the char in back of the cursor */
147 void input_backspace(char* command, int outputFd, int *cursor, int *len)
152 //fprintf(stderr, "\nerik: len=%d, cursor=%d, strlen(command)='%d'\n", *len, *cursor, strlen(command));
153 //xwrite(outputFd, command, *len);
158 xwrite(outputFd, "\b \b", 3);
160 memmove(command + *cursor, command + *cursor + 1,
161 BUFSIZ - *cursor + 1);
163 for (j = *cursor; j < (BUFSIZ - 1); j++) {
167 xwrite(outputFd, (command + j), 1);
170 xwrite(outputFd, " \b", 2);
172 while (j-- > *cursor)
173 xwrite(outputFd, "\b", 1);
179 /* Delete the char in front of the cursor */
180 void input_delete(char* command, int outputFd, int cursor, int *len)
187 memmove(command + cursor, command + cursor + 1,
188 BUFSIZ - cursor - 1);
189 for (j = cursor; j < (BUFSIZ - 1); j++) {
193 xwrite(outputFd, (command + j), 1);
196 xwrite(outputFd, " \b", 2);
199 xwrite(outputFd, "\b", 1);
203 /* Move forward one charactor */
204 void input_forward(int outputFd, int *cursor, int len)
207 xwrite(outputFd, "\033[C", 3);
212 /* Move back one charactor */
213 void input_backward(int outputFd, int *cursor)
216 xwrite(outputFd, "\033[D", 3);
223 #ifdef BB_FEATURE_SH_TAB_COMPLETION
224 char** username_tab_completion(char* command, int *num_matches)
226 char **matches = (char **) NULL;
228 fprintf(stderr, "\nin username_tab_completion\n");
233 char** exe_n_cwd_tab_completion(char* command, int *num_matches)
236 char **matches = (char **) NULL;
240 matches = malloc( sizeof(char*)*50);
242 /* Stick a wildcard onto the command, for later use */
243 strcat( command, "*");
245 /* Now wall the current directory */
246 dirName = get_current_dir_name();
247 dir = opendir(dirName);
249 /* Don't print an error, just shut up and return */
253 while ((next = readdir(dir)) != NULL) {
255 /* Some quick sanity checks */
256 if ((strcmp(next->d_name, "..") == 0)
257 || (strcmp(next->d_name, ".") == 0)) {
260 /* See if this matches */
261 if (check_wildcard_match(next->d_name, command) == TRUE) {
262 /* Cool, found a match. Add it to the list */
263 matches[*num_matches] = malloc(strlen(next->d_name)+1);
264 strcpy( matches[*num_matches], next->d_name);
266 //matches = realloc( matches, sizeof(char*)*(*num_matches));
273 void input_tab(char* command, char* prompt, int outputFd, int *cursor, int *len)
275 /* Do TAB completion */
276 static int num_matches=0;
277 static char **matches = (char **) NULL;
281 if (lastWasTab == FALSE) {
282 char *tmp, *tmp1, *matchBuf;
284 /* For now, we will not bother with trying to distinguish
285 * whether the cursor is in/at a command extression -- we
286 * will always try all possible matches. If you don't like
287 * that then feel free to fix it.
290 /* Make a local copy of the string -- up
291 * to the position of the cursor */
292 matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
293 strncpy(matchBuf, command, cursor);
296 /* skip past any command seperator tokens */
297 while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
299 /* skip any leading white space */
300 while (*tmp && isspace(*tmp))
304 /* skip any leading white space */
305 while (*tmp && isspace(*tmp))
308 /* Free up any memory already allocated */
311 matches = (char **) NULL;
314 /* If the word starts with `~' and there is no slash in the word,
315 * then try completing this word as a username. */
317 /* FIXME -- this check is broken! */
318 if (*tmp == '~' && !strchr(tmp, '/'))
319 matches = username_tab_completion(tmp, &num_matches);
321 /* Try to match any executable in our path and everything
322 * in the current working directory that matches. */
324 matches = exe_n_cwd_tab_completion(tmp, &num_matches);
326 /* Don't leak memory */
329 /* Did we find exactly one match? */
330 if (matches && num_matches==1) {
331 /* write out the matched command */
332 strncpy(command+pos, matches[0]+pos, strlen(matches[0])-pos);
335 xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
339 /* Ok -- the last char was a TAB. Since they
340 * just hit TAB again, print a list of all the
341 * available choices... */
342 if ( matches && num_matches>0 ) {
345 /* Go to the next line */
346 xwrite(outputFd, "\n", 1);
347 /* Print the list of matches */
348 for (i=0,col=0; i<num_matches; i++) {
350 sprintf(foo, "%-14s ", matches[i]);
351 col += xwrite(outputFd, foo, strlen(foo));
352 if (col > 60 && matches[i+1] != NULL) {
353 xwrite(outputFd, "\n", 1);
357 /* Go to the next line */
358 xwrite(outputFd, "\n", 1);
359 /* Rewrite the prompt */
360 xwrite(outputFd, prompt, strlen(prompt));
361 /* Rewrite the command */
362 xwrite(outputFd, command, len);
363 /* Put the cursor back to where it used to be */
364 for (cursor=len; cursor > pos; cursor--)
365 xwrite(outputFd, "\b", 1);
371 void get_previous_history(struct history **hp, char* command)
375 (*hp)->s = strdup(command);
379 void get_next_history(struct history **hp, char* command)
383 (*hp)->s = strdup(command);
388 * This function is used to grab a character buffer
389 * from the input file descriptor and allows you to
390 * a string with full command editing (sortof like
393 * The following standard commands are not implemented:
394 * ESC-b -- Move back one word
395 * ESC-f -- Move forward one word
396 * ESC-d -- Delete back one word
397 * ESC-h -- Delete forward one word
398 * CTL-t -- Transpose two characters
400 * Furthermore, the "vi" command editing keys are not implemented.
402 * TODO: implement TAB command completion. :)
404 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
407 int inputFd=fileno(stdin);
408 int outputFd=fileno(stdout);
415 int lastWasTab = FALSE;
417 struct history *hp = his_end;
421 getTermSettings(inputFd, (void*) &initial_settings);
422 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
423 new_settings.c_cc[VMIN] = 1;
424 new_settings.c_cc[VTIME] = 0;
425 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
426 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
427 new_settings.c_lflag &= ~(ECHO|ECHOCTL|ECHONL); /* Turn off echoing */
430 setTermSettings(inputFd, (void*) &new_settings);
432 memset(command, 0, BUFSIZ);
436 if ((ret = read(inputFd, &c, 1)) < 1)
438 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
444 *(command + len++ + 1) = c;
445 xwrite(outputFd, &c, 1);
449 /* Control-a -- Beginning of line */
450 input_home(outputFd, &cursor);
452 /* Control-b -- Move back one character */
453 input_backward(outputFd, &cursor);
456 /* Control-c -- leave the current line,
457 * and start over on the next line */
459 /* Go to the next line */
460 xwrite(outputFd, "\n", 1);
462 /* Rewrite the prompt */
463 xwrite(outputFd, prompt, strlen(prompt));
465 /* Reset the command string */
466 memset(command, 0, BUFSIZ);
471 /* Control-d -- Delete one character, or exit
472 * if the len=0 and no chars to delete */
474 xwrite(outputFd, "exit", 4);
477 input_delete(command, outputFd, cursor, &len);
481 /* Control-e -- End of line */
482 input_end(outputFd, &cursor, len);
485 /* Control-f -- Move forward one character */
486 input_forward(outputFd, &cursor, len);
490 /* Control-h and DEL */
491 input_backspace(command, outputFd, &cursor, &len);
494 #ifdef BB_FEATURE_SH_TAB_COMPLETION
495 input_tab(command, prompt, outputFd, &cursor, &len);
499 /* Control-n -- Get next command in history */
500 if (hp && hp->n && hp->n->s) {
501 get_next_history(&hp, command);
504 xwrite(outputFd, "\007", 1);
508 /* Control-p -- Get previous command from history */
510 get_previous_history(&hp, command);
513 xwrite(outputFd, "\007", 1);
517 /* escape sequence follows */
518 if ((ret = read(inputFd, &c, 1)) < 1)
521 if (c == '[') { /* 91 */
522 if ((ret = read(inputFd, &c, 1)) < 1)
527 /* Up Arrow -- Get previous command from history */
529 get_previous_history(&hp, command);
532 xwrite(outputFd, "\007", 1);
536 /* Down Arrow -- Get next command in history */
537 if (hp && hp->n && hp->n->s) {
538 get_next_history(&hp, command);
541 xwrite(outputFd, "\007", 1);
545 /* Rewrite the line with the selected history item */
547 /* erase old command from command line */
548 len = strlen(command)-strlen(hp->s);
551 input_delete(command, outputFd, cursor, &len);
553 input_backspace(command, outputFd, &cursor, &len);
554 input_home(outputFd, &cursor);
556 /* write new command */
557 strcpy(command, hp->s);
559 xwrite(outputFd, command, len);
563 /* Right Arrow -- Move forward one character */
564 input_forward(outputFd, &cursor, len);
567 /* Left Arrow -- Move back one character */
568 input_backward(outputFd, &cursor);
572 input_delete(command, outputFd, cursor, &len);
576 input_home(outputFd, &cursor);
580 input_end(outputFd, &cursor, len);
583 xwrite(outputFd, "\007", 1);
585 if (c == '1' || c == '3' || c == '4')
586 if ((ret = read(inputFd, &c, 1)) < 1)
587 return; /* read 126 (~) */
591 if ((ret = read(inputFd, &c, 1)) < 1)
596 input_home(outputFd, &cursor);
600 input_end(outputFd, &cursor, len);
603 xwrite(outputFd, "\007", 1);
610 default: /* If it's regular input, do the normal thing */
612 if (!isprint(c)) { /* Skip non-printable characters */
616 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
621 if (cursor == (len - 1)) { /* Append if at the end of the line */
622 *(command + cursor) = c;
623 } else { /* Insert otherwise */
624 memmove(command + cursor + 1, command + cursor,
627 *(command + cursor) = c;
629 for (j = cursor; j < len; j++)
630 xwrite(outputFd, command + j, 1);
631 for (; j > cursor; j--)
632 xwrite(outputFd, "\033[D", 3);
636 xwrite(outputFd, &c, 1);
644 if (break_out) /* Enter is the command terminator, no more input. */
649 setTermSettings(inputFd, (void *) &initial_settings);
653 /* Handle command history log */
656 struct history *h = his_end;
659 /* No previous history -- this memory is never freed */
660 h = his_front = malloc(sizeof(struct history));
661 h->n = malloc(sizeof(struct history));
664 h->s = strdup(command);
671 /* Add a new history command -- this memory is never freed */
672 h->n = malloc(sizeof(struct history));
677 h->s = strdup(command);
680 /* After max history, remove the oldest command */
681 if (history_counter >= MAX_HISTORY) {
683 struct history *p = his_front->n;
698 extern void cmdedit_init(void)
700 atexit(cmdedit_reset_term);
701 signal(SIGKILL, clean_up_and_die);
702 signal(SIGINT, clean_up_and_die);
703 signal(SIGQUIT, clean_up_and_die);
704 signal(SIGTERM, clean_up_and_die);
706 #endif /* BB_FEATURE_SH_COMMAND_EDITING */