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 */
51 static struct history *his_front = NULL; /* First element in command line list */
52 static struct history *his_end = NULL; /* Last element in command line list */
53 static struct termio old_term, new_term; /* Current termio and the previous termio before starting ash */
55 static int history_counter = 0; /* Number of commands in history list */
56 static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */
57 char *parsenextc; /* copy of parsefile->nextc */
66 /* Version of write which resumes after a signal is caught. */
67 int xwrite(int fd, char *buf, int nbytes)
76 i = write(fd, buf, n);
85 } else if (errno != EINTR) {
92 /* Version of ioctl that retries after a signal is caught. */
93 int xioctl(int fd, unsigned long request, char *arg)
96 while ((i = ioctl(fd, request, arg)) == -1 && errno == EINTR);
101 void cmdedit_reset_term(void)
104 xioctl(fileno(stdin), TCSETA, (void *) &old_term);
107 void gotaSignal(int sig)
109 cmdedit_reset_term();
110 fprintf(stdout, "\n");
114 void input_home(int outputFd, int *cursor)
115 { /* Command line input routines */
116 while (*cursor > 0) {
117 xwrite(outputFd, "\b", 1);
123 void input_delete(int outputFd, int cursor)
127 memmove(parsenextc + cursor, parsenextc + cursor + 1,
128 BUFSIZ - cursor - 1);
129 for (j = cursor; j < (BUFSIZ - 1); j++) {
130 if (!*(parsenextc + j))
133 xwrite(outputFd, (parsenextc + j), 1);
136 xwrite(outputFd, " \b", 2);
139 xwrite(outputFd, "\b", 1);
143 void input_end(int outputFd, int *cursor, int len)
145 while (*cursor < len) {
146 xwrite(outputFd, "\033[C", 3);
152 void input_backspace(int outputFd, int *cursor, int *len)
157 xwrite(outputFd, "\b \b", 3);
159 memmove(parsenextc + *cursor, parsenextc + *cursor + 1,
160 BUFSIZ - *cursor + 1);
162 for (j = *cursor; j < (BUFSIZ - 1); j++) {
163 if (!*(parsenextc + j))
166 xwrite(outputFd, (parsenextc + j), 1);
169 xwrite(outputFd, " \b", 2);
171 while (j-- > *cursor)
172 xwrite(outputFd, "\b", 1);
178 extern int cmdedit_read_input(int inputFd, int outputFd,
179 char command[BUFSIZ])
189 struct history *hp = his_end;
191 memset(command, 0, sizeof(command));
192 parsenextc = command;
194 xioctl(inputFd, TCGETA, (void *) &old_term);
195 memcpy(&new_term, &old_term, sizeof(struct termio));
196 new_term.c_cc[VMIN] = 1;
197 new_term.c_cc[VTIME] = 0;
198 new_term.c_lflag &= ~ICANON; /* unbuffered input */
199 new_term.c_lflag &= ~ECHO;
200 xioctl(inputFd, TCSETA, (void *) &new_term);
203 xioctl(inputFd, TCSETA, (void *) &new_term);
206 memset(parsenextc, 0, BUFSIZ);
210 if ((ret = read(inputFd, &c, 1)) < 1)
214 case 1: /* Control-A Beginning of line */
215 input_home(outputFd, &cursor);
217 case 5: /* Control-E EOL */
218 input_end(outputFd, &cursor, len);
220 case 4: /* Control-D */
222 input_delete(outputFd, cursor);
226 case '\b': /* Backspace */
228 input_backspace(outputFd, &cursor, &len);
230 case '\n': /* Enter */
231 *(parsenextc + len++ + 1) = c;
232 xwrite(outputFd, &c, 1);
235 case ESC: /* escape sequence follows */
236 if ((ret = read(inputFd, &c, 1)) < 1)
239 if (c == '[') { /* 91 */
240 if ((ret = read(inputFd, &c, 1)) < 1)
245 if (hp && hp->p) { /* Up */
251 if (hp && hp->n && hp->n->s) { /* Down */
258 len = strlen(parsenextc);
260 for (; cursor > 0; cursor--) /* return to begining of line */
261 xwrite(outputFd, "\b", 1);
263 for (j = 0; j < len; j++) /* erase old command */
264 xwrite(outputFd, " ", 1);
266 for (j = len; j > 0; j--) /* return to begining of line */
267 xwrite(outputFd, "\b", 1);
269 strcpy(parsenextc, hp->s); /* write new command */
271 xwrite(outputFd, parsenextc, len);
274 case 'C': /* Right */
276 xwrite(outputFd, "\033[C", 3);
282 xwrite(outputFd, "\033[D", 3);
286 case '3': /* Delete */
288 input_delete(outputFd, cursor);
292 case '1': /* Home (Ctrl-A) */
293 input_home(outputFd, &cursor);
295 case '4': /* End (Ctrl-E) */
296 input_end(outputFd, &cursor, len);
299 if (c == '1' || c == '3' || c == '4')
300 if ((ret = read(inputFd, &c, 1)) < 1)
301 return ret; /* read 126 (~) */
303 if (c == 'O') { /* 79 */
304 if ((ret = read(inputFd, &c, 1)) < 1)
307 case 'H': /* Home (xterm) */
308 input_home(outputFd, &cursor);
310 case 'F': /* End (xterm) */
311 input_end(outputFd, &cursor, len);
318 default: /* If it's regular input, do the normal thing */
320 if (!isprint(c)) /* Skip non-printable characters */
323 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
328 if (cursor == (len - 1)) { /* Append if at the end of the line */
329 *(parsenextc + cursor) = c;
330 } else { /* Insert otherwise */
331 memmove(parsenextc + cursor + 1, parsenextc + cursor,
334 *(parsenextc + cursor) = c;
336 for (j = cursor; j < len; j++)
337 xwrite(outputFd, parsenextc + j, 1);
338 for (; j > cursor; j--)
339 xwrite(outputFd, "\033[D", 3);
343 xwrite(outputFd, &c, 1);
347 if (break_out) /* Enter is the command terminator, no more input. */
352 xioctl(inputFd, TCSETA, (void *) &old_term);
356 if (*(parsenextc)) { /* Handle command history log */
358 struct history *h = his_end;
360 if (!h) { /* No previous history */
361 h = his_front = malloc(sizeof(struct history));
362 h->n = malloc(sizeof(struct history));
364 h->s = strdup(parsenextc);
371 } else { /* Add a new history command */
373 h->n = malloc(sizeof(struct history));
378 h->s = strdup(parsenextc);
381 if (history_counter >= MAX_HISTORY) { /* After max history, remove the last known command */
383 struct history *p = his_front->n;
398 extern void cmdedit_init(void)
400 atexit(cmdedit_reset_term);
401 signal(SIGINT, gotaSignal);
402 signal(SIGQUIT, gotaSignal);
403 signal(SIGTERM, gotaSignal);
405 #endif /* BB_FEATURE_SH_COMMAND_EDITING */