blob: e4c88c265ac744165d475cb5f3cced4cf2973581 [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/*
Erik Andersen61677fe2000-04-13 01:18:56 +00003 * Termios command line History and Editting, originally
4 * intended for NetBSD sh (ash)
Erik Andersen13456d12000-03-16 08:09:57 +00005 * Copyright (c) 1999
6 * Main code: Adam Rogoyski <rogoyski@cs.utexas.edu>
7 * Etc: Dave Cinege <dcinege@psychosis.com>
Erik Andersen61677fe2000-04-13 01:18:56 +00008 * Majorly adjusted/re-written for busybox:
9 * Erik Andersen <andersee@debian.org>
Erik Andersen13456d12000-03-16 08:09:57 +000010 *
11 * You may use this code as you wish, so long as the original author(s)
12 * are attributed in any redistributions of the source code.
13 * This code is 'as is' with no warranty.
14 * This code may safely be consumed by a BSD or GPL license.
15 *
16 * v 0.5 19990328 Initial release
17 *
18 * Future plans: Simple file and path name completion. (like BASH)
19 *
20 */
21
22/*
23 Usage and Known bugs:
24 Terminal key codes are not extensive, and more will probably
25 need to be added. This version was created on Debian GNU/Linux 2.x.
26 Delete, Backspace, Home, End, and the arrow keys were tested
27 to work in an Xterm and console. Ctrl-A also works as Home.
28 Ctrl-E also works as End. The binary size increase is <3K.
29
30 Editting will not display correctly for lines greater then the
31 terminal width. (more then one line.) However, history will.
32 */
33
34#include "internal.h"
35#ifdef BB_FEATURE_SH_COMMAND_EDITING
36
37#include <stdio.h>
38#include <errno.h>
39#include <unistd.h>
40#include <stdlib.h>
41#include <string.h>
Erik Andersen1d1d9502000-04-21 01:26:49 +000042#include <sys/ioctl.h>
Erik Andersen13456d12000-03-16 08:09:57 +000043#include <ctype.h>
44#include <signal.h>
45
Erik Andersen13456d12000-03-16 08:09:57 +000046
Erik Andersenc7c634b2000-03-19 05:28:55 +000047#define MAX_HISTORY 15 /* Maximum length of the linked list for the command line history */
Erik Andersen13456d12000-03-16 08:09:57 +000048
49#define ESC 27
50#define DEL 127
Erik Andersen6273f652000-03-17 01:12:41 +000051#define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0)
52#define whitespace(c) (((c) == ' ') || ((c) == '\t'))
Erik Andersen13456d12000-03-16 08:09:57 +000053
54static struct history *his_front = NULL; /* First element in command line list */
55static struct history *his_end = NULL; /* Last element in command line list */
Erik Andersen1d1d9502000-04-21 01:26:49 +000056
57/* ED: sparc termios is broken: revert back to old termio handling. */
58#ifdef BB_FEATURE_USE_TERMIOS
59
60#if #cpu(sparc)
61# include <termio.h>
62# define termios termio
63# define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
64# define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
65#else
66# include <termios.h>
67# define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
68# define getTermSettings(fd,argp) tcgetattr(fd, argp);
69#endif
70
71/* Current termio and the previous termio before starting sh */
72struct termios initial_settings, new_settings;
73#endif
74
75
Erik Andersen13456d12000-03-16 08:09:57 +000076
Erik Andersenf0657d32000-04-12 17:49:52 +000077static int cmdedit_termw = 80; /* actual terminal width */
78static int cmdedit_scroll = 27; /* width of EOL scrolling region */
Erik Andersen13456d12000-03-16 08:09:57 +000079static int history_counter = 0; /* Number of commands in history list */
Erik Andersenc7c634b2000-03-19 05:28:55 +000080static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */
Erik Andersen13456d12000-03-16 08:09:57 +000081
82struct history {
Erik Andersenc7c634b2000-03-19 05:28:55 +000083 char *s;
84 struct history *p;
85 struct history *n;
Erik Andersen13456d12000-03-16 08:09:57 +000086};
87
Erik Andersenf0657d32000-04-12 17:49:52 +000088#define xwrite write
Erik Andersen13456d12000-03-16 08:09:57 +000089
Erik Andersenf0657d32000-04-12 17:49:52 +000090void
91cmdedit_setwidth(int w)
Erik Andersen13456d12000-03-16 08:09:57 +000092{
Erik Andersen61677fe2000-04-13 01:18:56 +000093 if (w > 20) {
Erik Andersenf0657d32000-04-12 17:49:52 +000094 cmdedit_termw = w;
95 cmdedit_scroll = w / 3;
Erik Andersen61677fe2000-04-13 01:18:56 +000096 } else {
Erik Andersenf0657d32000-04-12 17:49:52 +000097 errorMsg("\n*** Error: minimum screen width is 21\n");
Erik Andersen61677fe2000-04-13 01:18:56 +000098 }
Erik Andersen13456d12000-03-16 08:09:57 +000099}
100
Erik Andersen61677fe2000-04-13 01:18:56 +0000101
Erik Andersen13456d12000-03-16 08:09:57 +0000102void cmdedit_reset_term(void)
103{
Erik Andersenc7c634b2000-03-19 05:28:55 +0000104 if (reset_term)
Erik Andersena6c75222000-04-18 00:00:52 +0000105 /* sparc and other have broken termios support: use old termio handling. */
Erik Andersen1d1d9502000-04-21 01:26:49 +0000106 setTermSettings(fileno(stdin), (void*) &initial_settings);
Erik Andersen13456d12000-03-16 08:09:57 +0000107}
108
Erik Andersenf0657d32000-04-12 17:49:52 +0000109void clean_up_and_die(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");
Erik Andersen1d1d9502000-04-21 01:26:49 +0000113 if (sig!=SIGINT)
114 exit(TRUE);
Erik Andersen13456d12000-03-16 08:09:57 +0000115}
116
Erik Andersenf0657d32000-04-12 17:49:52 +0000117/* Go to HOME position */
Erik Andersen13456d12000-03-16 08:09:57 +0000118void input_home(int outputFd, int *cursor)
Erik Andersenf0657d32000-04-12 17:49:52 +0000119{
Erik Andersenc7c634b2000-03-19 05:28:55 +0000120 while (*cursor > 0) {
121 xwrite(outputFd, "\b", 1);
122 --*cursor;
123 }
Erik Andersen13456d12000-03-16 08:09:57 +0000124}
125
Erik Andersenf0657d32000-04-12 17:49:52 +0000126/* Go to END position */
Erik Andersen13456d12000-03-16 08:09:57 +0000127void input_end(int outputFd, int *cursor, int len)
128{
Erik Andersenc7c634b2000-03-19 05:28:55 +0000129 while (*cursor < len) {
130 xwrite(outputFd, "\033[C", 3);
131 ++*cursor;
132 }
Erik Andersen13456d12000-03-16 08:09:57 +0000133}
134
Erik Andersenf0657d32000-04-12 17:49:52 +0000135/* Delete the char in back of the cursor */
136void input_backspace(char* command, int outputFd, int *cursor, int *len)
Erik Andersen13456d12000-03-16 08:09:57 +0000137{
Erik Andersenc7c634b2000-03-19 05:28:55 +0000138 int j = 0;
Erik Andersen13456d12000-03-16 08:09:57 +0000139
Erik Andersenc7c634b2000-03-19 05:28:55 +0000140 if (*cursor > 0) {
141 xwrite(outputFd, "\b \b", 3);
142 --*cursor;
Erik Andersenf0657d32000-04-12 17:49:52 +0000143 memmove(command + *cursor, command + *cursor + 1,
Erik Andersenc7c634b2000-03-19 05:28:55 +0000144 BUFSIZ - *cursor + 1);
Erik Andersen13456d12000-03-16 08:09:57 +0000145
Erik Andersenc7c634b2000-03-19 05:28:55 +0000146 for (j = *cursor; j < (BUFSIZ - 1); j++) {
Erik Andersenf0657d32000-04-12 17:49:52 +0000147 if (!*(command + j))
Erik Andersenc7c634b2000-03-19 05:28:55 +0000148 break;
149 else
Erik Andersenf0657d32000-04-12 17:49:52 +0000150 xwrite(outputFd, (command + j), 1);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000151 }
152
153 xwrite(outputFd, " \b", 2);
154
155 while (j-- > *cursor)
156 xwrite(outputFd, "\b", 1);
157
158 --*len;
Erik Andersen13456d12000-03-16 08:09:57 +0000159 }
Erik Andersen13456d12000-03-16 08:09:57 +0000160}
161
Erik Andersenf0657d32000-04-12 17:49:52 +0000162/* Delete the char in front of the cursor */
163void input_delete(char* command, int outputFd, int cursor, int *len)
164{
165 int j = 0;
Erik Andersena2685732000-04-09 18:27:46 +0000166
Erik Andersenf0657d32000-04-12 17:49:52 +0000167 if (cursor == *len)
168 return;
169
170 memmove(command + cursor, command + cursor + 1,
171 BUFSIZ - cursor - 1);
172 for (j = cursor; j < (BUFSIZ - 1); j++) {
173 if (!*(command + j))
174 break;
175 else
176 xwrite(outputFd, (command + j), 1);
177 }
178
179 xwrite(outputFd, " \b", 2);
180
181 while (j-- > cursor)
182 xwrite(outputFd, "\b", 1);
183 --*len;
184}
185
186/* Move forward one charactor */
187void input_forward(int outputFd, int *cursor, int len)
188{
189 if (*cursor < len) {
190 xwrite(outputFd, "\033[C", 3);
191 ++*cursor;
192 }
193}
194
195/* Move back one charactor */
196void input_backward(int outputFd, int *cursor)
197{
198 if (*cursor > 0) {
199 xwrite(outputFd, "\033[D", 3);
200 --*cursor;
201 }
202}
203
204
205
206#ifdef BB_FEATURE_SH_TAB_COMPLETION
207char** username_tab_completion(char* command, int *num_matches)
Erik Andersen6273f652000-03-17 01:12:41 +0000208{
Erik Andersenc7c634b2000-03-19 05:28:55 +0000209 char **matches = (char **) NULL;
210 *num_matches=0;
Erik Andersenf0657d32000-04-12 17:49:52 +0000211 fprintf(stderr, "\nin username_tab_completion\n");
Erik Andersenc7c634b2000-03-19 05:28:55 +0000212 return (matches);
Erik Andersen6273f652000-03-17 01:12:41 +0000213}
Erik Andersen1dbe3402000-03-19 10:46:06 +0000214
215#include <dirent.h>
Erik Andersenf0657d32000-04-12 17:49:52 +0000216char** exe_n_cwd_tab_completion(char* command, int *num_matches)
Erik Andersen6273f652000-03-17 01:12:41 +0000217{
Erik Andersen1dbe3402000-03-19 10:46:06 +0000218 char *dirName;
Erik Andersenc7c634b2000-03-19 05:28:55 +0000219 char **matches = (char **) NULL;
Erik Andersen1dbe3402000-03-19 10:46:06 +0000220 DIR *dir;
221 struct dirent *next;
222
223 matches = malloc( sizeof(char*)*50);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000224
Erik Andersen1dbe3402000-03-19 10:46:06 +0000225 /* Stick a wildcard onto the command, for later use */
226 strcat( command, "*");
Erik Andersenc7c634b2000-03-19 05:28:55 +0000227
Erik Andersen1dbe3402000-03-19 10:46:06 +0000228 /* Now wall the current directory */
229 dirName = get_current_dir_name();
230 dir = opendir(dirName);
231 if (!dir) {
232 /* Don't print an error, just shut up and return */
233 *num_matches=0;
234 return (matches);
235 }
236 while ((next = readdir(dir)) != NULL) {
Erik Andersenc7c634b2000-03-19 05:28:55 +0000237
Erik Andersen1dbe3402000-03-19 10:46:06 +0000238 /* Some quick sanity checks */
239 if ((strcmp(next->d_name, "..") == 0)
240 || (strcmp(next->d_name, ".") == 0)) {
241 continue;
242 }
243 /* See if this matches */
244 if (check_wildcard_match(next->d_name, command) == TRUE) {
245 /* Cool, found a match. Add it to the list */
246 matches[*num_matches] = malloc(strlen(next->d_name)+1);
247 strcpy( matches[*num_matches], next->d_name);
248 ++*num_matches;
249 //matches = realloc( matches, sizeof(char*)*(*num_matches));
250 }
251 }
252
Erik Andersenc7c634b2000-03-19 05:28:55 +0000253 return (matches);
Erik Andersen6273f652000-03-17 01:12:41 +0000254}
Erik Andersenf0657d32000-04-12 17:49:52 +0000255
Erik Andersen1d1d9502000-04-21 01:26:49 +0000256void input_tab(char* command, char* prompt, int outputFd, int *cursor, int *len)
Erik Andersenf0657d32000-04-12 17:49:52 +0000257{
258 /* Do TAB completion */
259 static int num_matches=0;
260 static char **matches = (char **) NULL;
261 int pos = cursor;
262
263
264 if (lastWasTab == FALSE) {
265 char *tmp, *tmp1, *matchBuf;
266
267 /* For now, we will not bother with trying to distinguish
268 * whether the cursor is in/at a command extression -- we
269 * will always try all possable matches. If you don't like
270 * that then feel free to fix it.
271 */
272
273 /* Make a local copy of the string -- up
274 * to the position of the cursor */
275 matchBuf = (char *) calloc(BUFSIZ, sizeof(char));
276 strncpy(matchBuf, command, cursor);
277 tmp=matchBuf;
278
279 /* skip past any command seperator tokens */
280 while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) {
281 tmp=++tmp1;
282 /* skip any leading white space */
283 while (*tmp && isspace(*tmp))
284 ++tmp;
285 }
286
287 /* skip any leading white space */
288 while (*tmp && isspace(*tmp))
289 ++tmp;
290
291 /* Free up any memory already allocated */
292 if (matches) {
293 free(matches);
294 matches = (char **) NULL;
295 }
296
297 /* If the word starts with `~' and there is no slash in the word,
298 * then try completing this word as a username. */
299
300 /* FIXME -- this check is broken! */
301 if (*tmp == '~' && !strchr(tmp, '/'))
302 matches = username_tab_completion(tmp, &num_matches);
303
304 /* Try to match any executable in our path and everything
305 * in the current working directory that matches. */
306 if (!matches)
307 matches = exe_n_cwd_tab_completion(tmp, &num_matches);
308
309 /* Don't leak memory */
310 free( matchBuf);
311
312 /* Did we find exactly one match? */
313 if (matches && num_matches==1) {
314 /* write out the matched command */
315 strncpy(command+pos, matches[0]+pos, strlen(matches[0])-pos);
316 len=strlen(command);
317 cursor=len;
318 xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos);
319 break;
320 }
321 } else {
322 /* Ok -- the last char was a TAB. Since they
323 * just hit TAB again, print a list of all the
324 * available choices... */
325 if ( matches && num_matches>0 ) {
326 int i, col;
327
328 /* Go to the next line */
329 xwrite(outputFd, "\n", 1);
330 /* Print the list of matches */
331 for (i=0,col=0; i<num_matches; i++) {
332 char foo[17];
333 sprintf(foo, "%-14s ", matches[i]);
334 col += xwrite(outputFd, foo, strlen(foo));
335 if (col > 60 && matches[i+1] != NULL) {
336 xwrite(outputFd, "\n", 1);
337 col = 0;
338 }
339 }
340 /* Go to the next line */
341 xwrite(outputFd, "\n", 1);
342 /* Rewrite the prompt */
343 xwrite(outputFd, prompt, strlen(prompt));
344 /* Rewrite the command */
345 xwrite(outputFd, command, len);
346 /* Put the cursor back to where it used to be */
347 for (cursor=len; cursor > pos; cursor--)
348 xwrite(outputFd, "\b", 1);
349 }
350 }
351}
352#endif
353
354void get_previous_history(struct history **hp, char* command)
355{
356 free((*hp)->s);
357 (*hp)->s = strdup(command);
358 *hp = (*hp)->p;
359}
360
361void get_next_history(struct history **hp, char* command)
362{
363 free((*hp)->s);
364 (*hp)->s = strdup(command);
365 *hp = (*hp)->n;
Erik Andersenf0657d32000-04-12 17:49:52 +0000366}
367
Erik Andersen6273f652000-03-17 01:12:41 +0000368/*
369 * This function is used to grab a character buffer
370 * from the input file descriptor and allows you to
371 * a string with full command editing (sortof like
372 * a mini readline).
373 *
374 * The following standard commands are not implemented:
375 * ESC-b -- Move back one word
376 * ESC-f -- Move forward one word
377 * ESC-d -- Delete back one word
378 * ESC-h -- Delete forward one word
379 * CTL-t -- Transpose two characters
380 *
381 * Furthermore, the "vi" command editing keys are not implemented.
382 *
383 * TODO: implement TAB command completion. :)
Erik Andersen6273f652000-03-17 01:12:41 +0000384 */
Erik Andersenf0657d32000-04-12 17:49:52 +0000385extern void cmdedit_read_input(char* prompt, char command[BUFSIZ])
Erik Andersen13456d12000-03-16 08:09:57 +0000386{
387
Erik Andersenf0657d32000-04-12 17:49:52 +0000388 int inputFd=fileno(stdin);
389 int outputFd=fileno(stdout);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000390 int nr = 0;
391 int len = 0;
392 int j = 0;
393 int cursor = 0;
394 int break_out = 0;
395 int ret = 0;
396 int lastWasTab = FALSE;
397 char c = 0;
398 struct history *hp = his_end;
Erik Andersen13456d12000-03-16 08:09:57 +0000399
Erik Andersenc7c634b2000-03-19 05:28:55 +0000400 memset(command, 0, sizeof(command));
Erik Andersenc7c634b2000-03-19 05:28:55 +0000401 if (!reset_term) {
Erik Andersen1d1d9502000-04-21 01:26:49 +0000402
403 getTermSettings(inputFd, (void*) &initial_settings);
404 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
405 new_settings.c_cc[VMIN] = 1;
406 new_settings.c_cc[VTIME] = 0;
407 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
408 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
409 new_settings.c_lflag &= ~(ECHO|ECHOCTL|ECHONL); /* Turn off echoing */
Erik Andersenc7c634b2000-03-19 05:28:55 +0000410 reset_term = 1;
Erik Andersenc7c634b2000-03-19 05:28:55 +0000411 }
Erik Andersen1d1d9502000-04-21 01:26:49 +0000412 setTermSettings(inputFd, (void*) &new_settings);
Erik Andersen13456d12000-03-16 08:09:57 +0000413
Erik Andersenf0657d32000-04-12 17:49:52 +0000414 memset(command, 0, BUFSIZ);
Erik Andersen13456d12000-03-16 08:09:57 +0000415
Erik Andersenc7c634b2000-03-19 05:28:55 +0000416 while (1) {
Erik Andersen6273f652000-03-17 01:12:41 +0000417
Erik Andersen13456d12000-03-16 08:09:57 +0000418 if ((ret = read(inputFd, &c, 1)) < 1)
Erik Andersenf0657d32000-04-12 17:49:52 +0000419 return;
Erik Andersen1d1d9502000-04-21 01:26:49 +0000420 //fprintf(stderr, "got a '%c' (%d)\n", c, c);
Erik Andersenf3b3d172000-04-09 18:24:05 +0000421
Erik Andersen13456d12000-03-16 08:09:57 +0000422 switch (c) {
Erik Andersenf0657d32000-04-12 17:49:52 +0000423 case '\n':
424 case '\r':
425 /* Enter */
426 *(command + len++ + 1) = c;
427 xwrite(outputFd, &c, 1);
428 break_out = 1;
429 break;
Erik Andersenc7c634b2000-03-19 05:28:55 +0000430 case 1:
431 /* Control-a -- Beginning of line */
432 input_home(outputFd, &cursor);
Erik Andersenf0657d32000-04-12 17:49:52 +0000433 case 2:
434 /* Control-b -- Move back one character */
435 input_backward(outputFd, &cursor);
436 break;
Erik Andersen1d1d9502000-04-21 01:26:49 +0000437 case 3:
438 /* Control-c -- leave the current line,
439 * and start over on the next line */
440
441 /* Go to the next line */
442 xwrite(outputFd, "\n", 1);
443
444 /* Rewrite the prompt */
445 xwrite(outputFd, prompt, strlen(prompt));
446
447 /* Reset the command string */
448 memset(command, 0, sizeof(command));
449 len = cursor = 0;
450
451 break;
Erik Andersenf0657d32000-04-12 17:49:52 +0000452 case 4:
453 /* Control-d -- Delete one character, or exit
454 * if the len=0 and no chars to delete */
455 if (len == 0) {
456 xwrite(outputFd, "exit", 4);
457 clean_up_and_die(0);
458 } else {
459 input_delete(command, outputFd, cursor, &len);
460 }
461 break;
Erik Andersenc7c634b2000-03-19 05:28:55 +0000462 case 5:
463 /* Control-e -- End of line */
464 input_end(outputFd, &cursor, len);
465 break;
Erik Andersenc7c634b2000-03-19 05:28:55 +0000466 case 6:
467 /* Control-f -- Move forward one character */
Erik Andersenf0657d32000-04-12 17:49:52 +0000468 input_forward(outputFd, &cursor, len);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000469 break;
Erik Andersenf0657d32000-04-12 17:49:52 +0000470 case '\b':
471 case DEL:
Erik Andersen1d1d9502000-04-21 01:26:49 +0000472 /* Control-h and DEL */
Erik Andersenf0657d32000-04-12 17:49:52 +0000473 input_backspace(command, outputFd, &cursor, &len);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000474 break;
475 case '\t':
Erik Andersena2685732000-04-09 18:27:46 +0000476#ifdef BB_FEATURE_SH_TAB_COMPLETION
Erik Andersen1d1d9502000-04-21 01:26:49 +0000477 input_tab(command, prompt, outputFd, &cursor, &len);
Erik Andersena2685732000-04-09 18:27:46 +0000478#endif
Erik Andersenc7c634b2000-03-19 05:28:55 +0000479 break;
Erik Andersenf0657d32000-04-12 17:49:52 +0000480 case 14:
481 /* Control-n -- Get next command in history */
482 if (hp && hp->n && hp->n->s) {
483 get_next_history(&hp, command);
484 goto rewrite_line;
485 } else {
486 xwrite(outputFd, "\007", 1);
487 }
488 break;
489 case 16:
490 /* Control-p -- Get previous command from history */
491 if (hp && hp->p) {
492 get_previous_history(&hp, command);
493 goto rewrite_line;
494 } else {
495 xwrite(outputFd, "\007", 1);
496 }
Erik Andersenc7c634b2000-03-19 05:28:55 +0000497 break;
498 case ESC:{
499 /* escape sequence follows */
500 if ((ret = read(inputFd, &c, 1)) < 1)
Erik Andersenf0657d32000-04-12 17:49:52 +0000501 return;
Erik Andersenc7c634b2000-03-19 05:28:55 +0000502
503 if (c == '[') { /* 91 */
504 if ((ret = read(inputFd, &c, 1)) < 1)
Erik Andersenf0657d32000-04-12 17:49:52 +0000505 return;
Erik Andersenc7c634b2000-03-19 05:28:55 +0000506
507 switch (c) {
508 case 'A':
Erik Andersenf0657d32000-04-12 17:49:52 +0000509 /* Up Arrow -- Get previous command from history */
Erik Andersenc7c634b2000-03-19 05:28:55 +0000510 if (hp && hp->p) {
Erik Andersenf0657d32000-04-12 17:49:52 +0000511 get_previous_history(&hp, command);
512 goto rewrite_line;
513 } else {
514 xwrite(outputFd, "\007", 1);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000515 }
516 break;
517 case 'B':
Erik Andersenf0657d32000-04-12 17:49:52 +0000518 /* Down Arrow -- Get next command in history */
Erik Andersenc7c634b2000-03-19 05:28:55 +0000519 if (hp && hp->n && hp->n->s) {
Erik Andersenf0657d32000-04-12 17:49:52 +0000520 get_next_history(&hp, command);
521 goto rewrite_line;
522 } else {
523 xwrite(outputFd, "\007", 1);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000524 }
525 break;
526
Erik Andersenf0657d32000-04-12 17:49:52 +0000527 /* Rewrite the line with the selected history item */
528 rewrite_line:
529 /* erase old command from command line */
530 len = strlen(command)-strlen(hp->s);
531 while (len>0)
532 input_backspace(command, outputFd, &cursor, &len);
533 input_home(outputFd, &cursor);
534
Erik Andersenc7c634b2000-03-19 05:28:55 +0000535 /* write new command */
Erik Andersenf0657d32000-04-12 17:49:52 +0000536 strcpy(command, hp->s);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000537 len = strlen(hp->s);
Erik Andersenf0657d32000-04-12 17:49:52 +0000538 xwrite(outputFd, command, len);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000539 cursor = len;
540 break;
541 case 'C':
542 /* Right Arrow -- Move forward one character */
Erik Andersenf0657d32000-04-12 17:49:52 +0000543 input_forward(outputFd, &cursor, len);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000544 break;
545 case 'D':
546 /* Left Arrow -- Move back one character */
Erik Andersenf0657d32000-04-12 17:49:52 +0000547 input_backward(outputFd, &cursor);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000548 break;
549 case '3':
550 /* Delete */
Erik Andersenf0657d32000-04-12 17:49:52 +0000551 input_delete(command, outputFd, cursor, &len);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000552 break;
553 case '1':
554 /* Home (Ctrl-A) */
555 input_home(outputFd, &cursor);
556 break;
557 case '4':
558 /* End (Ctrl-E) */
559 input_end(outputFd, &cursor, len);
560 break;
Erik Andersenf0657d32000-04-12 17:49:52 +0000561 default:
562 xwrite(outputFd, "\007", 1);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000563 }
564 if (c == '1' || c == '3' || c == '4')
565 if ((ret = read(inputFd, &c, 1)) < 1)
Erik Andersenf0657d32000-04-12 17:49:52 +0000566 return; /* read 126 (~) */
Erik Andersenc7c634b2000-03-19 05:28:55 +0000567 }
568 if (c == 'O') {
569 /* 79 */
570 if ((ret = read(inputFd, &c, 1)) < 1)
Erik Andersenf0657d32000-04-12 17:49:52 +0000571 return;
Erik Andersenc7c634b2000-03-19 05:28:55 +0000572 switch (c) {
573 case 'H':
574 /* Home (xterm) */
575 input_home(outputFd, &cursor);
576 break;
577 case 'F':
578 /* End (xterm) */
579 input_end(outputFd, &cursor, len);
580 break;
Erik Andersenf0657d32000-04-12 17:49:52 +0000581 default:
582 xwrite(outputFd, "\007", 1);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000583 }
584 }
585 c = 0;
586 break;
587 }
588
589 default: /* If it's regular input, do the normal thing */
590
Erik Andersenf0657d32000-04-12 17:49:52 +0000591 if (!isprint(c)) { /* Skip non-printable characters */
Erik Andersenc7c634b2000-03-19 05:28:55 +0000592 break;
Erik Andersenf0657d32000-04-12 17:49:52 +0000593 }
Erik Andersenc7c634b2000-03-19 05:28:55 +0000594
595 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
596 break;
597
598 len++;
599
600 if (cursor == (len - 1)) { /* Append if at the end of the line */
Erik Andersenf0657d32000-04-12 17:49:52 +0000601 *(command + cursor) = c;
Erik Andersenc7c634b2000-03-19 05:28:55 +0000602 } else { /* Insert otherwise */
Erik Andersenf0657d32000-04-12 17:49:52 +0000603 memmove(command + cursor + 1, command + cursor,
Erik Andersenc7c634b2000-03-19 05:28:55 +0000604 len - cursor - 1);
605
Erik Andersenf0657d32000-04-12 17:49:52 +0000606 *(command + cursor) = c;
Erik Andersenc7c634b2000-03-19 05:28:55 +0000607
608 for (j = cursor; j < len; j++)
Erik Andersenf0657d32000-04-12 17:49:52 +0000609 xwrite(outputFd, command + j, 1);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000610 for (; j > cursor; j--)
611 xwrite(outputFd, "\033[D", 3);
612 }
613
Erik Andersen13456d12000-03-16 08:09:57 +0000614 cursor++;
Erik Andersenc7c634b2000-03-19 05:28:55 +0000615 xwrite(outputFd, &c, 1);
616 break;
Erik Andersen13456d12000-03-16 08:09:57 +0000617 }
Erik Andersenc7c634b2000-03-19 05:28:55 +0000618 if (c == '\t')
619 lastWasTab = TRUE;
620 else
621 lastWasTab = FALSE;
622
623 if (break_out) /* Enter is the command terminator, no more input. */
624 break;
625 }
626
627 nr = len + 1;
Erik Andersen1d1d9502000-04-21 01:26:49 +0000628 setTermSettings(inputFd, (void *) &initial_settings);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000629 reset_term = 0;
630
631
632 /* Handle command history log */
Erik Andersenf0657d32000-04-12 17:49:52 +0000633 if (*(command)) {
Erik Andersenc7c634b2000-03-19 05:28:55 +0000634
635 struct history *h = his_end;
636
637 if (!h) {
638 /* No previous history */
639 h = his_front = malloc(sizeof(struct history));
640 h->n = malloc(sizeof(struct history));
641
642 h->p = NULL;
Erik Andersenf0657d32000-04-12 17:49:52 +0000643 h->s = strdup(command);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000644 h->n->p = h;
645 h->n->n = NULL;
646 h->n->s = NULL;
647 his_end = h->n;
648 history_counter++;
649 } else {
650 /* Add a new history command */
651 h->n = malloc(sizeof(struct history));
652
653 h->n->p = h;
654 h->n->n = NULL;
655 h->n->s = NULL;
Erik Andersenf0657d32000-04-12 17:49:52 +0000656 h->s = strdup(command);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000657 his_end = h->n;
658
659 /* After max history, remove the oldest command */
660 if (history_counter >= MAX_HISTORY) {
661
662 struct history *p = his_front->n;
663
664 p->p = NULL;
665 free(his_front->s);
666 free(his_front);
667 his_front = p;
668 } else {
669 history_counter++;
670 }
Erik Andersen13456d12000-03-16 08:09:57 +0000671 }
Erik Andersen6273f652000-03-17 01:12:41 +0000672 }
Erik Andersen13456d12000-03-16 08:09:57 +0000673
Erik Andersenf0657d32000-04-12 17:49:52 +0000674 return;
Erik Andersen13456d12000-03-16 08:09:57 +0000675}
676
677extern void cmdedit_init(void)
678{
Erik Andersenc7c634b2000-03-19 05:28:55 +0000679 atexit(cmdedit_reset_term);
Erik Andersen1d1d9502000-04-21 01:26:49 +0000680 signal(SIGKILL, clean_up_and_die);
Erik Andersenf0657d32000-04-12 17:49:52 +0000681 signal(SIGINT, clean_up_and_die);
682 signal(SIGQUIT, clean_up_and_die);
683 signal(SIGTERM, clean_up_and_die);
Erik Andersen13456d12000-03-16 08:09:57 +0000684}
Erik Andersenc7c634b2000-03-19 05:28:55 +0000685#endif /* BB_FEATURE_SH_COMMAND_EDITING */