Fix fg bug
[oweals/busybox.git] / shell / cmdedit.c
1 /*
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
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 */
54
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 */
58
59 struct 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.  */
67 int 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.  */
93 int 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
101 void cmdedit_reset_term(void)
102 {
103     if (reset_term)
104         xioctl(fileno(stdin), TCSETA, (void *) &old_term);
105 }
106
107 void gotaSignal(int sig)
108 {
109     cmdedit_reset_term();
110     fprintf(stdout, "\n");
111     exit(TRUE);
112 }
113
114 void 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
123 void 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
143 void 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
152 void 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
178 extern 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
398 extern 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 */