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);
118 #ifdef BB_FEATURE_CLEAN_UP
121 //while(his_front!=his_end) {
122 while(his_front!=his_end) {
132 void clean_up_and_die(int sig)
134 cmdedit_reset_term();
135 fprintf(stdout, "\n");
140 /* Go to HOME position */
141 void input_home(int outputFd, int *cursor)
143 while (*cursor > 0) {
144 xwrite(outputFd, "\b", 1);
149 /* Go to END position */
150 void input_end(int outputFd, int *cursor, int len)
152 while (*cursor < len) {
153 xwrite(outputFd, "\033[C", 3);
158 /* Delete the char in back of the cursor */
159 void input_backspace(char* command, int outputFd, int *cursor, int *len)
164 //fprintf(stderr, "\nerik: len=%d, cursor=%d, strlen(command)='%d'\n", *len, *cursor, strlen(command));
165 //xwrite(outputFd, command, *len);
170 xwrite(outputFd, "\b \b", 3);
172 memmove(command + *cursor, command + *cursor + 1,
173 BUFSIZ - *cursor + 1);
175 for (j = *cursor; j < (BUFSIZ - 1); j++) {
179 xwrite(outputFd, (command + j), 1);
182 xwrite(outputFd, " \b", 2);
184 while (j-- > *cursor)
185 xwrite(outputFd, "\b", 1);
191 /* Delete the char in front of the cursor */
192 void input_delete(char* command, int outputFd, int cursor, int *len)
199 memmove(command + cursor, command + cursor + 1,
200 BUFSIZ - cursor - 1);
201 for (j = cursor; j < (BUFSIZ - 1); j++) {
205 xwrite(outputFd, (command + j), 1);
208 xwrite(outputFd, " \b", 2);
211 xwrite(outputFd, "\b", 1);
215 /* Move forward one charactor */
216 void input_forward(int outputFd, int *cursor, int len)
219 xwrite(outputFd, "\033[C", 3);
224 /* Move back one charactor */
225 void input_backward(int outputFd, int *cursor)
228 xwrite(outputFd, "\033[D", 3);
235 #ifdef BB_FEATURE_SH_TAB_COMPLETION
236 char** username_tab_completion(char* command, int *num_matches)
238 char **matches = (char **) NULL;
240 fprintf(stderr, "\nin username_tab_completion\n");
245 char** exe_n_cwd_tab_completion(char* command, int *num_matches)
248 char **matches = (char **) NULL;
252 matches = malloc( sizeof(char*)*50);
254 /* Stick a wildcard onto the command, for later use */
255 strcat( command, "*");
257 /* Now wall the current directory */
258 dirName = get_current_dir_name();
259 dir = opendir(dirName);
261 /* Don't print an error, just shut up and return */
265 while ((next = readdir(dir)) != NULL) {
267 /* Some quick sanity checks */
268 if ((strcmp(next->d_name, "..") == 0)
269 || (strcmp(next->d_name, ".") == 0)) {
272 /* See if this matches */
273 if (check_wildcard_match(next->d_name, command) == TRUE) {
274 /* Cool, found a match. Add it to the list */
275 matches[*num_matches] = malloc(strlen(next->d_name)+1);
276 strcpy( matches[*num_matches], next->d_name);
278 //matches = realloc( matches, sizeof(char*)*(*num_matches));
285 void input_tab(char* command, char* prompt, int outputFd, int *cursor, int *len)
287 /* Do TAB completion */
288 static int num_matches=0;
289 static char **matches = (char **) NULL;
293 if (lastWasTab == FALSE) {
294 char *tmp, *tmp1, *matchBuf;
296 /* For now, we will not bother with trying to distinguish
297 * whether the cursor is in/at a command extression -- we
298 * will always try all possible matches. If you don't like
299 * that then feel free to fix it.
302 /* Make a local copy of the string -- up
303 * to the position of the cursor */
304 matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
305 strncpy(matchBuf, command, cursor);
308 /* skip past any command seperator tokens */
309 while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
311 /* skip any leading white space */
312 while (*tmp && isspace(*tmp))
316 /* skip any leading white space */
317 while (*tmp && isspace(*tmp))
320 /* Free up any memory already allocated */
323 matches = (char **) NULL;
326 /* If the word starts with `~' and there is no slash in the word,
327 * then try completing this word as a username. */
329 /* FIXME -- this check is broken! */
330 if (*tmp == '~' && !strchr(tmp, '/'))
331 matches = username_tab_completion(tmp, &num_matches);
333 /* Try to match any executable in our path and everything
334 * in the current working directory that matches. */
336 matches = exe_n_cwd_tab_completion(tmp, &num_matches);
338 /* Don't leak memory */
341 /* Did we find exactly one match? */
342 if (matches && num_matches==1) {
343 /* write out the matched command */
344 strncpy(command+pos, matches[0]+pos, strlen(matches[0])-pos);
347 xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
351 /* Ok -- the last char was a TAB. Since they
352 * just hit TAB again, print a list of all the
353 * available choices... */
354 if ( matches && num_matches>0 ) {
357 /* Go to the next line */
358 xwrite(outputFd, "\n", 1);
359 /* Print the list of matches */
360 for (i=0,col=0; i<num_matches; i++) {
362 sprintf(foo, "%-14s ", matches[i]);
363 col += xwrite(outputFd, foo, strlen(foo));
364 if (col > 60 && matches[i+1] != NULL) {
365 xwrite(outputFd, "\n", 1);
369 /* Go to the next line */
370 xwrite(outputFd, "\n", 1);
371 /* Rewrite the prompt */
372 xwrite(outputFd, prompt, strlen(prompt));
373 /* Rewrite the command */
374 xwrite(outputFd, command, len);
375 /* Put the cursor back to where it used to be */
376 for (cursor=len; cursor > pos; cursor--)
377 xwrite(outputFd, "\b", 1);
383 void get_previous_history(struct history **hp, char* command)
387 (*hp)->s = strdup(command);
391 void get_next_history(struct history **hp, char* command)
395 (*hp)->s = strdup(command);
400 * This function is used to grab a character buffer
401 * from the input file descriptor and allows you to
402 * a string with full command editing (sortof like
405 * The following standard commands are not implemented:
406 * ESC-b -- Move back one word
407 * ESC-f -- Move forward one word
408 * ESC-d -- Delete back one word
409 * ESC-h -- Delete forward one word
410 * CTL-t -- Transpose two characters
412 * Furthermore, the "vi" command editing keys are not implemented.
414 * TODO: implement TAB command completion. :)
416 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
419 int inputFd=fileno(stdin);
420 int outputFd=fileno(stdout);
427 int lastWasTab = FALSE;
429 struct history *hp = his_end;
433 getTermSettings(inputFd, (void*) &initial_settings);
434 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
435 new_settings.c_cc[VMIN] = 1;
436 new_settings.c_cc[VTIME] = 0;
437 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
438 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
439 new_settings.c_lflag &= ~(ECHO|ECHOCTL|ECHONL); /* Turn off echoing */
442 setTermSettings(inputFd, (void*) &new_settings);
444 memset(command, 0, BUFSIZ);
448 if ((ret = read(inputFd, &c, 1)) < 1)
450 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
456 *(command + len++ + 1) = c;
457 xwrite(outputFd, &c, 1);
461 /* Control-a -- Beginning of line */
462 input_home(outputFd, &cursor);
464 /* Control-b -- Move back one character */
465 input_backward(outputFd, &cursor);
468 /* Control-c -- leave the current line,
469 * and start over on the next line */
471 /* Go to the next line */
472 xwrite(outputFd, "\n", 1);
474 /* Rewrite the prompt */
475 xwrite(outputFd, prompt, strlen(prompt));
477 /* Reset the command string */
478 memset(command, 0, BUFSIZ);
483 /* Control-d -- Delete one character, or exit
484 * if the len=0 and no chars to delete */
486 xwrite(outputFd, "exit", 4);
489 input_delete(command, outputFd, cursor, &len);
493 /* Control-e -- End of line */
494 input_end(outputFd, &cursor, len);
497 /* Control-f -- Move forward one character */
498 input_forward(outputFd, &cursor, len);
502 /* Control-h and DEL */
503 input_backspace(command, outputFd, &cursor, &len);
506 #ifdef BB_FEATURE_SH_TAB_COMPLETION
507 input_tab(command, prompt, outputFd, &cursor, &len);
511 /* Control-n -- Get next command in history */
512 if (hp && hp->n && hp->n->s) {
513 get_next_history(&hp, command);
516 xwrite(outputFd, "\007", 1);
520 /* Control-p -- Get previous command from history */
522 get_previous_history(&hp, command);
525 xwrite(outputFd, "\007", 1);
529 /* escape sequence follows */
530 if ((ret = read(inputFd, &c, 1)) < 1)
533 if (c == '[') { /* 91 */
534 if ((ret = read(inputFd, &c, 1)) < 1)
539 /* Up Arrow -- Get previous command from history */
541 get_previous_history(&hp, command);
544 xwrite(outputFd, "\007", 1);
548 /* Down Arrow -- Get next command in history */
549 if (hp && hp->n && hp->n->s) {
550 get_next_history(&hp, command);
553 xwrite(outputFd, "\007", 1);
557 /* Rewrite the line with the selected history item */
559 /* erase old command from command line */
560 len = strlen(command)-strlen(hp->s);
563 input_delete(command, outputFd, cursor, &len);
565 input_backspace(command, outputFd, &cursor, &len);
566 input_home(outputFd, &cursor);
568 /* write new command */
569 strcpy(command, hp->s);
571 xwrite(outputFd, command, len);
575 /* Right Arrow -- Move forward one character */
576 input_forward(outputFd, &cursor, len);
579 /* Left Arrow -- Move back one character */
580 input_backward(outputFd, &cursor);
584 input_delete(command, outputFd, cursor, &len);
588 input_home(outputFd, &cursor);
592 input_end(outputFd, &cursor, len);
595 xwrite(outputFd, "\007", 1);
597 if (c == '1' || c == '3' || c == '4')
598 if ((ret = read(inputFd, &c, 1)) < 1)
599 return; /* read 126 (~) */
603 if ((ret = read(inputFd, &c, 1)) < 1)
608 input_home(outputFd, &cursor);
612 input_end(outputFd, &cursor, len);
615 xwrite(outputFd, "\007", 1);
622 default: /* If it's regular input, do the normal thing */
624 if (!isprint(c)) { /* Skip non-printable characters */
628 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
633 if (cursor == (len - 1)) { /* Append if at the end of the line */
634 *(command + cursor) = c;
635 } else { /* Insert otherwise */
636 memmove(command + cursor + 1, command + cursor,
639 *(command + cursor) = c;
641 for (j = cursor; j < len; j++)
642 xwrite(outputFd, command + j, 1);
643 for (; j > cursor; j--)
644 xwrite(outputFd, "\033[D", 3);
648 xwrite(outputFd, &c, 1);
656 if (break_out) /* Enter is the command terminator, no more input. */
661 setTermSettings(inputFd, (void *) &initial_settings);
665 /* Handle command history log */
668 struct history *h = his_end;
671 /* No previous history -- this memory is never freed */
672 h = his_front = malloc(sizeof(struct history));
673 h->n = malloc(sizeof(struct history));
676 h->s = strdup(command);
683 /* Add a new history command -- this memory is never freed */
684 h->n = malloc(sizeof(struct history));
689 h->s = strdup(command);
692 /* After max history, remove the oldest command */
693 if (history_counter >= MAX_HISTORY) {
695 struct history *p = his_front->n;
710 extern void cmdedit_init(void)
712 atexit(cmdedit_reset_term);
713 signal(SIGKILL, clean_up_and_die);
714 signal(SIGINT, clean_up_and_die);
715 signal(SIGQUIT, clean_up_and_die);
716 signal(SIGTERM, clean_up_and_die);
718 #endif /* BB_FEATURE_SH_COMMAND_EDITING */