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 #ifdef BB_FEATURE_SH_TAB_COMPLETION
182 char** username_completion_matches(char* command, int *num_matches)
184 char **matches = (char **) NULL;
186 fprintf(stderr, "\nin username_completion_matches\n");
191 char** find_path_executable_n_cwd_matches(char* command, int *num_matches)
194 char **matches = (char **) NULL;
198 matches = malloc( sizeof(char*)*50);
200 /* Stick a wildcard onto the command, for later use */
201 strcat( command, "*");
203 /* Now wall the current directory */
204 dirName = get_current_dir_name();
205 dir = opendir(dirName);
207 /* Don't print an error, just shut up and return */
211 while ((next = readdir(dir)) != NULL) {
213 /* Some quick sanity checks */
214 if ((strcmp(next->d_name, "..") == 0)
215 || (strcmp(next->d_name, ".") == 0)) {
218 /* See if this matches */
219 if (check_wildcard_match(next->d_name, command) == TRUE) {
220 /* Cool, found a match. Add it to the list */
221 matches[*num_matches] = malloc(strlen(next->d_name)+1);
222 strcpy( matches[*num_matches], next->d_name);
224 //matches = realloc( matches, sizeof(char*)*(*num_matches));
233 * This function is used to grab a character buffer
234 * from the input file descriptor and allows you to
235 * a string with full command editing (sortof like
238 * The following standard commands are not implemented:
239 * ESC-b -- Move back one word
240 * ESC-f -- Move forward one word
241 * ESC-d -- Delete back one word
242 * ESC-h -- Delete forward one word
243 * CTL-t -- Transpose two characters
245 * Furthermore, the "vi" command editing keys are not implemented.
247 * TODO: implement TAB command completion. :)
250 extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd,
251 char command[BUFSIZ])
260 int lastWasTab = FALSE;
262 struct history *hp = his_end;
264 memset(command, 0, sizeof(command));
265 parsenextc = command;
267 xioctl(inputFd, TCGETA, (void *) &old_term);
268 memcpy(&new_term, &old_term, sizeof(struct termio));
270 new_term.c_cc[VMIN] = 1;
271 new_term.c_cc[VTIME] = 0;
272 new_term.c_lflag &= ~ICANON; /* unbuffered input */
273 new_term.c_lflag &= ~ECHO;
274 xioctl(inputFd, TCSETA, (void *) &new_term);
277 xioctl(inputFd, TCSETA, (void *) &new_term);
280 memset(parsenextc, 0, BUFSIZ);
284 if ((ret = read(inputFd, &c, 1)) < 1)
287 fprintf(stderr, "\n\nkey=%d (%c)\n\n", c, c);
288 /* Go to the next line */
289 xwrite(outputFd, "\n", 1);
290 /* Rewrite the prompt */
291 xwrite(outputFd, prompt, strlen(prompt));
292 /* Rewrite the command */
293 xwrite(outputFd, parsenextc, len);
297 /* Control-a -- Beginning of line */
298 input_home(outputFd, &cursor);
300 /* Control-e -- End of line */
301 input_end(outputFd, &cursor, len);
304 /* Control-b -- Move back one character */
306 xwrite(outputFd, "\033[D", 3);
311 /* Control-f -- Move forward one character */
313 xwrite(outputFd, "\033[C", 3);
318 /* Control-d -- Delete one character */
320 input_delete(outputFd, cursor);
322 } else if (len == 0) {
328 /* Control-n -- Get next command */
329 if (hp && hp->n && hp->n->s) {
331 hp->s = strdup(parsenextc);
337 /* Control-p -- Get previous command */
340 hp->s = strdup(parsenextc);
346 #ifdef BB_FEATURE_SH_TAB_COMPLETION
348 /* Do TAB completion */
349 static int num_matches=0;
350 static char **matches = (char **) NULL;
354 if (lastWasTab == FALSE) {
355 char *tmp, *tmp1, *matchBuf;
357 /* For now, we will not bother with trying to distinguish
358 * whether the cursor is in/at a command extression -- we
359 * will always try all possable matches. If you don't like
360 * that then feel free to fix it.
363 /* Make a local copy of the string -- up
364 * to the position of the cursor */
365 matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
366 strncpy(matchBuf, parsenextc, cursor);
369 /* skip past any command seperator tokens */
370 while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
372 /* skip any leading white space */
373 while (*tmp && isspace(*tmp))
377 /* skip any leading white space */
378 while (*tmp && isspace(*tmp))
381 /* Free up any memory already allocated */
384 matches = (char **) NULL;
387 /* If the word starts with `~' and there is no slash in the word,
388 * then try completing this word as a username. */
390 /* FIXME -- this check is broken! */
391 if (*tmp == '~' && !strchr(tmp, '/'))
392 matches = username_completion_matches(tmp, &num_matches);
394 /* Try to match any executable in our path and everything
395 * in the current working directory that matches. */
397 matches = find_path_executable_n_cwd_matches(tmp, &num_matches);
399 /* Don't leak memory */
402 /* Did we find exactly one match? */
403 if (matches && num_matches==1) {
404 /* write out the matched command */
405 strncpy(parsenextc+pos, matches[0]+pos, strlen(matches[0])-pos);
406 len=strlen(parsenextc);
408 xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
412 /* Ok -- the last char was a TAB. Since they
413 * just hit TAB again, print a list of all the
414 * available choices... */
415 if ( matches && num_matches>0 ) {
418 /* Go to the next line */
419 xwrite(outputFd, "\n", 1);
420 /* Print the list of matches */
421 for (i=0,col=0; i<num_matches; i++) {
423 sprintf(foo, "%-14s ", matches[i]);
424 col += xwrite(outputFd, foo, strlen(foo));
425 if (col > 60 && matches[i+1] != NULL) {
426 xwrite(outputFd, "\n", 1);
430 /* Go to the next line */
431 xwrite(outputFd, "\n", 1);
432 /* Rewrite the prompt */
433 xwrite(outputFd, prompt, strlen(prompt));
434 /* Rewrite the command */
435 xwrite(outputFd, parsenextc, len);
436 /* Put the cursor back to where it used to be */
437 for (cursor=len; cursor > pos; cursor--)
438 xwrite(outputFd, "\b", 1);
449 input_backspace(outputFd, &cursor, &len);
453 *(parsenextc + len++ + 1) = c;
454 xwrite(outputFd, &c, 1);
458 /* escape sequence follows */
459 if ((ret = read(inputFd, &c, 1)) < 1)
462 if (c == '[') { /* 91 */
463 if ((ret = read(inputFd, &c, 1)) < 1)
468 /* Up Arrow -- Get previous command */
471 hp->s = strdup(parsenextc);
477 /* Down Arrow -- Get next command */
478 if (hp && hp->n && hp->n->s) {
480 hp->s = strdup(parsenextc);
486 /* This is where we rewrite the line
487 * using the selected history item */
489 len = strlen(parsenextc);
491 /* return to begining of line */
492 for (; cursor > 0; cursor--)
493 xwrite(outputFd, "\b", 1);
495 /* erase old command */
496 for (j = 0; j < len; j++)
497 xwrite(outputFd, " ", 1);
499 /* return to begining of line */
500 for (j = len; j > 0; j--)
501 xwrite(outputFd, "\b", 1);
503 memset(parsenextc, 0, BUFSIZ);
504 len = strlen(parsenextc);
505 /* write new command */
506 strcpy(parsenextc, hp->s);
508 xwrite(outputFd, parsenextc, len);
512 /* Right Arrow -- Move forward one character */
514 xwrite(outputFd, "\033[C", 3);
519 /* Left Arrow -- Move back one character */
521 xwrite(outputFd, "\033[D", 3);
528 input_delete(outputFd, cursor);
533 //case '5': case '6': /* pgup/pgdown */
539 input_home(outputFd, &cursor);
545 input_end(outputFd, &cursor, len);
548 if (c == '1' || c == '3' || c == '4')
549 if ((ret = read(inputFd, &c, 1)) < 1)
550 return ret; /* read 126 (~) */
554 if ((ret = read(inputFd, &c, 1)) < 1)
559 input_home(outputFd, &cursor);
563 input_end(outputFd, &cursor, len);
571 default: /* If it's regular input, do the normal thing */
573 if (!isprint(c)) /* Skip non-printable characters */
576 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
581 if (cursor == (len - 1)) { /* Append if at the end of the line */
582 *(parsenextc + cursor) = c;
583 } else { /* Insert otherwise */
584 memmove(parsenextc + cursor + 1, parsenextc + cursor,
587 *(parsenextc + cursor) = c;
589 for (j = cursor; j < len; j++)
590 xwrite(outputFd, parsenextc + j, 1);
591 for (; j > cursor; j--)
592 xwrite(outputFd, "\033[D", 3);
596 xwrite(outputFd, &c, 1);
604 if (break_out) /* Enter is the command terminator, no more input. */
609 xioctl(inputFd, TCSETA, (void *) &old_term);
613 /* Handle command history log */
616 struct history *h = his_end;
619 /* No previous history */
620 h = his_front = malloc(sizeof(struct history));
621 h->n = malloc(sizeof(struct history));
624 h->s = strdup(parsenextc);
631 /* Add a new history command */
632 h->n = malloc(sizeof(struct history));
637 h->s = strdup(parsenextc);
640 /* After max history, remove the oldest command */
641 if (history_counter >= MAX_HISTORY) {
643 struct history *p = his_front->n;
658 extern void cmdedit_init(void)
660 atexit(cmdedit_reset_term);
661 signal(SIGINT, prepareToDie);
662 signal(SIGQUIT, prepareToDie);
663 signal(SIGTERM, prepareToDie);
665 #endif /* BB_FEATURE_SH_COMMAND_EDITING */