source: MondoRescue/branches/2.2.5/mindi-busybox/libbb/lineedit.c @ 1765

Last change on this file since 1765 was 1765, checked in by Bruno Cornec, 12 years ago

Update to busybox 1.7.2

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