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 //fprintf(stderr, "\nerik: len=%d, cursor=%d, strlen(command)='%d'\n", *len, *cursor, strlen(command));
148 //xwrite(outputFd, command, *len);
153 xwrite(outputFd, "\b \b", 3);
155 memmove(command + *cursor, command + *cursor + 1,
156 BUFSIZ - *cursor + 1);
158 for (j = *cursor; j < (BUFSIZ - 1); j++) {
162 xwrite(outputFd, (command + j), 1);
165 xwrite(outputFd, " \b", 2);
167 while (j-- > *cursor)
168 xwrite(outputFd, "\b", 1);
174 /* Delete the char in front of the cursor */
175 void input_delete(char* command, int outputFd, int cursor, int *len)
182 memmove(command + cursor, command + cursor + 1,
183 BUFSIZ - cursor - 1);
184 for (j = cursor; j < (BUFSIZ - 1); j++) {
188 xwrite(outputFd, (command + j), 1);
191 xwrite(outputFd, " \b", 2);
194 xwrite(outputFd, "\b", 1);
198 /* Move forward one charactor */
199 void input_forward(int outputFd, int *cursor, int len)
202 xwrite(outputFd, "\033[C", 3);
207 /* Move back one charactor */
208 void input_backward(int outputFd, int *cursor)
211 xwrite(outputFd, "\033[D", 3);
218 #ifdef BB_FEATURE_SH_TAB_COMPLETION
219 char** username_tab_completion(char* command, int *num_matches)
221 char **matches = (char **) NULL;
223 fprintf(stderr, "\nin username_tab_completion\n");
228 char** exe_n_cwd_tab_completion(char* command, int *num_matches)
231 char **matches = (char **) NULL;
235 matches = malloc( sizeof(char*)*50);
237 /* Stick a wildcard onto the command, for later use */
238 strcat( command, "*");
240 /* Now wall the current directory */
241 dirName = get_current_dir_name();
242 dir = opendir(dirName);
244 /* Don't print an error, just shut up and return */
248 while ((next = readdir(dir)) != NULL) {
250 /* Some quick sanity checks */
251 if ((strcmp(next->d_name, "..") == 0)
252 || (strcmp(next->d_name, ".") == 0)) {
255 /* See if this matches */
256 if (check_wildcard_match(next->d_name, command) == TRUE) {
257 /* Cool, found a match. Add it to the list */
258 matches[*num_matches] = malloc(strlen(next->d_name)+1);
259 strcpy( matches[*num_matches], next->d_name);
261 //matches = realloc( matches, sizeof(char*)*(*num_matches));
268 void input_tab(char* command, char* prompt, int outputFd, int *cursor, int *len)
270 /* Do TAB completion */
271 static int num_matches=0;
272 static char **matches = (char **) NULL;
276 if (lastWasTab == FALSE) {
277 char *tmp, *tmp1, *matchBuf;
279 /* For now, we will not bother with trying to distinguish
280 * whether the cursor is in/at a command extression -- we
281 * will always try all possible matches. If you don't like
282 * that then feel free to fix it.
285 /* Make a local copy of the string -- up
286 * to the position of the cursor */
287 matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
288 strncpy(matchBuf, command, cursor);
291 /* skip past any command seperator tokens */
292 while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
294 /* skip any leading white space */
295 while (*tmp && isspace(*tmp))
299 /* skip any leading white space */
300 while (*tmp && isspace(*tmp))
303 /* Free up any memory already allocated */
306 matches = (char **) NULL;
309 /* If the word starts with `~' and there is no slash in the word,
310 * then try completing this word as a username. */
312 /* FIXME -- this check is broken! */
313 if (*tmp == '~' && !strchr(tmp, '/'))
314 matches = username_tab_completion(tmp, &num_matches);
316 /* Try to match any executable in our path and everything
317 * in the current working directory that matches. */
319 matches = exe_n_cwd_tab_completion(tmp, &num_matches);
321 /* Don't leak memory */
324 /* Did we find exactly one match? */
325 if (matches && num_matches==1) {
326 /* write out the matched command */
327 strncpy(command+pos, matches[0]+pos, strlen(matches[0])-pos);
330 xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
334 /* Ok -- the last char was a TAB. Since they
335 * just hit TAB again, print a list of all the
336 * available choices... */
337 if ( matches && num_matches>0 ) {
340 /* Go to the next line */
341 xwrite(outputFd, "\n", 1);
342 /* Print the list of matches */
343 for (i=0,col=0; i<num_matches; i++) {
345 sprintf(foo, "%-14s ", matches[i]);
346 col += xwrite(outputFd, foo, strlen(foo));
347 if (col > 60 && matches[i+1] != NULL) {
348 xwrite(outputFd, "\n", 1);
352 /* Go to the next line */
353 xwrite(outputFd, "\n", 1);
354 /* Rewrite the prompt */
355 xwrite(outputFd, prompt, strlen(prompt));
356 /* Rewrite the command */
357 xwrite(outputFd, command, len);
358 /* Put the cursor back to where it used to be */
359 for (cursor=len; cursor > pos; cursor--)
360 xwrite(outputFd, "\b", 1);
366 void get_previous_history(struct history **hp, char* command)
369 (*hp)->s = strdup(command);
373 void get_next_history(struct history **hp, char* command)
376 (*hp)->s = strdup(command);
381 * This function is used to grab a character buffer
382 * from the input file descriptor and allows you to
383 * a string with full command editing (sortof like
386 * The following standard commands are not implemented:
387 * ESC-b -- Move back one word
388 * ESC-f -- Move forward one word
389 * ESC-d -- Delete back one word
390 * ESC-h -- Delete forward one word
391 * CTL-t -- Transpose two characters
393 * Furthermore, the "vi" command editing keys are not implemented.
395 * TODO: implement TAB command completion. :)
397 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
400 int inputFd=fileno(stdin);
401 int outputFd=fileno(stdout);
408 int lastWasTab = FALSE;
410 struct history *hp = his_end;
412 memset(command, 0, sizeof(command));
415 getTermSettings(inputFd, (void*) &initial_settings);
416 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
417 new_settings.c_cc[VMIN] = 1;
418 new_settings.c_cc[VTIME] = 0;
419 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
420 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
421 new_settings.c_lflag &= ~(ECHO|ECHOCTL|ECHONL); /* Turn off echoing */
424 setTermSettings(inputFd, (void*) &new_settings);
426 memset(command, 0, BUFSIZ);
430 if ((ret = read(inputFd, &c, 1)) < 1)
432 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
438 *(command + len++ + 1) = c;
439 xwrite(outputFd, &c, 1);
443 /* Control-a -- Beginning of line */
444 input_home(outputFd, &cursor);
446 /* Control-b -- Move back one character */
447 input_backward(outputFd, &cursor);
450 /* Control-c -- leave the current line,
451 * and start over on the next line */
453 /* Go to the next line */
454 xwrite(outputFd, "\n", 1);
456 /* Rewrite the prompt */
457 xwrite(outputFd, prompt, strlen(prompt));
459 /* Reset the command string */
460 memset(command, 0, sizeof(command));
465 /* Control-d -- Delete one character, or exit
466 * if the len=0 and no chars to delete */
468 xwrite(outputFd, "exit", 4);
471 input_delete(command, outputFd, cursor, &len);
475 /* Control-e -- End of line */
476 input_end(outputFd, &cursor, len);
479 /* Control-f -- Move forward one character */
480 input_forward(outputFd, &cursor, len);
484 /* Control-h and DEL */
485 input_backspace(command, outputFd, &cursor, &len);
488 #ifdef BB_FEATURE_SH_TAB_COMPLETION
489 input_tab(command, prompt, outputFd, &cursor, &len);
493 /* Control-n -- Get next command in history */
494 if (hp && hp->n && hp->n->s) {
495 get_next_history(&hp, command);
498 xwrite(outputFd, "\007", 1);
502 /* Control-p -- Get previous command from history */
504 get_previous_history(&hp, command);
507 xwrite(outputFd, "\007", 1);
511 /* escape sequence follows */
512 if ((ret = read(inputFd, &c, 1)) < 1)
515 if (c == '[') { /* 91 */
516 if ((ret = read(inputFd, &c, 1)) < 1)
521 /* Up Arrow -- Get previous command from history */
523 get_previous_history(&hp, command);
526 xwrite(outputFd, "\007", 1);
530 /* Down Arrow -- Get next command in history */
531 if (hp && hp->n && hp->n->s) {
532 get_next_history(&hp, command);
535 xwrite(outputFd, "\007", 1);
539 /* Rewrite the line with the selected history item */
541 /* erase old command from command line */
542 len = strlen(command)-strlen(hp->s);
545 input_delete(command, outputFd, cursor, &len);
547 input_backspace(command, outputFd, &cursor, &len);
548 input_home(outputFd, &cursor);
550 /* write new command */
551 strcpy(command, hp->s);
553 xwrite(outputFd, command, len);
557 /* Right Arrow -- Move forward one character */
558 input_forward(outputFd, &cursor, len);
561 /* Left Arrow -- Move back one character */
562 input_backward(outputFd, &cursor);
566 input_delete(command, outputFd, cursor, &len);
570 input_home(outputFd, &cursor);
574 input_end(outputFd, &cursor, len);
577 xwrite(outputFd, "\007", 1);
579 if (c == '1' || c == '3' || c == '4')
580 if ((ret = read(inputFd, &c, 1)) < 1)
581 return; /* read 126 (~) */
585 if ((ret = read(inputFd, &c, 1)) < 1)
590 input_home(outputFd, &cursor);
594 input_end(outputFd, &cursor, len);
597 xwrite(outputFd, "\007", 1);
604 default: /* If it's regular input, do the normal thing */
606 if (!isprint(c)) { /* Skip non-printable characters */
610 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
615 if (cursor == (len - 1)) { /* Append if at the end of the line */
616 *(command + cursor) = c;
617 } else { /* Insert otherwise */
618 memmove(command + cursor + 1, command + cursor,
621 *(command + cursor) = c;
623 for (j = cursor; j < len; j++)
624 xwrite(outputFd, command + j, 1);
625 for (; j > cursor; j--)
626 xwrite(outputFd, "\033[D", 3);
630 xwrite(outputFd, &c, 1);
638 if (break_out) /* Enter is the command terminator, no more input. */
643 setTermSettings(inputFd, (void *) &initial_settings);
647 /* Handle command history log */
650 struct history *h = his_end;
653 /* No previous history */
654 h = his_front = malloc(sizeof(struct history));
655 h->n = malloc(sizeof(struct history));
658 h->s = strdup(command);
665 /* Add a new history command */
666 h->n = malloc(sizeof(struct history));
671 h->s = strdup(command);
674 /* After max history, remove the oldest command */
675 if (history_counter >= MAX_HISTORY) {
677 struct history *p = his_front->n;
692 extern void cmdedit_init(void)
694 atexit(cmdedit_reset_term);
695 signal(SIGKILL, clean_up_and_die);
696 signal(SIGINT, clean_up_and_die);
697 signal(SIGQUIT, clean_up_and_die);
698 signal(SIGTERM, clean_up_and_die);
700 #endif /* BB_FEATURE_SH_COMMAND_EDITING */