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");
187 char** find_path_executable_n_cwd_matches(char* command, int *num_matches)
189 char **matches = (char **) NULL;
190 matches = malloc(sizeof(char*)*100);
192 matches[0] = malloc(sizeof(char)*50);
193 matches[1] = malloc(sizeof(char)*50);
195 sprintf(matches[0], "Hello");
196 sprintf(matches[1], "Howdy");
199 // fprintf(stderr, "\nin find_path_executable_n_cwd_matches\n");
204 * This function is used to grab a character buffer
205 * from the input file descriptor and allows you to
206 * a string with full command editing (sortof like
209 * The following standard commands are not implemented:
210 * ESC-b -- Move back one word
211 * ESC-f -- Move forward one word
212 * ESC-d -- Delete back one word
213 * ESC-h -- Delete forward one word
214 * CTL-t -- Transpose two characters
216 * Furthermore, the "vi" command editing keys are not implemented.
218 * TODO: implement TAB command completion. :)
221 extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd,
222 char command[BUFSIZ])
231 int lastWasTab = FALSE;
233 struct history *hp = his_end;
235 memset(command, 0, sizeof(command));
236 parsenextc = command;
238 xioctl(inputFd, TCGETA, (void *) &old_term);
239 memcpy(&new_term, &old_term, sizeof(struct termio));
241 new_term.c_cc[VMIN] = 1;
242 new_term.c_cc[VTIME] = 0;
243 new_term.c_lflag &= ~ICANON; /* unbuffered input */
244 new_term.c_lflag &= ~ECHO;
245 xioctl(inputFd, TCSETA, (void *) &new_term);
248 xioctl(inputFd, TCSETA, (void *) &new_term);
251 memset(parsenextc, 0, BUFSIZ);
255 if ((ret = read(inputFd, &c, 1)) < 1)
260 /* Control-a -- Beginning of line */
261 input_home(outputFd, &cursor);
263 /* Control-e -- End of line */
264 input_end(outputFd, &cursor, len);
267 /* Control-b -- Move back one character */
269 xwrite(outputFd, "\033[D", 3);
274 /* Control-f -- Move forward one character */
276 xwrite(outputFd, "\033[C", 3);
281 /* Control-d -- Delete one character */
283 input_delete(outputFd, cursor);
285 } else if (len == 0) {
291 /* Control-n -- Get next command */
292 if (hp && hp->n && hp->n->s) {
294 hp->s = strdup(parsenextc);
300 /* Control-p -- Get previous command */
303 hp->s = strdup(parsenextc);
310 /* Do TAB completion */
311 static int num_matches=0;
312 static char **matches = (char **) NULL;
316 if (lastWasTab == FALSE) {
317 char *tmp, *tmp1, *matchBuf;
319 /* For now, we will not bother with trying to distinguish
320 * whether the cursor is in/at a command extression -- we
321 * will always try all possable matches. If you don't like
322 * that, feel free to fix it.
325 /* Make a local copy of the string -- up
326 * to the the position of the cursor */
327 matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
328 strncpy(matchBuf, parsenextc, cursor);
331 /* skip past any command seperator tokens */
332 while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
334 /* skip any leading white space */
335 while (*tmp && isspace(*tmp))
339 /* skip any leading white space */
340 while (*tmp && isspace(*tmp))
343 /* Free up any memory already allocated */
346 matches = (char **) NULL;
349 /* If the word starts in `~', and there is no slash in the word,
350 * then try completing this word as a username. */
351 if (*tmp == '~' && !strchr(tmp, '/'))
352 matches = username_completion_matches(tmp, &num_matches);
354 /* Try to match any executable in our patch, and everything
355 * in the current working directory that matches.
358 matches = find_path_executable_n_cwd_matches(tmp, &num_matches);
360 if ( matches && num_matches>0 ) {
363 fprintf(stderr, "\nTabbing...\n");
365 /* Make a list of the matches */
366 col += xwrite(outputFd, "\n", 1);
367 for (i=0,col=0; i<num_matches; i++) {
368 col += xwrite(outputFd, prompt, strlen(matches[i]));
369 if (col > 60 && matches[i+1] != NULL) {
370 xwrite(outputFd, "\n", 1);
374 xwrite(outputFd, "\n", 1);
377 fprintf(stderr, "len=%d\n", len);
379 /* Move to the beginning of the line */
380 input_home(outputFd, &len);
382 /* erase everything */
383 for (j = 0; j < len; j++)
384 xwrite(outputFd, " ", 1);
386 /* return to begining of line */
387 input_home(outputFd, &cursor);
389 /* Rewrite the prompt) */
390 xwrite(outputFd, prompt, strlen(prompt));
392 /* Rewrite the command */
393 len = strlen(parsenextc);
394 xwrite(outputFd, parsenextc, len);
396 /* Move back to where the cursor used to be */
397 for (cursor=pos; cursor > 0; cursor--)
398 xwrite(outputFd, "\b", 1);
401 //fprintf(stderr, "\nprompt='%s'\n", prompt);
409 input_backspace(outputFd, &cursor, &len);
413 *(parsenextc + len++ + 1) = c;
414 xwrite(outputFd, &c, 1);
418 /* escape sequence follows */
419 if ((ret = read(inputFd, &c, 1)) < 1)
422 if (c == '[') { /* 91 */
423 if ((ret = read(inputFd, &c, 1)) < 1)
428 /* Up Arrow -- Get previous command */
431 hp->s = strdup(parsenextc);
437 /* Down Arrow -- Get next command */
438 if (hp && hp->n && hp->n->s) {
440 hp->s = strdup(parsenextc);
446 /* This is where we rewrite the line
447 * using the selected history item */
449 len = strlen(parsenextc);
451 /* return to begining of line */
452 for (; cursor > 0; cursor--)
453 xwrite(outputFd, "\b", 1);
454 xwrite(outputFd, parsenextc, len);
456 /* erase old command */
457 for (j = 0; j < len; j++)
458 xwrite(outputFd, " ", 1);
460 /* return to begining of line */
461 for (j = len; j > 0; j--)
462 xwrite(outputFd, "\b", 1);
464 memset(parsenextc, 0, BUFSIZ);
465 /* write new command */
466 strcpy(parsenextc, hp->s);
468 xwrite(outputFd, parsenextc, len);
472 /* Right Arrow -- Move forward one character */
474 xwrite(outputFd, "\033[C", 3);
479 /* Left Arrow -- Move back one character */
481 xwrite(outputFd, "\033[D", 3);
488 input_delete(outputFd, cursor);
494 input_home(outputFd, &cursor);
498 input_end(outputFd, &cursor, len);
501 if (c == '1' || c == '3' || c == '4')
502 if ((ret = read(inputFd, &c, 1)) < 1)
503 return ret; /* read 126 (~) */
507 if ((ret = read(inputFd, &c, 1)) < 1)
512 input_home(outputFd, &cursor);
516 input_end(outputFd, &cursor, len);
524 default: /* If it's regular input, do the normal thing */
526 if (!isprint(c)) /* Skip non-printable characters */
529 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
534 if (cursor == (len - 1)) { /* Append if at the end of the line */
535 *(parsenextc + cursor) = c;
536 } else { /* Insert otherwise */
537 memmove(parsenextc + cursor + 1, parsenextc + cursor,
540 *(parsenextc + cursor) = c;
542 for (j = cursor; j < len; j++)
543 xwrite(outputFd, parsenextc + j, 1);
544 for (; j > cursor; j--)
545 xwrite(outputFd, "\033[D", 3);
549 xwrite(outputFd, &c, 1);
557 if (break_out) /* Enter is the command terminator, no more input. */
562 xioctl(inputFd, TCSETA, (void *) &old_term);
566 /* Handle command history log */
569 struct history *h = his_end;
572 /* No previous history */
573 h = his_front = malloc(sizeof(struct history));
574 h->n = malloc(sizeof(struct history));
577 h->s = strdup(parsenextc);
584 /* Add a new history command */
585 h->n = malloc(sizeof(struct history));
590 h->s = strdup(parsenextc);
593 /* After max history, remove the oldest command */
594 if (history_counter >= MAX_HISTORY) {
596 struct history *p = his_front->n;
611 extern void cmdedit_init(void)
613 atexit(cmdedit_reset_term);
614 signal(SIGINT, prepareToDie);
615 signal(SIGQUIT, prepareToDie);
616 signal(SIGTERM, prepareToDie);
618 #endif /* BB_FEATURE_SH_COMMAND_EDITING */