diff options
Diffstat (limited to 'cmdedit.c')
-rw-r--r-- | cmdedit.c | 1521 |
1 files changed, 0 insertions, 1521 deletions
diff --git a/cmdedit.c b/cmdedit.c deleted file mode 100644 index 16ec2f823..000000000 --- a/cmdedit.c +++ /dev/null | |||
@@ -1,1521 +0,0 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * Termios command line History and Editting. | ||
4 | * | ||
5 | * Copyright (c) 1986-2001 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 <andersee@debian.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 <stdio.h> | ||
35 | #include <errno.h> | ||
36 | #include <unistd.h> | ||
37 | #include <stdlib.h> | ||
38 | #include <string.h> | ||
39 | #include <sys/ioctl.h> | ||
40 | #include <ctype.h> | ||
41 | #include <signal.h> | ||
42 | #include <limits.h> | ||
43 | |||
44 | #include "busybox.h" | ||
45 | |||
46 | #ifdef BB_LOCALE_SUPPORT | ||
47 | #define Isprint(c) isprint((c)) | ||
48 | #else | ||
49 | #define Isprint(c) ( (c) >= ' ' && (c) != ((unsigned char)'\233') ) | ||
50 | #endif | ||
51 | |||
52 | #ifndef TEST | ||
53 | |||
54 | #define D(x) | ||
55 | |||
56 | #else | ||
57 | |||
58 | #define BB_FEATURE_COMMAND_EDITING | ||
59 | #define BB_FEATURE_COMMAND_TAB_COMPLETION | ||
60 | #define BB_FEATURE_COMMAND_USERNAME_COMPLETION | ||
61 | #define BB_FEATURE_NONPRINTABLE_INVERSE_PUT | ||
62 | #define BB_FEATURE_CLEAN_UP | ||
63 | |||
64 | #define D(x) x | ||
65 | |||
66 | #endif /* TEST */ | ||
67 | |||
68 | #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION | ||
69 | #include <dirent.h> | ||
70 | #include <sys/stat.h> | ||
71 | #endif | ||
72 | |||
73 | #ifdef BB_FEATURE_COMMAND_EDITING | ||
74 | |||
75 | #ifndef BB_FEATURE_COMMAND_TAB_COMPLETION | ||
76 | #undef BB_FEATURE_COMMAND_USERNAME_COMPLETION | ||
77 | #endif | ||
78 | |||
79 | #if defined(BB_FEATURE_COMMAND_USERNAME_COMPLETION) || defined(BB_FEATURE_SH_FANCY_PROMPT) | ||
80 | #define BB_FEATURE_GETUSERNAME_AND_HOMEDIR | ||
81 | #endif | ||
82 | |||
83 | #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR | ||
84 | # ifndef TEST | ||
85 | # include "pwd_grp/pwd.h" | ||
86 | # else | ||
87 | # include <pwd.h> | ||
88 | # endif /* TEST */ | ||
89 | #endif /* advanced FEATURES */ | ||
90 | |||
91 | |||
92 | |||
93 | struct history { | ||
94 | char *s; | ||
95 | struct history *p; | ||
96 | struct history *n; | ||
97 | }; | ||
98 | |||
99 | /* Maximum length of the linked list for the command line history */ | ||
100 | static const int MAX_HISTORY = 15; | ||
101 | |||
102 | /* First element in command line list */ | ||
103 | static struct history *his_front = NULL; | ||
104 | |||
105 | /* Last element in command line list */ | ||
106 | static struct history *his_end = NULL; | ||
107 | |||
108 | |||
109 | #include <termios.h> | ||
110 | #define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp) | ||
111 | #define getTermSettings(fd,argp) tcgetattr(fd, argp); | ||
112 | |||
113 | /* Current termio and the previous termio before starting sh */ | ||
114 | static struct termios initial_settings, new_settings; | ||
115 | |||
116 | |||
117 | static | ||
118 | volatile int cmdedit_termw = 80; /* actual terminal width */ | ||
119 | static int history_counter = 0; /* Number of commands in history list */ | ||
120 | static | ||
121 | volatile int handlers_sets = 0; /* Set next bites: */ | ||
122 | |||
123 | enum { | ||
124 | SET_ATEXIT = 1, /* when atexit() has been called | ||
125 | and get euid,uid,gid to fast compare */ | ||
126 | SET_WCHG_HANDLERS = 2, /* winchg signal handler */ | ||
127 | SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */ | ||
128 | }; | ||
129 | |||
130 | |||
131 | static int cmdedit_x; /* real x terminal position */ | ||
132 | static int cmdedit_y; /* pseudoreal y terminal position */ | ||
133 | static int cmdedit_prmt_len; /* lenght prompt without colores string */ | ||
134 | |||
135 | static int cursor; /* required global for signal handler */ | ||
136 | static int len; /* --- "" - - "" - -"- --""-- --""--- */ | ||
137 | static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */ | ||
138 | static | ||
139 | #ifndef BB_FEATURE_SH_FANCY_PROMPT | ||
140 | const | ||
141 | #endif | ||
142 | char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */ | ||
143 | |||
144 | #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR | ||
145 | static char *user_buf = ""; | ||
146 | static char *home_pwd_buf = ""; | ||
147 | static int my_euid; | ||
148 | #endif | ||
149 | |||
150 | #ifdef BB_FEATURE_SH_FANCY_PROMPT | ||
151 | static char *hostname_buf = ""; | ||
152 | static int num_ok_lines = 1; | ||
153 | #endif | ||
154 | |||
155 | |||
156 | #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION | ||
157 | |||
158 | #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR | ||
159 | static int my_euid; | ||
160 | #endif | ||
161 | |||
162 | static int my_uid; | ||
163 | static int my_gid; | ||
164 | |||
165 | #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */ | ||
166 | |||
167 | /* It seems that libc5 doesn't know what a sighandler_t is... */ | ||
168 | #if (__GLIBC__ <= 2) && (__GLIBC_MINOR__ < 1) | ||
169 | typedef void (*sighandler_t) (int); | ||
170 | #endif | ||
171 | |||
172 | static void cmdedit_setwidth(int w, int redraw_flg); | ||
173 | |||
174 | static void win_changed(int nsig) | ||
175 | { | ||
176 | struct winsize win = { 0, 0, 0, 0 }; | ||
177 | static sighandler_t previous_SIGWINCH_handler; /* for reset */ | ||
178 | |||
179 | /* emulate || signal call */ | ||
180 | if (nsig == -SIGWINCH || nsig == SIGWINCH) { | ||
181 | ioctl(0, TIOCGWINSZ, &win); | ||
182 | if (win.ws_col > 0) { | ||
183 | cmdedit_setwidth(win.ws_col, nsig == SIGWINCH); | ||
184 | } | ||
185 | } | ||
186 | /* Unix not all standart in recall signal */ | ||
187 | |||
188 | if (nsig == -SIGWINCH) /* save previous handler */ | ||
189 | previous_SIGWINCH_handler = signal(SIGWINCH, win_changed); | ||
190 | else if (nsig == SIGWINCH) /* signaled called handler */ | ||
191 | signal(SIGWINCH, win_changed); /* set for next call */ | ||
192 | else /* nsig == 0 */ | ||
193 | /* set previous handler */ | ||
194 | signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */ | ||
195 | } | ||
196 | |||
197 | static void cmdedit_reset_term(void) | ||
198 | { | ||
199 | if ((handlers_sets & SET_RESET_TERM) != 0) { | ||
200 | /* sparc and other have broken termios support: use old termio handling. */ | ||
201 | setTermSettings(fileno(stdin), (void *) &initial_settings); | ||
202 | handlers_sets &= ~SET_RESET_TERM; | ||
203 | } | ||
204 | if ((handlers_sets & SET_WCHG_HANDLERS) != 0) { | ||
205 | /* reset SIGWINCH handler to previous (default) */ | ||
206 | win_changed(0); | ||
207 | handlers_sets &= ~SET_WCHG_HANDLERS; | ||
208 | } | ||
209 | fflush(stdout); | ||
210 | #ifdef BB_FEATURE_CLEAN_UP | ||
211 | if (his_front) { | ||
212 | struct history *n; | ||
213 | |||
214 | while (his_front != his_end) { | ||
215 | n = his_front->n; | ||
216 | free(his_front->s); | ||
217 | free(his_front); | ||
218 | his_front = n; | ||
219 | } | ||
220 | } | ||
221 | #endif | ||
222 | } | ||
223 | |||
224 | |||
225 | /* special for recount position for scroll and remove terminal margin effect */ | ||
226 | static void cmdedit_set_out_char(int next_char) | ||
227 | { | ||
228 | |||
229 | int c = (int)((unsigned char) command_ps[cursor]); | ||
230 | |||
231 | if (c == 0) | ||
232 | c = ' '; /* destroy end char? */ | ||
233 | #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT | ||
234 | if (!Isprint(c)) { /* Inverse put non-printable characters */ | ||
235 | if (c >= 128) | ||
236 | c -= 128; | ||
237 | if (c < ' ') | ||
238 | c += '@'; | ||
239 | if (c == 127) | ||
240 | c = '?'; | ||
241 | printf("\033[7m%c\033[0m", c); | ||
242 | } else | ||
243 | #endif | ||
244 | putchar(c); | ||
245 | if (++cmdedit_x >= cmdedit_termw) { | ||
246 | /* terminal is scrolled down */ | ||
247 | cmdedit_y++; | ||
248 | cmdedit_x = 0; | ||
249 | |||
250 | if (!next_char) | ||
251 | next_char = ' '; | ||
252 | /* destroy "(auto)margin" */ | ||
253 | putchar(next_char); | ||
254 | putchar('\b'); | ||
255 | } | ||
256 | cursor++; | ||
257 | } | ||
258 | |||
259 | /* Move to end line. Bonus: rewrite line from cursor */ | ||
260 | static void input_end(void) | ||
261 | { | ||
262 | while (cursor < len) | ||
263 | cmdedit_set_out_char(0); | ||
264 | } | ||
265 | |||
266 | /* Go to the next line */ | ||
267 | static void goto_new_line(void) | ||
268 | { | ||
269 | input_end(); | ||
270 | if (cmdedit_x) | ||
271 | putchar('\n'); | ||
272 | } | ||
273 | |||
274 | |||
275 | static inline void out1str(const char *s) | ||
276 | { | ||
277 | fputs(s, stdout); | ||
278 | } | ||
279 | static inline void beep(void) | ||
280 | { | ||
281 | putchar('\007'); | ||
282 | } | ||
283 | |||
284 | /* Move back one charactor */ | ||
285 | /* special for slow terminal */ | ||
286 | static void input_backward(int num) | ||
287 | { | ||
288 | if (num > cursor) | ||
289 | num = cursor; | ||
290 | cursor -= num; /* new cursor (in command, not terminal) */ | ||
291 | |||
292 | if (cmdedit_x >= num) { /* no to up line */ | ||
293 | cmdedit_x -= num; | ||
294 | if (num < 4) | ||
295 | while (num-- > 0) | ||
296 | putchar('\b'); | ||
297 | |||
298 | else | ||
299 | printf("\033[%dD", num); | ||
300 | } else { | ||
301 | int count_y; | ||
302 | |||
303 | if (cmdedit_x) { | ||
304 | putchar('\r'); /* back to first terminal pos. */ | ||
305 | num -= cmdedit_x; /* set previous backward */ | ||
306 | } | ||
307 | count_y = 1 + num / cmdedit_termw; | ||
308 | printf("\033[%dA", count_y); | ||
309 | cmdedit_y -= count_y; | ||
310 | /* require forward after uping */ | ||
311 | cmdedit_x = cmdedit_termw * count_y - num; | ||
312 | printf("\033[%dC", cmdedit_x); /* set term cursor */ | ||
313 | } | ||
314 | } | ||
315 | |||
316 | static void put_prompt(void) | ||
317 | { | ||
318 | out1str(cmdedit_prompt); | ||
319 | cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */ | ||
320 | cursor = 0; | ||
321 | cmdedit_y = 0; /* new quasireal y */ | ||
322 | } | ||
323 | |||
324 | #ifndef BB_FEATURE_SH_FANCY_PROMPT | ||
325 | static void parse_prompt(const char *prmt_ptr) | ||
326 | { | ||
327 | cmdedit_prompt = prmt_ptr; | ||
328 | cmdedit_prmt_len = strlen(prmt_ptr); | ||
329 | put_prompt(); | ||
330 | } | ||
331 | #else | ||
332 | static void parse_prompt(const char *prmt_ptr) | ||
333 | { | ||
334 | int prmt_len = 0; | ||
335 | int sub_len = 0; | ||
336 | char flg_not_length = '['; | ||
337 | char *prmt_mem_ptr = xcalloc(1, 1); | ||
338 | char *pwd_buf = xgetcwd(0); | ||
339 | char buf2[PATH_MAX + 1]; | ||
340 | char buf[2]; | ||
341 | char c; | ||
342 | char *pbuf; | ||
343 | |||
344 | if (!pwd_buf) { | ||
345 | pwd_buf=(char *)unknown; | ||
346 | } | ||
347 | |||
348 | while (*prmt_ptr) { | ||
349 | pbuf = buf; | ||
350 | pbuf[1] = 0; | ||
351 | c = *prmt_ptr++; | ||
352 | if (c == '\\') { | ||
353 | const char *cp = prmt_ptr; | ||
354 | int l; | ||
355 | |||
356 | c = process_escape_sequence(&prmt_ptr); | ||
357 | if(prmt_ptr==cp) { | ||
358 | if (*cp == 0) | ||
359 | break; | ||
360 | c = *prmt_ptr++; | ||
361 | switch (c) { | ||
362 | #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR | ||
363 | case 'u': | ||
364 | pbuf = user_buf; | ||
365 | break; | ||
366 | #endif | ||
367 | case 'h': | ||
368 | pbuf = hostname_buf; | ||
369 | if (*pbuf == 0) { | ||
370 | pbuf = xcalloc(256, 1); | ||
371 | if (gethostname(pbuf, 255) < 0) { | ||
372 | strcpy(pbuf, "?"); | ||
373 | } else { | ||
374 | char *s = strchr(pbuf, '.'); | ||
375 | |||
376 | if (s) | ||
377 | *s = 0; | ||
378 | } | ||
379 | hostname_buf = pbuf; | ||
380 | } | ||
381 | break; | ||
382 | case '$': | ||
383 | c = my_euid == 0 ? '#' : '$'; | ||
384 | break; | ||
385 | #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR | ||
386 | case 'w': | ||
387 | pbuf = pwd_buf; | ||
388 | l = strlen(home_pwd_buf); | ||
389 | if (home_pwd_buf[0] != 0 && | ||
390 | strncmp(home_pwd_buf, pbuf, l) == 0 && | ||
391 | (pbuf[l]=='/' || pbuf[l]=='\0') && | ||
392 | strlen(pwd_buf+l)<PATH_MAX) { | ||
393 | pbuf = buf2; | ||
394 | *pbuf = '~'; | ||
395 | strcpy(pbuf+1, pwd_buf+l); | ||
396 | } | ||
397 | break; | ||
398 | #endif | ||
399 | case 'W': | ||
400 | pbuf = pwd_buf; | ||
401 | cp = strrchr(pbuf,'/'); | ||
402 | if ( (cp != NULL) && (cp != pbuf) ) | ||
403 | pbuf += (cp-pbuf)+1; | ||
404 | break; | ||
405 | case '!': | ||
406 | snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines); | ||
407 | break; | ||
408 | case 'e': case 'E': /* \e \E = \033 */ | ||
409 | c = '\033'; | ||
410 | break; | ||
411 | case 'x': case 'X': | ||
412 | for (l = 0; l < 3;) { | ||
413 | int h; | ||
414 | buf2[l++] = *prmt_ptr; | ||
415 | buf2[l] = 0; | ||
416 | h = strtol(buf2, &pbuf, 16); | ||
417 | if (h > UCHAR_MAX || (pbuf - buf2) < l) { | ||
418 | l--; | ||
419 | break; | ||
420 | } | ||
421 | prmt_ptr++; | ||
422 | } | ||
423 | buf2[l] = 0; | ||
424 | c = (char)strtol(buf2, 0, 16); | ||
425 | if(c==0) | ||
426 | c = '?'; | ||
427 | pbuf = buf; | ||
428 | break; | ||
429 | case '[': case ']': | ||
430 | if (c == flg_not_length) { | ||
431 | flg_not_length = flg_not_length == '[' ? ']' : '['; | ||
432 | continue; | ||
433 | } | ||
434 | break; | ||
435 | } | ||
436 | } | ||
437 | } | ||
438 | if(pbuf == buf) | ||
439 | *pbuf = c; | ||
440 | prmt_len += strlen(pbuf); | ||
441 | prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf); | ||
442 | if (flg_not_length == ']') | ||
443 | sub_len++; | ||
444 | } | ||
445 | if(pwd_buf!=(char *)unknown) | ||
446 | free(pwd_buf); | ||
447 | cmdedit_prompt = prmt_mem_ptr; | ||
448 | cmdedit_prmt_len = prmt_len - sub_len; | ||
449 | put_prompt(); | ||
450 | } | ||
451 | #endif | ||
452 | |||
453 | |||
454 | /* draw promt, editor line, and clear tail */ | ||
455 | static void redraw(int y, int back_cursor) | ||
456 | { | ||
457 | if (y > 0) /* up to start y */ | ||
458 | printf("\033[%dA", y); | ||
459 | putchar('\r'); | ||
460 | put_prompt(); | ||
461 | input_end(); /* rewrite */ | ||
462 | printf("\033[J"); /* destroy tail after cursor */ | ||
463 | input_backward(back_cursor); | ||
464 | } | ||
465 | |||
466 | /* Delete the char in front of the cursor */ | ||
467 | static void input_delete(void) | ||
468 | { | ||
469 | int j = cursor; | ||
470 | |||
471 | if (j == len) | ||
472 | return; | ||
473 | |||
474 | strcpy(command_ps + j, command_ps + j + 1); | ||
475 | len--; | ||
476 | input_end(); /* rewtite new line */ | ||
477 | cmdedit_set_out_char(0); /* destroy end char */ | ||
478 | input_backward(cursor - j); /* back to old pos cursor */ | ||
479 | } | ||
480 | |||
481 | /* Delete the char in back of the cursor */ | ||
482 | static void input_backspace(void) | ||
483 | { | ||
484 | if (cursor > 0) { | ||
485 | input_backward(1); | ||
486 | input_delete(); | ||
487 | } | ||
488 | } | ||
489 | |||
490 | |||
491 | /* Move forward one charactor */ | ||
492 | static void input_forward(void) | ||
493 | { | ||
494 | if (cursor < len) | ||
495 | cmdedit_set_out_char(command_ps[cursor + 1]); | ||
496 | } | ||
497 | |||
498 | |||
499 | static void cmdedit_setwidth(int w, int redraw_flg) | ||
500 | { | ||
501 | cmdedit_termw = cmdedit_prmt_len + 2; | ||
502 | if (w <= cmdedit_termw) { | ||
503 | cmdedit_termw = cmdedit_termw % w; | ||
504 | } | ||
505 | if (w > cmdedit_termw) { | ||
506 | cmdedit_termw = w; | ||
507 | |||
508 | if (redraw_flg) { | ||
509 | /* new y for current cursor */ | ||
510 | int new_y = (cursor + cmdedit_prmt_len) / w; | ||
511 | |||
512 | /* redraw */ | ||
513 | redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor); | ||
514 | fflush(stdout); | ||
515 | } | ||
516 | } | ||
517 | } | ||
518 | |||
519 | static void cmdedit_init(void) | ||
520 | { | ||
521 | cmdedit_prmt_len = 0; | ||
522 | if ((handlers_sets & SET_WCHG_HANDLERS) == 0) { | ||
523 | /* emulate usage handler to set handler and call yours work */ | ||
524 | win_changed(-SIGWINCH); | ||
525 | handlers_sets |= SET_WCHG_HANDLERS; | ||
526 | } | ||
527 | |||
528 | if ((handlers_sets & SET_ATEXIT) == 0) { | ||
529 | #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR | ||
530 | struct passwd *entry; | ||
531 | |||
532 | my_euid = geteuid(); | ||
533 | entry = getpwuid(my_euid); | ||
534 | if (entry) { | ||
535 | user_buf = xstrdup(entry->pw_name); | ||
536 | home_pwd_buf = xstrdup(entry->pw_dir); | ||
537 | } | ||
538 | #endif | ||
539 | |||
540 | #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION | ||
541 | |||
542 | #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR | ||
543 | my_euid = geteuid(); | ||
544 | #endif | ||
545 | my_uid = getuid(); | ||
546 | my_gid = getgid(); | ||
547 | #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */ | ||
548 | handlers_sets |= SET_ATEXIT; | ||
549 | atexit(cmdedit_reset_term); /* be sure to do this only once */ | ||
550 | } | ||
551 | } | ||
552 | |||
553 | #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION | ||
554 | |||
555 | static int is_execute(const struct stat *st) | ||
556 | { | ||
557 | if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) || | ||
558 | (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) || | ||
559 | (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) || | ||
560 | (st->st_mode & S_IXOTH)) return TRUE; | ||
561 | return FALSE; | ||
562 | } | ||
563 | |||
564 | #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION | ||
565 | |||
566 | static char **username_tab_completion(char *ud, int *num_matches) | ||
567 | { | ||
568 | struct passwd *entry; | ||
569 | int userlen; | ||
570 | char *temp; | ||
571 | |||
572 | |||
573 | ud++; /* ~user/... to user/... */ | ||
574 | userlen = strlen(ud); | ||
575 | |||
576 | if (num_matches == 0) { /* "~/..." or "~user/..." */ | ||
577 | char *sav_ud = ud - 1; | ||
578 | char *home = 0; | ||
579 | |||
580 | if (*ud == '/') { /* "~/..." */ | ||
581 | home = home_pwd_buf; | ||
582 | } else { | ||
583 | /* "~user/..." */ | ||
584 | temp = strchr(ud, '/'); | ||
585 | *temp = 0; /* ~user\0 */ | ||
586 | entry = getpwnam(ud); | ||
587 | *temp = '/'; /* restore ~user/... */ | ||
588 | ud = temp; | ||
589 | if (entry) | ||
590 | home = entry->pw_dir; | ||
591 | } | ||
592 | if (home) { | ||
593 | if ((userlen + strlen(home) + 1) < BUFSIZ) { | ||
594 | char temp2[BUFSIZ]; /* argument size */ | ||
595 | |||
596 | /* /home/user/... */ | ||
597 | sprintf(temp2, "%s%s", home, ud); | ||
598 | strcpy(sav_ud, temp2); | ||
599 | } | ||
600 | } | ||
601 | return 0; /* void, result save to argument :-) */ | ||
602 | } else { | ||
603 | /* "~[^/]*" */ | ||
604 | char **matches = (char **) NULL; | ||
605 | int nm = 0; | ||
606 | |||
607 | setpwent(); | ||
608 | |||
609 | while ((entry = getpwent()) != NULL) { | ||
610 | /* Null usernames should result in all users as possible completions. */ | ||
611 | if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) { | ||
612 | |||
613 | temp = xmalloc(3 + strlen(entry->pw_name)); | ||
614 | sprintf(temp, "~%s/", entry->pw_name); | ||
615 | matches = xrealloc(matches, (nm + 1) * sizeof(char *)); | ||
616 | |||
617 | matches[nm++] = temp; | ||
618 | } | ||
619 | } | ||
620 | |||
621 | endpwent(); | ||
622 | (*num_matches) = nm; | ||
623 | return (matches); | ||
624 | } | ||
625 | } | ||
626 | #endif /* BB_FEATURE_COMMAND_USERNAME_COMPLETION */ | ||
627 | |||
628 | enum { | ||
629 | FIND_EXE_ONLY = 0, | ||
630 | FIND_DIR_ONLY = 1, | ||
631 | FIND_FILE_ONLY = 2, | ||
632 | }; | ||
633 | |||
634 | static int path_parse(char ***p, int flags) | ||
635 | { | ||
636 | int npth; | ||
637 | char *tmp; | ||
638 | char *pth; | ||
639 | |||
640 | /* if not setenv PATH variable, to search cur dir "." */ | ||
641 | if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 || | ||
642 | /* PATH=<empty> or PATH=:<empty> */ | ||
643 | *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) { | ||
644 | return 1; | ||
645 | } | ||
646 | |||
647 | tmp = pth; | ||
648 | npth = 0; | ||
649 | |||
650 | for (;;) { | ||
651 | npth++; /* count words is + 1 count ':' */ | ||
652 | tmp = strchr(tmp, ':'); | ||
653 | if (tmp) { | ||
654 | if (*++tmp == 0) | ||
655 | break; /* :<empty> */ | ||
656 | } else | ||
657 | break; | ||
658 | } | ||
659 | |||
660 | *p = xmalloc(npth * sizeof(char *)); | ||
661 | |||
662 | tmp = pth; | ||
663 | (*p)[0] = xstrdup(tmp); | ||
664 | npth = 1; /* count words is + 1 count ':' */ | ||
665 | |||
666 | for (;;) { | ||
667 | tmp = strchr(tmp, ':'); | ||
668 | if (tmp) { | ||
669 | (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */ | ||
670 | if (*++tmp == 0) | ||
671 | break; /* :<empty> */ | ||
672 | } else | ||
673 | break; | ||
674 | (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */ | ||
675 | } | ||
676 | |||
677 | return npth; | ||
678 | } | ||
679 | |||
680 | static char *add_quote_for_spec_chars(char *found) | ||
681 | { | ||
682 | int l = 0; | ||
683 | char *s = xmalloc((strlen(found) + 1) * 2); | ||
684 | |||
685 | while (*found) { | ||
686 | if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found)) | ||
687 | s[l++] = '\\'; | ||
688 | s[l++] = *found++; | ||
689 | } | ||
690 | s[l] = 0; | ||
691 | return s; | ||
692 | } | ||
693 | |||
694 | static char **exe_n_cwd_tab_completion(char *command, int *num_matches, | ||
695 | int type) | ||
696 | { | ||
697 | |||
698 | char **matches = 0; | ||
699 | DIR *dir; | ||
700 | struct dirent *next; | ||
701 | char dirbuf[BUFSIZ]; | ||
702 | int nm = *num_matches; | ||
703 | struct stat st; | ||
704 | char *path1[1]; | ||
705 | char **paths = path1; | ||
706 | int npaths; | ||
707 | int i; | ||
708 | char *found; | ||
709 | char *pfind = strrchr(command, '/'); | ||
710 | |||
711 | path1[0] = "."; | ||
712 | |||
713 | if (pfind == NULL) { | ||
714 | /* no dir, if flags==EXE_ONLY - get paths, else "." */ | ||
715 | npaths = path_parse(&paths, type); | ||
716 | pfind = command; | ||
717 | } else { | ||
718 | /* with dir */ | ||
719 | /* save for change */ | ||
720 | strcpy(dirbuf, command); | ||
721 | /* set dir only */ | ||
722 | dirbuf[(pfind - command) + 1] = 0; | ||
723 | #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION | ||
724 | if (dirbuf[0] == '~') /* ~/... or ~user/... */ | ||
725 | username_tab_completion(dirbuf, 0); | ||
726 | #endif | ||
727 | /* "strip" dirname in command */ | ||
728 | pfind++; | ||
729 | |||
730 | paths[0] = dirbuf; | ||
731 | npaths = 1; /* only 1 dir */ | ||
732 | } | ||
733 | |||
734 | for (i = 0; i < npaths; i++) { | ||
735 | |||
736 | dir = opendir(paths[i]); | ||
737 | if (!dir) /* Don't print an error */ | ||
738 | continue; | ||
739 | |||
740 | while ((next = readdir(dir)) != NULL) { | ||
741 | char *str_found = next->d_name; | ||
742 | |||
743 | /* matched ? */ | ||
744 | if (strncmp(str_found, pfind, strlen(pfind))) | ||
745 | continue; | ||
746 | /* not see .name without .match */ | ||
747 | if (*str_found == '.' && *pfind == 0) { | ||
748 | if (*paths[i] == '/' && paths[i][1] == 0 | ||
749 | && str_found[1] == 0) str_found = ""; /* only "/" */ | ||
750 | else | ||
751 | continue; | ||
752 | } | ||
753 | found = concat_path_file(paths[i], str_found); | ||
754 | /* hmm, remover in progress? */ | ||
755 | if (stat(found, &st) < 0) | ||
756 | goto cont; | ||
757 | /* find with dirs ? */ | ||
758 | if (paths[i] != dirbuf) | ||
759 | strcpy(found, next->d_name); /* only name */ | ||
760 | if (S_ISDIR(st.st_mode)) { | ||
761 | /* name is directory */ | ||
762 | str_found = found; | ||
763 | found = concat_path_file(found, ""); | ||
764 | free(str_found); | ||
765 | str_found = add_quote_for_spec_chars(found); | ||
766 | } else { | ||
767 | /* not put found file if search only dirs for cd */ | ||
768 | if (type == FIND_DIR_ONLY) | ||
769 | goto cont; | ||
770 | str_found = add_quote_for_spec_chars(found); | ||
771 | if (type == FIND_FILE_ONLY || | ||
772 | (type == FIND_EXE_ONLY && is_execute(&st) == TRUE)) | ||
773 | strcat(str_found, " "); | ||
774 | } | ||
775 | /* Add it to the list */ | ||
776 | matches = xrealloc(matches, (nm + 1) * sizeof(char *)); | ||
777 | |||
778 | matches[nm++] = str_found; | ||
779 | cont: | ||
780 | free(found); | ||
781 | } | ||
782 | closedir(dir); | ||
783 | } | ||
784 | if (paths != path1) { | ||
785 | free(paths[0]); /* allocated memory only in first member */ | ||
786 | free(paths); | ||
787 | } | ||
788 | *num_matches = nm; | ||
789 | return (matches); | ||
790 | } | ||
791 | |||
792 | static int match_compare(const void *a, const void *b) | ||
793 | { | ||
794 | return strcmp(*(char **) a, *(char **) b); | ||
795 | } | ||
796 | |||
797 | |||
798 | |||
799 | #define QUOT (UCHAR_MAX+1) | ||
800 | |||
801 | #define collapse_pos(is, in) { \ | ||
802 | memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \ | ||
803 | memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); } | ||
804 | |||
805 | static int find_match(char *matchBuf, int *len_with_quotes) | ||
806 | { | ||
807 | int i, j; | ||
808 | int command_mode; | ||
809 | int c, c2; | ||
810 | int int_buf[BUFSIZ + 1]; | ||
811 | int pos_buf[BUFSIZ + 1]; | ||
812 | |||
813 | /* set to integer dimension characters and own positions */ | ||
814 | for (i = 0;; i++) { | ||
815 | int_buf[i] = (int) ((unsigned char) matchBuf[i]); | ||
816 | if (int_buf[i] == 0) { | ||
817 | pos_buf[i] = -1; /* indicator end line */ | ||
818 | break; | ||
819 | } else | ||
820 | pos_buf[i] = i; | ||
821 | } | ||
822 | |||
823 | /* mask \+symbol and convert '\t' to ' ' */ | ||
824 | for (i = j = 0; matchBuf[i]; i++, j++) | ||
825 | if (matchBuf[i] == '\\') { | ||
826 | collapse_pos(j, j + 1); | ||
827 | int_buf[j] |= QUOT; | ||
828 | i++; | ||
829 | #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT | ||
830 | if (matchBuf[i] == '\t') /* algorithm equivalent */ | ||
831 | int_buf[j] = ' ' | QUOT; | ||
832 | #endif | ||
833 | } | ||
834 | #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT | ||
835 | else if (matchBuf[i] == '\t') | ||
836 | int_buf[j] = ' '; | ||
837 | #endif | ||
838 | |||
839 | /* mask "symbols" or 'symbols' */ | ||
840 | c2 = 0; | ||
841 | for (i = 0; int_buf[i]; i++) { | ||
842 | c = int_buf[i]; | ||
843 | if (c == '\'' || c == '"') { | ||
844 | if (c2 == 0) | ||
845 | c2 = c; | ||
846 | else { | ||
847 | if (c == c2) | ||
848 | c2 = 0; | ||
849 | else | ||
850 | int_buf[i] |= QUOT; | ||
851 | } | ||
852 | } else if (c2 != 0 && c != '$') | ||
853 | int_buf[i] |= QUOT; | ||
854 | } | ||
855 | |||
856 | /* skip commands with arguments if line have commands delimiters */ | ||
857 | /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */ | ||
858 | for (i = 0; int_buf[i]; i++) { | ||
859 | c = int_buf[i]; | ||
860 | c2 = int_buf[i + 1]; | ||
861 | j = i ? int_buf[i - 1] : -1; | ||
862 | command_mode = 0; | ||
863 | if (c == ';' || c == '&' || c == '|') { | ||
864 | command_mode = 1 + (c == c2); | ||
865 | if (c == '&') { | ||
866 | if (j == '>' || j == '<') | ||
867 | command_mode = 0; | ||
868 | } else if (c == '|' && j == '>') | ||
869 | command_mode = 0; | ||
870 | } | ||
871 | if (command_mode) { | ||
872 | collapse_pos(0, i + command_mode); | ||
873 | i = -1; /* hack incremet */ | ||
874 | } | ||
875 | } | ||
876 | /* collapse `command...` */ | ||
877 | for (i = 0; int_buf[i]; i++) | ||
878 | if (int_buf[i] == '`') { | ||
879 | for (j = i + 1; int_buf[j]; j++) | ||
880 | if (int_buf[j] == '`') { | ||
881 | collapse_pos(i, j + 1); | ||
882 | j = 0; | ||
883 | break; | ||
884 | } | ||
885 | if (j) { | ||
886 | /* not found close ` - command mode, collapse all previous */ | ||
887 | collapse_pos(0, i + 1); | ||
888 | break; | ||
889 | } else | ||
890 | i--; /* hack incremet */ | ||
891 | } | ||
892 | |||
893 | /* collapse (command...(command...)...) or {command...{command...}...} */ | ||
894 | c = 0; /* "recursive" level */ | ||
895 | c2 = 0; | ||
896 | for (i = 0; int_buf[i]; i++) | ||
897 | if (int_buf[i] == '(' || int_buf[i] == '{') { | ||
898 | if (int_buf[i] == '(') | ||
899 | c++; | ||
900 | else | ||
901 | c2++; | ||
902 | collapse_pos(0, i + 1); | ||
903 | i = -1; /* hack incremet */ | ||
904 | } | ||
905 | for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++) | ||
906 | if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) { | ||
907 | if (int_buf[i] == ')') | ||
908 | c--; | ||
909 | else | ||
910 | c2--; | ||
911 | collapse_pos(0, i + 1); | ||
912 | i = -1; /* hack incremet */ | ||
913 | } | ||
914 | |||
915 | /* skip first not quote space */ | ||
916 | for (i = 0; int_buf[i]; i++) | ||
917 | if (int_buf[i] != ' ') | ||
918 | break; | ||
919 | if (i) | ||
920 | collapse_pos(0, i); | ||
921 | |||
922 | /* set find mode for completion */ | ||
923 | command_mode = FIND_EXE_ONLY; | ||
924 | for (i = 0; int_buf[i]; i++) | ||
925 | if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') { | ||
926 | if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY | ||
927 | && matchBuf[pos_buf[0]]=='c' | ||
928 | && matchBuf[pos_buf[1]]=='d' ) | ||
929 | command_mode = FIND_DIR_ONLY; | ||
930 | else { | ||
931 | command_mode = FIND_FILE_ONLY; | ||
932 | break; | ||
933 | } | ||
934 | } | ||
935 | /* "strlen" */ | ||
936 | for (i = 0; int_buf[i]; i++); | ||
937 | /* find last word */ | ||
938 | for (--i; i >= 0; i--) { | ||
939 | c = int_buf[i]; | ||
940 | if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') { | ||
941 | collapse_pos(0, i + 1); | ||
942 | break; | ||
943 | } | ||
944 | } | ||
945 | /* skip first not quoted '\'' or '"' */ | ||
946 | for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++); | ||
947 | /* collapse quote or unquote // or /~ */ | ||
948 | while ((int_buf[i] & ~QUOT) == '/' && | ||
949 | ((int_buf[i + 1] & ~QUOT) == '/' | ||
950 | || (int_buf[i + 1] & ~QUOT) == '~')) { | ||
951 | i++; | ||
952 | } | ||
953 | |||
954 | /* set only match and destroy quotes */ | ||
955 | j = 0; | ||
956 | for (c = 0; pos_buf[i] >= 0; i++) { | ||
957 | matchBuf[c++] = matchBuf[pos_buf[i]]; | ||
958 | j = pos_buf[i] + 1; | ||
959 | } | ||
960 | matchBuf[c] = 0; | ||
961 | /* old lenght matchBuf with quotes symbols */ | ||
962 | *len_with_quotes = j ? j - pos_buf[0] : 0; | ||
963 | |||
964 | return command_mode; | ||
965 | } | ||
966 | |||
967 | |||
968 | static void input_tab(int *lastWasTab) | ||
969 | { | ||
970 | /* Do TAB completion */ | ||
971 | static int num_matches; | ||
972 | static char **matches; | ||
973 | |||
974 | if (lastWasTab == 0) { /* free all memory */ | ||
975 | if (matches) { | ||
976 | while (num_matches > 0) | ||
977 | free(matches[--num_matches]); | ||
978 | free(matches); | ||
979 | matches = (char **) NULL; | ||
980 | } | ||
981 | return; | ||
982 | } | ||
983 | if (*lastWasTab == FALSE) { | ||
984 | |||
985 | char *tmp; | ||
986 | int len_found; | ||
987 | char matchBuf[BUFSIZ]; | ||
988 | int find_type; | ||
989 | int recalc_pos; | ||
990 | |||
991 | *lastWasTab = TRUE; /* flop trigger */ | ||
992 | |||
993 | /* Make a local copy of the string -- up | ||
994 | * to the position of the cursor */ | ||
995 | tmp = strncpy(matchBuf, command_ps, cursor); | ||
996 | tmp[cursor] = 0; | ||
997 | |||
998 | find_type = find_match(matchBuf, &recalc_pos); | ||
999 | |||
1000 | /* Free up any memory already allocated */ | ||
1001 | input_tab(0); | ||
1002 | |||
1003 | #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION | ||
1004 | /* If the word starts with `~' and there is no slash in the word, | ||
1005 | * then try completing this word as a username. */ | ||
1006 | |||
1007 | if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0) | ||
1008 | matches = username_tab_completion(matchBuf, &num_matches); | ||
1009 | #endif | ||
1010 | /* Try to match any executable in our path and everything | ||
1011 | * in the current working directory that matches. */ | ||
1012 | if (!matches) | ||
1013 | matches = | ||
1014 | exe_n_cwd_tab_completion(matchBuf, | ||
1015 | &num_matches, find_type); | ||
1016 | /* Remove duplicate found */ | ||
1017 | if(matches) { | ||
1018 | int i, j; | ||
1019 | /* bubble */ | ||
1020 | for(i=0; i<(num_matches-1); i++) | ||
1021 | for(j=i+1; j<num_matches; j++) | ||
1022 | if(matches[i]!=0 && matches[j]!=0 && | ||
1023 | strcmp(matches[i], matches[j])==0) { | ||
1024 | free(matches[j]); | ||
1025 | matches[j]=0; | ||
1026 | } | ||
1027 | j=num_matches; | ||
1028 | num_matches = 0; | ||
1029 | for(i=0; i<j; i++) | ||
1030 | if(matches[i]) { | ||
1031 | if(!strcmp(matches[i], "./")) | ||
1032 | matches[i][1]=0; | ||
1033 | else if(!strcmp(matches[i], "../")) | ||
1034 | matches[i][2]=0; | ||
1035 | matches[num_matches++]=matches[i]; | ||
1036 | } | ||
1037 | } | ||
1038 | /* Did we find exactly one match? */ | ||
1039 | if (!matches || num_matches > 1) { | ||
1040 | char *tmp1; | ||
1041 | |||
1042 | beep(); | ||
1043 | if (!matches) | ||
1044 | return; /* not found */ | ||
1045 | /* sort */ | ||
1046 | qsort(matches, num_matches, sizeof(char *), match_compare); | ||
1047 | |||
1048 | /* find minimal match */ | ||
1049 | tmp = xstrdup(matches[0]); | ||
1050 | for (tmp1 = tmp; *tmp1; tmp1++) | ||
1051 | for (len_found = 1; len_found < num_matches; len_found++) | ||
1052 | if (matches[len_found][(tmp1 - tmp)] != *tmp1) { | ||
1053 | *tmp1 = 0; | ||
1054 | break; | ||
1055 | } | ||
1056 | if (*tmp == 0) { /* have unique */ | ||
1057 | free(tmp); | ||
1058 | return; | ||
1059 | } | ||
1060 | } else { /* one match */ | ||
1061 | tmp = matches[0]; | ||
1062 | /* for next completion current found */ | ||
1063 | *lastWasTab = FALSE; | ||
1064 | } | ||
1065 | |||
1066 | len_found = strlen(tmp); | ||
1067 | /* have space to placed match? */ | ||
1068 | if ((len_found - strlen(matchBuf) + len) < BUFSIZ) { | ||
1069 | |||
1070 | /* before word for match */ | ||
1071 | command_ps[cursor - recalc_pos] = 0; | ||
1072 | /* save tail line */ | ||
1073 | strcpy(matchBuf, command_ps + cursor); | ||
1074 | /* add match */ | ||
1075 | strcat(command_ps, tmp); | ||
1076 | /* add tail */ | ||
1077 | strcat(command_ps, matchBuf); | ||
1078 | /* back to begin word for match */ | ||
1079 | input_backward(recalc_pos); | ||
1080 | /* new pos */ | ||
1081 | recalc_pos = cursor + len_found; | ||
1082 | /* new len */ | ||
1083 | len = strlen(command_ps); | ||
1084 | /* write out the matched command */ | ||
1085 | redraw(cmdedit_y, len - recalc_pos); | ||
1086 | } | ||
1087 | if (tmp != matches[0]) | ||
1088 | free(tmp); | ||
1089 | } else { | ||
1090 | /* Ok -- the last char was a TAB. Since they | ||
1091 | * just hit TAB again, print a list of all the | ||
1092 | * available choices... */ | ||
1093 | if (matches && num_matches > 0) { | ||
1094 | int i, col, l; | ||
1095 | int sav_cursor = cursor; /* change goto_new_line() */ | ||
1096 | |||
1097 | /* Go to the next line */ | ||
1098 | goto_new_line(); | ||
1099 | for (i = 0, col = 0; i < num_matches; i++) { | ||
1100 | l = strlen(matches[i]); | ||
1101 | if (l < 14) | ||
1102 | l = 14; | ||
1103 | printf("%-14s ", matches[i]); | ||
1104 | if ((l += 2) > 16) | ||
1105 | while (l % 16) { | ||
1106 | putchar(' '); | ||
1107 | l++; | ||
1108 | } | ||
1109 | col += l; | ||
1110 | col -= (col / cmdedit_termw) * cmdedit_termw; | ||
1111 | if (col > 60 && matches[i + 1] != NULL) { | ||
1112 | putchar('\n'); | ||
1113 | col = 0; | ||
1114 | } | ||
1115 | } | ||
1116 | /* Go to the next line and rewrite */ | ||
1117 | putchar('\n'); | ||
1118 | redraw(0, len - sav_cursor); | ||
1119 | } | ||
1120 | } | ||
1121 | } | ||
1122 | #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */ | ||
1123 | |||
1124 | static void get_previous_history(struct history **hp, struct history *p) | ||
1125 | { | ||
1126 | if ((*hp)->s) | ||
1127 | free((*hp)->s); | ||
1128 | (*hp)->s = xstrdup(command_ps); | ||
1129 | *hp = p; | ||
1130 | } | ||
1131 | |||
1132 | static inline void get_next_history(struct history **hp) | ||
1133 | { | ||
1134 | get_previous_history(hp, (*hp)->n); | ||
1135 | } | ||
1136 | |||
1137 | enum { | ||
1138 | ESC = 27, | ||
1139 | DEL = 127, | ||
1140 | }; | ||
1141 | |||
1142 | |||
1143 | /* | ||
1144 | * This function is used to grab a character buffer | ||
1145 | * from the input file descriptor and allows you to | ||
1146 | * a string with full command editing (sortof like | ||
1147 | * a mini readline). | ||
1148 | * | ||
1149 | * The following standard commands are not implemented: | ||
1150 | * ESC-b -- Move back one word | ||
1151 | * ESC-f -- Move forward one word | ||
1152 | * ESC-d -- Delete back one word | ||
1153 | * ESC-h -- Delete forward one word | ||
1154 | * CTL-t -- Transpose two characters | ||
1155 | * | ||
1156 | * Furthermore, the "vi" command editing keys are not implemented. | ||
1157 | * | ||
1158 | */ | ||
1159 | |||
1160 | |||
1161 | int cmdedit_read_input(char *prompt, char command[BUFSIZ]) | ||
1162 | { | ||
1163 | |||
1164 | int break_out = 0; | ||
1165 | int lastWasTab = FALSE; | ||
1166 | unsigned char c = 0; | ||
1167 | struct history *hp = his_end; | ||
1168 | |||
1169 | /* prepare before init handlers */ | ||
1170 | cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */ | ||
1171 | len = 0; | ||
1172 | command_ps = command; | ||
1173 | |||
1174 | getTermSettings(0, (void *) &initial_settings); | ||
1175 | memcpy(&new_settings, &initial_settings, sizeof(struct termios)); | ||
1176 | new_settings.c_lflag &= ~ICANON; /* unbuffered input */ | ||
1177 | /* Turn off echoing and CTRL-C, so we can trap it */ | ||
1178 | new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG); | ||
1179 | #ifndef linux | ||
1180 | /* Hmm, in linux c_cc[] not parsed if set ~ICANON */ | ||
1181 | new_settings.c_cc[VMIN] = 1; | ||
1182 | new_settings.c_cc[VTIME] = 0; | ||
1183 | /* Turn off CTRL-C, so we can trap it */ | ||
1184 | # ifndef _POSIX_VDISABLE | ||
1185 | # define _POSIX_VDISABLE '\0' | ||
1186 | # endif | ||
1187 | new_settings.c_cc[VINTR] = _POSIX_VDISABLE; | ||
1188 | #endif | ||
1189 | command[0] = 0; | ||
1190 | |||
1191 | setTermSettings(0, (void *) &new_settings); | ||
1192 | handlers_sets |= SET_RESET_TERM; | ||
1193 | |||
1194 | /* Now initialize things */ | ||
1195 | cmdedit_init(); | ||
1196 | /* Print out the command prompt */ | ||
1197 | parse_prompt(prompt); | ||
1198 | |||
1199 | while (1) { | ||
1200 | |||
1201 | fflush(stdout); /* buffered out to fast */ | ||
1202 | |||
1203 | if (safe_read(0, &c, 1) < 1) | ||
1204 | /* if we can't read input then exit */ | ||
1205 | goto prepare_to_die; | ||
1206 | |||
1207 | switch (c) { | ||
1208 | case '\n': | ||
1209 | case '\r': | ||
1210 | /* Enter */ | ||
1211 | goto_new_line(); | ||
1212 | break_out = 1; | ||
1213 | break; | ||
1214 | case 1: | ||
1215 | /* Control-a -- Beginning of line */ | ||
1216 | input_backward(cursor); | ||
1217 | break; | ||
1218 | case 2: | ||
1219 | /* Control-b -- Move back one character */ | ||
1220 | input_backward(1); | ||
1221 | break; | ||
1222 | case 3: | ||
1223 | /* Control-c -- stop gathering input */ | ||
1224 | goto_new_line(); | ||
1225 | command[0] = 0; | ||
1226 | len = 0; | ||
1227 | lastWasTab = FALSE; | ||
1228 | put_prompt(); | ||
1229 | break; | ||
1230 | case 4: | ||
1231 | /* Control-d -- Delete one character, or exit | ||
1232 | * if the len=0 and no chars to delete */ | ||
1233 | if (len == 0) { | ||
1234 | prepare_to_die: | ||
1235 | #if !defined(BB_ASH) | ||
1236 | printf("exit"); | ||
1237 | goto_new_line(); | ||
1238 | /* cmdedit_reset_term() called in atexit */ | ||
1239 | exit(EXIT_SUCCESS); | ||
1240 | #else | ||
1241 | break_out = -1; /* for control stoped jobs */ | ||
1242 | break; | ||
1243 | #endif | ||
1244 | } else { | ||
1245 | input_delete(); | ||
1246 | } | ||
1247 | break; | ||
1248 | case 5: | ||
1249 | /* Control-e -- End of line */ | ||
1250 | input_end(); | ||
1251 | break; | ||
1252 | case 6: | ||
1253 | /* Control-f -- Move forward one character */ | ||
1254 | input_forward(); | ||
1255 | break; | ||
1256 | case '\b': | ||
1257 | case DEL: | ||
1258 | /* Control-h and DEL */ | ||
1259 | input_backspace(); | ||
1260 | break; | ||
1261 | case '\t': | ||
1262 | #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION | ||
1263 | input_tab(&lastWasTab); | ||
1264 | #endif | ||
1265 | break; | ||
1266 | case 14: | ||
1267 | /* Control-n -- Get next command in history */ | ||
1268 | if (hp && hp->n && hp->n->s) { | ||
1269 | get_next_history(&hp); | ||
1270 | goto rewrite_line; | ||
1271 | } else { | ||
1272 | beep(); | ||
1273 | } | ||
1274 | break; | ||
1275 | case 16: | ||
1276 | /* Control-p -- Get previous command from history */ | ||
1277 | if (hp && hp->p) { | ||
1278 | get_previous_history(&hp, hp->p); | ||
1279 | goto rewrite_line; | ||
1280 | } else { | ||
1281 | beep(); | ||
1282 | } | ||
1283 | break; | ||
1284 | case 21: | ||
1285 | /* Control-U -- Clear line before cursor */ | ||
1286 | if (cursor) { | ||
1287 | strcpy(command, command + cursor); | ||
1288 | redraw(cmdedit_y, len -= cursor); | ||
1289 | } | ||
1290 | break; | ||
1291 | |||
1292 | case ESC:{ | ||
1293 | /* escape sequence follows */ | ||
1294 | if (safe_read(0, &c, 1) < 1) | ||
1295 | goto prepare_to_die; | ||
1296 | /* different vt100 emulations */ | ||
1297 | if (c == '[' || c == 'O') { | ||
1298 | if (safe_read(0, &c, 1) < 1) | ||
1299 | goto prepare_to_die; | ||
1300 | } | ||
1301 | switch (c) { | ||
1302 | #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION | ||
1303 | case '\t': /* Alt-Tab */ | ||
1304 | |||
1305 | input_tab(&lastWasTab); | ||
1306 | break; | ||
1307 | #endif | ||
1308 | case 'A': | ||
1309 | /* Up Arrow -- Get previous command from history */ | ||
1310 | if (hp && hp->p) { | ||
1311 | get_previous_history(&hp, hp->p); | ||
1312 | goto rewrite_line; | ||
1313 | } else { | ||
1314 | beep(); | ||
1315 | } | ||
1316 | break; | ||
1317 | case 'B': | ||
1318 | /* Down Arrow -- Get next command in history */ | ||
1319 | if (hp && hp->n && hp->n->s) { | ||
1320 | get_next_history(&hp); | ||
1321 | goto rewrite_line; | ||
1322 | } else { | ||
1323 | beep(); | ||
1324 | } | ||
1325 | break; | ||
1326 | |||
1327 | /* Rewrite the line with the selected history item */ | ||
1328 | rewrite_line: | ||
1329 | /* change command */ | ||
1330 | len = strlen(strcpy(command, hp->s)); | ||
1331 | /* redraw and go to end line */ | ||
1332 | redraw(cmdedit_y, 0); | ||
1333 | break; | ||
1334 | case 'C': | ||
1335 | /* Right Arrow -- Move forward one character */ | ||
1336 | input_forward(); | ||
1337 | break; | ||
1338 | case 'D': | ||
1339 | /* Left Arrow -- Move back one character */ | ||
1340 | input_backward(1); | ||
1341 | break; | ||
1342 | case '3': | ||
1343 | /* Delete */ | ||
1344 | input_delete(); | ||
1345 | break; | ||
1346 | case '1': | ||
1347 | case 'H': | ||
1348 | /* Home (Ctrl-A) */ | ||
1349 | input_backward(cursor); | ||
1350 | break; | ||
1351 | case '4': | ||
1352 | case 'F': | ||
1353 | /* End (Ctrl-E) */ | ||
1354 | input_end(); | ||
1355 | break; | ||
1356 | default: | ||
1357 | if (!(c >= '1' && c <= '9')) | ||
1358 | c = 0; | ||
1359 | beep(); | ||
1360 | } | ||
1361 | if (c >= '1' && c <= '9') | ||
1362 | do | ||
1363 | if (safe_read(0, &c, 1) < 1) | ||
1364 | goto prepare_to_die; | ||
1365 | while (c != '~'); | ||
1366 | break; | ||
1367 | } | ||
1368 | |||
1369 | default: /* If it's regular input, do the normal thing */ | ||
1370 | #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT | ||
1371 | /* Control-V -- Add non-printable symbol */ | ||
1372 | if (c == 22) { | ||
1373 | if (safe_read(0, &c, 1) < 1) | ||
1374 | goto prepare_to_die; | ||
1375 | if (c == 0) { | ||
1376 | beep(); | ||
1377 | break; | ||
1378 | } | ||
1379 | } else | ||
1380 | #endif | ||
1381 | if (!Isprint(c)) /* Skip non-printable characters */ | ||
1382 | break; | ||
1383 | |||
1384 | if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */ | ||
1385 | break; | ||
1386 | |||
1387 | len++; | ||
1388 | |||
1389 | if (cursor == (len - 1)) { /* Append if at the end of the line */ | ||
1390 | *(command + cursor) = c; | ||
1391 | *(command + cursor + 1) = 0; | ||
1392 | cmdedit_set_out_char(0); | ||
1393 | } else { /* Insert otherwise */ | ||
1394 | int sc = cursor; | ||
1395 | |||
1396 | memmove(command + sc + 1, command + sc, len - sc); | ||
1397 | *(command + sc) = c; | ||
1398 | sc++; | ||
1399 | /* rewrite from cursor */ | ||
1400 | input_end(); | ||
1401 | /* to prev x pos + 1 */ | ||
1402 | input_backward(cursor - sc); | ||
1403 | } | ||
1404 | |||
1405 | break; | ||
1406 | } | ||
1407 | if (break_out) /* Enter is the command terminator, no more input. */ | ||
1408 | break; | ||
1409 | |||
1410 | if (c != '\t') | ||
1411 | lastWasTab = FALSE; | ||
1412 | } | ||
1413 | |||
1414 | setTermSettings(0, (void *) &initial_settings); | ||
1415 | handlers_sets &= ~SET_RESET_TERM; | ||
1416 | |||
1417 | /* Handle command history log */ | ||
1418 | if (len) { /* no put empty line */ | ||
1419 | |||
1420 | struct history *h = his_end; | ||
1421 | char *ss; | ||
1422 | |||
1423 | ss = xstrdup(command); /* duplicate */ | ||
1424 | |||
1425 | if (h == 0) { | ||
1426 | /* No previous history -- this memory is never freed */ | ||
1427 | h = his_front = xmalloc(sizeof(struct history)); | ||
1428 | h->n = xmalloc(sizeof(struct history)); | ||
1429 | |||
1430 | h->p = NULL; | ||
1431 | h->s = ss; | ||
1432 | h->n->p = h; | ||
1433 | h->n->n = NULL; | ||
1434 | h->n->s = NULL; | ||
1435 | his_end = h->n; | ||
1436 | history_counter++; | ||
1437 | } else { | ||
1438 | /* Add a new history command -- this memory is never freed */ | ||
1439 | h->n = xmalloc(sizeof(struct history)); | ||
1440 | |||
1441 | h->n->p = h; | ||
1442 | h->n->n = NULL; | ||
1443 | h->n->s = NULL; | ||
1444 | h->s = ss; | ||
1445 | his_end = h->n; | ||
1446 | |||
1447 | /* After max history, remove the oldest command */ | ||
1448 | if (history_counter >= MAX_HISTORY) { | ||
1449 | |||
1450 | struct history *p = his_front->n; | ||
1451 | |||
1452 | p->p = NULL; | ||
1453 | free(his_front->s); | ||
1454 | free(his_front); | ||
1455 | his_front = p; | ||
1456 | } else { | ||
1457 | history_counter++; | ||
1458 | } | ||
1459 | } | ||
1460 | #if defined(BB_FEATURE_SH_FANCY_PROMPT) | ||
1461 | num_ok_lines++; | ||
1462 | #endif | ||
1463 | } | ||
1464 | if(break_out>0) { | ||
1465 | command[len++] = '\n'; /* set '\n' */ | ||
1466 | command[len] = 0; | ||
1467 | } | ||
1468 | #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_COMMAND_TAB_COMPLETION) | ||
1469 | input_tab(0); /* strong free */ | ||
1470 | #endif | ||
1471 | #if defined(BB_FEATURE_SH_FANCY_PROMPT) | ||
1472 | free(cmdedit_prompt); | ||
1473 | #endif | ||
1474 | cmdedit_reset_term(); | ||
1475 | return len; | ||
1476 | } | ||
1477 | |||
1478 | |||
1479 | |||
1480 | #endif /* BB_FEATURE_COMMAND_EDITING */ | ||
1481 | |||
1482 | |||
1483 | #ifdef TEST | ||
1484 | |||
1485 | const char *applet_name = "debug stuff usage"; | ||
1486 | const char *memory_exhausted = "Memory exhausted"; | ||
1487 | |||
1488 | #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT | ||
1489 | #include <locale.h> | ||
1490 | #endif | ||
1491 | |||
1492 | int main(int argc, char **argv) | ||
1493 | { | ||
1494 | char buff[BUFSIZ]; | ||
1495 | char *prompt = | ||
1496 | #if defined(BB_FEATURE_SH_FANCY_PROMPT) | ||
1497 | "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\ | ||
1498 | \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \ | ||
1499 | \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]"; | ||
1500 | #else | ||
1501 | "% "; | ||
1502 | #endif | ||
1503 | |||
1504 | #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT | ||
1505 | setlocale(LC_ALL, ""); | ||
1506 | #endif | ||
1507 | while(1) { | ||
1508 | int l; | ||
1509 | cmdedit_read_input(prompt, buff); | ||
1510 | l = strlen(buff); | ||
1511 | if(l==0) | ||
1512 | break; | ||
1513 | if(l > 0 && buff[l-1] == '\n') | ||
1514 | buff[l-1] = 0; | ||
1515 | printf("*** cmdedit_read_input() returned line =%s=\n", buff); | ||
1516 | } | ||
1517 | printf("*** cmdedit_read_input() detect ^C\n"); | ||
1518 | return 0; | ||
1519 | } | ||
1520 | |||
1521 | #endif /* TEST */ | ||