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 cmdedit_setwidth(int w)
101 cmdedit_scroll = w / 3;
103 errorMsg("\n*** Error: minimum screen width is 21\n");
108 void cmdedit_reset_term(void)
111 /* sparc and other have broken termios support: use old termio handling. */
112 setTermSettings(fileno(stdin), (void*) &initial_settings);
115 void clean_up_and_die(int sig)
117 cmdedit_reset_term();
118 fprintf(stdout, "\n");
123 /* Go to HOME position */
124 void input_home(int outputFd, int *cursor)
126 while (*cursor > 0) {
127 xwrite(outputFd, "\b", 1);
132 /* Go to END position */
133 void input_end(int outputFd, int *cursor, int len)
135 while (*cursor < len) {
136 xwrite(outputFd, "\033[C", 3);
141 /* Delete the char in back of the cursor */
142 void input_backspace(char* command, int outputFd, int *cursor, int *len)
147 xwrite(outputFd, "\b \b", 3);
149 memmove(command + *cursor, command + *cursor + 1,
150 BUFSIZ - *cursor + 1);
152 for (j = *cursor; j < (BUFSIZ - 1); j++) {
156 xwrite(outputFd, (command + j), 1);
159 xwrite(outputFd, " \b", 2);
161 while (j-- > *cursor)
162 xwrite(outputFd, "\b", 1);
168 /* Delete the char in front of the cursor */
169 void input_delete(char* command, int outputFd, int cursor, int *len)
176 memmove(command + cursor, command + cursor + 1,
177 BUFSIZ - cursor - 1);
178 for (j = cursor; j < (BUFSIZ - 1); j++) {
182 xwrite(outputFd, (command + j), 1);
185 xwrite(outputFd, " \b", 2);
188 xwrite(outputFd, "\b", 1);
192 /* Move forward one charactor */
193 void input_forward(int outputFd, int *cursor, int len)
196 xwrite(outputFd, "\033[C", 3);
201 /* Move back one charactor */
202 void input_backward(int outputFd, int *cursor)
205 xwrite(outputFd, "\033[D", 3);
212 #ifdef BB_FEATURE_SH_TAB_COMPLETION
213 char** username_tab_completion(char* command, int *num_matches)
215 char **matches = (char **) NULL;
217 fprintf(stderr, "\nin username_tab_completion\n");
222 char** exe_n_cwd_tab_completion(char* command, int *num_matches)
225 char **matches = (char **) NULL;
229 matches = malloc( sizeof(char*)*50);
231 /* Stick a wildcard onto the command, for later use */
232 strcat( command, "*");
234 /* Now wall the current directory */
235 dirName = get_current_dir_name();
236 dir = opendir(dirName);
238 /* Don't print an error, just shut up and return */
242 while ((next = readdir(dir)) != NULL) {
244 /* Some quick sanity checks */
245 if ((strcmp(next->d_name, "..") == 0)
246 || (strcmp(next->d_name, ".") == 0)) {
249 /* See if this matches */
250 if (check_wildcard_match(next->d_name, command) == TRUE) {
251 /* Cool, found a match. Add it to the list */
252 matches[*num_matches] = malloc(strlen(next->d_name)+1);
253 strcpy( matches[*num_matches], next->d_name);
255 //matches = realloc( matches, sizeof(char*)*(*num_matches));
262 void input_tab(char* command, char* prompt, int outputFd, int *cursor, int *len)
264 /* Do TAB completion */
265 static int num_matches=0;
266 static char **matches = (char **) NULL;
270 if (lastWasTab == FALSE) {
271 char *tmp, *tmp1, *matchBuf;
273 /* For now, we will not bother with trying to distinguish
274 * whether the cursor is in/at a command extression -- we
275 * will always try all possible matches. If you don't like
276 * that then feel free to fix it.
279 /* Make a local copy of the string -- up
280 * to the position of the cursor */
281 matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
282 strncpy(matchBuf, command, cursor);
285 /* skip past any command seperator tokens */
286 while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
288 /* skip any leading white space */
289 while (*tmp && isspace(*tmp))
293 /* skip any leading white space */
294 while (*tmp && isspace(*tmp))
297 /* Free up any memory already allocated */
300 matches = (char **) NULL;
303 /* If the word starts with `~' and there is no slash in the word,
304 * then try completing this word as a username. */
306 /* FIXME -- this check is broken! */
307 if (*tmp == '~' && !strchr(tmp, '/'))
308 matches = username_tab_completion(tmp, &num_matches);
310 /* Try to match any executable in our path and everything
311 * in the current working directory that matches. */
313 matches = exe_n_cwd_tab_completion(tmp, &num_matches);
315 /* Don't leak memory */
318 /* Did we find exactly one match? */
319 if (matches && num_matches==1) {
320 /* write out the matched command */
321 strncpy(command+pos, matches[0]+pos, strlen(matches[0])-pos);
324 xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
328 /* Ok -- the last char was a TAB. Since they
329 * just hit TAB again, print a list of all the
330 * available choices... */
331 if ( matches && num_matches>0 ) {
334 /* Go to the next line */
335 xwrite(outputFd, "\n", 1);
336 /* Print the list of matches */
337 for (i=0,col=0; i<num_matches; i++) {
339 sprintf(foo, "%-14s ", matches[i]);
340 col += xwrite(outputFd, foo, strlen(foo));
341 if (col > 60 && matches[i+1] != NULL) {
342 xwrite(outputFd, "\n", 1);
346 /* Go to the next line */
347 xwrite(outputFd, "\n", 1);
348 /* Rewrite the prompt */
349 xwrite(outputFd, prompt, strlen(prompt));
350 /* Rewrite the command */
351 xwrite(outputFd, command, len);
352 /* Put the cursor back to where it used to be */
353 for (cursor=len; cursor > pos; cursor--)
354 xwrite(outputFd, "\b", 1);
360 void get_previous_history(struct history **hp, char* command)
363 (*hp)->s = strdup(command);
367 void get_next_history(struct history **hp, char* command)
370 (*hp)->s = strdup(command);
375 * This function is used to grab a character buffer
376 * from the input file descriptor and allows you to
377 * a string with full command editing (sortof like
380 * The following standard commands are not implemented:
381 * ESC-b -- Move back one word
382 * ESC-f -- Move forward one word
383 * ESC-d -- Delete back one word
384 * ESC-h -- Delete forward one word
385 * CTL-t -- Transpose two characters
387 * Furthermore, the "vi" command editing keys are not implemented.
389 * TODO: implement TAB command completion. :)
391 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
394 int inputFd=fileno(stdin);
395 int outputFd=fileno(stdout);
402 int lastWasTab = FALSE;
404 struct history *hp = his_end;
406 memset(command, 0, sizeof(command));
409 getTermSettings(inputFd, (void*) &initial_settings);
410 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
411 new_settings.c_cc[VMIN] = 1;
412 new_settings.c_cc[VTIME] = 0;
413 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
414 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
415 new_settings.c_lflag &= ~(ECHO|ECHOCTL|ECHONL); /* Turn off echoing */
418 setTermSettings(inputFd, (void*) &new_settings);
420 memset(command, 0, BUFSIZ);
424 if ((ret = read(inputFd, &c, 1)) < 1)
426 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
432 *(command + len++ + 1) = c;
433 xwrite(outputFd, &c, 1);
437 /* Control-a -- Beginning of line */
438 input_home(outputFd, &cursor);
440 /* Control-b -- Move back one character */
441 input_backward(outputFd, &cursor);
444 /* Control-c -- leave the current line,
445 * and start over on the next line */
447 /* Go to the next line */
448 xwrite(outputFd, "\n", 1);
450 /* Rewrite the prompt */
451 xwrite(outputFd, prompt, strlen(prompt));
453 /* Reset the command string */
454 memset(command, 0, sizeof(command));
459 /* Control-d -- Delete one character, or exit
460 * if the len=0 and no chars to delete */
462 xwrite(outputFd, "exit", 4);
465 input_delete(command, outputFd, cursor, &len);
469 /* Control-e -- End of line */
470 input_end(outputFd, &cursor, len);
473 /* Control-f -- Move forward one character */
474 input_forward(outputFd, &cursor, len);
478 /* Control-h and DEL */
479 input_backspace(command, outputFd, &cursor, &len);
482 #ifdef BB_FEATURE_SH_TAB_COMPLETION
483 input_tab(command, prompt, outputFd, &cursor, &len);
487 /* Control-n -- Get next command in history */
488 if (hp && hp->n && hp->n->s) {
489 get_next_history(&hp, command);
492 xwrite(outputFd, "\007", 1);
496 /* Control-p -- Get previous command from history */
498 get_previous_history(&hp, command);
501 xwrite(outputFd, "\007", 1);
505 /* escape sequence follows */
506 if ((ret = read(inputFd, &c, 1)) < 1)
509 if (c == '[') { /* 91 */
510 if ((ret = read(inputFd, &c, 1)) < 1)
515 /* Up Arrow -- Get previous command from history */
517 get_previous_history(&hp, command);
520 xwrite(outputFd, "\007", 1);
524 /* Down Arrow -- Get next command in history */
525 if (hp && hp->n && hp->n->s) {
526 get_next_history(&hp, command);
529 xwrite(outputFd, "\007", 1);
533 /* Rewrite the line with the selected history item */
535 /* erase old command from command line */
536 len = strlen(command)-strlen(hp->s);
538 input_backspace(command, outputFd, &cursor, &len);
539 input_home(outputFd, &cursor);
541 /* write new command */
542 strcpy(command, hp->s);
544 xwrite(outputFd, command, len);
548 /* Right Arrow -- Move forward one character */
549 input_forward(outputFd, &cursor, len);
552 /* Left Arrow -- Move back one character */
553 input_backward(outputFd, &cursor);
557 input_delete(command, outputFd, cursor, &len);
561 input_home(outputFd, &cursor);
565 input_end(outputFd, &cursor, len);
568 xwrite(outputFd, "\007", 1);
570 if (c == '1' || c == '3' || c == '4')
571 if ((ret = read(inputFd, &c, 1)) < 1)
572 return; /* read 126 (~) */
576 if ((ret = read(inputFd, &c, 1)) < 1)
581 input_home(outputFd, &cursor);
585 input_end(outputFd, &cursor, len);
588 xwrite(outputFd, "\007", 1);
595 default: /* If it's regular input, do the normal thing */
597 if (!isprint(c)) { /* Skip non-printable characters */
601 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
606 if (cursor == (len - 1)) { /* Append if at the end of the line */
607 *(command + cursor) = c;
608 } else { /* Insert otherwise */
609 memmove(command + cursor + 1, command + cursor,
612 *(command + cursor) = c;
614 for (j = cursor; j < len; j++)
615 xwrite(outputFd, command + j, 1);
616 for (; j > cursor; j--)
617 xwrite(outputFd, "\033[D", 3);
621 xwrite(outputFd, &c, 1);
629 if (break_out) /* Enter is the command terminator, no more input. */
634 setTermSettings(inputFd, (void *) &initial_settings);
638 /* Handle command history log */
641 struct history *h = his_end;
644 /* No previous history */
645 h = his_front = malloc(sizeof(struct history));
646 h->n = malloc(sizeof(struct history));
649 h->s = strdup(command);
656 /* Add a new history command */
657 h->n = malloc(sizeof(struct history));
662 h->s = strdup(command);
665 /* After max history, remove the oldest command */
666 if (history_counter >= MAX_HISTORY) {
668 struct history *p = his_front->n;
683 extern void cmdedit_init(void)
685 atexit(cmdedit_reset_term);
686 signal(SIGKILL, clean_up_and_die);
687 signal(SIGINT, clean_up_and_die);
688 signal(SIGQUIT, clean_up_and_die);
689 signal(SIGTERM, clean_up_and_die);
691 #endif /* BB_FEATURE_SH_COMMAND_EDITING */