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