1 /* vi: set sw=4 ts=4: */
3 * Termios command line History and Editting for NetBSD sh (ash)
5 * Main code: Adam Rogoyski <rogoyski@cs.utexas.edu>
6 * Etc: Dave Cinege <dcinege@psychosis.com>
7 * Adjusted for busybox: Erik Andersen <andersee@debian.org>
9 * You may use this code as you wish, so long as the original author(s)
10 * are attributed in any redistributions of the source code.
11 * This code is 'as is' with no warranty.
12 * This code may safely be consumed by a BSD or GPL license.
14 * v 0.5 19990328 Initial release
16 * Future plans: Simple file and path name completion. (like BASH)
22 Terminal key codes are not extensive, and more will probably
23 need to be added. This version was created on Debian GNU/Linux 2.x.
24 Delete, Backspace, Home, End, and the arrow keys were tested
25 to work in an Xterm and console. Ctrl-A also works as Home.
26 Ctrl-E also works as End. The binary size increase is <3K.
28 Editting will not display correctly for lines greater then the
29 terminal width. (more then one line.) However, history will.
33 #ifdef BB_FEATURE_SH_COMMAND_EDITING
45 #define MAX_HISTORY 15 /* Maximum length of the linked list for the command line history */
49 #define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0)
50 #define whitespace(c) (((c) == ' ') || ((c) == '\t'))
52 static struct history *his_front = NULL; /* First element in command line list */
53 static struct history *his_end = NULL; /* Last element in command line list */
54 static struct termio old_term, new_term; /* Current termio and the previous termio before starting ash */
56 static int cmdedit_termw = 80; /* actual terminal width */
57 static int cmdedit_scroll = 27; /* width of EOL scrolling region */
58 static int history_counter = 0; /* Number of commands in history list */
59 static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */
70 cmdedit_setwidth(int w)
74 cmdedit_scroll = w / 3;
76 errorMsg("\n*** Error: minimum screen width is 21\n");
80 void cmdedit_reset_term(void)
83 ioctl(fileno(stdin), TCSETA, (void *) &old_term);
86 void clean_up_and_die(int sig)
89 fprintf(stdout, "\n");
93 /* Go to HOME position */
94 void input_home(int outputFd, int *cursor)
97 xwrite(outputFd, "\b", 1);
102 /* Go to END position */
103 void input_end(int outputFd, int *cursor, int len)
105 while (*cursor < len) {
106 xwrite(outputFd, "\033[C", 3);
111 /* Delete the char in back of the cursor */
112 void input_backspace(char* command, int outputFd, int *cursor, int *len)
117 xwrite(outputFd, "\b \b", 3);
119 memmove(command + *cursor, command + *cursor + 1,
120 BUFSIZ - *cursor + 1);
122 for (j = *cursor; j < (BUFSIZ - 1); j++) {
126 xwrite(outputFd, (command + j), 1);
129 xwrite(outputFd, " \b", 2);
131 while (j-- > *cursor)
132 xwrite(outputFd, "\b", 1);
138 /* Delete the char in front of the cursor */
139 void input_delete(char* command, int outputFd, int cursor, int *len)
146 memmove(command + cursor, command + cursor + 1,
147 BUFSIZ - cursor - 1);
148 for (j = cursor; j < (BUFSIZ - 1); j++) {
152 xwrite(outputFd, (command + j), 1);
155 xwrite(outputFd, " \b", 2);
158 xwrite(outputFd, "\b", 1);
162 /* Move forward one charactor */
163 void input_forward(int outputFd, int *cursor, int len)
166 xwrite(outputFd, "\033[C", 3);
171 /* Move back one charactor */
172 void input_backward(int outputFd, int *cursor)
175 xwrite(outputFd, "\033[D", 3);
182 #ifdef BB_FEATURE_SH_TAB_COMPLETION
183 char** username_tab_completion(char* command, int *num_matches)
185 char **matches = (char **) NULL;
187 fprintf(stderr, "\nin username_tab_completion\n");
192 char** exe_n_cwd_tab_completion(char* command, int *num_matches)
195 char **matches = (char **) NULL;
199 matches = malloc( sizeof(char*)*50);
201 /* Stick a wildcard onto the command, for later use */
202 strcat( command, "*");
204 /* Now wall the current directory */
205 dirName = get_current_dir_name();
206 dir = opendir(dirName);
208 /* Don't print an error, just shut up and return */
212 while ((next = readdir(dir)) != NULL) {
214 /* Some quick sanity checks */
215 if ((strcmp(next->d_name, "..") == 0)
216 || (strcmp(next->d_name, ".") == 0)) {
219 /* See if this matches */
220 if (check_wildcard_match(next->d_name, command) == TRUE) {
221 /* Cool, found a match. Add it to the list */
222 matches[*num_matches] = malloc(strlen(next->d_name)+1);
223 strcpy( matches[*num_matches], next->d_name);
225 //matches = realloc( matches, sizeof(char*)*(*num_matches));
232 void input_tab(char* command, int outputFd, int *cursor, int *len)
234 /* Do TAB completion */
235 static int num_matches=0;
236 static char **matches = (char **) NULL;
240 if (lastWasTab == FALSE) {
241 char *tmp, *tmp1, *matchBuf;
243 /* For now, we will not bother with trying to distinguish
244 * whether the cursor is in/at a command extression -- we
245 * will always try all possable matches. If you don't like
246 * that then feel free to fix it.
249 /* Make a local copy of the string -- up
250 * to the position of the cursor */
251 matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
252 strncpy(matchBuf, command, cursor);
255 /* skip past any command seperator tokens */
256 while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
258 /* skip any leading white space */
259 while (*tmp && isspace(*tmp))
263 /* skip any leading white space */
264 while (*tmp && isspace(*tmp))
267 /* Free up any memory already allocated */
270 matches = (char **) NULL;
273 /* If the word starts with `~' and there is no slash in the word,
274 * then try completing this word as a username. */
276 /* FIXME -- this check is broken! */
277 if (*tmp == '~' && !strchr(tmp, '/'))
278 matches = username_tab_completion(tmp, &num_matches);
280 /* Try to match any executable in our path and everything
281 * in the current working directory that matches. */
283 matches = exe_n_cwd_tab_completion(tmp, &num_matches);
285 /* Don't leak memory */
288 /* Did we find exactly one match? */
289 if (matches && num_matches==1) {
290 /* write out the matched command */
291 strncpy(command+pos, matches[0]+pos, strlen(matches[0])-pos);
294 xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
298 /* Ok -- the last char was a TAB. Since they
299 * just hit TAB again, print a list of all the
300 * available choices... */
301 if ( matches && num_matches>0 ) {
304 /* Go to the next line */
305 xwrite(outputFd, "\n", 1);
306 /* Print the list of matches */
307 for (i=0,col=0; i<num_matches; i++) {
309 sprintf(foo, "%-14s ", matches[i]);
310 col += xwrite(outputFd, foo, strlen(foo));
311 if (col > 60 && matches[i+1] != NULL) {
312 xwrite(outputFd, "\n", 1);
316 /* Go to the next line */
317 xwrite(outputFd, "\n", 1);
318 /* Rewrite the prompt */
319 xwrite(outputFd, prompt, strlen(prompt));
320 /* Rewrite the command */
321 xwrite(outputFd, command, len);
322 /* Put the cursor back to where it used to be */
323 for (cursor=len; cursor > pos; cursor--)
324 xwrite(outputFd, "\b", 1);
330 void get_previous_history(struct history **hp, char* command)
333 (*hp)->s = strdup(command);
337 void get_next_history(struct history **hp, char* command)
340 (*hp)->s = strdup(command);
343 cmdedit_redraw( NULL, hp->s, -2, -2);
347 /* prompt : if !=NULL, print the prompt
348 * command: the command line to be displayed
349 * where : where to display changes from.
350 * -1 for no change, -2 for new line
351 * cursor : desired location for the cursor.
352 * -1 for Beginning of line.
353 * -2 for End of Line,
356 cmdedit_redraw(char* prompt, char* command, int where, int cursor)
358 static char* last_command;
362 /* Rewrite the prompt and clean up static variables */
363 xwrite(outputFd, "\n", 1);
365 strcpy(last_command, prompt);
366 xwrite(outputFd, prompt, strlen(prompt));
368 last_command[0] = '\0';
369 xwrite(outputFd, "# ", 2);
371 cmdedit_width = cmdedit_termw - cmdedit_strlen(prompt);
372 } else if (strcmp(command, last_command) != 0) {
373 strcpy(last_command, prompt);
376 /* erase old command from command line */
377 len = strlen(command)-strlen(last_command);
379 input_backspace(command, outputFd, &cursor, &len);
380 input_home(outputFd, &cursor);
382 /* Rewrite the command */
383 xwrite(outputFd, command+where, len);
385 /* Put the where it is supposed to be */
386 for (cursor=len; cursor > where; cursor--)
387 xwrite(outputFd, "\b", 1);
389 /* write new command */
390 strcpy(command, hp->s);
392 xwrite(outputFd, command+where, len);
398 * This function is used to grab a character buffer
399 * from the input file descriptor and allows you to
400 * a string with full command editing (sortof like
403 * The following standard commands are not implemented:
404 * ESC-b -- Move back one word
405 * ESC-f -- Move forward one word
406 * ESC-d -- Delete back one word
407 * ESC-h -- Delete forward one word
408 * CTL-t -- Transpose two characters
410 * Furthermore, the "vi" command editing keys are not implemented.
412 * TODO: implement TAB command completion. :)
414 extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
417 int inputFd=fileno(stdin);
418 int outputFd=fileno(stdout);
425 int lastWasTab = FALSE;
427 struct history *hp = his_end;
429 memset(command, 0, sizeof(command));
431 ioctl(inputFd, TCGETA, (void *) &old_term);
432 memcpy(&new_term, &old_term, sizeof(struct termio));
434 new_term.c_cc[VMIN] = 1;
435 new_term.c_cc[VTIME] = 0;
436 new_term.c_lflag &= ~ICANON; /* unbuffered input */
437 new_term.c_lflag &= ~ECHO;
439 ioctl(inputFd, TCSETA, (void *) &new_term);
441 ioctl(inputFd, TCSETA, (void *) &new_term);
444 memset(command, 0, BUFSIZ);
448 if ((ret = read(inputFd, &c, 1)) < 1)
455 *(command + len++ + 1) = c;
456 xwrite(outputFd, &c, 1);
460 /* Control-a -- Beginning of line */
461 input_home(outputFd, &cursor);
463 /* Control-b -- Move back one character */
464 input_backward(outputFd, &cursor);
467 /* Control-d -- Delete one character, or exit
468 * if the len=0 and no chars to delete */
470 xwrite(outputFd, "exit", 4);
473 input_delete(command, outputFd, cursor, &len);
477 /* Control-e -- End of line */
478 input_end(outputFd, &cursor, len);
481 /* Control-f -- Move forward one character */
482 input_forward(outputFd, &cursor, len);
486 /* control-h and DEL */
487 input_backspace(command, outputFd, &cursor, &len);
490 #ifdef BB_FEATURE_SH_TAB_COMPLETION
491 input_tab(command, outputFd, &cursor, &len);
495 /* Control-n -- Get next command in history */
496 if (hp && hp->n && hp->n->s) {
497 get_next_history(&hp, command);
500 xwrite(outputFd, "\007", 1);
504 /* Control-p -- Get previous command from history */
506 get_previous_history(&hp, command);
509 xwrite(outputFd, "\007", 1);
513 /* escape sequence follows */
514 if ((ret = read(inputFd, &c, 1)) < 1)
517 if (c == '[') { /* 91 */
518 if ((ret = read(inputFd, &c, 1)) < 1)
523 /* Up Arrow -- Get previous command from history */
525 get_previous_history(&hp, command);
528 xwrite(outputFd, "\007", 1);
532 /* Down Arrow -- Get next command in history */
533 if (hp && hp->n && hp->n->s) {
534 get_next_history(&hp, command);
537 xwrite(outputFd, "\007", 1);
541 /* Rewrite the line with the selected history item */
543 /* erase old command from command line */
544 len = strlen(command)-strlen(hp->s);
546 input_backspace(command, outputFd, &cursor, &len);
547 input_home(outputFd, &cursor);
549 /* write new command */
550 strcpy(command, hp->s);
552 xwrite(outputFd, command, len);
556 /* Right Arrow -- Move forward one character */
557 input_forward(outputFd, &cursor, len);
560 /* Left Arrow -- Move back one character */
561 input_backward(outputFd, &cursor);
565 input_delete(command, outputFd, cursor, &len);
569 input_home(outputFd, &cursor);
573 input_end(outputFd, &cursor, len);
576 xwrite(outputFd, "\007", 1);
578 if (c == '1' || c == '3' || c == '4')
579 if ((ret = read(inputFd, &c, 1)) < 1)
580 return; /* read 126 (~) */
584 if ((ret = read(inputFd, &c, 1)) < 1)
589 input_home(outputFd, &cursor);
593 input_end(outputFd, &cursor, len);
596 xwrite(outputFd, "\007", 1);
603 default: /* If it's regular input, do the normal thing */
605 if (!isprint(c)) { /* Skip non-printable characters */
609 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
614 if (cursor == (len - 1)) { /* Append if at the end of the line */
615 *(command + cursor) = c;
616 } else { /* Insert otherwise */
617 memmove(command + cursor + 1, command + cursor,
620 *(command + cursor) = c;
622 for (j = cursor; j < len; j++)
623 xwrite(outputFd, command + j, 1);
624 for (; j > cursor; j--)
625 xwrite(outputFd, "\033[D", 3);
629 xwrite(outputFd, &c, 1);
637 if (break_out) /* Enter is the command terminator, no more input. */
642 ioctl(inputFd, TCSETA, (void *) &old_term);
646 /* Handle command history log */
649 struct history *h = his_end;
652 /* No previous history */
653 h = his_front = malloc(sizeof(struct history));
654 h->n = malloc(sizeof(struct history));
657 h->s = strdup(command);
664 /* Add a new history command */
665 h->n = malloc(sizeof(struct history));
670 h->s = strdup(command);
673 /* After max history, remove the oldest command */
674 if (history_counter >= MAX_HISTORY) {
676 struct history *p = his_front->n;
691 extern void cmdedit_init(void)
693 atexit(cmdedit_reset_term);
694 signal(SIGINT, clean_up_and_die);
695 signal(SIGQUIT, clean_up_and_die);
696 signal(SIGTERM, clean_up_and_die);
698 #endif /* BB_FEATURE_SH_COMMAND_EDITING */