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;
77 static int cmdedit_termw = 80; /* actual terminal width */
78 static int cmdedit_scroll = 27; /* width of EOL scrolling region */
79 static int history_counter = 0; /* Number of commands in history list */
80 static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */
91 cmdedit_setwidth(int w)
95 cmdedit_scroll = w / 3;
97 errorMsg("\n*** Error: minimum screen width is 21\n");
102 void cmdedit_reset_term(void)
105 /* sparc and other have broken termios support: use old termio handling. */
106 setTermSettings(fileno(stdin), (void*) &initial_settings);
109 void clean_up_and_die(int sig)
111 cmdedit_reset_term();
112 fprintf(stdout, "\n");
117 /* Go to HOME position */
118 void input_home(int outputFd, int *cursor)
120 while (*cursor > 0) {
121 xwrite(outputFd, "\b", 1);
126 /* Go to END position */
127 void input_end(int outputFd, int *cursor, int len)
129 while (*cursor < len) {
130 xwrite(outputFd, "\033[C", 3);
135 /* Delete the char in back of the cursor */
136 void input_backspace(char* command, int outputFd, int *cursor, int *len)
141 xwrite(outputFd, "\b \b", 3);
143 memmove(command + *cursor, command + *cursor + 1,
144 BUFSIZ - *cursor + 1);
146 for (j = *cursor; j < (BUFSIZ - 1); j++) {
150 xwrite(outputFd, (command + j), 1);
153 xwrite(outputFd, " \b", 2);
155 while (j-- > *cursor)
156 xwrite(outputFd, "\b", 1);
162 /* Delete the char in front of the cursor */
163 void input_delete(char* command, int outputFd, int cursor, int *len)
170 memmove(command + cursor, command + cursor + 1,
171 BUFSIZ - cursor - 1);
172 for (j = cursor; j < (BUFSIZ - 1); j++) {
176 xwrite(outputFd, (command + j), 1);
179 xwrite(outputFd, " \b", 2);
182 xwrite(outputFd, "\b", 1);
186 /* Move forward one charactor */
187 void input_forward(int outputFd, int *cursor, int len)
190 xwrite(outputFd, "\033[C", 3);
195 /* Move back one charactor */
196 void input_backward(int outputFd, int *cursor)
199 xwrite(outputFd, "\033[D", 3);
206 #ifdef BB_FEATURE_SH_TAB_COMPLETION
207 char** username_tab_completion(char* command, int *num_matches)
209 char **matches = (char **) NULL;
211 fprintf(stderr, "\nin username_tab_completion\n");
216 char** exe_n_cwd_tab_completion(char* command, int *num_matches)
219 char **matches = (char **) NULL;
223 matches = malloc( sizeof(char*)*50);
225 /* Stick a wildcard onto the command, for later use */
226 strcat( command, "*");
228 /* Now wall the current directory */
229 dirName = get_current_dir_name();
230 dir = opendir(dirName);
232 /* Don't print an error, just shut up and return */
236 while ((next = readdir(dir)) != NULL) {
238 /* Some quick sanity checks */
239 if ((strcmp(next->d_name, "..") == 0)
240 || (strcmp(next->d_name, ".") == 0)) {
243 /* See if this matches */
244 if (check_wildcard_match(next->d_name, command) == TRUE) {
245 /* Cool, found a match. Add it to the list */
246 matches[*num_matches] = malloc(strlen(next->d_name)+1);
247 strcpy( matches[*num_matches], next->d_name);
249 //matches = realloc( matches, sizeof(char*)*(*num_matches));
256 void input_tab(char* command, char* prompt, int outputFd, int *cursor, int *len)
258 /* Do TAB completion */
259 static int num_matches=0;
260 static char **matches = (char **) NULL;
264 if (lastWasTab == FALSE) {
265 char *tmp, *tmp1, *matchBuf;
267 /* For now, we will not bother with trying to distinguish
268 * whether the cursor is in/at a command extression -- we
269 * will always try all possable matches. If you don't like
270 * that then feel free to fix it.
273 /* Make a local copy of the string -- up
274 * to the position of the cursor */
275 matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
276 strncpy(matchBuf, command, cursor);
279 /* skip past any command seperator tokens */
280 while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
282 /* skip any leading white space */
283 while (*tmp && isspace(*tmp))
287 /* skip any leading white space */
288 while (*tmp && isspace(*tmp))
291 /* Free up any memory already allocated */
294 matches = (char **) NULL;
297 /* If the word starts with `~' and there is no slash in the word,
298 * then try completing this word as a username. */
300 /* FIXME -- this check is broken! */
301 if (*tmp == '~' && !strchr(tmp, '/'))
302 matches = username_tab_completion(tmp, &num_matches);
304 /* Try to match any executable in our path and everything
305 * in the current working directory that matches. */
307 matches = exe_n_cwd_tab_completion(tmp, &num_matches);
309 /* Don't leak memory */
312 /* Did we find exactly one match? */
313 if (matches && num_matches==1) {
314 /* write out the matched command */
315 strncpy(command+pos, matches[0]+pos, strlen(matches[0])-pos);
318 xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
322 /* Ok -- the last char was a TAB. Since they
323 * just hit TAB again, print a list of all the
324 * available choices... */
325 if ( matches && num_matches>0 ) {
328 /* Go to the next line */
329 xwrite(outputFd, "\n", 1);
330 /* Print the list of matches */
331 for (i=0,col=0; i<num_matches; i++) {
333 sprintf(foo, "%-14s ", matches[i]);
334 col += xwrite(outputFd, foo, strlen(foo));
335 if (col > 60 && matches[i+1] != NULL) {
336 xwrite(outputFd, "\n", 1);
340 /* Go to the next line */
341 xwrite(outputFd, "\n", 1);
342 /* Rewrite the prompt */
343 xwrite(outputFd, prompt, strlen(prompt));
344 /* Rewrite the command */
345 xwrite(outputFd, command, len);
346 /* Put the cursor back to where it used to be */
347 for (cursor=len; cursor > pos; cursor--)
348 xwrite(outputFd, "\b", 1);
354 void get_previous_history(struct history **hp, char* command)
357 (*hp)->s = strdup(command);
361 void get_next_history(struct history **hp, char* command)
364 (*hp)->s = strdup(command);
369 * This function is used to grab a character buffer
370 * from the input file descriptor and allows you to
371 * a string with full command editing (sortof like
374 * The following standard commands are not implemented:
375 * ESC-b -- Move back one word
376 * ESC-f -- Move forward one word
377 * ESC-d -- Delete back one word
378 * ESC-h -- Delete forward one word
379 * CTL-t -- Transpose two characters
381 * Furthermore, the "vi" command editing keys are not implemented.
383 * TODO: implement TAB command completion. :)
385 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
388 int inputFd=fileno(stdin);
389 int outputFd=fileno(stdout);
396 int lastWasTab = FALSE;
398 struct history *hp = his_end;
400 memset(command, 0, sizeof(command));
403 getTermSettings(inputFd, (void*) &initial_settings);
404 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
405 new_settings.c_cc[VMIN] = 1;
406 new_settings.c_cc[VTIME] = 0;
407 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
408 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
409 new_settings.c_lflag &= ~(ECHO|ECHOCTL|ECHONL); /* Turn off echoing */
412 setTermSettings(inputFd, (void*) &new_settings);
414 memset(command, 0, BUFSIZ);
418 if ((ret = read(inputFd, &c, 1)) < 1)
420 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
426 *(command + len++ + 1) = c;
427 xwrite(outputFd, &c, 1);
431 /* Control-a -- Beginning of line */
432 input_home(outputFd, &cursor);
434 /* Control-b -- Move back one character */
435 input_backward(outputFd, &cursor);
438 /* Control-c -- leave the current line,
439 * and start over on the next line */
441 /* Go to the next line */
442 xwrite(outputFd, "\n", 1);
444 /* Rewrite the prompt */
445 xwrite(outputFd, prompt, strlen(prompt));
447 /* Reset the command string */
448 memset(command, 0, sizeof(command));
453 /* Control-d -- Delete one character, or exit
454 * if the len=0 and no chars to delete */
456 xwrite(outputFd, "exit", 4);
459 input_delete(command, outputFd, cursor, &len);
463 /* Control-e -- End of line */
464 input_end(outputFd, &cursor, len);
467 /* Control-f -- Move forward one character */
468 input_forward(outputFd, &cursor, len);
472 /* Control-h and DEL */
473 input_backspace(command, outputFd, &cursor, &len);
476 #ifdef BB_FEATURE_SH_TAB_COMPLETION
477 input_tab(command, prompt, outputFd, &cursor, &len);
481 /* Control-n -- Get next command in history */
482 if (hp && hp->n && hp->n->s) {
483 get_next_history(&hp, command);
486 xwrite(outputFd, "\007", 1);
490 /* Control-p -- Get previous command from history */
492 get_previous_history(&hp, command);
495 xwrite(outputFd, "\007", 1);
499 /* escape sequence follows */
500 if ((ret = read(inputFd, &c, 1)) < 1)
503 if (c == '[') { /* 91 */
504 if ((ret = read(inputFd, &c, 1)) < 1)
509 /* Up Arrow -- Get previous command from history */
511 get_previous_history(&hp, command);
514 xwrite(outputFd, "\007", 1);
518 /* Down Arrow -- Get next command in history */
519 if (hp && hp->n && hp->n->s) {
520 get_next_history(&hp, command);
523 xwrite(outputFd, "\007", 1);
527 /* Rewrite the line with the selected history item */
529 /* erase old command from command line */
530 len = strlen(command)-strlen(hp->s);
532 input_backspace(command, outputFd, &cursor, &len);
533 input_home(outputFd, &cursor);
535 /* write new command */
536 strcpy(command, hp->s);
538 xwrite(outputFd, command, len);
542 /* Right Arrow -- Move forward one character */
543 input_forward(outputFd, &cursor, len);
546 /* Left Arrow -- Move back one character */
547 input_backward(outputFd, &cursor);
551 input_delete(command, outputFd, cursor, &len);
555 input_home(outputFd, &cursor);
559 input_end(outputFd, &cursor, len);
562 xwrite(outputFd, "\007", 1);
564 if (c == '1' || c == '3' || c == '4')
565 if ((ret = read(inputFd, &c, 1)) < 1)
566 return; /* read 126 (~) */
570 if ((ret = read(inputFd, &c, 1)) < 1)
575 input_home(outputFd, &cursor);
579 input_end(outputFd, &cursor, len);
582 xwrite(outputFd, "\007", 1);
589 default: /* If it's regular input, do the normal thing */
591 if (!isprint(c)) { /* Skip non-printable characters */
595 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
600 if (cursor == (len - 1)) { /* Append if at the end of the line */
601 *(command + cursor) = c;
602 } else { /* Insert otherwise */
603 memmove(command + cursor + 1, command + cursor,
606 *(command + cursor) = c;
608 for (j = cursor; j < len; j++)
609 xwrite(outputFd, command + j, 1);
610 for (; j > cursor; j--)
611 xwrite(outputFd, "\033[D", 3);
615 xwrite(outputFd, &c, 1);
623 if (break_out) /* Enter is the command terminator, no more input. */
628 setTermSettings(inputFd, (void *) &initial_settings);
632 /* Handle command history log */
635 struct history *h = his_end;
638 /* No previous history */
639 h = his_front = malloc(sizeof(struct history));
640 h->n = malloc(sizeof(struct history));
643 h->s = strdup(command);
650 /* Add a new history command */
651 h->n = malloc(sizeof(struct history));
656 h->s = strdup(command);
659 /* After max history, remove the oldest command */
660 if (history_counter >= MAX_HISTORY) {
662 struct history *p = his_front->n;
677 extern void cmdedit_init(void)
679 atexit(cmdedit_reset_term);
680 signal(SIGKILL, clean_up_and_die);
681 signal(SIGINT, clean_up_and_die);
682 signal(SIGQUIT, clean_up_and_die);
683 signal(SIGTERM, clean_up_and_die);
685 #endif /* BB_FEATURE_SH_COMMAND_EDITING */