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 history_counter = 0; /* Number of commands in history list */
57 static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */
58 char *parsenextc; /* copy of parsefile->nextc */
67 /* Version of write which resumes after a signal is caught. */
68 int xwrite(int fd, char *buf, int nbytes)
77 i = write(fd, buf, n);
86 } else if (errno != EINTR) {
93 /* Version of ioctl that retries after a signal is caught. */
94 int xioctl(int fd, unsigned long request, char *arg)
98 while ((i = ioctl(fd, request, arg)) == -1 && errno == EINTR);
103 void cmdedit_reset_term(void)
106 xioctl(fileno(stdin), TCSETA, (void *) &old_term);
109 void prepareToDie(int sig)
111 cmdedit_reset_term();
112 fprintf(stdout, "\n");
116 void input_home(int outputFd, int *cursor)
117 { /* Command line input routines */
118 while (*cursor > 0) {
119 xwrite(outputFd, "\b", 1);
125 void input_delete(int outputFd, int cursor)
129 memmove(parsenextc + cursor, parsenextc + cursor + 1,
130 BUFSIZ - cursor - 1);
131 for (j = cursor; j < (BUFSIZ - 1); j++) {
132 if (!*(parsenextc + j))
135 xwrite(outputFd, (parsenextc + j), 1);
138 xwrite(outputFd, " \b", 2);
141 xwrite(outputFd, "\b", 1);
145 void input_end(int outputFd, int *cursor, int len)
147 while (*cursor < len) {
148 xwrite(outputFd, "\033[C", 3);
154 void input_backspace(int outputFd, int *cursor, int *len)
159 xwrite(outputFd, "\b \b", 3);
161 memmove(parsenextc + *cursor, parsenextc + *cursor + 1,
162 BUFSIZ - *cursor + 1);
164 for (j = *cursor; j < (BUFSIZ - 1); j++) {
165 if (!*(parsenextc + j))
168 xwrite(outputFd, (parsenextc + j), 1);
171 xwrite(outputFd, " \b", 2);
173 while (j-- > *cursor)
174 xwrite(outputFd, "\b", 1);
180 char** username_completion_matches(char* command, int *num_matches)
182 char **matches = (char **) NULL;
184 fprintf(stderr, "\nin username_completion_matches\n");
189 char** find_path_executable_n_cwd_matches(char* command, int *num_matches)
192 char **matches = (char **) NULL;
196 matches = malloc( sizeof(char*)*50);
198 /* Stick a wildcard onto the command, for later use */
199 strcat( command, "*");
201 /* Now wall the current directory */
202 dirName = get_current_dir_name();
203 dir = opendir(dirName);
205 /* Don't print an error, just shut up and return */
209 while ((next = readdir(dir)) != NULL) {
211 /* Some quick sanity checks */
212 if ((strcmp(next->d_name, "..") == 0)
213 || (strcmp(next->d_name, ".") == 0)) {
216 /* See if this matches */
217 if (check_wildcard_match(next->d_name, command) == TRUE) {
218 /* Cool, found a match. Add it to the list */
219 matches[*num_matches] = malloc(strlen(next->d_name)+1);
220 strcpy( matches[*num_matches], next->d_name);
222 //matches = realloc( matches, sizeof(char*)*(*num_matches));
230 * This function is used to grab a character buffer
231 * from the input file descriptor and allows you to
232 * a string with full command editing (sortof like
235 * The following standard commands are not implemented:
236 * ESC-b -- Move back one word
237 * ESC-f -- Move forward one word
238 * ESC-d -- Delete back one word
239 * ESC-h -- Delete forward one word
240 * CTL-t -- Transpose two characters
242 * Furthermore, the "vi" command editing keys are not implemented.
244 * TODO: implement TAB command completion. :)
247 extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd,
248 char command[BUFSIZ])
257 int lastWasTab = FALSE;
259 struct history *hp = his_end;
261 memset(command, 0, sizeof(command));
262 parsenextc = command;
264 xioctl(inputFd, TCGETA, (void *) &old_term);
265 memcpy(&new_term, &old_term, sizeof(struct termio));
267 new_term.c_cc[VMIN] = 1;
268 new_term.c_cc[VTIME] = 0;
269 new_term.c_lflag &= ~ICANON; /* unbuffered input */
270 new_term.c_lflag &= ~ECHO;
271 xioctl(inputFd, TCSETA, (void *) &new_term);
274 xioctl(inputFd, TCSETA, (void *) &new_term);
277 memset(parsenextc, 0, BUFSIZ);
281 if ((ret = read(inputFd, &c, 1)) < 1)
284 fprintf(stderr, "\n\nkey=%d (%c)\n\n", c, c);
285 /* Go to the next line */
286 xwrite(outputFd, "\n", 1);
287 /* Rewrite the prompt */
288 xwrite(outputFd, prompt, strlen(prompt));
289 /* Rewrite the command */
290 xwrite(outputFd, parsenextc, len);
294 /* Control-a -- Beginning of line */
295 input_home(outputFd, &cursor);
297 /* Control-e -- End of line */
298 input_end(outputFd, &cursor, len);
301 /* Control-b -- Move back one character */
303 xwrite(outputFd, "\033[D", 3);
308 /* Control-f -- Move forward one character */
310 xwrite(outputFd, "\033[C", 3);
315 /* Control-d -- Delete one character */
317 input_delete(outputFd, cursor);
319 } else if (len == 0) {
325 /* Control-n -- Get next command */
326 if (hp && hp->n && hp->n->s) {
328 hp->s = strdup(parsenextc);
334 /* Control-p -- Get previous command */
337 hp->s = strdup(parsenextc);
344 /* Do TAB completion */
345 static int num_matches=0;
346 static char **matches = (char **) NULL;
350 if (lastWasTab == FALSE) {
351 char *tmp, *tmp1, *matchBuf;
353 /* For now, we will not bother with trying to distinguish
354 * whether the cursor is in/at a command extression -- we
355 * will always try all possable matches. If you don't like
356 * that then feel free to fix it.
359 /* Make a local copy of the string -- up
360 * to the position of the cursor */
361 matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
362 strncpy(matchBuf, parsenextc, cursor);
365 /* skip past any command seperator tokens */
366 while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
368 /* skip any leading white space */
369 while (*tmp && isspace(*tmp))
373 /* skip any leading white space */
374 while (*tmp && isspace(*tmp))
377 /* Free up any memory already allocated */
380 matches = (char **) NULL;
383 /* If the word starts with `~' and there is no slash in the word,
384 * then try completing this word as a username. */
386 /* FIXME -- this check is broken! */
387 if (*tmp == '~' && !strchr(tmp, '/'))
388 matches = username_completion_matches(tmp, &num_matches);
390 /* Try to match any executable in our path and everything
391 * in the current working directory that matches. */
393 matches = find_path_executable_n_cwd_matches(tmp, &num_matches);
395 /* Don't leak memory */
398 /* Did we find exactly one match? */
399 if (matches && num_matches==1) {
400 /* write out the matched command */
401 strncpy(parsenextc+pos, matches[0]+pos, strlen(matches[0])-pos);
402 len=strlen(parsenextc);
404 xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
408 /* Ok -- the last char was a TAB. Since they
409 * just hit TAB again, print a list of all the
410 * available choices... */
411 if ( matches && num_matches>0 ) {
414 /* Go to the next line */
415 xwrite(outputFd, "\n", 1);
416 /* Print the list of matches */
417 for (i=0,col=0; i<num_matches; i++) {
419 sprintf(foo, "%-14s ", matches[i]);
420 col += xwrite(outputFd, foo, strlen(foo));
421 if (col > 60 && matches[i+1] != NULL) {
422 xwrite(outputFd, "\n", 1);
426 /* Go to the next line */
427 xwrite(outputFd, "\n", 1);
428 /* Rewrite the prompt */
429 xwrite(outputFd, prompt, strlen(prompt));
430 /* Rewrite the command */
431 xwrite(outputFd, parsenextc, len);
432 /* Put the cursor back to where it used to be */
433 for (cursor=len; cursor > pos; cursor--)
434 xwrite(outputFd, "\b", 1);
442 input_backspace(outputFd, &cursor, &len);
446 *(parsenextc + len++ + 1) = c;
447 xwrite(outputFd, &c, 1);
451 /* escape sequence follows */
452 if ((ret = read(inputFd, &c, 1)) < 1)
455 if (c == '[') { /* 91 */
456 if ((ret = read(inputFd, &c, 1)) < 1)
461 /* Up Arrow -- Get previous command */
464 hp->s = strdup(parsenextc);
470 /* Down Arrow -- Get next command */
471 if (hp && hp->n && hp->n->s) {
473 hp->s = strdup(parsenextc);
479 /* This is where we rewrite the line
480 * using the selected history item */
482 len = strlen(parsenextc);
484 /* return to begining of line */
485 for (; cursor > 0; cursor--)
486 xwrite(outputFd, "\b", 1);
488 /* erase old command */
489 for (j = 0; j < len; j++)
490 xwrite(outputFd, " ", 1);
492 /* return to begining of line */
493 for (j = len; j > 0; j--)
494 xwrite(outputFd, "\b", 1);
496 memset(parsenextc, 0, BUFSIZ);
497 len = strlen(parsenextc);
498 /* write new command */
499 strcpy(parsenextc, hp->s);
501 xwrite(outputFd, parsenextc, len);
505 /* Right Arrow -- Move forward one character */
507 xwrite(outputFd, "\033[C", 3);
512 /* Left Arrow -- Move back one character */
514 xwrite(outputFd, "\033[D", 3);
521 input_delete(outputFd, cursor);
526 //case '5': case '6': /* pgup/pgdown */
532 input_home(outputFd, &cursor);
538 input_end(outputFd, &cursor, len);
541 if (c == '1' || c == '3' || c == '4')
542 if ((ret = read(inputFd, &c, 1)) < 1)
543 return ret; /* read 126 (~) */
547 if ((ret = read(inputFd, &c, 1)) < 1)
552 input_home(outputFd, &cursor);
556 input_end(outputFd, &cursor, len);
564 default: /* If it's regular input, do the normal thing */
566 if (!isprint(c)) /* Skip non-printable characters */
569 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
574 if (cursor == (len - 1)) { /* Append if at the end of the line */
575 *(parsenextc + cursor) = c;
576 } else { /* Insert otherwise */
577 memmove(parsenextc + cursor + 1, parsenextc + cursor,
580 *(parsenextc + cursor) = c;
582 for (j = cursor; j < len; j++)
583 xwrite(outputFd, parsenextc + j, 1);
584 for (; j > cursor; j--)
585 xwrite(outputFd, "\033[D", 3);
589 xwrite(outputFd, &c, 1);
597 if (break_out) /* Enter is the command terminator, no more input. */
602 xioctl(inputFd, TCSETA, (void *) &old_term);
606 /* Handle command history log */
609 struct history *h = his_end;
612 /* No previous history */
613 h = his_front = malloc(sizeof(struct history));
614 h->n = malloc(sizeof(struct history));
617 h->s = strdup(parsenextc);
624 /* Add a new history command */
625 h->n = malloc(sizeof(struct history));
630 h->s = strdup(parsenextc);
633 /* After max history, remove the oldest command */
634 if (history_counter >= MAX_HISTORY) {
636 struct history *p = his_front->n;
651 extern void cmdedit_init(void)
653 atexit(cmdedit_reset_term);
654 signal(SIGINT, prepareToDie);
655 signal(SIGQUIT, prepareToDie);
656 signal(SIGTERM, prepareToDie);
658 #endif /* BB_FEATURE_SH_COMMAND_EDITING */