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