2 * Termios command line History and Editting for NetBSD sh (ash)
4 * Main code: Adam Rogoyski <rogoyski@cs.utexas.edu>
5 * Etc: Dave Cinege <dcinege@psychosis.com>
6 * Adjusted for busybox: Erik Andersen <andersee@debian.org>
8 * You may use this code as you wish, so long as the original author(s)
9 * are attributed in any redistributions of the source code.
10 * This code is 'as is' with no warranty.
11 * This code may safely be consumed by a BSD or GPL license.
13 * v 0.5 19990328 Initial release
15 * Future plans: Simple file and path name completion. (like BASH)
21 Terminal key codes are not extensive, and more will probably
22 need to be added. This version was created on Debian GNU/Linux 2.x.
23 Delete, Backspace, Home, End, and the arrow keys were tested
24 to work in an Xterm and console. Ctrl-A also works as Home.
25 Ctrl-E also works as End. The binary size increase is <3K.
27 Editting will not display correctly for lines greater then the
28 terminal width. (more then one line.) However, history will.
32 #ifdef BB_FEATURE_SH_COMMAND_EDITING
46 #define MAX_HISTORY 15 /* Maximum length of the linked list for the command line history */
50 #define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0)
51 #define whitespace(c) (((c) == ' ') || ((c) == '\t'))
53 static struct history *his_front = NULL; /* First element in command line list */
54 static struct history *his_end = NULL; /* Last element in command line list */
55 static struct termio old_term, new_term; /* Current termio and the previous termio before starting ash */
57 static int history_counter = 0; /* Number of commands in history list */
58 static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */
59 char *parsenextc; /* copy of parsefile->nextc */
68 /* Version of write which resumes after a signal is caught. */
69 int xwrite(int fd, char *buf, int nbytes)
78 i = write(fd, buf, n);
87 } else if (errno != EINTR) {
94 /* Version of ioctl that retries after a signal is caught. */
95 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* matchBuf)
182 fprintf(stderr, "\nin username_completion_matches\n");
183 return ( (char**) NULL);
185 char **command_completion_matches( char* matchBuf)
187 fprintf(stderr, "\nin command_completion_matches\n");
188 return ( (char**) NULL);
190 char **directory_completion_matches( char* matchBuf)
192 fprintf(stderr, "\nin directory_completion_matches\n");
193 return ( (char**) NULL);
197 * This function is used to grab a character buffer
198 * from the input file descriptor and allows you to
199 * a string with full command editing (sortof like
202 * The following standard commands are not implemented:
203 * ESC-b -- Move back one word
204 * ESC-f -- Move forward one word
205 * ESC-d -- Delete back one word
206 * ESC-h -- Delete forward one word
207 * CTL-t -- Transpose two characters
209 * Furthermore, the "vi" command editing keys are not implemented.
211 * TODO: implement TAB command completion. :)
214 extern int cmdedit_read_input(int inputFd, int outputFd,
215 char command[BUFSIZ])
224 int lastWasTab = FALSE;
225 char **matches = (char **)NULL;
227 struct history *hp = his_end;
229 memset(command, 0, sizeof(command));
230 parsenextc = command;
232 xioctl(inputFd, TCGETA, (void *) &old_term);
233 memcpy(&new_term, &old_term, sizeof(struct termio));
234 new_term.c_cc[VMIN] = 1;
235 new_term.c_cc[VTIME] = 0;
236 new_term.c_lflag &= ~ICANON; /* unbuffered input */
237 new_term.c_lflag &= ~ECHO;
238 xioctl(inputFd, TCSETA, (void *) &new_term);
241 xioctl(inputFd, TCSETA, (void *) &new_term);
244 memset(parsenextc, 0, BUFSIZ);
248 if ((ret = read(inputFd, &c, 1)) < 1)
253 /* Control-a -- Beginning of line */
254 input_home(outputFd, &cursor);
256 /* Control-e -- End of line */
257 input_end(outputFd, &cursor, len);
260 /* Control-b -- Move back one character */
262 xwrite(outputFd, "\033[D", 3);
267 /* Control-f -- Move forward one character */
269 xwrite(outputFd, "\033[C", 3);
274 /* Control-d -- Delete one character */
276 input_delete(outputFd, cursor);
278 } else if (len == 0) {
284 /* Control-n -- Get next command */
285 if (hp && hp->n && hp->n->s) {
287 hp->s = strdup(parsenextc);
293 /* Control-p -- Get previous command */
296 hp->s = strdup(parsenextc);
303 /* Do TAB completion */
304 int in_command_position=0, ti=len-1;
306 if (lastWasTab == FALSE) {
312 matches = (char **)NULL;
315 matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
317 /* Make a local copy of the string -- up
318 * to the the position of the cursor */
319 strcpy( matchBuf, parsenextc);
320 matchBuf[cursor+1] = '\0';
322 /* skip leading white space */
324 while (*tmp && isspace(*tmp)) {
329 /* Determine if this is a command word or not */
330 //while ((ti > -1) && (whitespace (matchBuf[ti]))) {
331 //printf("\nti=%d\n", ti);
334 printf("\nti=%d\n", ti);
337 in_command_position++;
338 } else if (member(matchBuf[ti], ";|&{(`")) {
339 int this_char, prev_char;
340 in_command_position++;
341 /* Handle the two character tokens `>&', `<&', and `>|'.
342 We are not in a command position after one of these. */
343 this_char = matchBuf[ti];
344 prev_char = matchBuf[ti - 1];
346 if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) ||
347 (this_char == '|' && prev_char == '>')) {
348 in_command_position = 0;
350 /* For now, do not bother with catching quoted
351 * expressions and marking them as not in command
352 * positions. Some other day. Or not.
354 //else if (char_is_quoted (matchBuf, ti)) {
355 // in_command_position = 0;
358 printf("\nin_command_position=%d\n", in_command_position);
359 /* If the word starts in `~', and there is no slash in the word,
360 * then try completing this word as a username. */
361 if (*matchBuf == '~' && !strchr (matchBuf, '/'))
362 matches = username_completion_matches(matchBuf);
364 /* If this word is in a command position, then complete over possible
365 * command names, including aliases, built-ins, and executables. */
366 if (!matches && in_command_position) {
367 matches = command_completion_matches(matchBuf);
369 /* If we are attempting command completion and nothing matches,
370 * then try and match directories as a last resort... */
372 matches = directory_completion_matches(matchBuf);
375 printf("\nprinting match list\n");
377 /* Rewrite the whole line (for debugging) */
378 for (; cursor > 0; cursor--)
379 xwrite(outputFd, "\b", 1);
380 len = strlen(parsenextc);
381 xwrite(outputFd, parsenextc, len);
388 input_backspace(outputFd, &cursor, &len);
392 *(parsenextc + len++ + 1) = c;
393 xwrite(outputFd, &c, 1);
397 /* escape sequence follows */
398 if ((ret = read(inputFd, &c, 1)) < 1)
401 if (c == '[') { /* 91 */
402 if ((ret = read(inputFd, &c, 1)) < 1)
407 /* Up Arrow -- Get previous command */
410 hp->s = strdup(parsenextc);
416 /* Down Arrow -- Get next command */
417 if (hp && hp->n && hp->n->s) {
419 hp->s = strdup(parsenextc);
425 /* This is where we rewrite the line
426 * using the selected history item */
428 len = strlen(parsenextc);
430 /* return to begining of line */
431 for (; cursor > 0; cursor--)
432 xwrite(outputFd, "\b", 1);
433 xwrite(outputFd, parsenextc, len);
435 /* erase old command */
436 for (j = 0; j < len; j++)
437 xwrite(outputFd, " ", 1);
439 /* return to begining of line */
440 for (j = len; j > 0; j--)
441 xwrite(outputFd, "\b", 1);
443 memset(parsenextc, 0, BUFSIZ);
444 /* write new command */
445 strcpy(parsenextc, hp->s);
447 xwrite(outputFd, parsenextc, len);
451 /* Right Arrow -- Move forward one character */
453 xwrite(outputFd, "\033[C", 3);
458 /* Left Arrow -- Move back one character */
460 xwrite(outputFd, "\033[D", 3);
467 input_delete(outputFd, cursor);
473 input_home(outputFd, &cursor);
477 input_end(outputFd, &cursor, len);
480 if (c == '1' || c == '3' || c == '4')
481 if ((ret = read(inputFd, &c, 1)) < 1)
482 return ret; /* read 126 (~) */
486 if ((ret = read(inputFd, &c, 1)) < 1)
491 input_home(outputFd, &cursor);
495 input_end(outputFd, &cursor, len);
503 default: /* If it's regular input, do the normal thing */
505 if (!isprint(c)) /* Skip non-printable characters */
508 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
513 if (cursor == (len - 1)) { /* Append if at the end of the line */
514 *(parsenextc + cursor) = c;
515 } else { /* Insert otherwise */
516 memmove(parsenextc + cursor + 1, parsenextc + cursor,
519 *(parsenextc + cursor) = c;
521 for (j = cursor; j < len; j++)
522 xwrite(outputFd, parsenextc + j, 1);
523 for (; j > cursor; j--)
524 xwrite(outputFd, "\033[D", 3);
528 xwrite(outputFd, &c, 1);
536 if (break_out) /* Enter is the command terminator, no more input. */
541 xioctl(inputFd, TCSETA, (void *) &old_term);
545 /* Handle command history log */
548 struct history *h = his_end;
551 /* No previous history */
552 h = his_front = malloc(sizeof(struct history));
553 h->n = malloc(sizeof(struct history));
555 h->s = strdup(parsenextc);
562 /* Add a new history command */
563 h->n = malloc(sizeof(struct history));
567 h->s = strdup(parsenextc);
570 /* After max history, remove the oldest command */
571 if (history_counter >= MAX_HISTORY) {
573 struct history *p = his_front->n;
588 extern void cmdedit_init(void)
590 atexit(cmdedit_reset_term);
591 signal(SIGINT, prepareToDie);
592 signal(SIGQUIT, prepareToDie);
593 signal(SIGTERM, prepareToDie);
595 #endif /* BB_FEATURE_SH_COMMAND_EDITING */