blob: d1604f1d1dc41423bcfe03bc1cfb90d135b44867 [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
50
51static struct history *his_front = NULL; /* First element in command line list */
52static struct history *his_end = NULL; /* Last element in command line list */
53static struct termio old_term, new_term; /* Current termio and the previous termio before starting ash */
54
55static int history_counter = 0; /* Number of commands in history list */
56static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */
57char *parsenextc; /* copy of parsefile->nextc */
58
59struct history {
60 char *s;
61 struct history *p;
62 struct history *n;
63};
64
65
66/* Version of write which resumes after a signal is caught. */
67int xwrite(int fd, char *buf, int nbytes)
68{
69 int ntry;
70 int i;
71 int n;
72
73 n = nbytes;
74 ntry = 0;
75 for (;;) {
76 i = write(fd, buf, n);
77 if (i > 0) {
78 if ((n -= i) <= 0)
79 return nbytes;
80 buf += i;
81 ntry = 0;
82 } else if (i == 0) {
83 if (++ntry > 10)
84 return nbytes - n;
85 } else if (errno != EINTR) {
86 return -1;
87 }
88 }
89}
90
91
92/* Version of ioctl that retries after a signal is caught. */
93int xioctl(int fd, unsigned long request, char *arg)
94{
95 int i;
96 while ((i = ioctl(fd, request, arg)) == -1 && errno == EINTR);
97 return i;
98}
99
100
101void cmdedit_reset_term(void)
102{
103 if (reset_term)
104 xioctl(fileno(stdin), TCSETA, (void *) &old_term);
105}
106
107void gotaSignal(int sig)
108{
109 cmdedit_reset_term();
110 fprintf(stdout, "\n");
111 exit(TRUE);
112}
113
114void input_home(int outputFd, int *cursor)
115{ /* Command line input routines */
116 while (*cursor > 0) {
117 xwrite(outputFd, "\b", 1);
118 --*cursor;
119 }
120}
121
122
123void input_delete(int outputFd, int cursor)
124{
125 int j = 0;
126
127 memmove(parsenextc + cursor, parsenextc + cursor + 1,
128 BUFSIZ - cursor - 1);
129 for (j = cursor; j < (BUFSIZ - 1); j++) {
130 if (!*(parsenextc + j))
131 break;
132 else
133 xwrite(outputFd, (parsenextc + j), 1);
134 }
135
136 xwrite(outputFd, " \b", 2);
137
138 while (j-- > cursor)
139 xwrite(outputFd, "\b", 1);
140}
141
142
143void input_end(int outputFd, int *cursor, int len)
144{
145 while (*cursor < len) {
146 xwrite(outputFd, "\033[C", 3);
147 ++*cursor;
148 }
149}
150
151
152void input_backspace(int outputFd, int *cursor, int *len)
153{
154 int j = 0;
155
156 if (*cursor > 0) {
157 xwrite(outputFd, "\b \b", 3);
158 --*cursor;
159 memmove(parsenextc + *cursor, parsenextc + *cursor + 1,
160 BUFSIZ - *cursor + 1);
161
162 for (j = *cursor; j < (BUFSIZ - 1); j++) {
163 if (!*(parsenextc + j))
164 break;
165 else
166 xwrite(outputFd, (parsenextc + j), 1);
167 }
168
169 xwrite(outputFd, " \b", 2);
170
171 while (j-- > *cursor)
172 xwrite(outputFd, "\b", 1);
173
174 --*len;
175 }
176}
177
178extern int cmdedit_read_input(int inputFd, int outputFd,
179 char command[BUFSIZ])
180{
181
182 int nr = 0;
183 int len = 0;
184 int j = 0;
185 int cursor = 0;
186 int break_out = 0;
187 int ret = 0;
188 char c = 0;
189 struct history *hp = his_end;
190
191 memset(command, 0, sizeof(command));
192 parsenextc = command;
193 if (!reset_term) {
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);
201 reset_term = 1;
202 } else {
203 xioctl(inputFd, TCSETA, (void *) &new_term);
204 }
205
206 memset(parsenextc, 0, BUFSIZ);
207
208 while (1) {
209
210 if ((ret = read(inputFd, &c, 1)) < 1)
211 return ret;
212
213 switch (c) {
214 case 1: /* Control-A Beginning of line */
215 input_home(outputFd, &cursor);
216 break;
217 case 5: /* Control-E EOL */
218 input_end(outputFd, &cursor, len);
219 break;
220 case 4: /* Control-D */
221 if (cursor != len) {
222 input_delete(outputFd, cursor);
223 len--;
224 }
225 break;
226 case '\b': /* Backspace */
227 case DEL:
228 input_backspace(outputFd, &cursor, &len);
229 break;
230 case '\n': /* Enter */
231 *(parsenextc + len++ + 1) = c;
232 xwrite(outputFd, &c, 1);
233 break_out = 1;
234 break;
235 case ESC: /* escape sequence follows */
236 if ((ret = read(inputFd, &c, 1)) < 1)
237 return ret;
238
239 if (c == '[') { /* 91 */
240 if ((ret = read(inputFd, &c, 1)) < 1)
241 return ret;
242
243 switch (c) {
244 case 'A':
245 if (hp && hp->p) { /* Up */
246 hp = hp->p;
247 goto hop;
248 }
249 break;
250 case 'B':
251 if (hp && hp->n && hp->n->s) { /* Down */
252 hp = hp->n;
253 goto hop;
254 }
255 break;
256
257 hop: /* hop */
258 len = strlen(parsenextc);
259
260 for (; cursor > 0; cursor--) /* return to begining of line */
261 xwrite(outputFd, "\b", 1);
262
263 for (j = 0; j < len; j++) /* erase old command */
264 xwrite(outputFd, " ", 1);
265
266 for (j = len; j > 0; j--) /* return to begining of line */
267 xwrite(outputFd, "\b", 1);
268
269 strcpy(parsenextc, hp->s); /* write new command */
270 len = strlen(hp->s);
271 xwrite(outputFd, parsenextc, len);
272 cursor = len;
273 break;
274 case 'C': /* Right */
275 if (cursor < len) {
276 xwrite(outputFd, "\033[C", 3);
277 cursor++;
278 }
279 break;
280 case 'D': /* Left */
281 if (cursor > 0) {
282 xwrite(outputFd, "\033[D", 3);
283 cursor--;
284 }
285 break;
286 case '3': /* Delete */
287 if (cursor != len) {
288 input_delete(outputFd, cursor);
289 len--;
290 }
291 break;
292 case '1': /* Home (Ctrl-A) */
293 input_home(outputFd, &cursor);
294 break;
295 case '4': /* End (Ctrl-E) */
296 input_end(outputFd, &cursor, len);
297 break;
298 }
299 if (c == '1' || c == '3' || c == '4')
300 if ((ret = read(inputFd, &c, 1)) < 1)
301 return ret; /* read 126 (~) */
302 }
303 if (c == 'O') { /* 79 */
304 if ((ret = read(inputFd, &c, 1)) < 1)
305 return ret;
306 switch (c) {
307 case 'H': /* Home (xterm) */
308 input_home(outputFd, &cursor);
309 break;
310 case 'F': /* End (xterm) */
311 input_end(outputFd, &cursor, len);
312 break;
313 }
314 }
315 c = 0;
316 break;
317
318 default: /* If it's regular input, do the normal thing */
319
320 if (!isprint(c)) /* Skip non-printable characters */
321 break;
322
323 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
324 break;
325
326 len++;
327
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,
332 len - cursor - 1);
333
334 *(parsenextc + cursor) = c;
335
336 for (j = cursor; j < len; j++)
337 xwrite(outputFd, parsenextc + j, 1);
338 for (; j > cursor; j--)
339 xwrite(outputFd, "\033[D", 3);
340 }
341
342 cursor++;
343 xwrite(outputFd, &c, 1);
344 break;
345 }
346
347 if (break_out) /* Enter is the command terminator, no more input. */
348 break;
349 }
350
351 nr = len + 1;
352 xioctl(inputFd, TCSETA, (void *) &old_term);
353 reset_term = 0;
354
355
356 if (*(parsenextc)) { /* Handle command history log */
357
358 struct history *h = his_end;
359
360 if (!h) { /* No previous history */
361 h = his_front = malloc(sizeof(struct history));
362 h->n = malloc(sizeof(struct history));
363 h->p = NULL;
364 h->s = strdup(parsenextc);
365
366 h->n->p = h;
367 h->n->n = NULL;
368 h->n->s = NULL;
369 his_end = h->n;
370 history_counter++;
371 } else { /* Add a new history command */
372
373 h->n = malloc(sizeof(struct history));
374
375 h->n->p = h;
376 h->n->n = NULL;
377 h->n->s = NULL;
378 h->s = strdup(parsenextc);
379 his_end = h->n;
380
381 if (history_counter >= MAX_HISTORY) { /* After max history, remove the last known command */
382
383 struct history *p = his_front->n;
384
385 p->p = NULL;
386 free(his_front->s);
387 free(his_front);
388 his_front = p;
389 } else {
390 history_counter++;
391 }
392 }
393 }
394
395 return nr;
396}
397
398extern void cmdedit_init(void)
399{
400 atexit(cmdedit_reset_term);
401 signal(SIGINT, gotaSignal);
402 signal(SIGQUIT, gotaSignal);
403 signal(SIGTERM, gotaSignal);
404}
405#endif /* BB_FEATURE_SH_COMMAND_EDITING */