- improve wording
[oweals/busybox.git] / libbb / lineedit.c
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
63 static line_input_t *state;
64
65 static struct termios initial_settings, new_settings;
66
67 static volatile unsigned cmdedit_termw = 80;        /* actual terminal width */
68
69 static int cmdedit_x;           /* real x terminal position */
70 static int cmdedit_y;           /* pseudoreal y terminal position */
71 static int cmdedit_prmt_len;    /* length of prompt (without colors etc) */
72
73 static unsigned cursor;
74 static unsigned command_len;
75 static char *command_ps;
76 static const char *cmdedit_prompt;
77
78 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
79 static char *hostname_buf;
80 static int num_ok_lines = 1;
81 #endif
82
83 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
84 static char *user_buf = (char*)"";
85 static char *home_pwd_buf = (char*)"";
86 #endif
87
88 #if ENABLE_FEATURE_TAB_COMPLETION
89 static int my_uid;
90 static 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' */
96 static 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) */
133 static void input_end(void)
134 {
135         while (cursor < command_len)
136                 cmdedit_set_out_char(' ');
137 }
138
139 /* Go to the next line */
140 static void goto_new_line(void)
141 {
142         input_end();
143         if (cmdedit_x)
144                 putchar('\n');
145 }
146
147
148 static void out1str(const char *s)
149 {
150         if (s)
151                 fputs(s, stdout);
152 }
153
154 static void beep(void)
155 {
156         putchar('\007');
157 }
158
159 /* Move back one character */
160 /* (optimized for slow terminals) */
161 static 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                         printf("\b\b\b\b" + (4-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 column; go up; go to correct column */
187         printf("\r" "\033[%dA" "\033[%dC", count_y, cmdedit_x);
188 }
189
190 static 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 */
200 static 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
213 static char *delbuf;  /* a (malloced) place to store deleted characters */
214 static char *delp;
215 static 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 */
220 static 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
249 static 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 */
267 static 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 */
276 static 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
285 static char **matches;
286 static unsigned num_matches;
287
288 static 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
298 static 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
309 static 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
359 enum {
360         FIND_EXE_ONLY = 0,
361         FIND_DIR_ONLY = 1,
362         FIND_FILE_ONLY = 2,
363 };
364
365 static 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
411 static 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] = (char*)".";
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                         const 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
504 static 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  */
672 static 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
708 static 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
722 static int match_compare(const void *a, const void *b)
723 {
724         return strcmp(*(char**)a, *(char**)b);
725 }
726
727 /* Do TAB completion */
728 static 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 */
854 static 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
863 static 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 */
879 static void 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 */
913 static void 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
932 static 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
981 static void
982 vi_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
990 static void
991 vi_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
1010 static void
1011 vi_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
1020 static void
1021 vi_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
1042 static void
1043 vi_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
1051 static void
1052 vi_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
1080 static 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
1087 static 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
1214 static sighandler_t previous_SIGWINCH_handler;
1215
1216 static 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
1228 static 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
1255 int read_line_input(const char* prompt, char* command, int maxsize, line_input_t *st)
1256 {
1257         int lastWasTab = FALSE;
1258         unsigned int ic;
1259         unsigned char c;
1260         smallint break_out = 0;
1261 #if ENABLE_FEATURE_EDITING_VI
1262         smallint vi_cmdmode = 0;
1263         smalluint prevc;
1264 #endif
1265
1266 // FIXME: audit & improve this
1267         if (maxsize > BUFSIZ)
1268                 maxsize = BUFSIZ;
1269
1270         /* With null flags, no other fields are ever used */
1271         state = st ? st : (line_input_t*) &const_int_0;
1272 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1273         if (state->flags & SAVE_HISTORY)
1274                 load_history(state->hist_file);
1275 #endif
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
1740 line_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
1750 int 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
1769 const char *applet_name = "debug stuff usage";
1770
1771 int 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 */