1 /* vi: set sw=4 ts=4: */
3 * Termios command line History and Editing.
5 * Copyright (c) 1986-2003 may safely be consumed by a BSD or GPL license.
6 * Written by: Vladimir Oleynik <dzo@simtreas.ru>
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)
14 * This code is 'as is' with no warranty.
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.
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
34 /* FIXME: obsolete CONFIG item? */
35 #define ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT 0
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
48 /* Entire file (except TESTing part) sits inside this #if */
49 #if ENABLE_FEATURE_EDITING
51 #if ENABLE_LOCALE_SUPPORT
52 #define Isprint(c) isprint(c)
54 #define Isprint(c) ((c) >= ' ' && (c) != ((unsigned char)'\233'))
57 #define ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR \
58 (ENABLE_FEATURE_USERNAME_COMPLETION || ENABLE_FEATURE_EDITING_FANCY_PROMPT)
59 #define USE_FEATURE_GETUSERNAME_AND_HOMEDIR(...)
60 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
61 #undef USE_FEATURE_GETUSERNAME_AND_HOMEDIR
62 #define USE_FEATURE_GETUSERNAME_AND_HOMEDIR(...) __VA_ARGS__
66 /* We use int16_t for positions, need to limit line len */
67 MAX_LINELEN = CONFIG_FEATURE_EDITING_MAX_LEN < 0x7ff0
68 ? CONFIG_FEATURE_EDITING_MAX_LEN
72 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
73 static const char null_str[] ALIGN1 = "";
76 /* We try to minimize both static and stack usage. */
80 volatile unsigned cmdedit_termw; /* = 80; */ /* actual terminal width */
81 sighandler_t previous_SIGWINCH_handler;
84 int cmdedit_x; /* real x terminal position */
85 int cmdedit_y; /* pseudoreal y terminal position */
86 int cmdedit_prmt_len; /* length of prompt (without colors etc) */
92 const char *cmdedit_prompt;
93 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
94 int num_ok_lines; /* = 1; */
97 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
99 char *home_pwd_buf; /* = (char*)null_str; */
102 #if ENABLE_FEATURE_TAB_COMPLETION
104 unsigned num_matches;
107 #if ENABLE_FEATURE_EDITING_VI
108 #define DELBUFSIZ 128
110 smallint newdelflag; /* whether delbuf should be reused yet */
111 char delbuf[DELBUFSIZ]; /* a place to store deleted characters */
114 /* Formerly these were big buffers on stack: */
115 #if ENABLE_FEATURE_TAB_COMPLETION
116 char exe_n_cwd_tab_completion__dirbuf[MAX_LINELEN];
117 char input_tab__matchBuf[MAX_LINELEN];
118 int16_t find_match__int_buf[MAX_LINELEN + 1]; /* need to have 9 bits at least */
119 int16_t find_match__pos_buf[MAX_LINELEN + 1];
123 /* Make it reside in writable memory, yet make compiler understand
124 * that it is not going to change. */
125 static struct statics *const ptr_to_statics __attribute__ ((section (".data")));
127 #define S (*ptr_to_statics)
128 #define state (S.state )
129 #define cmdedit_termw (S.cmdedit_termw )
130 #define previous_SIGWINCH_handler (S.previous_SIGWINCH_handler)
131 #define cmdedit_x (S.cmdedit_x )
132 #define cmdedit_y (S.cmdedit_y )
133 #define cmdedit_prmt_len (S.cmdedit_prmt_len)
134 #define cursor (S.cursor )
135 #define command_len (S.command_len )
136 #define command_ps (S.command_ps )
137 #define cmdedit_prompt (S.cmdedit_prompt )
138 #define num_ok_lines (S.num_ok_lines )
139 #define user_buf (S.user_buf )
140 #define home_pwd_buf (S.home_pwd_buf )
141 #define matches (S.matches )
142 #define num_matches (S.num_matches )
143 #define delptr (S.delptr )
144 #define newdelflag (S.newdelflag )
145 #define delbuf (S.delbuf )
147 #define INIT_S() do { \
148 (*(struct statics**)&ptr_to_statics) = xzalloc(sizeof(S)); \
150 cmdedit_termw = 80; \
151 USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines = 1;) \
152 USE_FEATURE_GETUSERNAME_AND_HOMEDIR(home_pwd_buf = (char*)null_str;) \
154 static void deinit_S(void)
156 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
157 /* This one is allocated only if FANCY_PROMPT is on
158 * (otherwise it points to verbatim prompt (NOT malloced) */
159 free((char*)cmdedit_prompt);
161 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
163 if (home_pwd_buf != null_str)
166 free(ptr_to_statics);
168 #define DEINIT_S() deinit_S()
170 /* Put 'command_ps[cursor]', cursor++.
171 * Advance cursor on screen. If we reached right margin, scroll text up
172 * and remove terminal margin effect by printing 'next_char' */
173 static void cmdedit_set_out_char(int next_char)
175 int c = (unsigned char)command_ps[cursor];
178 /* erase character after end of input string */
181 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
182 /* Display non-printable characters in reverse */
190 printf("\033[7m%c\033[0m", c);
196 if (++cmdedit_x >= cmdedit_termw) {
197 /* terminal is scrolled down */
200 /* destroy "(auto)margin" */
201 bb_putchar(next_char);
204 // Huh? What if command_ps[cursor] == '\0' (we are at the end already?)
208 /* Move to end of line (by printing all chars till the end) */
209 static void input_end(void)
211 while (cursor < command_len)
212 cmdedit_set_out_char(' ');
215 /* Go to the next line */
216 static void goto_new_line(void)
224 static void out1str(const char *s)
230 static void beep(void)
235 /* Move back one character */
236 /* (optimized for slow terminals) */
237 static void input_backward(unsigned num)
247 if (cmdedit_x >= num) {
250 /* This is longer by 5 bytes on x86.
251 * Also gets mysteriously
252 * miscompiled for some ARM users.
253 * printf(("\b\b\b\b" + 4) - num);
261 printf("\033[%uD", num);
265 /* Need to go one or more lines up */
267 count_y = 1 + (num / cmdedit_termw);
268 cmdedit_y -= count_y;
269 cmdedit_x = cmdedit_termw * count_y - num;
270 /* go to 1st column; go up; go to correct column */
271 printf("\r" "\033[%dA" "\033[%dC", count_y, cmdedit_x);
274 static void put_prompt(void)
276 out1str(cmdedit_prompt);
277 cmdedit_x = cmdedit_prmt_len;
279 // Huh? what if cmdedit_prmt_len >= width?
280 cmdedit_y = 0; /* new quasireal y */
283 /* draw prompt, editor line, and clear tail */
284 static void redraw(int y, int back_cursor)
286 if (y > 0) /* up to start y */
287 printf("\033[%dA", y);
290 input_end(); /* rewrite */
291 printf("\033[J"); /* erase after cursor */
292 input_backward(back_cursor);
295 /* Delete the char in front of the cursor, optionally saving it
296 * for later putback */
297 #if !ENABLE_FEATURE_EDITING_VI
298 static void input_delete(void)
299 #define input_delete(save) input_delete()
301 static void input_delete(int save)
306 if (j == command_len)
309 #if ENABLE_FEATURE_EDITING_VI
315 if ((delptr - delbuf) < DELBUFSIZ)
316 *delptr++ = command_ps[j];
320 strcpy(command_ps + j, command_ps + j + 1);
322 input_end(); /* rewrite new line */
323 cmdedit_set_out_char(' '); /* erase char */
324 input_backward(cursor - j); /* back to old pos cursor */
327 #if ENABLE_FEATURE_EDITING_VI
328 static void put(void)
331 int j = delptr - delbuf;
336 /* open hole and then fill it */
337 memmove(command_ps + cursor + j, command_ps + cursor, command_len - cursor + 1);
338 strncpy(command_ps + cursor, delbuf, j);
340 input_end(); /* rewrite new line */
341 input_backward(cursor - ocursor - j + 1); /* at end of new text */
345 /* Delete the char in back of the cursor */
346 static void input_backspace(void)
354 /* Move forward one character */
355 static void input_forward(void)
357 if (cursor < command_len)
358 cmdedit_set_out_char(command_ps[cursor + 1]);
361 #if ENABLE_FEATURE_TAB_COMPLETION
363 static void free_tab_completion_data(void)
367 free(matches[--num_matches]);
373 static void add_match(char *matched)
375 int nm = num_matches;
378 matches = xrealloc(matches, nm1 * sizeof(char *));
379 matches[nm] = matched;
383 #if ENABLE_FEATURE_USERNAME_COMPLETION
384 static void username_tab_completion(char *ud, char *with_shash_flg)
386 struct passwd *entry;
389 ud++; /* ~user/... to user/... */
390 userlen = strlen(ud);
392 if (with_shash_flg) { /* "~/..." or "~user/..." */
393 char *sav_ud = ud - 1;
396 if (*ud == '/') { /* "~/..." */
401 temp = strchr(ud, '/');
402 *temp = '\0'; /* ~user\0 */
403 entry = getpwnam(ud);
404 *temp = '/'; /* restore ~user/... */
407 home = entry->pw_dir;
410 if ((userlen + strlen(home) + 1) < MAX_LINELEN) {
412 sprintf(sav_ud, "%s%s", home, ud);
417 /* Using _r function to avoid pulling in static buffers */
420 struct passwd *result;
423 while (!getpwent_r(&pwd, line_buff, sizeof(line_buff), &result)) {
424 /* Null usernames should result in all users as possible completions. */
425 if (/*!userlen || */ strncmp(ud, pwd.pw_name, userlen) == 0) {
426 add_match(xasprintf("~%s/", pwd.pw_name));
432 #endif /* FEATURE_COMMAND_USERNAME_COMPLETION */
440 static int path_parse(char ***p, int flags)
447 /* if not setenv PATH variable, to search cur dir "." */
448 if (flags != FIND_EXE_ONLY)
451 if (state->flags & WITH_PATH_LOOKUP)
452 pth = state->path_lookup;
454 pth = getenv("PATH");
455 /* PATH=<empty> or PATH=:<empty> */
456 if (!pth || !pth[0] || LONE_CHAR(pth, ':'))
460 npth = 1; /* path component count */
462 tmp = strchr(tmp, ':');
466 break; /* :<empty> */
470 res = xmalloc(npth * sizeof(char*));
471 res[0] = tmp = xstrdup(pth);
474 tmp = strchr(tmp, ':');
477 *tmp++ = '\0'; /* ':' -> '\0' */
479 break; /* :<empty> */
486 static void exe_n_cwd_tab_completion(char *command, int type)
492 char **paths = path1;
496 char *pfind = strrchr(command, '/');
497 /* char dirbuf[MAX_LINELEN]; */
498 #define dirbuf (S.exe_n_cwd_tab_completion__dirbuf)
501 path1[0] = (char*)".";
504 /* no dir, if flags==EXE_ONLY - get paths, else "." */
505 npaths = path_parse(&paths, type);
508 /* dirbuf = ".../.../.../" */
509 safe_strncpy(dirbuf, command, (pfind - command) + 2);
510 #if ENABLE_FEATURE_USERNAME_COMPLETION
511 if (dirbuf[0] == '~') /* ~/... or ~user/... */
512 username_tab_completion(dirbuf, dirbuf);
515 /* point to 'l' in "..../last_component" */
519 for (i = 0; i < npaths; i++) {
520 dir = opendir(paths[i]);
521 if (!dir) /* Don't print an error */
524 while ((next = readdir(dir)) != NULL) {
526 const char *str_found = next->d_name;
529 if (strncmp(str_found, pfind, strlen(pfind)))
531 /* not see .name without .match */
532 if (*str_found == '.' && *pfind == 0) {
533 if (NOT_LONE_CHAR(paths[i], '/') || str_found[1])
535 str_found = ""; /* only "/" */
537 found = concat_path_file(paths[i], str_found);
538 /* hmm, remover in progress? */
539 if (lstat(found, &st) < 0)
541 /* find with dirs? */
542 if (paths[i] != dirbuf)
543 strcpy(found, next->d_name); /* only name */
545 len1 = strlen(found);
546 found = xrealloc(found, len1 + 2);
548 found[len1+1] = '\0';
550 if (S_ISDIR(st.st_mode)) {
551 /* name is directory */
552 if (found[len1-1] != '/') {
556 /* not put found file if search only dirs for cd */
557 if (type == FIND_DIR_ONLY)
560 /* Add it to the list */
568 if (paths != path1) {
569 free(paths[0]); /* allocated memory only in first member */
575 #define QUOT (UCHAR_MAX+1)
577 #define collapse_pos(is, in) do { \
578 memmove(int_buf+(is), int_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
579 memmove(pos_buf+(is), pos_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
582 static int find_match(char *matchBuf, int *len_with_quotes)
587 /* int16_t int_buf[MAX_LINELEN + 1]; */
588 /* int16_t pos_buf[MAX_LINELEN + 1]; */
589 #define int_buf (S.find_match__int_buf)
590 #define pos_buf (S.find_match__pos_buf)
592 /* set to integer dimension characters and own positions */
594 int_buf[i] = (unsigned char)matchBuf[i];
595 if (int_buf[i] == 0) {
596 pos_buf[i] = -1; /* indicator end line */
602 /* mask \+symbol and convert '\t' to ' ' */
603 for (i = j = 0; matchBuf[i]; i++, j++)
604 if (matchBuf[i] == '\\') {
605 collapse_pos(j, j + 1);
608 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
609 if (matchBuf[i] == '\t') /* algorithm equivalent */
610 int_buf[j] = ' ' | QUOT;
613 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
614 else if (matchBuf[i] == '\t')
618 /* mask "symbols" or 'symbols' */
620 for (i = 0; int_buf[i]; i++) {
622 if (c == '\'' || c == '"') {
631 } else if (c2 != 0 && c != '$')
635 /* skip commands with arguments if line has commands delimiters */
636 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
637 for (i = 0; int_buf[i]; i++) {
640 j = i ? int_buf[i - 1] : -1;
642 if (c == ';' || c == '&' || c == '|') {
643 command_mode = 1 + (c == c2);
645 if (j == '>' || j == '<')
647 } else if (c == '|' && j == '>')
651 collapse_pos(0, i + command_mode);
652 i = -1; /* hack incremet */
655 /* collapse `command...` */
656 for (i = 0; int_buf[i]; i++)
657 if (int_buf[i] == '`') {
658 for (j = i + 1; int_buf[j]; j++)
659 if (int_buf[j] == '`') {
660 collapse_pos(i, j + 1);
665 /* not found close ` - command mode, collapse all previous */
666 collapse_pos(0, i + 1);
669 i--; /* hack incremet */
672 /* collapse (command...(command...)...) or {command...{command...}...} */
673 c = 0; /* "recursive" level */
675 for (i = 0; int_buf[i]; i++)
676 if (int_buf[i] == '(' || int_buf[i] == '{') {
677 if (int_buf[i] == '(')
681 collapse_pos(0, i + 1);
682 i = -1; /* hack incremet */
684 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
685 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
686 if (int_buf[i] == ')')
690 collapse_pos(0, i + 1);
691 i = -1; /* hack incremet */
694 /* skip first not quote space */
695 for (i = 0; int_buf[i]; i++)
696 if (int_buf[i] != ' ')
701 /* set find mode for completion */
702 command_mode = FIND_EXE_ONLY;
703 for (i = 0; int_buf[i]; i++)
704 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
705 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
706 && matchBuf[pos_buf[0]] == 'c'
707 && matchBuf[pos_buf[1]] == 'd'
709 command_mode = FIND_DIR_ONLY;
711 command_mode = FIND_FILE_ONLY;
715 for (i = 0; int_buf[i]; i++)
718 for (--i; i >= 0; i--) {
720 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
721 collapse_pos(0, i + 1);
725 /* skip first not quoted '\'' or '"' */
726 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++)
728 /* collapse quote or unquote // or /~ */
729 while ((int_buf[i] & ~QUOT) == '/'
730 && ((int_buf[i+1] & ~QUOT) == '/' || (int_buf[i+1] & ~QUOT) == '~')
735 /* set only match and destroy quotes */
737 for (c = 0; pos_buf[i] >= 0; i++) {
738 matchBuf[c++] = matchBuf[pos_buf[i]];
742 /* old length matchBuf with quotes symbols */
743 *len_with_quotes = j ? j - pos_buf[0] : 0;
751 * display by column (original idea from ls applet,
752 * very optimized by me :)
754 static void showfiles(void)
757 int column_width = 0;
758 int nfiles = num_matches;
762 /* find the longest file name- use that as the column width */
763 for (row = 0; row < nrows; row++) {
764 l = strlen(matches[row]);
765 if (column_width < l)
768 column_width += 2; /* min space for columns */
769 ncols = cmdedit_termw / column_width;
774 nrows++; /* round up fractionals */
778 for (row = 0; row < nrows; row++) {
782 for (nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
783 printf("%s%-*s", matches[n],
784 (int)(column_width - strlen(matches[n])), "");
790 static char *add_quote_for_spec_chars(char *found)
793 char *s = xmalloc((strlen(found) + 1) * 2);
796 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
804 /* Do TAB completion */
805 static void input_tab(smallint *lastWasTab)
807 if (!(state->flags & TAB_COMPLETION))
813 /* char matchBuf[MAX_LINELEN]; */
814 #define matchBuf (S.input_tab__matchBuf)
818 *lastWasTab = TRUE; /* flop trigger */
820 /* Make a local copy of the string -- up
821 * to the position of the cursor */
822 tmp = strncpy(matchBuf, command_ps, cursor);
825 find_type = find_match(matchBuf, &recalc_pos);
827 /* Free up any memory already allocated */
828 free_tab_completion_data();
830 #if ENABLE_FEATURE_USERNAME_COMPLETION
831 /* If the word starts with `~' and there is no slash in the word,
832 * then try completing this word as a username. */
833 if (state->flags & USERNAME_COMPLETION)
834 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
835 username_tab_completion(matchBuf, NULL);
837 /* Try to match any executable in our path and everything
838 * in the current working directory */
840 exe_n_cwd_tab_completion(matchBuf, find_type);
841 /* Sort, then remove any duplicates found */
844 qsort_string_vector(matches, num_matches);
845 for (i = 0; i < num_matches - 1; ++i) {
846 if (matches[i] && matches[i+1]) { /* paranoia */
847 if (strcmp(matches[i], matches[i+1]) == 0) {
849 matches[i] = NULL; /* paranoia */
851 matches[n++] = matches[i];
855 matches[n] = matches[i];
858 /* Did we find exactly one match? */
859 if (!matches || num_matches > 1) {
862 return; /* not found */
863 /* find minimal match */
864 tmp1 = xstrdup(matches[0]);
865 for (tmp = tmp1; *tmp; tmp++)
866 for (len_found = 1; len_found < num_matches; len_found++)
867 if (matches[len_found][(tmp - tmp1)] != *tmp) {
871 if (*tmp1 == '\0') { /* have unique */
875 tmp = add_quote_for_spec_chars(tmp1);
877 } else { /* one match */
878 tmp = add_quote_for_spec_chars(matches[0]);
879 /* for next completion current found */
882 len_found = strlen(tmp);
883 if (tmp[len_found-1] != '/') {
884 tmp[len_found] = ' ';
885 tmp[len_found+1] = '\0';
888 len_found = strlen(tmp);
889 /* have space to placed match? */
890 if ((len_found - strlen(matchBuf) + command_len) < MAX_LINELEN) {
891 /* before word for match */
892 command_ps[cursor - recalc_pos] = '\0';
894 strcpy(matchBuf, command_ps + cursor);
896 strcat(command_ps, tmp);
898 strcat(command_ps, matchBuf);
899 /* back to begin word for match */
900 input_backward(recalc_pos);
902 recalc_pos = cursor + len_found;
904 command_len = strlen(command_ps);
905 /* write out the matched command */
906 redraw(cmdedit_y, command_len - recalc_pos);
911 /* Ok -- the last char was a TAB. Since they
912 * just hit TAB again, print a list of all the
913 * available choices... */
914 if (matches && num_matches > 0) {
915 int sav_cursor = cursor; /* change goto_new_line() */
917 /* Go to the next line */
920 redraw(0, command_len - sav_cursor);
925 #endif /* FEATURE_COMMAND_TAB_COMPLETION */
930 /* state->flags is already checked to be nonzero */
931 static void get_previous_history(void)
933 if (command_ps[0] != '\0' || state->history[state->cur_history] == NULL) {
934 free(state->history[state->cur_history]);
935 state->history[state->cur_history] = xstrdup(command_ps);
937 state->cur_history--;
940 static int get_next_history(void)
942 if (state->flags & DO_HISTORY) {
943 int ch = state->cur_history;
944 if (ch < state->cnt_history) {
945 get_previous_history(); /* save the current history line */
946 state->cur_history = ch + 1;
947 return state->cur_history;
954 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
955 /* state->flags is already checked to be nonzero */
956 static void load_history(const char *fromfile)
961 /* NB: do not trash old history if file can't be opened */
963 fp = fopen(fromfile, "r");
965 /* clean up old history */
966 for (hi = state->cnt_history; hi > 0;) {
968 free(state->history[hi]);
971 for (hi = 0; hi < MAX_HISTORY;) {
972 char *hl = xmalloc_fgetline(fp);
978 if (l >= MAX_LINELEN)
979 hl[MAX_LINELEN-1] = '\0';
980 if (l == 0 || hl[0] == ' ') {
984 state->history[hi++] = hl;
987 state->cur_history = state->cnt_history = hi;
991 /* state->flags is already checked to be nonzero */
992 static void save_history(const char *tofile)
996 fp = fopen(tofile, "w");
1000 for (i = 0; i < state->cnt_history; i++) {
1001 fprintf(fp, "%s\n", state->history[i]);
1007 #define load_history(a) ((void)0)
1008 #define save_history(a) ((void)0)
1009 #endif /* FEATURE_COMMAND_SAVEHISTORY */
1011 static void remember_in_history(const char *str)
1015 if (!(state->flags & DO_HISTORY))
1018 i = state->cnt_history;
1019 free(state->history[MAX_HISTORY]);
1020 state->history[MAX_HISTORY] = NULL;
1021 /* After max history, remove the oldest command */
1022 if (i >= MAX_HISTORY) {
1023 free(state->history[0]);
1024 for (i = 0; i < MAX_HISTORY-1; i++)
1025 state->history[i] = state->history[i+1];
1027 // Maybe "if (!i || strcmp(history[i-1], command) != 0) ..."
1028 // (i.e. do not save dups?)
1029 state->history[i++] = xstrdup(str);
1030 state->cur_history = i;
1031 state->cnt_history = i;
1032 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1033 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1034 save_history(state->hist_file);
1036 USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;)
1039 #else /* MAX_HISTORY == 0 */
1040 #define remember_in_history(a) ((void)0)
1041 #endif /* MAX_HISTORY */
1045 * This function is used to grab a character buffer
1046 * from the input file descriptor and allows you to
1047 * a string with full command editing (sort of like
1050 * The following standard commands are not implemented:
1051 * ESC-b -- Move back one word
1052 * ESC-f -- Move forward one word
1053 * ESC-d -- Delete back one word
1054 * ESC-h -- Delete forward one word
1055 * CTL-t -- Transpose two characters
1057 * Minimalist vi-style command line editing available if configured.
1058 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
1061 #if ENABLE_FEATURE_EDITING_VI
1063 vi_Word_motion(char *command, int eat)
1065 while (cursor < command_len && !isspace(command[cursor]))
1067 if (eat) while (cursor < command_len && isspace(command[cursor]))
1072 vi_word_motion(char *command, int eat)
1074 if (isalnum(command[cursor]) || command[cursor] == '_') {
1075 while (cursor < command_len
1076 && (isalnum(command[cursor+1]) || command[cursor+1] == '_'))
1078 } else if (ispunct(command[cursor])) {
1079 while (cursor < command_len && ispunct(command[cursor+1]))
1083 if (cursor < command_len)
1086 if (eat && cursor < command_len && isspace(command[cursor]))
1087 while (cursor < command_len && isspace(command[cursor]))
1092 vi_End_motion(char *command)
1095 while (cursor < command_len && isspace(command[cursor]))
1097 while (cursor < command_len-1 && !isspace(command[cursor+1]))
1102 vi_end_motion(char *command)
1104 if (cursor >= command_len-1)
1107 while (cursor < command_len-1 && isspace(command[cursor]))
1109 if (cursor >= command_len-1)
1111 if (isalnum(command[cursor]) || command[cursor] == '_') {
1112 while (cursor < command_len-1
1113 && (isalnum(command[cursor+1]) || command[cursor+1] == '_')
1117 } else if (ispunct(command[cursor])) {
1118 while (cursor < command_len-1 && ispunct(command[cursor+1]))
1124 vi_Back_motion(char *command)
1126 while (cursor > 0 && isspace(command[cursor-1]))
1128 while (cursor > 0 && !isspace(command[cursor-1]))
1133 vi_back_motion(char *command)
1138 while (cursor > 0 && isspace(command[cursor]))
1142 if (isalnum(command[cursor]) || command[cursor] == '_') {
1144 && (isalnum(command[cursor-1]) || command[cursor-1] == '_')
1148 } else if (ispunct(command[cursor])) {
1149 while (cursor > 0 && ispunct(command[cursor-1]))
1157 * read_line_input and its helpers
1160 #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
1161 static void parse_and_put_prompt(const char *prmt_ptr)
1163 cmdedit_prompt = prmt_ptr;
1164 cmdedit_prmt_len = strlen(prmt_ptr);
1168 static void parse_and_put_prompt(const char *prmt_ptr)
1171 size_t cur_prmt_len = 0;
1172 char flg_not_length = '[';
1173 char *prmt_mem_ptr = xzalloc(1);
1174 char *cwd_buf = xrealloc_getcwd_or_warn(NULL);
1179 cmdedit_prmt_len = 0;
1182 cwd_buf = (char *)bb_msg_unknown;
1185 cbuf[1] = '\0'; /* never changes */
1188 char *free_me = NULL;
1193 const char *cp = prmt_ptr;
1196 c = bb_process_escape_sequence(&prmt_ptr);
1197 if (prmt_ptr == cp) {
1203 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1205 pbuf = user_buf ? user_buf : (char*)"";
1209 pbuf = free_me = safe_gethostname();
1210 *strchrnul(pbuf, '.') = '\0';
1213 c = (geteuid() == 0 ? '#' : '$');
1215 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1217 /* /home/user[/something] -> ~[/something] */
1219 l = strlen(home_pwd_buf);
1221 && strncmp(home_pwd_buf, cwd_buf, l) == 0
1222 && (cwd_buf[l]=='/' || cwd_buf[l]=='\0')
1223 && strlen(cwd_buf + l) < PATH_MAX
1225 pbuf = free_me = xasprintf("~%s", cwd_buf + l);
1231 cp = strrchr(pbuf, '/');
1232 if (cp != NULL && cp != pbuf)
1233 pbuf += (cp-pbuf) + 1;
1236 pbuf = free_me = xasprintf("%d", num_ok_lines);
1238 case 'e': case 'E': /* \e \E = \033 */
1241 case 'x': case 'X': {
1243 for (l = 0; l < 3;) {
1245 buf2[l++] = *prmt_ptr;
1247 h = strtoul(buf2, &pbuf, 16);
1248 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
1254 c = (char)strtoul(buf2, NULL, 16);
1261 if (c == flg_not_length) {
1262 flg_not_length = (flg_not_length == '[' ? ']' : '[');
1270 cur_prmt_len = strlen(pbuf);
1271 prmt_len += cur_prmt_len;
1272 if (flg_not_length != ']')
1273 cmdedit_prmt_len += cur_prmt_len;
1274 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
1278 if (cwd_buf != (char *)bb_msg_unknown)
1280 cmdedit_prompt = prmt_mem_ptr;
1285 static void cmdedit_setwidth(unsigned w, int redraw_flg)
1289 /* new y for current cursor */
1290 int new_y = (cursor + cmdedit_prmt_len) / w;
1292 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), command_len - cursor);
1297 static void win_changed(int nsig)
1300 get_terminal_width_height(0, &width, NULL);
1301 cmdedit_setwidth(width, nsig /* - just a yes/no flag */);
1302 if (nsig == SIGWINCH)
1303 signal(SIGWINCH, win_changed); /* rearm ourself */
1307 * The emacs and vi modes share much of the code in the big
1308 * command loop. Commands entered when in vi's command mode (aka
1309 * "escape mode") get an extra bit added to distinguish them --
1310 * this keeps them from being self-inserted. This clutters the
1311 * big switch a bit, but keeps all the code in one place.
1316 /* leave out the "vi-mode"-only case labels if vi editing isn't
1318 #define vi_case(caselabel) USE_FEATURE_EDITING(case caselabel)
1320 /* convert uppercase ascii to equivalent control char, for readability */
1322 #define CTRL(a) ((a) & ~0x40)
1325 * -1 on read errors or EOF, or on bare Ctrl-D,
1326 * 0 on ctrl-C (the line entered is still returned in 'command'),
1327 * >0 length of input string, including terminating '\n'
1329 int read_line_input(const char *prompt, char *command, int maxsize, line_input_t *st)
1331 #if ENABLE_FEATURE_TAB_COMPLETION
1332 smallint lastWasTab = FALSE;
1336 smallint break_out = 0;
1337 #if ENABLE_FEATURE_EDITING_VI
1338 smallint vi_cmdmode = 0;
1341 struct termios initial_settings;
1342 struct termios new_settings;
1346 if (tcgetattr(STDIN_FILENO, &initial_settings) < 0
1347 || !(initial_settings.c_lflag & ECHO)
1349 /* Happens when e.g. stty -echo was run before */
1351 parse_and_put_prompt(prompt);
1353 if (fgets(command, maxsize, stdin) == NULL)
1354 len = -1; /* EOF or error */
1356 len = strlen(command);
1361 // FIXME: audit & improve this
1362 if (maxsize > MAX_LINELEN)
1363 maxsize = MAX_LINELEN;
1365 /* With null flags, no other fields are ever used */
1366 state = st ? st : (line_input_t*) &const_int_0;
1367 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1368 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1369 load_history(state->hist_file);
1372 /* prepare before init handlers */
1373 cmdedit_y = 0; /* quasireal y, not true if line > xt*yt */
1375 command_ps = command;
1378 new_settings = initial_settings;
1379 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1380 /* Turn off echoing and CTRL-C, so we can trap it */
1381 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1382 /* Hmm, in linux c_cc[] is not parsed if ICANON is off */
1383 new_settings.c_cc[VMIN] = 1;
1384 new_settings.c_cc[VTIME] = 0;
1385 /* Turn off CTRL-C, so we can trap it */
1386 #ifndef _POSIX_VDISABLE
1387 #define _POSIX_VDISABLE '\0'
1389 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1390 tcsetattr(STDIN_FILENO, TCSANOW, &new_settings);
1392 /* Now initialize things */
1393 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
1394 win_changed(0); /* do initial resizing */
1395 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1397 struct passwd *entry;
1399 entry = getpwuid(geteuid());
1401 user_buf = xstrdup(entry->pw_name);
1402 home_pwd_buf = xstrdup(entry->pw_dir);
1406 /* Print out the command prompt */
1407 parse_and_put_prompt(prompt);
1412 if (nonblock_safe_read(STDIN_FILENO, &c, 1) < 1) {
1413 /* if we can't read input then exit */
1414 goto prepare_to_die;
1419 #if ENABLE_FEATURE_EDITING_VI
1435 /* Control-a -- Beginning of line */
1436 input_backward(cursor);
1441 vi_case('\x7f'|vbit:) /* DEL */
1442 /* Control-b -- Move back one character */
1446 vi_case(CTRL('C')|vbit:)
1447 /* Control-c -- stop gathering input */
1450 break_out = -1; /* "do not append '\n'" */
1453 /* Control-d -- Delete one character, or exit
1454 * if the len=0 and no chars to delete */
1455 if (command_len == 0) {
1458 /* to control stopped jobs */
1459 break_out = command_len = -1;
1467 /* Control-e -- End of line */
1473 /* Control-f -- Move forward one character */
1478 case '\x7f': /* DEL */
1479 /* Control-h and DEL */
1483 #if ENABLE_FEATURE_TAB_COMPLETION
1485 input_tab(&lastWasTab);
1490 /* Control-k -- clear to end of line */
1491 command[cursor] = 0;
1492 command_len = cursor;
1496 vi_case(CTRL('L')|vbit:)
1497 /* Control-l -- clear screen */
1499 redraw(0, command_len - cursor);
1504 vi_case(CTRL('N')|vbit:)
1506 /* Control-n -- Get next command in history */
1507 if (get_next_history())
1511 vi_case(CTRL('P')|vbit:)
1513 /* Control-p -- Get previous command from history */
1514 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1515 get_previous_history();
1523 vi_case(CTRL('U')|vbit:)
1524 /* Control-U -- Clear line before cursor */
1526 strcpy(command, command + cursor);
1527 command_len -= cursor;
1528 redraw(cmdedit_y, command_len);
1532 vi_case(CTRL('W')|vbit:)
1533 /* Control-W -- Remove the last word */
1534 while (cursor > 0 && isspace(command[cursor-1]))
1536 while (cursor > 0 && !isspace(command[cursor-1]))
1540 #if ENABLE_FEATURE_EDITING_VI
1545 input_backward(cursor);
1566 vi_Word_motion(command, 1);
1569 vi_word_motion(command, 1);
1572 vi_End_motion(command);
1575 vi_end_motion(command);
1578 vi_Back_motion(command);
1581 vi_back_motion(command);
1596 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1597 goto prepare_to_die;
1598 if (c == (prevc & 0xff)) {
1600 input_backward(cursor);
1610 case 'w': /* "dw", "cw" */
1611 vi_word_motion(command, vi_cmdmode);
1613 case 'W': /* 'dW', 'cW' */
1614 vi_Word_motion(command, vi_cmdmode);
1616 case 'e': /* 'de', 'ce' */
1617 vi_end_motion(command);
1620 case 'E': /* 'dE', 'cE' */
1621 vi_End_motion(command);
1626 input_backward(cursor - sc);
1627 while (nc-- > cursor)
1630 case 'b': /* "db", "cb" */
1631 case 'B': /* implemented as B */
1633 vi_back_motion(command);
1635 vi_Back_motion(command);
1636 while (sc-- > cursor)
1639 case ' ': /* "d ", "c " */
1642 case '$': /* "d$", "c$" */
1644 while (cursor < command_len)
1657 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1658 goto prepare_to_die;
1662 *(command + cursor) = c;
1667 #endif /* FEATURE_COMMAND_EDITING_VI */
1669 case '\x1b': /* ESC */
1671 #if ENABLE_FEATURE_EDITING_VI
1672 if (state->flags & VI_MODE) {
1673 /* ESC: insert mode --> command mode */
1679 /* escape sequence follows */
1680 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1681 goto prepare_to_die;
1682 /* different vt100 emulations */
1683 if (c == '[' || c == 'O') {
1686 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1687 goto prepare_to_die;
1689 if (c >= '1' && c <= '9') {
1690 unsigned char dummy;
1692 if (safe_read(STDIN_FILENO, &dummy, 1) < 1)
1693 goto prepare_to_die;
1699 #if ENABLE_FEATURE_TAB_COMPLETION
1700 case '\t': /* Alt-Tab */
1701 input_tab(&lastWasTab);
1706 /* Up Arrow -- Get previous command from history */
1707 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1708 get_previous_history();
1714 /* Down Arrow -- Get next command in history */
1715 if (!get_next_history())
1718 /* Rewrite the line with the selected history item */
1719 /* change command */
1720 command_len = strlen(strcpy(command, state->history[state->cur_history]));
1721 /* redraw and go to eol (bol, in vi */
1722 redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
1726 /* Right Arrow -- Move forward one character */
1730 /* Left Arrow -- Move back one character */
1737 case '1': // vt100? linux vt? or what?
1738 case '7': // vt100? linux vt? or what?
1739 case 'H': /* xterm's <Home> */
1740 input_backward(cursor);
1742 case '4': // vt100? linux vt? or what?
1743 case '8': // vt100? linux vt? or what?
1744 case 'F': /* xterm's <End> */
1753 default: /* If it's regular input, do the normal thing */
1755 /* Control-V -- force insert of next char */
1756 if (c == CTRL('V')) {
1757 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1758 goto prepare_to_die;
1765 #if ENABLE_FEATURE_EDITING_VI
1766 if (vi_cmdmode) /* Don't self-insert */
1769 if (command_len >= (maxsize - 2)) /* Need to leave space for enter */
1773 if (cursor == (command_len - 1)) { /* Append if at the end of the line */
1774 command[cursor] = c;
1775 command[cursor+1] = '\0';
1776 cmdedit_set_out_char(' ');
1777 } else { /* Insert otherwise */
1780 memmove(command + sc + 1, command + sc, command_len - sc);
1783 /* rewrite from cursor */
1785 /* to prev x pos + 1 */
1786 input_backward(cursor - sc);
1790 if (break_out) /* Enter is the command terminator, no more input. */
1793 #if ENABLE_FEATURE_TAB_COMPLETION
1799 if (command_len > 0)
1800 remember_in_history(command);
1802 if (break_out > 0) {
1803 command[command_len++] = '\n';
1804 command[command_len] = '\0';
1807 #if ENABLE_FEATURE_TAB_COMPLETION
1808 free_tab_completion_data();
1811 /* restore initial_settings */
1812 tcsetattr(STDIN_FILENO, TCSANOW, &initial_settings);
1813 /* restore SIGWINCH handler */
1814 signal(SIGWINCH, previous_SIGWINCH_handler);
1822 line_input_t *new_line_input_t(int flags)
1824 line_input_t *n = xzalloc(sizeof(*n));
1831 #undef read_line_input
1832 int read_line_input(const char* prompt, char* command, int maxsize)
1834 fputs(prompt, stdout);
1836 fgets(command, maxsize, stdin);
1837 return strlen(command);
1840 #endif /* FEATURE_COMMAND_EDITING */
1851 const char *applet_name = "debug stuff usage";
1853 int main(int argc, char **argv)
1855 char buff[MAX_LINELEN];
1857 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1858 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:"
1859 "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] "
1860 "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1865 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1866 setlocale(LC_ALL, "");
1870 l = read_line_input(prompt, buff);
1871 if (l <= 0 || buff[l-1] != '\n')
1874 printf("*** read_line_input() returned line =%s=\n", buff);
1876 printf("*** read_line_input() detect ^D\n");