blob: 314e8cd661804940a03a4c15a61162911f7c57e6 [file] [log] [blame]
Erik Andersen13456d12000-03-16 08:09:57 +00001/*
2 * Termios command line History and Editting for NetBSD sh (ash)
3 * Copyright (c) 1999
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>
7 *
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.
12 *
13 * v 0.5 19990328 Initial release
14 *
15 * Future plans: Simple file and path name completion. (like BASH)
16 *
17 */
18
19/*
20 Usage and Known bugs:
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.
26
27 Editting will not display correctly for lines greater then the
28 terminal width. (more then one line.) However, history will.
29 */
30
31#include "internal.h"
32#ifdef BB_FEATURE_SH_COMMAND_EDITING
33
34#include <stdio.h>
35#include <errno.h>
36#include <unistd.h>
37#include <stdlib.h>
38#include <string.h>
39#include <termio.h>
40#include <ctype.h>
41#include <signal.h>
42
43#include "cmdedit.h"
44
45
46#define MAX_HISTORY 15 /* Maximum length of the linked list for the command line history */
47
48#define ESC 27
49#define DEL 127
Erik Andersen6273f652000-03-17 01:12:41 +000050#define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0)
51#define whitespace(c) (((c) == ' ') || ((c) == '\t'))
Erik Andersen13456d12000-03-16 08:09:57 +000052
53static struct history *his_front = NULL; /* First element in command line list */
54static struct history *his_end = NULL; /* Last element in command line list */
55static struct termio old_term, new_term; /* Current termio and the previous termio before starting ash */
56
57static int history_counter = 0; /* Number of commands in history list */
58static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */
59char *parsenextc; /* copy of parsefile->nextc */
60
61struct history {
62 char *s;
63 struct history *p;
64 struct history *n;
65};
66
67
68/* Version of write which resumes after a signal is caught. */
69int xwrite(int fd, char *buf, int nbytes)
70{
71 int ntry;
72 int i;
73 int n;
74
75 n = nbytes;
76 ntry = 0;
77 for (;;) {
78 i = write(fd, buf, n);
79 if (i > 0) {
80 if ((n -= i) <= 0)
81 return nbytes;
82 buf += i;
83 ntry = 0;
84 } else if (i == 0) {
85 if (++ntry > 10)
86 return nbytes - n;
87 } else if (errno != EINTR) {
88 return -1;
89 }
90 }
91}
92
93
94/* Version of ioctl that retries after a signal is caught. */
95int xioctl(int fd, unsigned long request, char *arg)
96{
97 int i;
98 while ((i = ioctl(fd, request, arg)) == -1 && errno == EINTR);
99 return i;
100}
101
102
103void cmdedit_reset_term(void)
104{
105 if (reset_term)
106 xioctl(fileno(stdin), TCSETA, (void *) &old_term);
107}
108
Erik Andersen6273f652000-03-17 01:12:41 +0000109void prepareToDie(int sig)
Erik Andersen13456d12000-03-16 08:09:57 +0000110{
111 cmdedit_reset_term();
112 fprintf(stdout, "\n");
113 exit(TRUE);
114}
115
116void input_home(int outputFd, int *cursor)
117{ /* Command line input routines */
118 while (*cursor > 0) {
119 xwrite(outputFd, "\b", 1);
120 --*cursor;
121 }
122}
123
124
125void input_delete(int outputFd, int cursor)
126{
127 int j = 0;
128
129 memmove(parsenextc + cursor, parsenextc + cursor + 1,
130 BUFSIZ - cursor - 1);
131 for (j = cursor; j < (BUFSIZ - 1); j++) {
132 if (!*(parsenextc + j))
133 break;
134 else
135 xwrite(outputFd, (parsenextc + j), 1);
136 }
137
138 xwrite(outputFd, " \b", 2);
139
140 while (j-- > cursor)
141 xwrite(outputFd, "\b", 1);
142}
143
144
145void input_end(int outputFd, int *cursor, int len)
146{
147 while (*cursor < len) {
148 xwrite(outputFd, "\033[C", 3);
149 ++*cursor;
150 }
151}
152
153
154void input_backspace(int outputFd, int *cursor, int *len)
155{
156 int j = 0;
157
158 if (*cursor > 0) {
159 xwrite(outputFd, "\b \b", 3);
160 --*cursor;
161 memmove(parsenextc + *cursor, parsenextc + *cursor + 1,
162 BUFSIZ - *cursor + 1);
163
164 for (j = *cursor; j < (BUFSIZ - 1); j++) {
165 if (!*(parsenextc + j))
166 break;
167 else
168 xwrite(outputFd, (parsenextc + j), 1);
169 }
170
171 xwrite(outputFd, " \b", 2);
172
173 while (j-- > *cursor)
174 xwrite(outputFd, "\b", 1);
175
176 --*len;
177 }
178}
179
Erik Andersen6273f652000-03-17 01:12:41 +0000180char **username_completion_matches( char* matchBuf)
181{
182 fprintf(stderr, "\nin username_completion_matches\n");
183 return ( (char**) NULL);
184}
185char **command_completion_matches( char* matchBuf)
186{
187 fprintf(stderr, "\nin command_completion_matches\n");
188 return ( (char**) NULL);
189}
190char **directory_completion_matches( char* matchBuf)
191{
192 fprintf(stderr, "\nin directory_completion_matches\n");
193 return ( (char**) NULL);
194}
195
196/*
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
200 * a mini readline).
201 *
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
208 *
209 * Furthermore, the "vi" command editing keys are not implemented.
210 *
211 * TODO: implement TAB command completion. :)
212 *
213 */
Erik Andersen13456d12000-03-16 08:09:57 +0000214extern int cmdedit_read_input(int inputFd, int outputFd,
215 char command[BUFSIZ])
216{
217
218 int nr = 0;
219 int len = 0;
220 int j = 0;
221 int cursor = 0;
222 int break_out = 0;
223 int ret = 0;
Erik Andersen6273f652000-03-17 01:12:41 +0000224 int lastWasTab = FALSE;
225 char **matches = (char **)NULL;
Erik Andersen13456d12000-03-16 08:09:57 +0000226 char c = 0;
227 struct history *hp = his_end;
228
229 memset(command, 0, sizeof(command));
230 parsenextc = command;
231 if (!reset_term) {
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);
239 reset_term = 1;
240 } else {
241 xioctl(inputFd, TCSETA, (void *) &new_term);
242 }
243
244 memset(parsenextc, 0, BUFSIZ);
245
246 while (1) {
247
248 if ((ret = read(inputFd, &c, 1)) < 1)
249 return ret;
Erik Andersen6273f652000-03-17 01:12:41 +0000250
Erik Andersen13456d12000-03-16 08:09:57 +0000251 switch (c) {
Erik Andersen6273f652000-03-17 01:12:41 +0000252 case 1:
253 /* Control-a -- Beginning of line */
Erik Andersen13456d12000-03-16 08:09:57 +0000254 input_home(outputFd, &cursor);
Erik Andersen6273f652000-03-17 01:12:41 +0000255 case 5:
256 /* Control-e -- End of line */
Erik Andersen13456d12000-03-16 08:09:57 +0000257 input_end(outputFd, &cursor, len);
258 break;
Erik Andersen6273f652000-03-17 01:12:41 +0000259 case 2:
260 /* Control-b -- Move back one character */
261 if (cursor > 0) {
262 xwrite(outputFd, "\033[D", 3);
263 cursor--;
264 }
265 break;
266 case 6:
267 /* Control-f -- Move forward one character */
268 if (cursor < len) {
269 xwrite(outputFd, "\033[C", 3);
270 cursor++;
271 }
272 break;
273 case 4:
274 /* Control-d -- Delete one character */
Erik Andersen13456d12000-03-16 08:09:57 +0000275 if (cursor != len) {
276 input_delete(outputFd, cursor);
277 len--;
Erik Andersen6273f652000-03-17 01:12:41 +0000278 } else if (len == 0) {
279 prepareToDie(0);
280 exit(0);
Erik Andersen13456d12000-03-16 08:09:57 +0000281 }
282 break;
Erik Andersen6273f652000-03-17 01:12:41 +0000283 case 14:
284 /* Control-n -- Get next command */
285 if (hp && hp->n && hp->n->s) {
286 free( hp->s);
287 hp->s = strdup(parsenextc);
288 hp = hp->n;
289 goto hop;
290 }
291 break;
292 case 16:
293 /* Control-p -- Get previous command */
294 if (hp && hp->p) {
295 free( hp->s);
296 hp->s = strdup(parsenextc);
297 hp = hp->p;
298 goto hop;
299 }
300 break;
301 case '\t':
302 {
303 /* Do TAB completion */
304 int in_command_position=0, ti=len-1;
305
306 if (lastWasTab == FALSE) {
307 char *tmp;
308 char *matchBuf;
309
310 if (matches) {
311 free(matches);
312 matches = (char **)NULL;
313 }
314
315 matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
316
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';
321
322 /* skip leading white space */
323 tmp = matchBuf;
324 while (*tmp && isspace(*tmp)) {
325 (tmp)++;
326 ti++;
327 }
328
329 /* Determine if this is a command word or not */
330 //while ((ti > -1) && (whitespace (matchBuf[ti]))) {
331//printf("\nti=%d\n", ti);
332 // ti--;
333 // }
334printf("\nti=%d\n", ti);
335
336 if (ti < 0) {
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];
345
346 if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) ||
347 (this_char == '|' && prev_char == '>')) {
348 in_command_position = 0;
349 }
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.
353 */
354 //else if (char_is_quoted (matchBuf, ti)) {
355 // in_command_position = 0;
356 //}
357 }
358printf("\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);
363
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);
368
369 /* If we are attempting command completion and nothing matches,
370 * then try and match directories as a last resort... */
371 if (!matches)
372 matches = directory_completion_matches(matchBuf);
373 }
374 } else {
375 printf("\nprinting match list\n");
376 }
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);
382 cursor = len;
383 break;
384 }
385 case '\b':
Erik Andersen13456d12000-03-16 08:09:57 +0000386 case DEL:
Erik Andersen6273f652000-03-17 01:12:41 +0000387 /* Backspace */
Erik Andersen13456d12000-03-16 08:09:57 +0000388 input_backspace(outputFd, &cursor, &len);
389 break;
Erik Andersen6273f652000-03-17 01:12:41 +0000390 case '\n':
391 /* Enter */
Erik Andersen13456d12000-03-16 08:09:57 +0000392 *(parsenextc + len++ + 1) = c;
393 xwrite(outputFd, &c, 1);
394 break_out = 1;
395 break;
Erik Andersen6273f652000-03-17 01:12:41 +0000396 case ESC: {
397 /* escape sequence follows */
Erik Andersen13456d12000-03-16 08:09:57 +0000398 if ((ret = read(inputFd, &c, 1)) < 1)
399 return ret;
400
401 if (c == '[') { /* 91 */
402 if ((ret = read(inputFd, &c, 1)) < 1)
403 return ret;
Erik Andersen6273f652000-03-17 01:12:41 +0000404
Erik Andersen13456d12000-03-16 08:09:57 +0000405 switch (c) {
406 case 'A':
Erik Andersen6273f652000-03-17 01:12:41 +0000407 /* Up Arrow -- Get previous command */
408 if (hp && hp->p) {
409 free( hp->s);
410 hp->s = strdup(parsenextc);
Erik Andersen13456d12000-03-16 08:09:57 +0000411 hp = hp->p;
412 goto hop;
413 }
414 break;
415 case 'B':
Erik Andersen6273f652000-03-17 01:12:41 +0000416 /* Down Arrow -- Get next command */
417 if (hp && hp->n && hp->n->s) {
418 free( hp->s);
419 hp->s = strdup(parsenextc);
Erik Andersen13456d12000-03-16 08:09:57 +0000420 hp = hp->n;
421 goto hop;
422 }
423 break;
424
Erik Andersen6273f652000-03-17 01:12:41 +0000425 /* This is where we rewrite the line
426 * using the selected history item */
427 hop:
Erik Andersen13456d12000-03-16 08:09:57 +0000428 len = strlen(parsenextc);
429
Erik Andersen6273f652000-03-17 01:12:41 +0000430 /* return to begining of line */
431 for (; cursor > 0; cursor--)
Erik Andersen13456d12000-03-16 08:09:57 +0000432 xwrite(outputFd, "\b", 1);
Erik Andersen6273f652000-03-17 01:12:41 +0000433 xwrite(outputFd, parsenextc, len);
Erik Andersen13456d12000-03-16 08:09:57 +0000434
Erik Andersen6273f652000-03-17 01:12:41 +0000435 /* erase old command */
436 for (j = 0; j < len; j++)
Erik Andersen13456d12000-03-16 08:09:57 +0000437 xwrite(outputFd, " ", 1);
438
Erik Andersen6273f652000-03-17 01:12:41 +0000439 /* return to begining of line */
440 for (j = len; j > 0; j--)
Erik Andersen13456d12000-03-16 08:09:57 +0000441 xwrite(outputFd, "\b", 1);
442
Erik Andersen6273f652000-03-17 01:12:41 +0000443 memset(parsenextc, 0, BUFSIZ);
444 /* write new command */
445 strcpy(parsenextc, hp->s);
Erik Andersen13456d12000-03-16 08:09:57 +0000446 len = strlen(hp->s);
447 xwrite(outputFd, parsenextc, len);
448 cursor = len;
449 break;
Erik Andersen6273f652000-03-17 01:12:41 +0000450 case 'C':
451 /* Right Arrow -- Move forward one character */
Erik Andersen13456d12000-03-16 08:09:57 +0000452 if (cursor < len) {
453 xwrite(outputFd, "\033[C", 3);
454 cursor++;
455 }
456 break;
Erik Andersen6273f652000-03-17 01:12:41 +0000457 case 'D':
458 /* Left Arrow -- Move back one character */
Erik Andersen13456d12000-03-16 08:09:57 +0000459 if (cursor > 0) {
460 xwrite(outputFd, "\033[D", 3);
461 cursor--;
462 }
463 break;
Erik Andersen6273f652000-03-17 01:12:41 +0000464 case '3':
465 /* Delete */
Erik Andersen13456d12000-03-16 08:09:57 +0000466 if (cursor != len) {
467 input_delete(outputFd, cursor);
468 len--;
469 }
470 break;
Erik Andersen6273f652000-03-17 01:12:41 +0000471 case '1':
472 /* Home (Ctrl-A) */
Erik Andersen13456d12000-03-16 08:09:57 +0000473 input_home(outputFd, &cursor);
474 break;
Erik Andersen6273f652000-03-17 01:12:41 +0000475 case '4':
476 /* End (Ctrl-E) */
Erik Andersen13456d12000-03-16 08:09:57 +0000477 input_end(outputFd, &cursor, len);
478 break;
479 }
480 if (c == '1' || c == '3' || c == '4')
481 if ((ret = read(inputFd, &c, 1)) < 1)
482 return ret; /* read 126 (~) */
483 }
Erik Andersen6273f652000-03-17 01:12:41 +0000484 if (c == 'O') {
485 /* 79 */
Erik Andersen13456d12000-03-16 08:09:57 +0000486 if ((ret = read(inputFd, &c, 1)) < 1)
487 return ret;
488 switch (c) {
Erik Andersen6273f652000-03-17 01:12:41 +0000489 case 'H':
490 /* Home (xterm) */
Erik Andersen13456d12000-03-16 08:09:57 +0000491 input_home(outputFd, &cursor);
492 break;
Erik Andersen6273f652000-03-17 01:12:41 +0000493 case 'F':
494 /* End (xterm) */
Erik Andersen13456d12000-03-16 08:09:57 +0000495 input_end(outputFd, &cursor, len);
496 break;
497 }
498 }
499 c = 0;
500 break;
Erik Andersen6273f652000-03-17 01:12:41 +0000501 }
Erik Andersen13456d12000-03-16 08:09:57 +0000502
503 default: /* If it's regular input, do the normal thing */
504
505 if (!isprint(c)) /* Skip non-printable characters */
506 break;
507
508 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
509 break;
510
511 len++;
512
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,
517 len - cursor - 1);
518
519 *(parsenextc + cursor) = c;
520
521 for (j = cursor; j < len; j++)
522 xwrite(outputFd, parsenextc + j, 1);
523 for (; j > cursor; j--)
524 xwrite(outputFd, "\033[D", 3);
525 }
526
527 cursor++;
528 xwrite(outputFd, &c, 1);
529 break;
530 }
Erik Andersen6273f652000-03-17 01:12:41 +0000531 if (c=='\t')
532 lastWasTab = TRUE;
533 else
534 lastWasTab = FALSE;
Erik Andersen13456d12000-03-16 08:09:57 +0000535
536 if (break_out) /* Enter is the command terminator, no more input. */
537 break;
538 }
539
540 nr = len + 1;
541 xioctl(inputFd, TCSETA, (void *) &old_term);
542 reset_term = 0;
543
544
Erik Andersen6273f652000-03-17 01:12:41 +0000545 /* Handle command history log */
546 if (*(parsenextc)) {
Erik Andersen13456d12000-03-16 08:09:57 +0000547
548 struct history *h = his_end;
549
Erik Andersen6273f652000-03-17 01:12:41 +0000550 if (!h) {
551 /* No previous history */
Erik Andersen13456d12000-03-16 08:09:57 +0000552 h = his_front = malloc(sizeof(struct history));
553 h->n = malloc(sizeof(struct history));
554 h->p = NULL;
555 h->s = strdup(parsenextc);
Erik Andersen13456d12000-03-16 08:09:57 +0000556 h->n->p = h;
557 h->n->n = NULL;
558 h->n->s = NULL;
559 his_end = h->n;
560 history_counter++;
Erik Andersen6273f652000-03-17 01:12:41 +0000561 } else {
562 /* Add a new history command */
Erik Andersen13456d12000-03-16 08:09:57 +0000563 h->n = malloc(sizeof(struct history));
Erik Andersen13456d12000-03-16 08:09:57 +0000564 h->n->p = h;
565 h->n->n = NULL;
566 h->n->s = NULL;
567 h->s = strdup(parsenextc);
568 his_end = h->n;
569
Erik Andersen6273f652000-03-17 01:12:41 +0000570 /* After max history, remove the oldest command */
571 if (history_counter >= MAX_HISTORY) {
Erik Andersen13456d12000-03-16 08:09:57 +0000572
573 struct history *p = his_front->n;
574
575 p->p = NULL;
576 free(his_front->s);
577 free(his_front);
578 his_front = p;
579 } else {
580 history_counter++;
581 }
582 }
583 }
584
585 return nr;
586}
587
588extern void cmdedit_init(void)
589{
590 atexit(cmdedit_reset_term);
Erik Andersen6273f652000-03-17 01:12:41 +0000591 signal(SIGINT, prepareToDie);
592 signal(SIGQUIT, prepareToDie);
593 signal(SIGTERM, prepareToDie);
Erik Andersen13456d12000-03-16 08:09:57 +0000594}
595#endif /* BB_FEATURE_SH_COMMAND_EDITING */