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)
374 (*hp)->s = strdup(command);
378 void get_next_history(struct history **hp, char* command)
381 (*hp)->s = strdup(command);
386 * This function is used to grab a character buffer
387 * from the input file descriptor and allows you to
388 * a string with full command editing (sortof like
391 * The following standard commands are not implemented:
392 * ESC-b -- Move back one word
393 * ESC-f -- Move forward one word
394 * ESC-d -- Delete back one word
395 * ESC-h -- Delete forward one word
396 * CTL-t -- Transpose two characters
398 * Furthermore, the "vi" command editing keys are not implemented.
400 * TODO: implement TAB command completion. :)
402 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
405 int inputFd=fileno(stdin);
406 int outputFd=fileno(stdout);
413 int lastWasTab = FALSE;
415 struct history *hp = his_end;
419 getTermSettings(inputFd, (void*) &initial_settings);
420 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
421 new_settings.c_cc[VMIN] = 1;
422 new_settings.c_cc[VTIME] = 0;
423 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
424 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
425 new_settings.c_lflag &= ~(ECHO|ECHOCTL|ECHONL); /* Turn off echoing */
428 setTermSettings(inputFd, (void*) &new_settings);
430 memset(command, 0, BUFSIZ);
434 if ((ret = read(inputFd, &c, 1)) < 1)
436 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
442 *(command + len++ + 1) = c;
443 xwrite(outputFd, &c, 1);
447 /* Control-a -- Beginning of line */
448 input_home(outputFd, &cursor);
450 /* Control-b -- Move back one character */
451 input_backward(outputFd, &cursor);
454 /* Control-c -- leave the current line,
455 * and start over on the next line */
457 /* Go to the next line */
458 xwrite(outputFd, "\n", 1);
460 /* Rewrite the prompt */
461 xwrite(outputFd, prompt, strlen(prompt));
463 /* Reset the command string */
464 memset(command, 0, BUFSIZ);
469 /* Control-d -- Delete one character, or exit
470 * if the len=0 and no chars to delete */
472 xwrite(outputFd, "exit", 4);
475 input_delete(command, outputFd, cursor, &len);
479 /* Control-e -- End of line */
480 input_end(outputFd, &cursor, len);
483 /* Control-f -- Move forward one character */
484 input_forward(outputFd, &cursor, len);
488 /* Control-h and DEL */
489 input_backspace(command, outputFd, &cursor, &len);
492 #ifdef BB_FEATURE_SH_TAB_COMPLETION
493 input_tab(command, prompt, outputFd, &cursor, &len);
497 /* Control-n -- Get next command in history */
498 if (hp && hp->n && hp->n->s) {
499 get_next_history(&hp, command);
502 xwrite(outputFd, "\007", 1);
506 /* Control-p -- Get previous command from history */
508 get_previous_history(&hp, command);
511 xwrite(outputFd, "\007", 1);
515 /* escape sequence follows */
516 if ((ret = read(inputFd, &c, 1)) < 1)
519 if (c == '[') { /* 91 */
520 if ((ret = read(inputFd, &c, 1)) < 1)
525 /* Up Arrow -- Get previous command from history */
527 get_previous_history(&hp, command);
530 xwrite(outputFd, "\007", 1);
534 /* Down Arrow -- Get next command in history */
535 if (hp && hp->n && hp->n->s) {
536 get_next_history(&hp, command);
539 xwrite(outputFd, "\007", 1);
543 /* Rewrite the line with the selected history item */
545 /* erase old command from command line */
546 len = strlen(command)-strlen(hp->s);
549 input_delete(command, outputFd, cursor, &len);
551 input_backspace(command, outputFd, &cursor, &len);
552 input_home(outputFd, &cursor);
554 /* write new command */
555 strcpy(command, hp->s);
557 xwrite(outputFd, command, len);
561 /* Right Arrow -- Move forward one character */
562 input_forward(outputFd, &cursor, len);
565 /* Left Arrow -- Move back one character */
566 input_backward(outputFd, &cursor);
570 input_delete(command, outputFd, cursor, &len);
574 input_home(outputFd, &cursor);
578 input_end(outputFd, &cursor, len);
581 xwrite(outputFd, "\007", 1);
583 if (c == '1' || c == '3' || c == '4')
584 if ((ret = read(inputFd, &c, 1)) < 1)
585 return; /* read 126 (~) */
589 if ((ret = read(inputFd, &c, 1)) < 1)
594 input_home(outputFd, &cursor);
598 input_end(outputFd, &cursor, len);
601 xwrite(outputFd, "\007", 1);
608 default: /* If it's regular input, do the normal thing */
610 if (!isprint(c)) { /* Skip non-printable characters */
614 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
619 if (cursor == (len - 1)) { /* Append if at the end of the line */
620 *(command + cursor) = c;
621 } else { /* Insert otherwise */
622 memmove(command + cursor + 1, command + cursor,
625 *(command + cursor) = c;
627 for (j = cursor; j < len; j++)
628 xwrite(outputFd, command + j, 1);
629 for (; j > cursor; j--)
630 xwrite(outputFd, "\033[D", 3);
634 xwrite(outputFd, &c, 1);
642 if (break_out) /* Enter is the command terminator, no more input. */
647 setTermSettings(inputFd, (void *) &initial_settings);
651 /* Handle command history log */
654 struct history *h = his_end;
657 /* No previous history */
658 h = his_front = malloc(sizeof(struct history));
659 h->n = malloc(sizeof(struct history));
662 h->s = strdup(command);
669 /* Add a new history command */
670 h->n = malloc(sizeof(struct history));
675 h->s = strdup(command);
678 /* After max history, remove the oldest command */
679 if (history_counter >= MAX_HISTORY) {
681 struct history *p = his_front->n;
696 extern void cmdedit_init(void)
698 atexit(cmdedit_reset_term);
699 signal(SIGKILL, clean_up_and_die);
700 signal(SIGINT, clean_up_and_die);
701 signal(SIGQUIT, clean_up_and_die);
702 signal(SIGTERM, clean_up_and_die);
704 #endif /* BB_FEATURE_SH_COMMAND_EDITING */