Rev 2714 | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 2714 | Rev 3022 | ||
---|---|---|---|
1 | /* |
1 | /* |
2 | * Termios command line History and Editting for NetBSD sh (ash) |
2 | * Termios command line History and Editting for NetBSD sh (ash) |
3 | * Copyright (c) 1999 |
3 | * Copyright (c) 1999 |
4 | * Main code: Adam Rogoyski <rogoyski@cs.utexas.edu> |
4 | * Main code: Adam Rogoyski <rogoyski@cs.utexas.edu> |
5 | * Etc: Dave Cinege <dcinege@psychosis.com> |
5 | * Etc: Dave Cinege <dcinege@psychosis.com> |
6 | * |
6 | * |
7 | * You may use this code as you wish, so long as the original author(s) |
7 | * You may use this code as you wish, so long as the original author(s) |
8 | * are attributed in any redistributions of the source code. |
8 | * are attributed in any redistributions of the source code. |
9 | * This code is 'as is' with no warranty. |
9 | * This code is 'as is' with no warranty. |
10 | * This code may safely be consumed by a BSD or GPL license. |
10 | * This code may safely be consumed by a BSD or GPL license. |
11 | * |
11 | * |
12 | * v 0.5 19990328 Initial release |
12 | * v 0.5 19990328 Initial release |
13 | * |
13 | * |
14 | * Future plans: Simple file and path name completion. (like BASH) |
14 | * Future plans: Simple file and path name completion. (like BASH) |
15 | * |
15 | * |
16 | */ |
16 | */ |
17 | 17 | ||
18 | /* |
18 | /* |
19 | Usage and Known bugs: |
19 | Usage and Known bugs: |
20 | Terminal key codes are not extensive, and more will probably |
20 | Terminal key codes are not extensive, and more will probably |
21 | need to be added. This version was created on Debian GNU/Linux 2.x. |
21 | need to be added. This version was created on Debian GNU/Linux 2.x. |
22 | Delete, Backspace, Home, End, and the arrow keys were tested |
22 | Delete, Backspace, Home, End, and the arrow keys were tested |
23 | to work in an Xterm and console. Ctrl-A also works as Home. |
23 | to work in an Xterm and console. Ctrl-A also works as Home. |
24 | Ctrl-E also works as End. The binary size increase is <3K. |
24 | Ctrl-E also works as End. The binary size increase is <3K. |
25 | |
25 | |
26 | Editting will not display correctly for lines greater then the |
26 | Editting will not display correctly for lines greater then the |
27 | terminal width. (more then one line.) However, history will. |
27 | terminal width. (more then one line.) However, history will. |
28 | */ |
28 | */ |
29 | 29 | ||
30 | #include <stdio.h> |
30 | #include <stdio.h> |
31 | #include <unistd.h> |
31 | #include <unistd.h> |
32 | #include <stdlib.h> |
32 | #include <stdlib.h> |
33 | #include <string.h> |
33 | #include <string.h> |
34 | #include <termios.h> |
34 | #include <termios.h> |
35 | #include <ctype.h> |
35 | #include <ctype.h> |
36 | #include <sys/ioctl.h> |
36 | #include <sys/ioctl.h> |
37 | 37 | ||
38 | #include "input.h" |
38 | #include "input.h" |
39 | #include "output.h" |
39 | #include "output.h" |
40 | 40 | ||
41 | #ifdef HETIO |
41 | #ifdef HETIO |
42 | 42 | ||
43 | #include "hetio.h" |
43 | #include "hetio.h" |
44 | 44 | ||
45 | 45 | ||
46 | #define MAX_HISTORY 15 /* Maximum length of the linked list for the command line history */ |
46 | #define MAX_HISTORY 15 /* Maximum length of the linked list for the command line history */ |
47 | 47 | ||
48 | #define ESC 27 |
48 | #define ESC 27 |
49 | #define DEL 127 |
49 | #define DEL 127 |
50 | 50 | ||
51 | static struct history *his_front = NULL; /* First element in command line list */ |
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 */ |
52 | static struct history *his_end = NULL; /* Last element in command line list */ |
53 | static struct termios old_term, new_term; /* Current termio and the previous termio before starting ash */ |
53 | static struct termios old_term, new_term; /* Current termio and the previous termio before starting ash */ |
54 | 54 | ||
55 | static int history_counter = 0; /* Number of commands in history list */ |
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 */ |
56 | static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */ |
57 | //static int hetio_inter = 0; |
57 | //static int hetio_inter = 0; |
58 | int hetio_inter = 0; |
58 | int hetio_inter = 0; |
59 | 59 | ||
60 | struct history |
60 | struct history |
61 | { |
61 | { |
62 | char *s; |
62 | char *s; |
63 | struct history *p; |
63 | struct history *p; |
64 | struct history *n; |
64 | struct history *n; |
65 | }; |
65 | }; |
66 | 66 | ||
67 | 67 | ||
68 | void input_delete (int); |
68 | void input_delete (int); |
69 | void input_home (int *); |
69 | void input_home (int *); |
70 | void input_end (int *, int); |
70 | void input_end (int *, int); |
71 | void input_backspace (int *, int *); |
71 | void input_backspace (int *, int *); |
72 | 72 | ||
73 | 73 | ||
74 | 74 | ||
75 | void hetio_init(void) |
75 | void hetio_init(void) |
76 | { |
76 | { |
77 | hetio_inter = 1; |
77 | hetio_inter = 1; |
78 | } |
78 | } |
79 | 79 | ||
80 | 80 | ||
81 | void hetio_reset_term(void) |
81 | void hetio_reset_term(void) |
82 | { |
82 | { |
83 | if (reset_term) |
83 | if (reset_term) |
84 | tcsetattr(1, TCSANOW, &old_term); |
84 | tcsetattr(1, TCSANOW, &old_term); |
85 | } |
85 | } |
86 | 86 | ||
87 | 87 | ||
88 | void setIO(struct termios *new, struct termios *old) /* Set terminal IO to canonical mode, and save old term settings. */ |
88 | void setIO(struct termios *new, struct termios *old) /* Set terminal IO to canonical mode, and save old term settings. */ |
89 | { |
89 | { |
90 | tcgetattr(0, old); |
90 | tcgetattr(0, old); |
91 | memcpy(new, old, sizeof(*new)); |
91 | memcpy(new, old, sizeof(*new)); |
92 | new->c_cc[VMIN] = 1; |
92 | new->c_cc[VMIN] = 1; |
93 | new->c_cc[VTIME] = 0; |
93 | new->c_cc[VTIME] = 0; |
94 | new->c_lflag &= ~ICANON; /* unbuffered input */ |
94 | new->c_lflag &= ~ICANON; /* unbuffered input */ |
95 | new->c_lflag &= ~ECHO; |
95 | new->c_lflag &= ~ECHO; |
96 | tcsetattr(0, TCSANOW, new); |
96 | tcsetattr(0, TCSANOW, new); |
97 | } |
97 | } |
98 | 98 | ||
99 | void input_home(int *cursor) /* Command line input routines */ |
99 | void input_home(int *cursor) /* Command line input routines */ |
100 | { |
100 | { |
101 | while (*cursor > 0) { |
101 | while (*cursor > 0) { |
102 | out1c('\b'); |
102 | out1c('\b'); |
103 | --*cursor; |
103 | --*cursor; |
104 | } |
104 | } |
105 | flushout(&output); |
105 | flushout(&output); |
106 | } |
106 | } |
107 | 107 | ||
108 | 108 | ||
109 | void input_delete(int cursor) |
109 | void input_delete(int cursor) |
110 | { |
110 | { |
111 | int j = 0; |
111 | int j = 0; |
112 | 112 | ||
113 | memmove(parsenextc + cursor, parsenextc + cursor + 1, |
113 | memmove(parsenextc + cursor, parsenextc + cursor + 1, |
114 | BUFSIZ - cursor - 1); |
114 | BUFSIZ - cursor - 1); |
115 | for (j = cursor; j < (BUFSIZ - 1); j++) { |
115 | for (j = cursor; j < (BUFSIZ - 1); j++) { |
116 | if (!*(parsenextc + j)) |
116 | if (!*(parsenextc + j)) |
117 | break; |
117 | break; |
118 | else |
118 | else |
119 | out1c(*(parsenextc + j)); |
119 | out1c(*(parsenextc + j)); |
120 | } |
120 | } |
121 | 121 | ||
122 | out1str(" \b"); |
122 | out1str(" \b"); |
123 | 123 | ||
124 | while (j-- > cursor) |
124 | while (j-- > cursor) |
125 | out1c('\b'); |
125 | out1c('\b'); |
126 | flushout(&output); |
126 | flushout(&output); |
127 | } |
127 | } |
128 | 128 | ||
129 | 129 | ||
130 | void input_end(int *cursor, int len) |
130 | void input_end(int *cursor, int len) |
131 | { |
131 | { |
132 | while (*cursor < len) { |
132 | while (*cursor < len) { |
133 | out1str("\033[C"); |
133 | out1str("\033[C"); |
134 | ++*cursor; |
134 | ++*cursor; |
135 | } |
135 | } |
136 | flushout(&output); |
136 | flushout(&output); |
137 | } |
137 | } |
138 | 138 | ||
139 | 139 | ||
140 | void |
140 | void |
141 | input_backspace(int *cursor, int *len) |
141 | input_backspace(int *cursor, int *len) |
142 | { |
142 | { |
143 | int j = 0; |
143 | int j = 0; |
144 | 144 | ||
145 | if (*cursor > 0) { |
145 | if (*cursor > 0) { |
146 | out1str("\b \b"); |
146 | out1str("\b \b"); |
147 | --*cursor; |
147 | --*cursor; |
148 | memmove(parsenextc + *cursor, parsenextc + *cursor + 1, |
148 | memmove(parsenextc + *cursor, parsenextc + *cursor + 1, |
149 | BUFSIZ - *cursor + 1); |
149 | BUFSIZ - *cursor + 1); |
150 | 150 | ||
151 | for (j = *cursor; j < (BUFSIZ - 1); j++) { |
151 | for (j = *cursor; j < (BUFSIZ - 1); j++) { |
152 | if (!*(parsenextc + j)) |
152 | if (!*(parsenextc + j)) |
153 | break; |
153 | break; |
154 | else |
154 | else |
155 | out1c(*(parsenextc + j)); |
155 | out1c(*(parsenextc + j)); |
156 | } |
156 | } |
157 | 157 | ||
158 | out1str(" \b"); |
158 | out1str(" \b"); |
159 | 159 | ||
160 | while (j-- > *cursor) |
160 | while (j-- > *cursor) |
161 | out1c('\b'); |
161 | out1c('\b'); |
162 | 162 | ||
163 | --*len; |
163 | --*len; |
164 | flushout(&output); |
164 | flushout(&output); |
165 | } |
165 | } |
166 | } |
166 | } |
167 | 167 | ||
168 | int hetio_read_input(int fd) |
168 | int hetio_read_input(int fd) |
169 | { |
169 | { |
170 | int nr = 0; |
170 | int nr = 0; |
171 | 171 | ||
172 | if (!hetio_inter) { /* Are we an interactive shell? */ |
172 | if (!hetio_inter) { /* Are we an interactive shell? */ |
173 | return -255; |
173 | return -255; |
174 | } else { |
174 | } else { |
175 | int len = 0; |
175 | int len = 0; |
176 | int j = 0; |
176 | int j = 0; |
177 | int cursor = 0; |
177 | int cursor = 0; |
178 | int break_out = 0; |
178 | int break_out = 0; |
179 | int ret = 0; |
179 | int ret = 0; |
180 | char c = 0; |
180 | char c = 0; |
181 | struct history *hp = his_end; |
181 | struct history *hp = his_end; |
182 | 182 | ||
183 | if (!reset_term) { |
183 | if (!reset_term) { |
184 | setIO(&new_term, &old_term); |
184 | setIO(&new_term, &old_term); |
185 | reset_term = 1; |
185 | reset_term = 1; |
186 | } else { |
186 | } else { |
187 | tcsetattr(0, TCSANOW, &new_term); |
187 | tcsetattr(0, TCSANOW, &new_term); |
188 | } |
188 | } |
189 | 189 | ||
190 | memset(parsenextc, 0, BUFSIZ); |
190 | memset(parsenextc, 0, BUFSIZ); |
191 | 191 | ||
192 | while (1) { |
192 | while (1) { |
193 | if ((ret = read(fd, &c, 1)) < 1) |
193 | if ((ret = read(fd, &c, 1)) < 1) |
194 | return ret; |
194 | return ret; |
195 | 195 | ||
196 | switch (c) { |
196 | switch (c) { |
197 | case 1: /* Control-A Beginning of line */ |
197 | case 1: /* Control-A Beginning of line */ |
198 | input_home(&cursor); |
198 | input_home(&cursor); |
199 | break; |
199 | break; |
200 | case 5: /* Control-E EOL */ |
200 | case 5: /* Control-E EOL */ |
201 | input_end(&cursor, len); |
201 | input_end(&cursor, len); |
202 | break; |
202 | break; |
203 | case 4: /* Control-D */ |
203 | case 4: /* Control-D */ |
204 | #ifndef CTRL_D_DELETE |
204 | #ifndef CTRL_D_DELETE |
205 | return 0; |
205 | return 0; |
206 | #else |
206 | #else |
207 | if (cursor != len) { |
207 | if (cursor != len) { |
208 | input_delete(cursor); |
208 | input_delete(cursor); |
209 | len--; |
209 | len--; |
210 | } |
210 | } |
211 | break; |
211 | break; |
212 | #endif |
212 | #endif |
213 | case '\b': /* Backspace */ |
213 | case '\b': /* Backspace */ |
214 | case DEL: |
214 | case DEL: |
215 | input_backspace(&cursor, &len); |
215 | input_backspace(&cursor, &len); |
216 | break; |
216 | break; |
217 | case '\n': /* Enter */ |
217 | case '\n': /* Enter */ |
218 | *(parsenextc + len++ + 1) = c; |
218 | *(parsenextc + len++ + 1) = c; |
219 | out1c(c); |
219 | out1c(c); |
220 | flushout(&output); |
220 | flushout(&output); |
221 | break_out = 1; |
221 | break_out = 1; |
222 | break; |
222 | break; |
223 | case ESC: /* escape sequence follows */ |
223 | case ESC: /* escape sequence follows */ |
224 | if ((ret = read(fd, &c, 1)) < 1) |
224 | if ((ret = read(fd, &c, 1)) < 1) |
225 | return ret; |
225 | return ret; |
226 | 226 | ||
227 | if (c == '[' || c == 'O' ) { /* 91 */ |
227 | if (c == '[' || c == 'O' ) { /* 91 */ |
228 | if ((ret = read(fd, &c, 1)) < 1) |
228 | if ((ret = read(fd, &c, 1)) < 1) |
229 | return ret; |
229 | return ret; |
230 | 230 | ||
231 | switch (c) { |
231 | switch (c) { |
232 | case 'A': |
232 | case 'A': |
233 | if (hp && hp->p) { /* Up */ |
233 | if (hp && hp->p) { /* Up */ |
234 | hp = hp->p; |
234 | hp = hp->p; |
235 | goto hop; |
235 | goto hop; |
236 | } |
236 | } |
237 | break; |
237 | break; |
238 | case 'B': |
238 | case 'B': |
239 | if (hp && hp->n && hp->n->s) { /* Down */ |
239 | if (hp && hp->n && hp->n->s) { /* Down */ |
240 | hp = hp->n; |
240 | hp = hp->n; |
241 | goto hop; |
241 | goto hop; |
242 | } |
242 | } |
243 | break; |
243 | break; |
244 | 244 | ||
245 | hop: /* hop */ |
245 | hop: /* hop */ |
246 | len = strlen(parsenextc); |
246 | len = strlen(parsenextc); |
247 | 247 | ||
248 | for (; cursor > 0; cursor--) /* return to begining of line */ |
248 | for (; cursor > 0; cursor--) /* return to begining of line */ |
249 | out1c('\b'); |
249 | out1c('\b'); |
250 | 250 | ||
251 | for (j = 0; j < len; j++) /* erase old command */ |
251 | for (j = 0; j < len; j++) /* erase old command */ |
252 | out1c(' '); |
252 | out1c(' '); |
253 | 253 | ||
254 | for (j = len; j > 0; j--) /* return to begining of line */ |
254 | for (j = len; j > 0; j--) /* return to begining of line */ |
255 | out1c('\b'); |
255 | out1c('\b'); |
256 | 256 | ||
257 | strcpy (parsenextc, hp->s); /* write new command */ |
257 | strcpy (parsenextc, hp->s); /* write new command */ |
258 | len = strlen (hp->s); |
258 | len = strlen (hp->s); |
259 | out1str(parsenextc); |
259 | out1str(parsenextc); |
260 | flushout(&output); |
260 | flushout(&output); |
261 | cursor = len; |
261 | cursor = len; |
262 | break; |
262 | break; |
263 | case 'C': /* Right */ |
263 | case 'C': /* Right */ |
264 | if (cursor < len) { |
264 | if (cursor < len) { |
265 | out1str("\033[C"); |
265 | out1str("\033[C"); |
266 | cursor++; |
266 | cursor++; |
267 | flushout(&output); |
267 | flushout(&output); |
268 | } |
268 | } |
269 | break; |
269 | break; |
270 | case 'D': /* Left */ |
270 | case 'D': /* Left */ |
271 | if (cursor > 0) { |
271 | if (cursor > 0) { |
272 | out1str("\033[D"); |
272 | out1str("\033[D"); |
273 | cursor--; |
273 | cursor--; |
274 | flushout(&output); |
274 | flushout(&output); |
275 | } |
275 | } |
276 | break; |
276 | break; |
277 | case '3': /* Delete */ |
277 | case '3': /* Delete */ |
278 | if (cursor != len) { |
278 | if (cursor != len) { |
279 | input_delete(cursor); |
279 | input_delete(cursor); |
280 | len--; |
280 | len--; |
281 | } |
281 | } |
282 | break; |
282 | break; |
283 | case 'H': /* Home (xterm) */ |
283 | case 'H': /* Home (xterm) */ |
284 | case '1': /* Home (Ctrl-A) */ |
284 | case '1': /* Home (Ctrl-A) */ |
285 | input_home(&cursor); |
285 | input_home(&cursor); |
286 | break; |
286 | break; |
287 | case 'F': /* End (xterm_ */ |
287 | case 'F': /* End (xterm_ */ |
288 | case '4': /* End (Ctrl-E) */ |
288 | case '4': /* End (Ctrl-E) */ |
289 | input_end(&cursor, len); |
289 | input_end(&cursor, len); |
290 | break; |
290 | break; |
291 | } |
291 | } |
292 | if (c == '1' || c == '3' || c == '4') |
292 | if (c == '1' || c == '3' || c == '4') |
293 | if ((ret = read(fd, &c, 1)) < 1) |
293 | if ((ret = read(fd, &c, 1)) < 1) |
294 | return ret; /* read 126 (~) */ |
294 | return ret; /* read 126 (~) */ |
295 | } |
295 | } |
296 | 296 | ||
297 | c = 0; |
297 | c = 0; |
298 | break; |
298 | break; |
299 | 299 | ||
300 | default: /* If it's regular input, do the normal thing */ |
300 | default: /* If it's regular input, do the normal thing */ |
301 | 301 | ||
302 | if (!isprint(c)) /* Skip non-printable characters */ |
302 | if (!isprint(c)) /* Skip non-printable characters */ |
303 | break; |
303 | break; |
304 | 304 | ||
305 | if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */ |
305 | if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */ |
306 | break; |
306 | break; |
307 | 307 | ||
308 | len++; |
308 | len++; |
309 | 309 | ||
310 | if (cursor == (len - 1)) { /* Append if at the end of the line */ |
310 | if (cursor == (len - 1)) { /* Append if at the end of the line */ |
311 | *(parsenextc + cursor) = c; |
311 | *(parsenextc + cursor) = c; |
312 | } else { /* Insert otherwise */ |
312 | } else { /* Insert otherwise */ |
313 | memmove(parsenextc + cursor + 1, parsenextc + cursor, |
313 | memmove(parsenextc + cursor + 1, parsenextc + cursor, |
314 | len - cursor - 1); |
314 | len - cursor - 1); |
315 | 315 | ||
316 | *(parsenextc + cursor) = c; |
316 | *(parsenextc + cursor) = c; |
317 | 317 | ||
318 | for (j = cursor; j < len; j++) |
318 | for (j = cursor; j < len; j++) |
319 | out1c(*(parsenextc + j)); |
319 | out1c(*(parsenextc + j)); |
320 | for (; j > cursor; j--) |
320 | for (; j > cursor; j--) |
321 | out1str("\033[D"); |
321 | out1str("\033[D"); |
322 | } |
322 | } |
323 | 323 | ||
324 | cursor++; |
324 | cursor++; |
325 | out1c(c); |
325 | out1c(c); |
326 | flushout(&output); |
326 | flushout(&output); |
327 | break; |
327 | break; |
328 | } |
328 | } |
329 | 329 | ||
330 | if (break_out) /* Enter is the command terminator, no more input. */ |
330 | if (break_out) /* Enter is the command terminator, no more input. */ |
331 | break; |
331 | break; |
332 | } |
332 | } |
333 | 333 | ||
334 | nr = len + 1; |
334 | nr = len + 1; |
335 | tcsetattr(0, TCSANOW, &old_term); |
335 | tcsetattr(0, TCSANOW, &old_term); |
336 | 336 | ||
337 | 337 | ||
338 | if (*(parsenextc)) { /* Handle command history log */ |
338 | if (*(parsenextc)) { /* Handle command history log */ |
339 | struct history *h = his_end; |
339 | struct history *h = his_end; |
340 | 340 | ||
341 | if (!h) { /* No previous history */ |
341 | if (!h) { /* No previous history */ |
342 | h = his_front = malloc(sizeof (struct history)); |
342 | h = his_front = malloc(sizeof (struct history)); |
343 | h->n = malloc(sizeof (struct history)); |
343 | h->n = malloc(sizeof (struct history)); |
344 | h->p = NULL; |
344 | h->p = NULL; |
345 | h->s = strdup(parsenextc); |
345 | h->s = strdup(parsenextc); |
346 | 346 | ||
347 | h->n->p = h; |
347 | h->n->p = h; |
348 | h->n->n = NULL; |
348 | h->n->n = NULL; |
349 | h->n->s = NULL; |
349 | h->n->s = NULL; |
350 | his_end = h->n; |
350 | his_end = h->n; |
351 | history_counter++; |
351 | history_counter++; |
352 | } else { /* Add a new history command */ |
352 | } else { /* Add a new history command */ |
353 | 353 | ||
354 | h->n = malloc(sizeof (struct history)); |
354 | h->n = malloc(sizeof (struct history)); |
355 | 355 | ||
356 | h->n->p = h; |
356 | h->n->p = h; |
357 | h->n->n = NULL; |
357 | h->n->n = NULL; |
358 | h->n->s = NULL; |
358 | h->n->s = NULL; |
359 | h->s = strdup(parsenextc); |
359 | h->s = strdup(parsenextc); |
360 | his_end = h->n; |
360 | his_end = h->n; |
361 | 361 | ||
362 | if (history_counter >= MAX_HISTORY) { /* After max history, remove the last known command */ |
362 | if (history_counter >= MAX_HISTORY) { /* After max history, remove the last known command */ |
363 | struct history *p = his_front->n; |
363 | struct history *p = his_front->n; |
364 | 364 | ||
365 | p->p = NULL; |
365 | p->p = NULL; |
366 | free(his_front->s); |
366 | free(his_front->s); |
367 | free(his_front); |
367 | free(his_front); |
368 | his_front = p; |
368 | his_front = p; |
369 | } else { |
369 | } else { |
370 | history_counter++; |
370 | history_counter++; |
371 | } |
371 | } |
372 | } |
372 | } |
373 | } |
373 | } |
374 | } |
374 | } |
375 | 375 | ||
376 | return nr; |
376 | return nr; |
377 | } |
377 | } |
378 | #endif |
378 | #endif |
379 | 379 |