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