blob: 8a7a5fb034b8a578c79456feee3dbcf77af8d011 [file] [log] [blame]
Erik Andersenc7c634b2000-03-19 05:28:55 +00001/* vi: set sw=4 ts=4: */
Erik Andersen13456d12000-03-16 08:09:57 +00002/*
3 * Termios command line History and Editting for NetBSD sh (ash)
4 * Copyright (c) 1999
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>
8 *
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.
13 *
14 * v 0.5 19990328 Initial release
15 *
16 * Future plans: Simple file and path name completion. (like BASH)
17 *
18 */
19
20/*
21 Usage and Known bugs:
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.
27
28 Editting will not display correctly for lines greater then the
29 terminal width. (more then one line.) However, history will.
30 */
31
32#include "internal.h"
33#ifdef BB_FEATURE_SH_COMMAND_EDITING
34
35#include <stdio.h>
36#include <errno.h>
37#include <unistd.h>
38#include <stdlib.h>
39#include <string.h>
40#include <termio.h>
41#include <ctype.h>
42#include <signal.h>
43
Erik Andersen13456d12000-03-16 08:09:57 +000044
Erik Andersenc7c634b2000-03-19 05:28:55 +000045#define MAX_HISTORY 15 /* Maximum length of the linked list for the command line history */
Erik Andersen13456d12000-03-16 08:09:57 +000046
47#define ESC 27
48#define DEL 127
Erik Andersen6273f652000-03-17 01:12:41 +000049#define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0)
50#define whitespace(c) (((c) == ' ') || ((c) == '\t'))
Erik Andersen13456d12000-03-16 08:09:57 +000051
52static struct history *his_front = NULL; /* First element in command line list */
53static struct history *his_end = NULL; /* Last element in command line list */
54static struct termio old_term, new_term; /* Current termio and the previous termio before starting ash */
55
56static int history_counter = 0; /* Number of commands in history list */
Erik Andersenc7c634b2000-03-19 05:28:55 +000057static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */
58char *parsenextc; /* copy of parsefile->nextc */
Erik Andersen13456d12000-03-16 08:09:57 +000059
60struct history {
Erik Andersenc7c634b2000-03-19 05:28:55 +000061 char *s;
62 struct history *p;
63 struct history *n;
Erik Andersen13456d12000-03-16 08:09:57 +000064};
65
66
67/* Version of write which resumes after a signal is caught. */
68int xwrite(int fd, char *buf, int nbytes)
69{
Erik Andersenc7c634b2000-03-19 05:28:55 +000070 int ntry;
71 int i;
72 int n;
Erik Andersen13456d12000-03-16 08:09:57 +000073
Erik Andersenc7c634b2000-03-19 05:28:55 +000074 n = nbytes;
75 ntry = 0;
76 for (;;) {
77 i = write(fd, buf, n);
78 if (i > 0) {
79 if ((n -= i) <= 0)
80 return nbytes;
81 buf += i;
82 ntry = 0;
83 } else if (i == 0) {
84 if (++ntry > 10)
85 return nbytes - n;
86 } else if (errno != EINTR) {
87 return -1;
88 }
Erik Andersen13456d12000-03-16 08:09:57 +000089 }
Erik Andersen13456d12000-03-16 08:09:57 +000090}
91
92
93/* Version of ioctl that retries after a signal is caught. */
94int xioctl(int fd, unsigned long request, char *arg)
95{
Erik Andersenc7c634b2000-03-19 05:28:55 +000096 int i;
97
98 while ((i = ioctl(fd, request, arg)) == -1 && errno == EINTR);
99 return i;
Erik Andersen13456d12000-03-16 08:09:57 +0000100}
101
102
103void cmdedit_reset_term(void)
104{
Erik Andersenc7c634b2000-03-19 05:28:55 +0000105 if (reset_term)
106 xioctl(fileno(stdin), TCSETA, (void *) &old_term);
Erik Andersen13456d12000-03-16 08:09:57 +0000107}
108
Erik Andersen6273f652000-03-17 01:12:41 +0000109void prepareToDie(int sig)
Erik Andersen13456d12000-03-16 08:09:57 +0000110{
Erik Andersenc7c634b2000-03-19 05:28:55 +0000111 cmdedit_reset_term();
112 fprintf(stdout, "\n");
113 exit(TRUE);
Erik Andersen13456d12000-03-16 08:09:57 +0000114}
115
116void input_home(int outputFd, int *cursor)
Erik Andersenc7c634b2000-03-19 05:28:55 +0000117{ /* Command line input routines */
118 while (*cursor > 0) {
119 xwrite(outputFd, "\b", 1);
120 --*cursor;
121 }
Erik Andersen13456d12000-03-16 08:09:57 +0000122}
123
124
125void input_delete(int outputFd, int cursor)
126{
Erik Andersenc7c634b2000-03-19 05:28:55 +0000127 int j = 0;
Erik Andersen13456d12000-03-16 08:09:57 +0000128
Erik Andersenc7c634b2000-03-19 05:28:55 +0000129 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 }
Erik Andersen13456d12000-03-16 08:09:57 +0000137
Erik Andersenc7c634b2000-03-19 05:28:55 +0000138 xwrite(outputFd, " \b", 2);
Erik Andersen13456d12000-03-16 08:09:57 +0000139
Erik Andersenc7c634b2000-03-19 05:28:55 +0000140 while (j-- > cursor)
141 xwrite(outputFd, "\b", 1);
Erik Andersen13456d12000-03-16 08:09:57 +0000142}
143
144
145void input_end(int outputFd, int *cursor, int len)
146{
Erik Andersenc7c634b2000-03-19 05:28:55 +0000147 while (*cursor < len) {
148 xwrite(outputFd, "\033[C", 3);
149 ++*cursor;
150 }
Erik Andersen13456d12000-03-16 08:09:57 +0000151}
152
153
154void input_backspace(int outputFd, int *cursor, int *len)
155{
Erik Andersenc7c634b2000-03-19 05:28:55 +0000156 int j = 0;
Erik Andersen13456d12000-03-16 08:09:57 +0000157
Erik Andersenc7c634b2000-03-19 05:28:55 +0000158 if (*cursor > 0) {
159 xwrite(outputFd, "\b \b", 3);
160 --*cursor;
161 memmove(parsenextc + *cursor, parsenextc + *cursor + 1,
162 BUFSIZ - *cursor + 1);
Erik Andersen13456d12000-03-16 08:09:57 +0000163
Erik Andersenc7c634b2000-03-19 05:28:55 +0000164 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;
Erik Andersen13456d12000-03-16 08:09:57 +0000177 }
Erik Andersen13456d12000-03-16 08:09:57 +0000178}
179
Erik Andersenc7c634b2000-03-19 05:28:55 +0000180char** username_completion_matches(char* command, int *num_matches)
Erik Andersen6273f652000-03-17 01:12:41 +0000181{
Erik Andersenc7c634b2000-03-19 05:28:55 +0000182 char **matches = (char **) NULL;
183 *num_matches=0;
184 fprintf(stderr, "\nin username_completion_matches\n");
185 return (matches);
Erik Andersen6273f652000-03-17 01:12:41 +0000186}
Erik Andersenc7c634b2000-03-19 05:28:55 +0000187char** find_path_executable_n_cwd_matches(char* command, int *num_matches)
Erik Andersen6273f652000-03-17 01:12:41 +0000188{
Erik Andersenc7c634b2000-03-19 05:28:55 +0000189 char **matches = (char **) NULL;
190 matches = malloc(sizeof(char*)*100);
191
192 matches[0] = malloc(sizeof(char)*50);
193 matches[1] = malloc(sizeof(char)*50);
194
195 sprintf(matches[0], "Hello");
196 sprintf(matches[1], "Howdy");
197 *num_matches=2;
198
199// fprintf(stderr, "\nin find_path_executable_n_cwd_matches\n");
200 return (matches);
Erik Andersen6273f652000-03-17 01:12:41 +0000201}
202
203/*
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
207 * a mini readline).
208 *
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
215 *
216 * Furthermore, the "vi" command editing keys are not implemented.
217 *
218 * TODO: implement TAB command completion. :)
219 *
220 */
Erik Andersenc7c634b2000-03-19 05:28:55 +0000221extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd,
222 char command[BUFSIZ])
Erik Andersen13456d12000-03-16 08:09:57 +0000223{
224
Erik Andersenc7c634b2000-03-19 05:28:55 +0000225 int nr = 0;
226 int len = 0;
227 int j = 0;
228 int cursor = 0;
229 int break_out = 0;
230 int ret = 0;
231 int lastWasTab = FALSE;
232 char c = 0;
233 struct history *hp = his_end;
Erik Andersen13456d12000-03-16 08:09:57 +0000234
Erik Andersenc7c634b2000-03-19 05:28:55 +0000235 memset(command, 0, sizeof(command));
236 parsenextc = command;
237 if (!reset_term) {
238 xioctl(inputFd, TCGETA, (void *) &old_term);
239 memcpy(&new_term, &old_term, sizeof(struct termio));
Erik Andersen13456d12000-03-16 08:09:57 +0000240
Erik Andersenc7c634b2000-03-19 05:28:55 +0000241 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);
246 reset_term = 1;
247 } else {
248 xioctl(inputFd, TCSETA, (void *) &new_term);
249 }
Erik Andersen13456d12000-03-16 08:09:57 +0000250
Erik Andersenc7c634b2000-03-19 05:28:55 +0000251 memset(parsenextc, 0, BUFSIZ);
Erik Andersen13456d12000-03-16 08:09:57 +0000252
Erik Andersenc7c634b2000-03-19 05:28:55 +0000253 while (1) {
Erik Andersen6273f652000-03-17 01:12:41 +0000254
Erik Andersen13456d12000-03-16 08:09:57 +0000255 if ((ret = read(inputFd, &c, 1)) < 1)
Erik Andersenc7c634b2000-03-19 05:28:55 +0000256 return ret;
257
Erik Andersen13456d12000-03-16 08:09:57 +0000258 switch (c) {
Erik Andersenc7c634b2000-03-19 05:28:55 +0000259 case 1:
260 /* Control-a -- Beginning of line */
261 input_home(outputFd, &cursor);
262 case 5:
263 /* Control-e -- End of line */
264 input_end(outputFd, &cursor, len);
265 break;
266 case 2:
267 /* Control-b -- Move back one character */
268 if (cursor > 0) {
269 xwrite(outputFd, "\033[D", 3);
270 cursor--;
271 }
272 break;
273 case 6:
274 /* Control-f -- Move forward one character */
275 if (cursor < len) {
276 xwrite(outputFd, "\033[C", 3);
277 cursor++;
278 }
279 break;
280 case 4:
281 /* Control-d -- Delete one character */
282 if (cursor != len) {
283 input_delete(outputFd, cursor);
284 len--;
285 } else if (len == 0) {
286 prepareToDie(0);
287 exit(0);
288 }
289 break;
290 case 14:
291 /* Control-n -- Get next command */
292 if (hp && hp->n && hp->n->s) {
293 free(hp->s);
294 hp->s = strdup(parsenextc);
295 hp = hp->n;
296 goto hop;
297 }
298 break;
299 case 16:
300 /* Control-p -- Get previous command */
301 if (hp && hp->p) {
302 free(hp->s);
303 hp->s = strdup(parsenextc);
304 hp = hp->p;
305 goto hop;
306 }
307 break;
308 case '\t':
309 {
310 /* Do TAB completion */
311 static int num_matches=0;
312 static char **matches = (char **) NULL;
313 int pos = cursor;
314
Erik Andersen13456d12000-03-16 08:09:57 +0000315
Erik Andersenc7c634b2000-03-19 05:28:55 +0000316 if (lastWasTab == FALSE) {
317 char *tmp, *tmp1, *matchBuf;
Erik Andersen13456d12000-03-16 08:09:57 +0000318
Erik Andersenc7c634b2000-03-19 05:28:55 +0000319 /* 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.
323 */
324
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);
329 tmp=matchBuf;
Erik Andersen13456d12000-03-16 08:09:57 +0000330
Erik Andersenc7c634b2000-03-19 05:28:55 +0000331 /* skip past any command seperator tokens */
332 while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
333 tmp=++tmp1;
334 /* skip any leading white space */
335 while (*tmp && isspace(*tmp))
336 ++tmp;
337 }
Erik Andersen13456d12000-03-16 08:09:57 +0000338
Erik Andersenc7c634b2000-03-19 05:28:55 +0000339 /* skip any leading white space */
340 while (*tmp && isspace(*tmp))
341 ++tmp;
Erik Andersen13456d12000-03-16 08:09:57 +0000342
Erik Andersenc7c634b2000-03-19 05:28:55 +0000343 /* Free up any memory already allocated */
344 if (matches) {
345 free(matches);
346 matches = (char **) NULL;
347 }
348
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);
353
354 /* Try to match any executable in our patch, and everything
355 * in the current working directory that matches.
356 */
357 if (!matches)
358 matches = find_path_executable_n_cwd_matches(tmp, &num_matches);
359 } else {
360 if ( matches && num_matches>0 ) {
361 int i, col;
362
363 fprintf(stderr, "\nTabbing...\n");
364
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);
371 col = 0;
372 }
373 }
374 xwrite(outputFd, "\n", 1);
375
376 len+=strlen(prompt);
377 fprintf(stderr, "len=%d\n", len);
378
379 /* Move to the beginning of the line */
380 input_home(outputFd, &len);
381
382 /* erase everything */
383 for (j = 0; j < len; j++)
384 xwrite(outputFd, " ", 1);
385
386 /* return to begining of line */
387 input_home(outputFd, &cursor);
388
389 /* Rewrite the prompt) */
390 xwrite(outputFd, prompt, strlen(prompt));
391
392 /* Rewrite the command */
393 len = strlen(parsenextc);
394 xwrite(outputFd, parsenextc, len);
395
396 /* Move back to where the cursor used to be */
397 for (cursor=pos; cursor > 0; cursor--)
398 xwrite(outputFd, "\b", 1);
399 cursor = pos;
400
401 //fprintf(stderr, "\nprompt='%s'\n", prompt);
402 }
403 }
404 break;
405 }
406 case '\b':
407 case DEL:
408 /* Backspace */
409 input_backspace(outputFd, &cursor, &len);
410 break;
411 case '\n':
412 /* Enter */
413 *(parsenextc + len++ + 1) = c;
414 xwrite(outputFd, &c, 1);
415 break_out = 1;
416 break;
417 case ESC:{
418 /* escape sequence follows */
419 if ((ret = read(inputFd, &c, 1)) < 1)
420 return ret;
421
422 if (c == '[') { /* 91 */
423 if ((ret = read(inputFd, &c, 1)) < 1)
424 return ret;
425
426 switch (c) {
427 case 'A':
428 /* Up Arrow -- Get previous command */
429 if (hp && hp->p) {
430 free(hp->s);
431 hp->s = strdup(parsenextc);
432 hp = hp->p;
433 goto hop;
434 }
435 break;
436 case 'B':
437 /* Down Arrow -- Get next command */
438 if (hp && hp->n && hp->n->s) {
439 free(hp->s);
440 hp->s = strdup(parsenextc);
441 hp = hp->n;
442 goto hop;
443 }
444 break;
445
446 /* This is where we rewrite the line
447 * using the selected history item */
448 hop:
449 len = strlen(parsenextc);
450
451 /* return to begining of line */
452 for (; cursor > 0; cursor--)
453 xwrite(outputFd, "\b", 1);
454 xwrite(outputFd, parsenextc, len);
455
456 /* erase old command */
457 for (j = 0; j < len; j++)
458 xwrite(outputFd, " ", 1);
459
460 /* return to begining of line */
461 for (j = len; j > 0; j--)
462 xwrite(outputFd, "\b", 1);
463
464 memset(parsenextc, 0, BUFSIZ);
465 /* write new command */
466 strcpy(parsenextc, hp->s);
467 len = strlen(hp->s);
468 xwrite(outputFd, parsenextc, len);
469 cursor = len;
470 break;
471 case 'C':
472 /* Right Arrow -- Move forward one character */
473 if (cursor < len) {
474 xwrite(outputFd, "\033[C", 3);
475 cursor++;
476 }
477 break;
478 case 'D':
479 /* Left Arrow -- Move back one character */
480 if (cursor > 0) {
481 xwrite(outputFd, "\033[D", 3);
482 cursor--;
483 }
484 break;
485 case '3':
486 /* Delete */
487 if (cursor != len) {
488 input_delete(outputFd, cursor);
489 len--;
490 }
491 break;
492 case '1':
493 /* Home (Ctrl-A) */
494 input_home(outputFd, &cursor);
495 break;
496 case '4':
497 /* End (Ctrl-E) */
498 input_end(outputFd, &cursor, len);
499 break;
500 }
501 if (c == '1' || c == '3' || c == '4')
502 if ((ret = read(inputFd, &c, 1)) < 1)
503 return ret; /* read 126 (~) */
504 }
505 if (c == 'O') {
506 /* 79 */
507 if ((ret = read(inputFd, &c, 1)) < 1)
508 return ret;
509 switch (c) {
510 case 'H':
511 /* Home (xterm) */
512 input_home(outputFd, &cursor);
513 break;
514 case 'F':
515 /* End (xterm) */
516 input_end(outputFd, &cursor, len);
517 break;
518 }
519 }
520 c = 0;
521 break;
522 }
523
524 default: /* If it's regular input, do the normal thing */
525
526 if (!isprint(c)) /* Skip non-printable characters */
527 break;
528
529 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
530 break;
531
532 len++;
533
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,
538 len - cursor - 1);
539
540 *(parsenextc + cursor) = c;
541
542 for (j = cursor; j < len; j++)
543 xwrite(outputFd, parsenextc + j, 1);
544 for (; j > cursor; j--)
545 xwrite(outputFd, "\033[D", 3);
546 }
547
Erik Andersen13456d12000-03-16 08:09:57 +0000548 cursor++;
Erik Andersenc7c634b2000-03-19 05:28:55 +0000549 xwrite(outputFd, &c, 1);
550 break;
Erik Andersen13456d12000-03-16 08:09:57 +0000551 }
Erik Andersenc7c634b2000-03-19 05:28:55 +0000552 if (c == '\t')
553 lastWasTab = TRUE;
554 else
555 lastWasTab = FALSE;
556
557 if (break_out) /* Enter is the command terminator, no more input. */
558 break;
559 }
560
561 nr = len + 1;
562 xioctl(inputFd, TCSETA, (void *) &old_term);
563 reset_term = 0;
564
565
566 /* Handle command history log */
567 if (*(parsenextc)) {
568
569 struct history *h = his_end;
570
571 if (!h) {
572 /* No previous history */
573 h = his_front = malloc(sizeof(struct history));
574 h->n = malloc(sizeof(struct history));
575
576 h->p = NULL;
577 h->s = strdup(parsenextc);
578 h->n->p = h;
579 h->n->n = NULL;
580 h->n->s = NULL;
581 his_end = h->n;
582 history_counter++;
583 } else {
584 /* Add a new history command */
585 h->n = malloc(sizeof(struct history));
586
587 h->n->p = h;
588 h->n->n = NULL;
589 h->n->s = NULL;
590 h->s = strdup(parsenextc);
591 his_end = h->n;
592
593 /* After max history, remove the oldest command */
594 if (history_counter >= MAX_HISTORY) {
595
596 struct history *p = his_front->n;
597
598 p->p = NULL;
599 free(his_front->s);
600 free(his_front);
601 his_front = p;
602 } else {
603 history_counter++;
604 }
Erik Andersen13456d12000-03-16 08:09:57 +0000605 }
Erik Andersen6273f652000-03-17 01:12:41 +0000606 }
Erik Andersen13456d12000-03-16 08:09:57 +0000607
Erik Andersenc7c634b2000-03-19 05:28:55 +0000608 return nr;
Erik Andersen13456d12000-03-16 08:09:57 +0000609}
610
611extern void cmdedit_init(void)
612{
Erik Andersenc7c634b2000-03-19 05:28:55 +0000613 atexit(cmdedit_reset_term);
614 signal(SIGINT, prepareToDie);
615 signal(SIGQUIT, prepareToDie);
616 signal(SIGTERM, prepareToDie);
Erik Andersen13456d12000-03-16 08:09:57 +0000617}
Erik Andersenc7c634b2000-03-19 05:28:55 +0000618#endif /* BB_FEATURE_SH_COMMAND_EDITING */