diff options
Diffstat (limited to 'shell/cmdedit.c')
-rw-r--r-- | shell/cmdedit.c | 1924 |
1 files changed, 1924 insertions, 0 deletions
diff --git a/shell/cmdedit.c b/shell/cmdedit.c new file mode 100644 index 000000000..ceaa2e885 --- /dev/null +++ b/shell/cmdedit.c | |||
@@ -0,0 +1,1924 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * Termios command line History and Editing. | ||
4 | * | ||
5 | * Copyright (c) 1986-2003 may safely be consumed by a BSD or GPL license. | ||
6 | * Written by: Vladimir Oleynik <dzo@simtreas.ru> | ||
7 | * | ||
8 | * Used ideas: | ||
9 | * Adam Rogoyski <rogoyski@cs.utexas.edu> | ||
10 | * Dave Cinege <dcinege@psychosis.com> | ||
11 | * Jakub Jelinek (c) 1995 | ||
12 | * Erik Andersen <andersen@codepoet.org> (Majorly adjusted for busybox) | ||
13 | * | ||
14 | * This code is 'as is' with no warranty. | ||
15 | * | ||
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. | ||
26 | |||
27 | Small bugs (simple effect): | ||
28 | - not true viewing if terminal size (x*y symbols) less | ||
29 | size (prompt + editor`s line + 2 symbols) | ||
30 | - not true viewing if length prompt less terminal width | ||
31 | */ | ||
32 | |||
33 | |||
34 | #include "busybox.h" | ||
35 | #include <stdio.h> | ||
36 | #include <errno.h> | ||
37 | #include <unistd.h> | ||
38 | #include <stdlib.h> | ||
39 | #include <string.h> | ||
40 | #include <sys/ioctl.h> | ||
41 | #include <ctype.h> | ||
42 | #include <signal.h> | ||
43 | #include <limits.h> | ||
44 | |||
45 | #include "cmdedit.h" | ||
46 | |||
47 | |||
48 | #ifdef CONFIG_LOCALE_SUPPORT | ||
49 | #define Isprint(c) isprint((c)) | ||
50 | #else | ||
51 | #define Isprint(c) ( (c) >= ' ' && (c) != ((unsigned char)'\233') ) | ||
52 | #endif | ||
53 | |||
54 | #ifdef TEST | ||
55 | |||
56 | /* pretect redefined for test */ | ||
57 | #undef CONFIG_FEATURE_COMMAND_EDITING | ||
58 | #undef CONFIG_FEATURE_COMMAND_TAB_COMPLETION | ||
59 | #undef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION | ||
60 | #undef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT | ||
61 | #undef CONFIG_FEATURE_CLEAN_UP | ||
62 | |||
63 | #define CONFIG_FEATURE_COMMAND_EDITING | ||
64 | #define CONFIG_FEATURE_COMMAND_TAB_COMPLETION | ||
65 | #define CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION | ||
66 | #define CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT | ||
67 | #define CONFIG_FEATURE_CLEAN_UP | ||
68 | |||
69 | #endif /* TEST */ | ||
70 | |||
71 | #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION | ||
72 | #include <dirent.h> | ||
73 | #include <sys/stat.h> | ||
74 | #endif | ||
75 | |||
76 | #ifdef CONFIG_FEATURE_COMMAND_EDITING | ||
77 | |||
78 | #if defined(CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION) || defined(CONFIG_FEATURE_SH_FANCY_PROMPT) | ||
79 | #define CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR | ||
80 | #endif | ||
81 | |||
82 | /* Maximum length of the linked list for the command line history */ | ||
83 | #ifndef CONFIG_FEATURE_COMMAND_HISTORY | ||
84 | #define MAX_HISTORY 15 | ||
85 | #else | ||
86 | #define MAX_HISTORY (CONFIG_FEATURE_COMMAND_HISTORY + 0) | ||
87 | #endif | ||
88 | |||
89 | #if MAX_HISTORY > 0 | ||
90 | static char *history[MAX_HISTORY+1]; /* history + current */ | ||
91 | /* saved history lines */ | ||
92 | static int n_history; | ||
93 | /* current pointer to history line */ | ||
94 | static int cur_history; | ||
95 | #endif | ||
96 | |||
97 | #include <termios.h> | ||
98 | #define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp) | ||
99 | #define getTermSettings(fd,argp) tcgetattr(fd, argp); | ||
100 | |||
101 | /* Current termio and the previous termio before starting sh */ | ||
102 | static struct termios initial_settings, new_settings; | ||
103 | |||
104 | |||
105 | static | ||
106 | volatile int cmdedit_termw = 80; /* actual terminal width */ | ||
107 | static | ||
108 | volatile int handlers_sets = 0; /* Set next bites: */ | ||
109 | |||
110 | enum { | ||
111 | SET_ATEXIT = 1, /* when atexit() has been called | ||
112 | and get euid,uid,gid to fast compare */ | ||
113 | SET_WCHG_HANDLERS = 2, /* winchg signal handler */ | ||
114 | SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */ | ||
115 | }; | ||
116 | |||
117 | |||
118 | static int cmdedit_x; /* real x terminal position */ | ||
119 | static int cmdedit_y; /* pseudoreal y terminal position */ | ||
120 | static int cmdedit_prmt_len; /* lenght prompt without colores string */ | ||
121 | |||
122 | static int cursor; /* required global for signal handler */ | ||
123 | static int len; /* --- "" - - "" - -"- --""-- --""--- */ | ||
124 | static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */ | ||
125 | static | ||
126 | #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT | ||
127 | const | ||
128 | #endif | ||
129 | char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */ | ||
130 | |||
131 | #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR | ||
132 | static char *user_buf = ""; | ||
133 | static char *home_pwd_buf = ""; | ||
134 | static int my_euid; | ||
135 | #endif | ||
136 | |||
137 | #ifdef CONFIG_FEATURE_SH_FANCY_PROMPT | ||
138 | static char *hostname_buf; | ||
139 | static int num_ok_lines = 1; | ||
140 | #endif | ||
141 | |||
142 | |||
143 | #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION | ||
144 | |||
145 | #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR | ||
146 | static int my_euid; | ||
147 | #endif | ||
148 | |||
149 | static int my_uid; | ||
150 | static int my_gid; | ||
151 | |||
152 | #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */ | ||
153 | |||
154 | static void cmdedit_setwidth(int w, int redraw_flg); | ||
155 | |||
156 | static void win_changed(int nsig) | ||
157 | { | ||
158 | static sighandler_t previous_SIGWINCH_handler; /* for reset */ | ||
159 | |||
160 | /* emulate || signal call */ | ||
161 | if (nsig == -SIGWINCH || nsig == SIGWINCH) { | ||
162 | int width = 0; | ||
163 | get_terminal_width_height(0, &width, NULL); | ||
164 | cmdedit_setwidth(width, nsig == SIGWINCH); | ||
165 | } | ||
166 | /* Unix not all standart in recall signal */ | ||
167 | |||
168 | if (nsig == -SIGWINCH) /* save previous handler */ | ||
169 | previous_SIGWINCH_handler = signal(SIGWINCH, win_changed); | ||
170 | else if (nsig == SIGWINCH) /* signaled called handler */ | ||
171 | signal(SIGWINCH, win_changed); /* set for next call */ | ||
172 | else /* nsig == 0 */ | ||
173 | /* set previous handler */ | ||
174 | signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */ | ||
175 | } | ||
176 | |||
177 | static void cmdedit_reset_term(void) | ||
178 | { | ||
179 | if ((handlers_sets & SET_RESET_TERM) != 0) { | ||
180 | /* sparc and other have broken termios support: use old termio handling. */ | ||
181 | setTermSettings(STDIN_FILENO, (void *) &initial_settings); | ||
182 | handlers_sets &= ~SET_RESET_TERM; | ||
183 | } | ||
184 | if ((handlers_sets & SET_WCHG_HANDLERS) != 0) { | ||
185 | /* reset SIGWINCH handler to previous (default) */ | ||
186 | win_changed(0); | ||
187 | handlers_sets &= ~SET_WCHG_HANDLERS; | ||
188 | } | ||
189 | fflush(stdout); | ||
190 | } | ||
191 | |||
192 | |||
193 | /* special for recount position for scroll and remove terminal margin effect */ | ||
194 | static void cmdedit_set_out_char(int next_char) | ||
195 | { | ||
196 | |||
197 | int c = (int)((unsigned char) command_ps[cursor]); | ||
198 | |||
199 | if (c == 0) | ||
200 | c = ' '; /* destroy end char? */ | ||
201 | #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT | ||
202 | if (!Isprint(c)) { /* Inverse put non-printable characters */ | ||
203 | if (c >= 128) | ||
204 | c -= 128; | ||
205 | if (c < ' ') | ||
206 | c += '@'; | ||
207 | if (c == 127) | ||
208 | c = '?'; | ||
209 | printf("\033[7m%c\033[0m", c); | ||
210 | } else | ||
211 | #endif | ||
212 | if (initial_settings.c_lflag & ECHO) putchar(c); | ||
213 | if (++cmdedit_x >= cmdedit_termw) { | ||
214 | /* terminal is scrolled down */ | ||
215 | cmdedit_y++; | ||
216 | cmdedit_x = 0; | ||
217 | |||
218 | if (!next_char) | ||
219 | next_char = ' '; | ||
220 | /* destroy "(auto)margin" */ | ||
221 | putchar(next_char); | ||
222 | putchar('\b'); | ||
223 | } | ||
224 | cursor++; | ||
225 | } | ||
226 | |||
227 | /* Move to end line. Bonus: rewrite line from cursor */ | ||
228 | static void input_end(void) | ||
229 | { | ||
230 | while (cursor < len) | ||
231 | cmdedit_set_out_char(0); | ||
232 | } | ||
233 | |||
234 | /* Go to the next line */ | ||
235 | static void goto_new_line(void) | ||
236 | { | ||
237 | input_end(); | ||
238 | if (cmdedit_x) | ||
239 | putchar('\n'); | ||
240 | } | ||
241 | |||
242 | |||
243 | static void out1str(const char *s) | ||
244 | { | ||
245 | if ( s ) | ||
246 | fputs(s, stdout); | ||
247 | } | ||
248 | |||
249 | static void beep(void) | ||
250 | { | ||
251 | putchar('\007'); | ||
252 | } | ||
253 | |||
254 | /* Move back one character */ | ||
255 | /* special for slow terminal */ | ||
256 | static void input_backward(int num) | ||
257 | { | ||
258 | if (num > cursor) | ||
259 | num = cursor; | ||
260 | cursor -= num; /* new cursor (in command, not terminal) */ | ||
261 | |||
262 | if (cmdedit_x >= num) { /* no to up line */ | ||
263 | cmdedit_x -= num; | ||
264 | if (num < 4) | ||
265 | while (num-- > 0) | ||
266 | putchar('\b'); | ||
267 | else | ||
268 | printf("\033[%dD", num); | ||
269 | } else { | ||
270 | int count_y; | ||
271 | |||
272 | if (cmdedit_x) { | ||
273 | putchar('\r'); /* back to first terminal pos. */ | ||
274 | num -= cmdedit_x; /* set previous backward */ | ||
275 | } | ||
276 | count_y = 1 + num / cmdedit_termw; | ||
277 | printf("\033[%dA", count_y); | ||
278 | cmdedit_y -= count_y; | ||
279 | /* require forward after uping */ | ||
280 | cmdedit_x = cmdedit_termw * count_y - num; | ||
281 | printf("\033[%dC", cmdedit_x); /* set term cursor */ | ||
282 | } | ||
283 | } | ||
284 | |||
285 | static void put_prompt(void) | ||
286 | { | ||
287 | out1str(cmdedit_prompt); | ||
288 | cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */ | ||
289 | cursor = 0; | ||
290 | cmdedit_y = 0; /* new quasireal y */ | ||
291 | } | ||
292 | |||
293 | #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT | ||
294 | static void parse_prompt(const char *prmt_ptr) | ||
295 | { | ||
296 | cmdedit_prompt = prmt_ptr; | ||
297 | cmdedit_prmt_len = strlen(prmt_ptr); | ||
298 | put_prompt(); | ||
299 | } | ||
300 | #else | ||
301 | static void parse_prompt(const char *prmt_ptr) | ||
302 | { | ||
303 | int prmt_len = 0; | ||
304 | size_t cur_prmt_len = 0; | ||
305 | char flg_not_length = '['; | ||
306 | char *prmt_mem_ptr = xzalloc(1); | ||
307 | char *pwd_buf = xgetcwd(0); | ||
308 | char buf2[PATH_MAX + 1]; | ||
309 | char buf[2]; | ||
310 | char c; | ||
311 | char *pbuf; | ||
312 | |||
313 | if (!pwd_buf) { | ||
314 | pwd_buf=(char *)bb_msg_unknown; | ||
315 | } | ||
316 | |||
317 | while (*prmt_ptr) { | ||
318 | pbuf = buf; | ||
319 | pbuf[1] = 0; | ||
320 | c = *prmt_ptr++; | ||
321 | if (c == '\\') { | ||
322 | const char *cp = prmt_ptr; | ||
323 | int l; | ||
324 | |||
325 | c = bb_process_escape_sequence(&prmt_ptr); | ||
326 | if(prmt_ptr==cp) { | ||
327 | if (*cp == 0) | ||
328 | break; | ||
329 | c = *prmt_ptr++; | ||
330 | switch (c) { | ||
331 | #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR | ||
332 | case 'u': | ||
333 | pbuf = user_buf; | ||
334 | break; | ||
335 | #endif | ||
336 | case 'h': | ||
337 | pbuf = hostname_buf; | ||
338 | if (pbuf == 0) { | ||
339 | pbuf = xzalloc(256); | ||
340 | if (gethostname(pbuf, 255) < 0) { | ||
341 | strcpy(pbuf, "?"); | ||
342 | } else { | ||
343 | char *s = strchr(pbuf, '.'); | ||
344 | |||
345 | if (s) | ||
346 | *s = 0; | ||
347 | } | ||
348 | hostname_buf = pbuf; | ||
349 | } | ||
350 | break; | ||
351 | case '$': | ||
352 | c = my_euid == 0 ? '#' : '$'; | ||
353 | break; | ||
354 | #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR | ||
355 | case 'w': | ||
356 | pbuf = pwd_buf; | ||
357 | l = strlen(home_pwd_buf); | ||
358 | if (home_pwd_buf[0] != 0 && | ||
359 | strncmp(home_pwd_buf, pbuf, l) == 0 && | ||
360 | (pbuf[l]=='/' || pbuf[l]=='\0') && | ||
361 | strlen(pwd_buf+l)<PATH_MAX) { | ||
362 | pbuf = buf2; | ||
363 | *pbuf = '~'; | ||
364 | strcpy(pbuf+1, pwd_buf+l); | ||
365 | } | ||
366 | break; | ||
367 | #endif | ||
368 | case 'W': | ||
369 | pbuf = pwd_buf; | ||
370 | cp = strrchr(pbuf,'/'); | ||
371 | if ( (cp != NULL) && (cp != pbuf) ) | ||
372 | pbuf += (cp-pbuf)+1; | ||
373 | break; | ||
374 | case '!': | ||
375 | snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines); | ||
376 | break; | ||
377 | case 'e': case 'E': /* \e \E = \033 */ | ||
378 | c = '\033'; | ||
379 | break; | ||
380 | case 'x': case 'X': | ||
381 | for (l = 0; l < 3;) { | ||
382 | int h; | ||
383 | buf2[l++] = *prmt_ptr; | ||
384 | buf2[l] = 0; | ||
385 | h = strtol(buf2, &pbuf, 16); | ||
386 | if (h > UCHAR_MAX || (pbuf - buf2) < l) { | ||
387 | l--; | ||
388 | break; | ||
389 | } | ||
390 | prmt_ptr++; | ||
391 | } | ||
392 | buf2[l] = 0; | ||
393 | c = (char)strtol(buf2, 0, 16); | ||
394 | if(c==0) | ||
395 | c = '?'; | ||
396 | pbuf = buf; | ||
397 | break; | ||
398 | case '[': case ']': | ||
399 | if (c == flg_not_length) { | ||
400 | flg_not_length = flg_not_length == '[' ? ']' : '['; | ||
401 | continue; | ||
402 | } | ||
403 | break; | ||
404 | } | ||
405 | } | ||
406 | } | ||
407 | if(pbuf == buf) | ||
408 | *pbuf = c; | ||
409 | cur_prmt_len = strlen(pbuf); | ||
410 | prmt_len += cur_prmt_len; | ||
411 | if (flg_not_length != ']') | ||
412 | cmdedit_prmt_len += cur_prmt_len; | ||
413 | prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf); | ||
414 | } | ||
415 | if(pwd_buf!=(char *)bb_msg_unknown) | ||
416 | free(pwd_buf); | ||
417 | cmdedit_prompt = prmt_mem_ptr; | ||
418 | put_prompt(); | ||
419 | } | ||
420 | #endif | ||
421 | |||
422 | |||
423 | /* draw prompt, editor line, and clear tail */ | ||
424 | static void redraw(int y, int back_cursor) | ||
425 | { | ||
426 | if (y > 0) /* up to start y */ | ||
427 | printf("\033[%dA", y); | ||
428 | putchar('\r'); | ||
429 | put_prompt(); | ||
430 | input_end(); /* rewrite */ | ||
431 | printf("\033[J"); /* destroy tail after cursor */ | ||
432 | input_backward(back_cursor); | ||
433 | } | ||
434 | |||
435 | #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI | ||
436 | #define DELBUFSIZ 128 | ||
437 | static char *delbuf; /* a (malloced) place to store deleted characters */ | ||
438 | static char *delp; | ||
439 | static char newdelflag; /* whether delbuf should be reused yet */ | ||
440 | #endif | ||
441 | |||
442 | /* Delete the char in front of the cursor, optionally saving it | ||
443 | * for later putback */ | ||
444 | static void input_delete(int save) | ||
445 | { | ||
446 | int j = cursor; | ||
447 | |||
448 | if (j == len) | ||
449 | return; | ||
450 | |||
451 | #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI | ||
452 | if (save) { | ||
453 | if (newdelflag) { | ||
454 | if (!delbuf) | ||
455 | delbuf = malloc(DELBUFSIZ); | ||
456 | /* safe if malloc fails */ | ||
457 | delp = delbuf; | ||
458 | newdelflag = 0; | ||
459 | } | ||
460 | if (delbuf && (delp - delbuf < DELBUFSIZ)) | ||
461 | *delp++ = command_ps[j]; | ||
462 | } | ||
463 | #endif | ||
464 | |||
465 | strcpy(command_ps + j, command_ps + j + 1); | ||
466 | len--; | ||
467 | input_end(); /* rewrite new line */ | ||
468 | cmdedit_set_out_char(0); /* destroy end char */ | ||
469 | input_backward(cursor - j); /* back to old pos cursor */ | ||
470 | } | ||
471 | |||
472 | #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI | ||
473 | static void put(void) | ||
474 | { | ||
475 | int ocursor, j = delp - delbuf; | ||
476 | if (j == 0) | ||
477 | return; | ||
478 | ocursor = cursor; | ||
479 | /* open hole and then fill it */ | ||
480 | memmove(command_ps + cursor + j, command_ps + cursor, len - cursor + 1); | ||
481 | strncpy(command_ps + cursor, delbuf, j); | ||
482 | len += j; | ||
483 | input_end(); /* rewrite new line */ | ||
484 | input_backward(cursor-ocursor-j+1); /* at end of new text */ | ||
485 | } | ||
486 | #endif | ||
487 | |||
488 | /* Delete the char in back of the cursor */ | ||
489 | static void input_backspace(void) | ||
490 | { | ||
491 | if (cursor > 0) { | ||
492 | input_backward(1); | ||
493 | input_delete(0); | ||
494 | } | ||
495 | } | ||
496 | |||
497 | |||
498 | /* Move forward one character */ | ||
499 | static void input_forward(void) | ||
500 | { | ||
501 | if (cursor < len) | ||
502 | cmdedit_set_out_char(command_ps[cursor + 1]); | ||
503 | } | ||
504 | |||
505 | static void cmdedit_setwidth(int w, int redraw_flg) | ||
506 | { | ||
507 | cmdedit_termw = cmdedit_prmt_len + 2; | ||
508 | if (w <= cmdedit_termw) { | ||
509 | cmdedit_termw = cmdedit_termw % w; | ||
510 | } | ||
511 | if (w > cmdedit_termw) { | ||
512 | cmdedit_termw = w; | ||
513 | |||
514 | if (redraw_flg) { | ||
515 | /* new y for current cursor */ | ||
516 | int new_y = (cursor + cmdedit_prmt_len) / w; | ||
517 | |||
518 | /* redraw */ | ||
519 | redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor); | ||
520 | fflush(stdout); | ||
521 | } | ||
522 | } | ||
523 | } | ||
524 | |||
525 | static void cmdedit_init(void) | ||
526 | { | ||
527 | cmdedit_prmt_len = 0; | ||
528 | if ((handlers_sets & SET_WCHG_HANDLERS) == 0) { | ||
529 | /* emulate usage handler to set handler and call yours work */ | ||
530 | win_changed(-SIGWINCH); | ||
531 | handlers_sets |= SET_WCHG_HANDLERS; | ||
532 | } | ||
533 | |||
534 | if ((handlers_sets & SET_ATEXIT) == 0) { | ||
535 | #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR | ||
536 | struct passwd *entry; | ||
537 | |||
538 | my_euid = geteuid(); | ||
539 | entry = getpwuid(my_euid); | ||
540 | if (entry) { | ||
541 | user_buf = xstrdup(entry->pw_name); | ||
542 | home_pwd_buf = xstrdup(entry->pw_dir); | ||
543 | } | ||
544 | #endif | ||
545 | |||
546 | #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION | ||
547 | |||
548 | #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR | ||
549 | my_euid = geteuid(); | ||
550 | #endif | ||
551 | my_uid = getuid(); | ||
552 | my_gid = getgid(); | ||
553 | #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */ | ||
554 | handlers_sets |= SET_ATEXIT; | ||
555 | atexit(cmdedit_reset_term); /* be sure to do this only once */ | ||
556 | } | ||
557 | } | ||
558 | |||
559 | #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION | ||
560 | |||
561 | static char **matches; | ||
562 | static int num_matches; | ||
563 | static char *add_char_to_match; | ||
564 | |||
565 | static void add_match(char *matched, int add_char) | ||
566 | { | ||
567 | int nm = num_matches; | ||
568 | int nm1 = nm + 1; | ||
569 | |||
570 | matches = xrealloc(matches, nm1 * sizeof(char *)); | ||
571 | add_char_to_match = xrealloc(add_char_to_match, nm1); | ||
572 | matches[nm] = matched; | ||
573 | add_char_to_match[nm] = (char)add_char; | ||
574 | num_matches++; | ||
575 | } | ||
576 | |||
577 | static int is_execute(const struct stat *st) | ||
578 | { | ||
579 | if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) || | ||
580 | (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) || | ||
581 | (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) || | ||
582 | (st->st_mode & S_IXOTH)) return TRUE; | ||
583 | return FALSE; | ||
584 | } | ||
585 | |||
586 | #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION | ||
587 | |||
588 | static void username_tab_completion(char *ud, char *with_shash_flg) | ||
589 | { | ||
590 | struct passwd *entry; | ||
591 | int userlen; | ||
592 | |||
593 | ud++; /* ~user/... to user/... */ | ||
594 | userlen = strlen(ud); | ||
595 | |||
596 | if (with_shash_flg) { /* "~/..." or "~user/..." */ | ||
597 | char *sav_ud = ud - 1; | ||
598 | char *home = 0; | ||
599 | char *temp; | ||
600 | |||
601 | if (*ud == '/') { /* "~/..." */ | ||
602 | home = home_pwd_buf; | ||
603 | } else { | ||
604 | /* "~user/..." */ | ||
605 | temp = strchr(ud, '/'); | ||
606 | *temp = 0; /* ~user\0 */ | ||
607 | entry = getpwnam(ud); | ||
608 | *temp = '/'; /* restore ~user/... */ | ||
609 | ud = temp; | ||
610 | if (entry) | ||
611 | home = entry->pw_dir; | ||
612 | } | ||
613 | if (home) { | ||
614 | if ((userlen + strlen(home) + 1) < BUFSIZ) { | ||
615 | char temp2[BUFSIZ]; /* argument size */ | ||
616 | |||
617 | /* /home/user/... */ | ||
618 | sprintf(temp2, "%s%s", home, ud); | ||
619 | strcpy(sav_ud, temp2); | ||
620 | } | ||
621 | } | ||
622 | } else { | ||
623 | /* "~[^/]*" */ | ||
624 | setpwent(); | ||
625 | |||
626 | while ((entry = getpwent()) != NULL) { | ||
627 | /* Null usernames should result in all users as possible completions. */ | ||
628 | if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) { | ||
629 | add_match(xasprintf("~%s", entry->pw_name), '/'); | ||
630 | } | ||
631 | } | ||
632 | |||
633 | endpwent(); | ||
634 | } | ||
635 | } | ||
636 | #endif /* CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION */ | ||
637 | |||
638 | enum { | ||
639 | FIND_EXE_ONLY = 0, | ||
640 | FIND_DIR_ONLY = 1, | ||
641 | FIND_FILE_ONLY = 2, | ||
642 | }; | ||
643 | |||
644 | #ifdef CONFIG_ASH | ||
645 | const char *cmdedit_path_lookup; | ||
646 | #else | ||
647 | #define cmdedit_path_lookup getenv("PATH") | ||
648 | #endif | ||
649 | |||
650 | static int path_parse(char ***p, int flags) | ||
651 | { | ||
652 | int npth; | ||
653 | const char *tmp; | ||
654 | const char *pth; | ||
655 | |||
656 | /* if not setenv PATH variable, to search cur dir "." */ | ||
657 | if (flags != FIND_EXE_ONLY || (pth = cmdedit_path_lookup) == 0 || | ||
658 | /* PATH=<empty> or PATH=:<empty> */ | ||
659 | *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) { | ||
660 | return 1; | ||
661 | } | ||
662 | |||
663 | tmp = pth; | ||
664 | npth = 0; | ||
665 | |||
666 | for (;;) { | ||
667 | npth++; /* count words is + 1 count ':' */ | ||
668 | tmp = strchr(tmp, ':'); | ||
669 | if (tmp) { | ||
670 | if (*++tmp == 0) | ||
671 | break; /* :<empty> */ | ||
672 | } else | ||
673 | break; | ||
674 | } | ||
675 | |||
676 | *p = xmalloc(npth * sizeof(char *)); | ||
677 | |||
678 | tmp = pth; | ||
679 | (*p)[0] = xstrdup(tmp); | ||
680 | npth = 1; /* count words is + 1 count ':' */ | ||
681 | |||
682 | for (;;) { | ||
683 | tmp = strchr(tmp, ':'); | ||
684 | if (tmp) { | ||
685 | (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */ | ||
686 | if (*++tmp == 0) | ||
687 | break; /* :<empty> */ | ||
688 | } else | ||
689 | break; | ||
690 | (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */ | ||
691 | } | ||
692 | |||
693 | return npth; | ||
694 | } | ||
695 | |||
696 | static char *add_quote_for_spec_chars(char *found, int add) | ||
697 | { | ||
698 | int l = 0; | ||
699 | char *s = xmalloc((strlen(found) + 1) * 2); | ||
700 | |||
701 | while (*found) { | ||
702 | if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found)) | ||
703 | s[l++] = '\\'; | ||
704 | s[l++] = *found++; | ||
705 | } | ||
706 | if(add) | ||
707 | s[l++] = (char)add; | ||
708 | s[l] = 0; | ||
709 | return s; | ||
710 | } | ||
711 | |||
712 | static void exe_n_cwd_tab_completion(char *command, int type) | ||
713 | { | ||
714 | DIR *dir; | ||
715 | struct dirent *next; | ||
716 | char dirbuf[BUFSIZ]; | ||
717 | struct stat st; | ||
718 | char *path1[1]; | ||
719 | char **paths = path1; | ||
720 | int npaths; | ||
721 | int i; | ||
722 | char *found; | ||
723 | char *pfind = strrchr(command, '/'); | ||
724 | |||
725 | path1[0] = "."; | ||
726 | |||
727 | if (pfind == NULL) { | ||
728 | /* no dir, if flags==EXE_ONLY - get paths, else "." */ | ||
729 | npaths = path_parse(&paths, type); | ||
730 | pfind = command; | ||
731 | } else { | ||
732 | /* with dir */ | ||
733 | /* save for change */ | ||
734 | strcpy(dirbuf, command); | ||
735 | /* set dir only */ | ||
736 | dirbuf[(pfind - command) + 1] = 0; | ||
737 | #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION | ||
738 | if (dirbuf[0] == '~') /* ~/... or ~user/... */ | ||
739 | username_tab_completion(dirbuf, dirbuf); | ||
740 | #endif | ||
741 | /* "strip" dirname in command */ | ||
742 | pfind++; | ||
743 | |||
744 | paths[0] = dirbuf; | ||
745 | npaths = 1; /* only 1 dir */ | ||
746 | } | ||
747 | |||
748 | for (i = 0; i < npaths; i++) { | ||
749 | |||
750 | dir = opendir(paths[i]); | ||
751 | if (!dir) /* Don't print an error */ | ||
752 | continue; | ||
753 | |||
754 | while ((next = readdir(dir)) != NULL) { | ||
755 | char *str_found = next->d_name; | ||
756 | int add_chr = 0; | ||
757 | |||
758 | /* matched ? */ | ||
759 | if (strncmp(str_found, pfind, strlen(pfind))) | ||
760 | continue; | ||
761 | /* not see .name without .match */ | ||
762 | if (*str_found == '.' && *pfind == 0) { | ||
763 | if (*paths[i] == '/' && paths[i][1] == 0 | ||
764 | && str_found[1] == 0) str_found = ""; /* only "/" */ | ||
765 | else | ||
766 | continue; | ||
767 | } | ||
768 | found = concat_path_file(paths[i], str_found); | ||
769 | /* hmm, remover in progress? */ | ||
770 | if (stat(found, &st) < 0) | ||
771 | goto cont; | ||
772 | /* find with dirs ? */ | ||
773 | if (paths[i] != dirbuf) | ||
774 | strcpy(found, next->d_name); /* only name */ | ||
775 | if (S_ISDIR(st.st_mode)) { | ||
776 | /* name is directory */ | ||
777 | char *e = found + strlen(found) - 1; | ||
778 | |||
779 | add_chr = '/'; | ||
780 | if(*e == '/') | ||
781 | *e = '\0'; | ||
782 | } else { | ||
783 | /* not put found file if search only dirs for cd */ | ||
784 | if (type == FIND_DIR_ONLY) | ||
785 | goto cont; | ||
786 | if (type == FIND_FILE_ONLY || | ||
787 | (type == FIND_EXE_ONLY && is_execute(&st))) | ||
788 | add_chr = ' '; | ||
789 | } | ||
790 | /* Add it to the list */ | ||
791 | add_match(found, add_chr); | ||
792 | continue; | ||
793 | cont: | ||
794 | free(found); | ||
795 | } | ||
796 | closedir(dir); | ||
797 | } | ||
798 | if (paths != path1) { | ||
799 | free(paths[0]); /* allocated memory only in first member */ | ||
800 | free(paths); | ||
801 | } | ||
802 | } | ||
803 | |||
804 | |||
805 | #define QUOT (UCHAR_MAX+1) | ||
806 | |||
807 | #define collapse_pos(is, in) { \ | ||
808 | memmove(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \ | ||
809 | memmove(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); } | ||
810 | |||
811 | static int find_match(char *matchBuf, int *len_with_quotes) | ||
812 | { | ||
813 | int i, j; | ||
814 | int command_mode; | ||
815 | int c, c2; | ||
816 | int int_buf[BUFSIZ + 1]; | ||
817 | int pos_buf[BUFSIZ + 1]; | ||
818 | |||
819 | /* set to integer dimension characters and own positions */ | ||
820 | for (i = 0;; i++) { | ||
821 | int_buf[i] = (int) ((unsigned char) matchBuf[i]); | ||
822 | if (int_buf[i] == 0) { | ||
823 | pos_buf[i] = -1; /* indicator end line */ | ||
824 | break; | ||
825 | } else | ||
826 | pos_buf[i] = i; | ||
827 | } | ||
828 | |||
829 | /* mask \+symbol and convert '\t' to ' ' */ | ||
830 | for (i = j = 0; matchBuf[i]; i++, j++) | ||
831 | if (matchBuf[i] == '\\') { | ||
832 | collapse_pos(j, j + 1); | ||
833 | int_buf[j] |= QUOT; | ||
834 | i++; | ||
835 | #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT | ||
836 | if (matchBuf[i] == '\t') /* algorithm equivalent */ | ||
837 | int_buf[j] = ' ' | QUOT; | ||
838 | #endif | ||
839 | } | ||
840 | #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT | ||
841 | else if (matchBuf[i] == '\t') | ||
842 | int_buf[j] = ' '; | ||
843 | #endif | ||
844 | |||
845 | /* mask "symbols" or 'symbols' */ | ||
846 | c2 = 0; | ||
847 | for (i = 0; int_buf[i]; i++) { | ||
848 | c = int_buf[i]; | ||
849 | if (c == '\'' || c == '"') { | ||
850 | if (c2 == 0) | ||
851 | c2 = c; | ||
852 | else { | ||
853 | if (c == c2) | ||
854 | c2 = 0; | ||
855 | else | ||
856 | int_buf[i] |= QUOT; | ||
857 | } | ||
858 | } else if (c2 != 0 && c != '$') | ||
859 | int_buf[i] |= QUOT; | ||
860 | } | ||
861 | |||
862 | /* skip commands with arguments if line have commands delimiters */ | ||
863 | /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */ | ||
864 | for (i = 0; int_buf[i]; i++) { | ||
865 | c = int_buf[i]; | ||
866 | c2 = int_buf[i + 1]; | ||
867 | j = i ? int_buf[i - 1] : -1; | ||
868 | command_mode = 0; | ||
869 | if (c == ';' || c == '&' || c == '|') { | ||
870 | command_mode = 1 + (c == c2); | ||
871 | if (c == '&') { | ||
872 | if (j == '>' || j == '<') | ||
873 | command_mode = 0; | ||
874 | } else if (c == '|' && j == '>') | ||
875 | command_mode = 0; | ||
876 | } | ||
877 | if (command_mode) { | ||
878 | collapse_pos(0, i + command_mode); | ||
879 | i = -1; /* hack incremet */ | ||
880 | } | ||
881 | } | ||
882 | /* collapse `command...` */ | ||
883 | for (i = 0; int_buf[i]; i++) | ||
884 | if (int_buf[i] == '`') { | ||
885 | for (j = i + 1; int_buf[j]; j++) | ||
886 | if (int_buf[j] == '`') { | ||
887 | collapse_pos(i, j + 1); | ||
888 | j = 0; | ||
889 | break; | ||
890 | } | ||
891 | if (j) { | ||
892 | /* not found close ` - command mode, collapse all previous */ | ||
893 | collapse_pos(0, i + 1); | ||
894 | break; | ||
895 | } else | ||
896 | i--; /* hack incremet */ | ||
897 | } | ||
898 | |||
899 | /* collapse (command...(command...)...) or {command...{command...}...} */ | ||
900 | c = 0; /* "recursive" level */ | ||
901 | c2 = 0; | ||
902 | for (i = 0; int_buf[i]; i++) | ||
903 | if (int_buf[i] == '(' || int_buf[i] == '{') { | ||
904 | if (int_buf[i] == '(') | ||
905 | c++; | ||
906 | else | ||
907 | c2++; | ||
908 | collapse_pos(0, i + 1); | ||
909 | i = -1; /* hack incremet */ | ||
910 | } | ||
911 | for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++) | ||
912 | if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) { | ||
913 | if (int_buf[i] == ')') | ||
914 | c--; | ||
915 | else | ||
916 | c2--; | ||
917 | collapse_pos(0, i + 1); | ||
918 | i = -1; /* hack incremet */ | ||
919 | } | ||
920 | |||
921 | /* skip first not quote space */ | ||
922 | for (i = 0; int_buf[i]; i++) | ||
923 | if (int_buf[i] != ' ') | ||
924 | break; | ||
925 | if (i) | ||
926 | collapse_pos(0, i); | ||
927 | |||
928 | /* set find mode for completion */ | ||
929 | command_mode = FIND_EXE_ONLY; | ||
930 | for (i = 0; int_buf[i]; i++) | ||
931 | if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') { | ||
932 | if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY | ||
933 | && matchBuf[pos_buf[0]]=='c' | ||
934 | && matchBuf[pos_buf[1]]=='d' ) | ||
935 | command_mode = FIND_DIR_ONLY; | ||
936 | else { | ||
937 | command_mode = FIND_FILE_ONLY; | ||
938 | break; | ||
939 | } | ||
940 | } | ||
941 | /* "strlen" */ | ||
942 | for (i = 0; int_buf[i]; i++); | ||
943 | /* find last word */ | ||
944 | for (--i; i >= 0; i--) { | ||
945 | c = int_buf[i]; | ||
946 | if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') { | ||
947 | collapse_pos(0, i + 1); | ||
948 | break; | ||
949 | } | ||
950 | } | ||
951 | /* skip first not quoted '\'' or '"' */ | ||
952 | for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++); | ||
953 | /* collapse quote or unquote // or /~ */ | ||
954 | while ((int_buf[i] & ~QUOT) == '/' && | ||
955 | ((int_buf[i + 1] & ~QUOT) == '/' | ||
956 | || (int_buf[i + 1] & ~QUOT) == '~')) { | ||
957 | i++; | ||
958 | } | ||
959 | |||
960 | /* set only match and destroy quotes */ | ||
961 | j = 0; | ||
962 | for (c = 0; pos_buf[i] >= 0; i++) { | ||
963 | matchBuf[c++] = matchBuf[pos_buf[i]]; | ||
964 | j = pos_buf[i] + 1; | ||
965 | } | ||
966 | matchBuf[c] = 0; | ||
967 | /* old lenght matchBuf with quotes symbols */ | ||
968 | *len_with_quotes = j ? j - pos_buf[0] : 0; | ||
969 | |||
970 | return command_mode; | ||
971 | } | ||
972 | |||
973 | /* | ||
974 | display by column original ideas from ls applet, | ||
975 | very optimize by my :) | ||
976 | */ | ||
977 | static void showfiles(void) | ||
978 | { | ||
979 | int ncols, row; | ||
980 | int column_width = 0; | ||
981 | int nfiles = num_matches; | ||
982 | int nrows = nfiles; | ||
983 | char str_add_chr[2]; | ||
984 | int l; | ||
985 | |||
986 | /* find the longest file name- use that as the column width */ | ||
987 | for (row = 0; row < nrows; row++) { | ||
988 | l = strlen(matches[row]); | ||
989 | if(add_char_to_match[row]) | ||
990 | l++; | ||
991 | if (column_width < l) | ||
992 | column_width = l; | ||
993 | } | ||
994 | column_width += 2; /* min space for columns */ | ||
995 | ncols = cmdedit_termw / column_width; | ||
996 | |||
997 | if (ncols > 1) { | ||
998 | nrows /= ncols; | ||
999 | if(nfiles % ncols) | ||
1000 | nrows++; /* round up fractionals */ | ||
1001 | } else { | ||
1002 | ncols = 1; | ||
1003 | } | ||
1004 | str_add_chr[1] = 0; | ||
1005 | for (row = 0; row < nrows; row++) { | ||
1006 | int n = row; | ||
1007 | int nc; | ||
1008 | int acol; | ||
1009 | |||
1010 | for(nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) { | ||
1011 | str_add_chr[0] = add_char_to_match[n]; | ||
1012 | acol = str_add_chr[0] ? column_width - 1 : column_width; | ||
1013 | printf("%s%s", matches[n], str_add_chr); | ||
1014 | l = strlen(matches[n]); | ||
1015 | while(l < acol) { | ||
1016 | putchar(' '); | ||
1017 | l++; | ||
1018 | } | ||
1019 | } | ||
1020 | str_add_chr[0] = add_char_to_match[n]; | ||
1021 | printf("%s%s\n", matches[n], str_add_chr); | ||
1022 | } | ||
1023 | } | ||
1024 | |||
1025 | |||
1026 | static void input_tab(int *lastWasTab) | ||
1027 | { | ||
1028 | /* Do TAB completion */ | ||
1029 | if (lastWasTab == 0) { /* free all memory */ | ||
1030 | if (matches) { | ||
1031 | while (num_matches > 0) | ||
1032 | free(matches[--num_matches]); | ||
1033 | free(matches); | ||
1034 | matches = (char **) NULL; | ||
1035 | free(add_char_to_match); | ||
1036 | add_char_to_match = NULL; | ||
1037 | } | ||
1038 | return; | ||
1039 | } | ||
1040 | if (! *lastWasTab) { | ||
1041 | |||
1042 | char *tmp, *tmp1; | ||
1043 | int len_found; | ||
1044 | char matchBuf[BUFSIZ]; | ||
1045 | int find_type; | ||
1046 | int recalc_pos; | ||
1047 | |||
1048 | *lastWasTab = TRUE; /* flop trigger */ | ||
1049 | |||
1050 | /* Make a local copy of the string -- up | ||
1051 | * to the position of the cursor */ | ||
1052 | tmp = strncpy(matchBuf, command_ps, cursor); | ||
1053 | tmp[cursor] = 0; | ||
1054 | |||
1055 | find_type = find_match(matchBuf, &recalc_pos); | ||
1056 | |||
1057 | /* Free up any memory already allocated */ | ||
1058 | input_tab(0); | ||
1059 | |||
1060 | #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION | ||
1061 | /* If the word starts with `~' and there is no slash in the word, | ||
1062 | * then try completing this word as a username. */ | ||
1063 | |||
1064 | if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0) | ||
1065 | username_tab_completion(matchBuf, NULL); | ||
1066 | if (!matches) | ||
1067 | #endif | ||
1068 | /* Try to match any executable in our path and everything | ||
1069 | * in the current working directory that matches. */ | ||
1070 | exe_n_cwd_tab_completion(matchBuf, find_type); | ||
1071 | /* Remove duplicate found and sort */ | ||
1072 | if(matches) { | ||
1073 | int i, j, n, srt; | ||
1074 | /* bubble */ | ||
1075 | n = num_matches; | ||
1076 | for(i=0; i<(n-1); i++) { | ||
1077 | for(j=i+1; j<n; j++) { | ||
1078 | if(matches[i]!=NULL && matches[j]!=NULL) { | ||
1079 | srt = strcmp(matches[i], matches[j]); | ||
1080 | if(srt == 0) { | ||
1081 | free(matches[j]); | ||
1082 | matches[j]=0; | ||
1083 | } else if(srt > 0) { | ||
1084 | tmp1 = matches[i]; | ||
1085 | matches[i] = matches[j]; | ||
1086 | matches[j] = tmp1; | ||
1087 | srt = add_char_to_match[i]; | ||
1088 | add_char_to_match[i] = add_char_to_match[j]; | ||
1089 | add_char_to_match[j] = srt; | ||
1090 | } | ||
1091 | } | ||
1092 | } | ||
1093 | } | ||
1094 | j = n; | ||
1095 | n = 0; | ||
1096 | for(i=0; i<j; i++) | ||
1097 | if(matches[i]) { | ||
1098 | matches[n]=matches[i]; | ||
1099 | add_char_to_match[n]=add_char_to_match[i]; | ||
1100 | n++; | ||
1101 | } | ||
1102 | num_matches = n; | ||
1103 | } | ||
1104 | /* Did we find exactly one match? */ | ||
1105 | if (!matches || num_matches > 1) { | ||
1106 | |||
1107 | beep(); | ||
1108 | if (!matches) | ||
1109 | return; /* not found */ | ||
1110 | /* find minimal match */ | ||
1111 | tmp1 = xstrdup(matches[0]); | ||
1112 | for (tmp = tmp1; *tmp; tmp++) | ||
1113 | for (len_found = 1; len_found < num_matches; len_found++) | ||
1114 | if (matches[len_found][(tmp - tmp1)] != *tmp) { | ||
1115 | *tmp = 0; | ||
1116 | break; | ||
1117 | } | ||
1118 | if (*tmp1 == 0) { /* have unique */ | ||
1119 | free(tmp1); | ||
1120 | return; | ||
1121 | } | ||
1122 | tmp = add_quote_for_spec_chars(tmp1, 0); | ||
1123 | free(tmp1); | ||
1124 | } else { /* one match */ | ||
1125 | tmp = add_quote_for_spec_chars(matches[0], add_char_to_match[0]); | ||
1126 | /* for next completion current found */ | ||
1127 | *lastWasTab = FALSE; | ||
1128 | } | ||
1129 | len_found = strlen(tmp); | ||
1130 | /* have space to placed match? */ | ||
1131 | if ((len_found - strlen(matchBuf) + len) < BUFSIZ) { | ||
1132 | |||
1133 | /* before word for match */ | ||
1134 | command_ps[cursor - recalc_pos] = 0; | ||
1135 | /* save tail line */ | ||
1136 | strcpy(matchBuf, command_ps + cursor); | ||
1137 | /* add match */ | ||
1138 | strcat(command_ps, tmp); | ||
1139 | /* add tail */ | ||
1140 | strcat(command_ps, matchBuf); | ||
1141 | /* back to begin word for match */ | ||
1142 | input_backward(recalc_pos); | ||
1143 | /* new pos */ | ||
1144 | recalc_pos = cursor + len_found; | ||
1145 | /* new len */ | ||
1146 | len = strlen(command_ps); | ||
1147 | /* write out the matched command */ | ||
1148 | redraw(cmdedit_y, len - recalc_pos); | ||
1149 | } | ||
1150 | free(tmp); | ||
1151 | } else { | ||
1152 | /* Ok -- the last char was a TAB. Since they | ||
1153 | * just hit TAB again, print a list of all the | ||
1154 | * available choices... */ | ||
1155 | if (matches && num_matches > 0) { | ||
1156 | int sav_cursor = cursor; /* change goto_new_line() */ | ||
1157 | |||
1158 | /* Go to the next line */ | ||
1159 | goto_new_line(); | ||
1160 | showfiles(); | ||
1161 | redraw(0, len - sav_cursor); | ||
1162 | } | ||
1163 | } | ||
1164 | } | ||
1165 | #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */ | ||
1166 | |||
1167 | #if MAX_HISTORY > 0 | ||
1168 | static void get_previous_history(void) | ||
1169 | { | ||
1170 | if(command_ps[0] != 0 || history[cur_history] == 0) { | ||
1171 | free(history[cur_history]); | ||
1172 | history[cur_history] = xstrdup(command_ps); | ||
1173 | } | ||
1174 | cur_history--; | ||
1175 | } | ||
1176 | |||
1177 | static int get_next_history(void) | ||
1178 | { | ||
1179 | int ch = cur_history; | ||
1180 | |||
1181 | if (ch < n_history) { | ||
1182 | get_previous_history(); /* save the current history line */ | ||
1183 | cur_history = ch + 1; | ||
1184 | return cur_history; | ||
1185 | } else { | ||
1186 | beep(); | ||
1187 | return 0; | ||
1188 | } | ||
1189 | } | ||
1190 | |||
1191 | #ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY | ||
1192 | void load_history ( const char *fromfile ) | ||
1193 | { | ||
1194 | FILE *fp; | ||
1195 | int hi; | ||
1196 | |||
1197 | /* cleanup old */ | ||
1198 | |||
1199 | for(hi = n_history; hi > 0; ) { | ||
1200 | hi--; | ||
1201 | free ( history [hi] ); | ||
1202 | } | ||
1203 | |||
1204 | if (( fp = fopen ( fromfile, "r" ))) { | ||
1205 | |||
1206 | for ( hi = 0; hi < MAX_HISTORY; ) { | ||
1207 | char * hl = xmalloc_getline(fp); | ||
1208 | int l; | ||
1209 | |||
1210 | if(!hl) | ||
1211 | break; | ||
1212 | l = strlen(hl); | ||
1213 | if(l >= BUFSIZ) | ||
1214 | hl[BUFSIZ-1] = 0; | ||
1215 | if(l == 0 || hl[0] == ' ') { | ||
1216 | free(hl); | ||
1217 | continue; | ||
1218 | } | ||
1219 | history [hi++] = hl; | ||
1220 | } | ||
1221 | fclose ( fp ); | ||
1222 | } | ||
1223 | cur_history = n_history = hi; | ||
1224 | } | ||
1225 | |||
1226 | void save_history ( const char *tofile ) | ||
1227 | { | ||
1228 | FILE *fp = fopen ( tofile, "w" ); | ||
1229 | |||
1230 | if ( fp ) { | ||
1231 | int i; | ||
1232 | |||
1233 | for ( i = 0; i < n_history; i++ ) { | ||
1234 | fprintf(fp, "%s\n", history [i]); | ||
1235 | } | ||
1236 | fclose ( fp ); | ||
1237 | } | ||
1238 | } | ||
1239 | #endif | ||
1240 | |||
1241 | #endif | ||
1242 | |||
1243 | enum { | ||
1244 | ESC = 27, | ||
1245 | DEL = 127, | ||
1246 | }; | ||
1247 | |||
1248 | |||
1249 | /* | ||
1250 | * This function is used to grab a character buffer | ||
1251 | * from the input file descriptor and allows you to | ||
1252 | * a string with full command editing (sort of like | ||
1253 | * a mini readline). | ||
1254 | * | ||
1255 | * The following standard commands are not implemented: | ||
1256 | * ESC-b -- Move back one word | ||
1257 | * ESC-f -- Move forward one word | ||
1258 | * ESC-d -- Delete back one word | ||
1259 | * ESC-h -- Delete forward one word | ||
1260 | * CTL-t -- Transpose two characters | ||
1261 | * | ||
1262 | * Minimalist vi-style command line editing available if configured. | ||
1263 | * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us> | ||
1264 | * | ||
1265 | */ | ||
1266 | |||
1267 | #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI | ||
1268 | static int vi_mode; | ||
1269 | |||
1270 | void setvimode ( int viflag ) | ||
1271 | { | ||
1272 | vi_mode = viflag; | ||
1273 | } | ||
1274 | |||
1275 | static void | ||
1276 | vi_Word_motion(char *command, int eat) | ||
1277 | { | ||
1278 | while (cursor < len && !isspace(command[cursor])) | ||
1279 | input_forward(); | ||
1280 | if (eat) while (cursor < len && isspace(command[cursor])) | ||
1281 | input_forward(); | ||
1282 | } | ||
1283 | |||
1284 | static void | ||
1285 | vi_word_motion(char *command, int eat) | ||
1286 | { | ||
1287 | if (isalnum(command[cursor]) || command[cursor] == '_') { | ||
1288 | while (cursor < len && | ||
1289 | (isalnum(command[cursor+1]) || | ||
1290 | command[cursor+1] == '_')) | ||
1291 | input_forward(); | ||
1292 | } else if (ispunct(command[cursor])) { | ||
1293 | while (cursor < len && | ||
1294 | (ispunct(command[cursor+1]))) | ||
1295 | input_forward(); | ||
1296 | } | ||
1297 | |||
1298 | if (cursor < len) | ||
1299 | input_forward(); | ||
1300 | |||
1301 | if (eat && cursor < len && isspace(command[cursor])) | ||
1302 | while (cursor < len && isspace(command[cursor])) | ||
1303 | input_forward(); | ||
1304 | } | ||
1305 | |||
1306 | static void | ||
1307 | vi_End_motion(char *command) | ||
1308 | { | ||
1309 | input_forward(); | ||
1310 | while (cursor < len && isspace(command[cursor])) | ||
1311 | input_forward(); | ||
1312 | while (cursor < len-1 && !isspace(command[cursor+1])) | ||
1313 | input_forward(); | ||
1314 | } | ||
1315 | |||
1316 | static void | ||
1317 | vi_end_motion(char *command) | ||
1318 | { | ||
1319 | if (cursor >= len-1) | ||
1320 | return; | ||
1321 | input_forward(); | ||
1322 | while (cursor < len-1 && isspace(command[cursor])) | ||
1323 | input_forward(); | ||
1324 | if (cursor >= len-1) | ||
1325 | return; | ||
1326 | if (isalnum(command[cursor]) || command[cursor] == '_') { | ||
1327 | while (cursor < len-1 && | ||
1328 | (isalnum(command[cursor+1]) || | ||
1329 | command[cursor+1] == '_')) | ||
1330 | input_forward(); | ||
1331 | } else if (ispunct(command[cursor])) { | ||
1332 | while (cursor < len-1 && | ||
1333 | (ispunct(command[cursor+1]))) | ||
1334 | input_forward(); | ||
1335 | } | ||
1336 | } | ||
1337 | |||
1338 | static void | ||
1339 | vi_Back_motion(char *command) | ||
1340 | { | ||
1341 | while (cursor > 0 && isspace(command[cursor-1])) | ||
1342 | input_backward(1); | ||
1343 | while (cursor > 0 && !isspace(command[cursor-1])) | ||
1344 | input_backward(1); | ||
1345 | } | ||
1346 | |||
1347 | static void | ||
1348 | vi_back_motion(char *command) | ||
1349 | { | ||
1350 | if (cursor <= 0) | ||
1351 | return; | ||
1352 | input_backward(1); | ||
1353 | while (cursor > 0 && isspace(command[cursor])) | ||
1354 | input_backward(1); | ||
1355 | if (cursor <= 0) | ||
1356 | return; | ||
1357 | if (isalnum(command[cursor]) || command[cursor] == '_') { | ||
1358 | while (cursor > 0 && | ||
1359 | (isalnum(command[cursor-1]) || | ||
1360 | command[cursor-1] == '_')) | ||
1361 | input_backward(1); | ||
1362 | } else if (ispunct(command[cursor])) { | ||
1363 | while (cursor > 0 && | ||
1364 | (ispunct(command[cursor-1]))) | ||
1365 | input_backward(1); | ||
1366 | } | ||
1367 | } | ||
1368 | #endif | ||
1369 | |||
1370 | /* | ||
1371 | * the emacs and vi modes share much of the code in the big | ||
1372 | * command loop. commands entered when in vi's command mode (aka | ||
1373 | * "escape mode") get an extra bit added to distinguish them -- | ||
1374 | * this keeps them from being self-inserted. this clutters the | ||
1375 | * big switch a bit, but keeps all the code in one place. | ||
1376 | */ | ||
1377 | |||
1378 | #define vbit 0x100 | ||
1379 | |||
1380 | /* leave out the "vi-mode"-only case labels if vi editing isn't | ||
1381 | * configured. */ | ||
1382 | #define vi_case(caselabel) USE_FEATURE_COMMAND_EDITING(caselabel) | ||
1383 | |||
1384 | /* convert uppercase ascii to equivalent control char, for readability */ | ||
1385 | #define CNTRL(uc_char) ((uc_char) - 0x40) | ||
1386 | |||
1387 | |||
1388 | int cmdedit_read_input(char *prompt, char command[BUFSIZ]) | ||
1389 | { | ||
1390 | |||
1391 | int break_out = 0; | ||
1392 | int lastWasTab = FALSE; | ||
1393 | unsigned char c; | ||
1394 | unsigned int ic; | ||
1395 | #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI | ||
1396 | unsigned int prevc; | ||
1397 | int vi_cmdmode = 0; | ||
1398 | #endif | ||
1399 | /* prepare before init handlers */ | ||
1400 | cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */ | ||
1401 | len = 0; | ||
1402 | command_ps = command; | ||
1403 | |||
1404 | getTermSettings(0, (void *) &initial_settings); | ||
1405 | memcpy(&new_settings, &initial_settings, sizeof(struct termios)); | ||
1406 | new_settings.c_lflag &= ~ICANON; /* unbuffered input */ | ||
1407 | /* Turn off echoing and CTRL-C, so we can trap it */ | ||
1408 | new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG); | ||
1409 | /* Hmm, in linux c_cc[] not parsed if set ~ICANON */ | ||
1410 | new_settings.c_cc[VMIN] = 1; | ||
1411 | new_settings.c_cc[VTIME] = 0; | ||
1412 | /* Turn off CTRL-C, so we can trap it */ | ||
1413 | # ifndef _POSIX_VDISABLE | ||
1414 | # define _POSIX_VDISABLE '\0' | ||
1415 | # endif | ||
1416 | new_settings.c_cc[VINTR] = _POSIX_VDISABLE; | ||
1417 | command[0] = 0; | ||
1418 | |||
1419 | setTermSettings(0, (void *) &new_settings); | ||
1420 | handlers_sets |= SET_RESET_TERM; | ||
1421 | |||
1422 | /* Now initialize things */ | ||
1423 | cmdedit_init(); | ||
1424 | /* Print out the command prompt */ | ||
1425 | parse_prompt(prompt); | ||
1426 | |||
1427 | while (1) { | ||
1428 | |||
1429 | fflush(stdout); /* buffered out to fast */ | ||
1430 | |||
1431 | if (safe_read(0, &c, 1) < 1) | ||
1432 | /* if we can't read input then exit */ | ||
1433 | goto prepare_to_die; | ||
1434 | |||
1435 | ic = c; | ||
1436 | |||
1437 | #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI | ||
1438 | newdelflag = 1; | ||
1439 | if (vi_cmdmode) | ||
1440 | ic |= vbit; | ||
1441 | #endif | ||
1442 | switch (ic) | ||
1443 | { | ||
1444 | case '\n': | ||
1445 | case '\r': | ||
1446 | vi_case( case '\n'|vbit: ) | ||
1447 | vi_case( case '\r'|vbit: ) | ||
1448 | /* Enter */ | ||
1449 | goto_new_line(); | ||
1450 | break_out = 1; | ||
1451 | break; | ||
1452 | case CNTRL('A'): | ||
1453 | vi_case( case '0'|vbit: ) | ||
1454 | /* Control-a -- Beginning of line */ | ||
1455 | input_backward(cursor); | ||
1456 | break; | ||
1457 | case CNTRL('B'): | ||
1458 | vi_case( case 'h'|vbit: ) | ||
1459 | vi_case( case '\b'|vbit: ) | ||
1460 | vi_case( case DEL|vbit: ) | ||
1461 | /* Control-b -- Move back one character */ | ||
1462 | input_backward(1); | ||
1463 | break; | ||
1464 | case CNTRL('C'): | ||
1465 | vi_case( case CNTRL('C')|vbit: ) | ||
1466 | /* Control-c -- stop gathering input */ | ||
1467 | goto_new_line(); | ||
1468 | #ifndef CONFIG_ASH | ||
1469 | command[0] = 0; | ||
1470 | len = 0; | ||
1471 | lastWasTab = FALSE; | ||
1472 | put_prompt(); | ||
1473 | #else | ||
1474 | len = 0; | ||
1475 | break_out = -1; /* to control traps */ | ||
1476 | #endif | ||
1477 | break; | ||
1478 | case CNTRL('D'): | ||
1479 | /* Control-d -- Delete one character, or exit | ||
1480 | * if the len=0 and no chars to delete */ | ||
1481 | if (len == 0) { | ||
1482 | errno = 0; | ||
1483 | prepare_to_die: | ||
1484 | #if !defined(CONFIG_ASH) | ||
1485 | printf("exit"); | ||
1486 | goto_new_line(); | ||
1487 | /* cmdedit_reset_term() called in atexit */ | ||
1488 | exit(EXIT_SUCCESS); | ||
1489 | #else | ||
1490 | /* to control stopped jobs */ | ||
1491 | len = break_out = -1; | ||
1492 | break; | ||
1493 | #endif | ||
1494 | } else { | ||
1495 | input_delete(0); | ||
1496 | } | ||
1497 | break; | ||
1498 | case CNTRL('E'): | ||
1499 | vi_case( case '$'|vbit: ) | ||
1500 | /* Control-e -- End of line */ | ||
1501 | input_end(); | ||
1502 | break; | ||
1503 | case CNTRL('F'): | ||
1504 | vi_case( case 'l'|vbit: ) | ||
1505 | vi_case( case ' '|vbit: ) | ||
1506 | /* Control-f -- Move forward one character */ | ||
1507 | input_forward(); | ||
1508 | break; | ||
1509 | case '\b': | ||
1510 | case DEL: | ||
1511 | /* Control-h and DEL */ | ||
1512 | input_backspace(); | ||
1513 | break; | ||
1514 | case '\t': | ||
1515 | #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION | ||
1516 | input_tab(&lastWasTab); | ||
1517 | #endif | ||
1518 | break; | ||
1519 | case CNTRL('K'): | ||
1520 | /* Control-k -- clear to end of line */ | ||
1521 | *(command + cursor) = 0; | ||
1522 | len = cursor; | ||
1523 | printf("\033[J"); | ||
1524 | break; | ||
1525 | case CNTRL('L'): | ||
1526 | vi_case( case CNTRL('L')|vbit: ) | ||
1527 | /* Control-l -- clear screen */ | ||
1528 | printf("\033[H"); | ||
1529 | redraw(0, len-cursor); | ||
1530 | break; | ||
1531 | #if MAX_HISTORY > 0 | ||
1532 | case CNTRL('N'): | ||
1533 | vi_case( case CNTRL('N')|vbit: ) | ||
1534 | vi_case( case 'j'|vbit: ) | ||
1535 | /* Control-n -- Get next command in history */ | ||
1536 | if (get_next_history()) | ||
1537 | goto rewrite_line; | ||
1538 | break; | ||
1539 | case CNTRL('P'): | ||
1540 | vi_case( case CNTRL('P')|vbit: ) | ||
1541 | vi_case( case 'k'|vbit: ) | ||
1542 | /* Control-p -- Get previous command from history */ | ||
1543 | if (cur_history > 0) { | ||
1544 | get_previous_history(); | ||
1545 | goto rewrite_line; | ||
1546 | } else { | ||
1547 | beep(); | ||
1548 | } | ||
1549 | break; | ||
1550 | #endif | ||
1551 | case CNTRL('U'): | ||
1552 | vi_case( case CNTRL('U')|vbit: ) | ||
1553 | /* Control-U -- Clear line before cursor */ | ||
1554 | if (cursor) { | ||
1555 | strcpy(command, command + cursor); | ||
1556 | redraw(cmdedit_y, len -= cursor); | ||
1557 | } | ||
1558 | break; | ||
1559 | case CNTRL('W'): | ||
1560 | vi_case( case CNTRL('W')|vbit: ) | ||
1561 | /* Control-W -- Remove the last word */ | ||
1562 | while (cursor > 0 && isspace(command[cursor-1])) | ||
1563 | input_backspace(); | ||
1564 | while (cursor > 0 &&!isspace(command[cursor-1])) | ||
1565 | input_backspace(); | ||
1566 | break; | ||
1567 | #if ENABLE_FEATURE_COMMAND_EDITING_VI | ||
1568 | case 'i'|vbit: | ||
1569 | vi_cmdmode = 0; | ||
1570 | break; | ||
1571 | case 'I'|vbit: | ||
1572 | input_backward(cursor); | ||
1573 | vi_cmdmode = 0; | ||
1574 | break; | ||
1575 | case 'a'|vbit: | ||
1576 | input_forward(); | ||
1577 | vi_cmdmode = 0; | ||
1578 | break; | ||
1579 | case 'A'|vbit: | ||
1580 | input_end(); | ||
1581 | vi_cmdmode = 0; | ||
1582 | break; | ||
1583 | case 'x'|vbit: | ||
1584 | input_delete(1); | ||
1585 | break; | ||
1586 | case 'X'|vbit: | ||
1587 | if (cursor > 0) { | ||
1588 | input_backward(1); | ||
1589 | input_delete(1); | ||
1590 | } | ||
1591 | break; | ||
1592 | case 'W'|vbit: | ||
1593 | vi_Word_motion(command, 1); | ||
1594 | break; | ||
1595 | case 'w'|vbit: | ||
1596 | vi_word_motion(command, 1); | ||
1597 | break; | ||
1598 | case 'E'|vbit: | ||
1599 | vi_End_motion(command); | ||
1600 | break; | ||
1601 | case 'e'|vbit: | ||
1602 | vi_end_motion(command); | ||
1603 | break; | ||
1604 | case 'B'|vbit: | ||
1605 | vi_Back_motion(command); | ||
1606 | break; | ||
1607 | case 'b'|vbit: | ||
1608 | vi_back_motion(command); | ||
1609 | break; | ||
1610 | case 'C'|vbit: | ||
1611 | vi_cmdmode = 0; | ||
1612 | /* fall through */ | ||
1613 | case 'D'|vbit: | ||
1614 | goto clear_to_eol; | ||
1615 | |||
1616 | case 'c'|vbit: | ||
1617 | vi_cmdmode = 0; | ||
1618 | /* fall through */ | ||
1619 | case 'd'|vbit: | ||
1620 | { | ||
1621 | int nc, sc; | ||
1622 | sc = cursor; | ||
1623 | prevc = ic; | ||
1624 | if (safe_read(0, &c, 1) < 1) | ||
1625 | goto prepare_to_die; | ||
1626 | if (c == (prevc & 0xff)) { | ||
1627 | /* "cc", "dd" */ | ||
1628 | input_backward(cursor); | ||
1629 | goto clear_to_eol; | ||
1630 | break; | ||
1631 | } | ||
1632 | switch(c) { | ||
1633 | case 'w': | ||
1634 | case 'W': | ||
1635 | case 'e': | ||
1636 | case 'E': | ||
1637 | switch (c) { | ||
1638 | case 'w': /* "dw", "cw" */ | ||
1639 | vi_word_motion(command, vi_cmdmode); | ||
1640 | break; | ||
1641 | case 'W': /* 'dW', 'cW' */ | ||
1642 | vi_Word_motion(command, vi_cmdmode); | ||
1643 | break; | ||
1644 | case 'e': /* 'de', 'ce' */ | ||
1645 | vi_end_motion(command); | ||
1646 | input_forward(); | ||
1647 | break; | ||
1648 | case 'E': /* 'dE', 'cE' */ | ||
1649 | vi_End_motion(command); | ||
1650 | input_forward(); | ||
1651 | break; | ||
1652 | } | ||
1653 | nc = cursor; | ||
1654 | input_backward(cursor - sc); | ||
1655 | while (nc-- > cursor) | ||
1656 | input_delete(1); | ||
1657 | break; | ||
1658 | case 'b': /* "db", "cb" */ | ||
1659 | case 'B': /* implemented as B */ | ||
1660 | if (c == 'b') | ||
1661 | vi_back_motion(command); | ||
1662 | else | ||
1663 | vi_Back_motion(command); | ||
1664 | while (sc-- > cursor) | ||
1665 | input_delete(1); | ||
1666 | break; | ||
1667 | case ' ': /* "d ", "c " */ | ||
1668 | input_delete(1); | ||
1669 | break; | ||
1670 | case '$': /* "d$", "c$" */ | ||
1671 | clear_to_eol: | ||
1672 | while (cursor < len) | ||
1673 | input_delete(1); | ||
1674 | break; | ||
1675 | } | ||
1676 | } | ||
1677 | break; | ||
1678 | case 'p'|vbit: | ||
1679 | input_forward(); | ||
1680 | /* fallthrough */ | ||
1681 | case 'P'|vbit: | ||
1682 | put(); | ||
1683 | break; | ||
1684 | case 'r'|vbit: | ||
1685 | if (safe_read(0, &c, 1) < 1) | ||
1686 | goto prepare_to_die; | ||
1687 | if (c == 0) | ||
1688 | beep(); | ||
1689 | else { | ||
1690 | *(command + cursor) = c; | ||
1691 | putchar(c); | ||
1692 | putchar('\b'); | ||
1693 | } | ||
1694 | break; | ||
1695 | #endif /* CONFIG_FEATURE_COMMAND_EDITING_VI */ | ||
1696 | |||
1697 | case ESC: | ||
1698 | |||
1699 | #if ENABLE_FEATURE_COMMAND_EDITING_VI | ||
1700 | if (vi_mode) { | ||
1701 | /* ESC: insert mode --> command mode */ | ||
1702 | vi_cmdmode = 1; | ||
1703 | input_backward(1); | ||
1704 | break; | ||
1705 | } | ||
1706 | #endif | ||
1707 | /* escape sequence follows */ | ||
1708 | if (safe_read(0, &c, 1) < 1) | ||
1709 | goto prepare_to_die; | ||
1710 | /* different vt100 emulations */ | ||
1711 | if (c == '[' || c == 'O') { | ||
1712 | vi_case( case '['|vbit: ) | ||
1713 | vi_case( case 'O'|vbit: ) | ||
1714 | if (safe_read(0, &c, 1) < 1) | ||
1715 | goto prepare_to_die; | ||
1716 | } | ||
1717 | if (c >= '1' && c <= '9') { | ||
1718 | unsigned char dummy; | ||
1719 | |||
1720 | if (safe_read(0, &dummy, 1) < 1) | ||
1721 | goto prepare_to_die; | ||
1722 | if(dummy != '~') | ||
1723 | c = 0; | ||
1724 | } | ||
1725 | switch (c) { | ||
1726 | #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION | ||
1727 | case '\t': /* Alt-Tab */ | ||
1728 | |||
1729 | input_tab(&lastWasTab); | ||
1730 | break; | ||
1731 | #endif | ||
1732 | #if MAX_HISTORY > 0 | ||
1733 | case 'A': | ||
1734 | /* Up Arrow -- Get previous command from history */ | ||
1735 | if (cur_history > 0) { | ||
1736 | get_previous_history(); | ||
1737 | goto rewrite_line; | ||
1738 | } else { | ||
1739 | beep(); | ||
1740 | } | ||
1741 | break; | ||
1742 | case 'B': | ||
1743 | /* Down Arrow -- Get next command in history */ | ||
1744 | if (!get_next_history()) | ||
1745 | break; | ||
1746 | /* Rewrite the line with the selected history item */ | ||
1747 | rewrite_line: | ||
1748 | /* change command */ | ||
1749 | len = strlen(strcpy(command, history[cur_history])); | ||
1750 | /* redraw and go to eol (bol, in vi */ | ||
1751 | #if ENABLE_FEATURE_COMMAND_EDITING_VI | ||
1752 | redraw(cmdedit_y, vi_mode ? 9999:0); | ||
1753 | #else | ||
1754 | redraw(cmdedit_y, 0); | ||
1755 | #endif | ||
1756 | break; | ||
1757 | #endif | ||
1758 | case 'C': | ||
1759 | /* Right Arrow -- Move forward one character */ | ||
1760 | input_forward(); | ||
1761 | break; | ||
1762 | case 'D': | ||
1763 | /* Left Arrow -- Move back one character */ | ||
1764 | input_backward(1); | ||
1765 | break; | ||
1766 | case '3': | ||
1767 | /* Delete */ | ||
1768 | input_delete(0); | ||
1769 | break; | ||
1770 | case '1': | ||
1771 | case 'H': | ||
1772 | /* <Home> */ | ||
1773 | input_backward(cursor); | ||
1774 | break; | ||
1775 | case '4': | ||
1776 | case 'F': | ||
1777 | /* <End> */ | ||
1778 | input_end(); | ||
1779 | break; | ||
1780 | default: | ||
1781 | c = 0; | ||
1782 | beep(); | ||
1783 | } | ||
1784 | break; | ||
1785 | |||
1786 | default: /* If it's regular input, do the normal thing */ | ||
1787 | #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT | ||
1788 | /* Control-V -- Add non-printable symbol */ | ||
1789 | if (c == CNTRL('V')) { | ||
1790 | if (safe_read(0, &c, 1) < 1) | ||
1791 | goto prepare_to_die; | ||
1792 | if (c == 0) { | ||
1793 | beep(); | ||
1794 | break; | ||
1795 | } | ||
1796 | } else | ||
1797 | #endif | ||
1798 | { | ||
1799 | #if ENABLE_FEATURE_COMMAND_EDITING_VI | ||
1800 | if (vi_cmdmode) /* don't self-insert */ | ||
1801 | break; | ||
1802 | #endif | ||
1803 | if (!Isprint(c)) /* Skip non-printable characters */ | ||
1804 | break; | ||
1805 | } | ||
1806 | |||
1807 | if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */ | ||
1808 | break; | ||
1809 | |||
1810 | len++; | ||
1811 | |||
1812 | if (cursor == (len - 1)) { /* Append if at the end of the line */ | ||
1813 | *(command + cursor) = c; | ||
1814 | *(command + cursor + 1) = 0; | ||
1815 | cmdedit_set_out_char(0); | ||
1816 | } else { /* Insert otherwise */ | ||
1817 | int sc = cursor; | ||
1818 | |||
1819 | memmove(command + sc + 1, command + sc, len - sc); | ||
1820 | *(command + sc) = c; | ||
1821 | sc++; | ||
1822 | /* rewrite from cursor */ | ||
1823 | input_end(); | ||
1824 | /* to prev x pos + 1 */ | ||
1825 | input_backward(cursor - sc); | ||
1826 | } | ||
1827 | |||
1828 | break; | ||
1829 | } | ||
1830 | if (break_out) /* Enter is the command terminator, no more input. */ | ||
1831 | break; | ||
1832 | |||
1833 | if (c != '\t') | ||
1834 | lastWasTab = FALSE; | ||
1835 | } | ||
1836 | |||
1837 | setTermSettings(0, (void *) &initial_settings); | ||
1838 | handlers_sets &= ~SET_RESET_TERM; | ||
1839 | |||
1840 | #if MAX_HISTORY > 0 | ||
1841 | /* Handle command history log */ | ||
1842 | /* cleanup may be saved current command line */ | ||
1843 | if (len> 0) { /* no put empty line */ | ||
1844 | int i = n_history; | ||
1845 | |||
1846 | free(history[MAX_HISTORY]); | ||
1847 | history[MAX_HISTORY] = 0; | ||
1848 | /* After max history, remove the oldest command */ | ||
1849 | if (i >= MAX_HISTORY) { | ||
1850 | free(history[0]); | ||
1851 | for(i = 0; i < (MAX_HISTORY-1); i++) | ||
1852 | history[i] = history[i+1]; | ||
1853 | } | ||
1854 | history[i++] = xstrdup(command); | ||
1855 | cur_history = i; | ||
1856 | n_history = i; | ||
1857 | #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT) | ||
1858 | num_ok_lines++; | ||
1859 | #endif | ||
1860 | } | ||
1861 | #else /* MAX_HISTORY == 0 */ | ||
1862 | #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT) | ||
1863 | if (len > 0) { /* no put empty line */ | ||
1864 | num_ok_lines++; | ||
1865 | } | ||
1866 | #endif | ||
1867 | #endif /* MAX_HISTORY > 0 */ | ||
1868 | if (break_out > 0) { | ||
1869 | command[len++] = '\n'; /* set '\n' */ | ||
1870 | command[len] = 0; | ||
1871 | } | ||
1872 | #if defined(CONFIG_FEATURE_CLEAN_UP) && defined(CONFIG_FEATURE_COMMAND_TAB_COMPLETION) | ||
1873 | input_tab(0); /* strong free */ | ||
1874 | #endif | ||
1875 | #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT) | ||
1876 | free(cmdedit_prompt); | ||
1877 | #endif | ||
1878 | cmdedit_reset_term(); | ||
1879 | return len; | ||
1880 | } | ||
1881 | |||
1882 | |||
1883 | |||
1884 | #endif /* CONFIG_FEATURE_COMMAND_EDITING */ | ||
1885 | |||
1886 | |||
1887 | #ifdef TEST | ||
1888 | |||
1889 | const char *applet_name = "debug stuff usage"; | ||
1890 | |||
1891 | #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT | ||
1892 | #include <locale.h> | ||
1893 | #endif | ||
1894 | |||
1895 | int main(int argc, char **argv) | ||
1896 | { | ||
1897 | char buff[BUFSIZ]; | ||
1898 | char *prompt = | ||
1899 | #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT) | ||
1900 | "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\ | ||
1901 | \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \ | ||
1902 | \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]"; | ||
1903 | #else | ||
1904 | "% "; | ||
1905 | #endif | ||
1906 | |||
1907 | #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT | ||
1908 | setlocale(LC_ALL, ""); | ||
1909 | #endif | ||
1910 | while(1) { | ||
1911 | int l; | ||
1912 | l = cmdedit_read_input(prompt, buff); | ||
1913 | if(l > 0 && buff[l-1] == '\n') { | ||
1914 | buff[l-1] = 0; | ||
1915 | printf("*** cmdedit_read_input() returned line =%s=\n", buff); | ||
1916 | } else { | ||
1917 | break; | ||
1918 | } | ||
1919 | } | ||
1920 | printf("*** cmdedit_read_input() detect ^D\n"); | ||
1921 | return 0; | ||
1922 | } | ||
1923 | |||
1924 | #endif /* TEST */ | ||