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 static void input_delete(int save)
301 if (j == command_len)
304 #if ENABLE_FEATURE_EDITING_VI
310 if ((delptr - delbuf) < DELBUFSIZ)
311 *delptr++ = command_ps[j];
315 strcpy(command_ps + j, command_ps + j + 1);
317 input_end(); /* rewrite new line */
318 cmdedit_set_out_char(' '); /* erase char */
319 input_backward(cursor - j); /* back to old pos cursor */
322 #if ENABLE_FEATURE_EDITING_VI
323 static void put(void)
326 int j = delptr - delbuf;
331 /* open hole and then fill it */
332 memmove(command_ps + cursor + j, command_ps + cursor, command_len - cursor + 1);
333 strncpy(command_ps + cursor, delbuf, j);
335 input_end(); /* rewrite new line */
336 input_backward(cursor - ocursor - j + 1); /* at end of new text */
340 /* Delete the char in back of the cursor */
341 static void input_backspace(void)
349 /* Move forward one character */
350 static void input_forward(void)
352 if (cursor < command_len)
353 cmdedit_set_out_char(command_ps[cursor + 1]);
356 #if ENABLE_FEATURE_TAB_COMPLETION
358 static void free_tab_completion_data(void)
362 free(matches[--num_matches]);
368 static void add_match(char *matched)
370 int nm = num_matches;
373 matches = xrealloc(matches, nm1 * sizeof(char *));
374 matches[nm] = matched;
378 #if ENABLE_FEATURE_USERNAME_COMPLETION
379 static void username_tab_completion(char *ud, char *with_shash_flg)
381 struct passwd *entry;
384 ud++; /* ~user/... to user/... */
385 userlen = strlen(ud);
387 if (with_shash_flg) { /* "~/..." or "~user/..." */
388 char *sav_ud = ud - 1;
391 if (*ud == '/') { /* "~/..." */
396 temp = strchr(ud, '/');
397 *temp = '\0'; /* ~user\0 */
398 entry = getpwnam(ud);
399 *temp = '/'; /* restore ~user/... */
402 home = entry->pw_dir;
405 if ((userlen + strlen(home) + 1) < MAX_LINELEN) {
407 sprintf(sav_ud, "%s%s", home, ud);
412 /* Using _r function to avoid pulling in static buffers */
415 struct passwd *result;
418 while (!getpwent_r(&pwd, line_buff, sizeof(line_buff), &result)) {
419 /* Null usernames should result in all users as possible completions. */
420 if (/*!userlen || */ strncmp(ud, pwd.pw_name, userlen) == 0) {
421 add_match(xasprintf("~%s/", pwd.pw_name));
427 #endif /* FEATURE_COMMAND_USERNAME_COMPLETION */
435 static int path_parse(char ***p, int flags)
442 /* if not setenv PATH variable, to search cur dir "." */
443 if (flags != FIND_EXE_ONLY)
446 if (state->flags & WITH_PATH_LOOKUP)
447 pth = state->path_lookup;
449 pth = getenv("PATH");
450 /* PATH=<empty> or PATH=:<empty> */
451 if (!pth || !pth[0] || LONE_CHAR(pth, ':'))
455 npth = 1; /* path component count */
457 tmp = strchr(tmp, ':');
461 break; /* :<empty> */
465 res = xmalloc(npth * sizeof(char*));
466 res[0] = tmp = xstrdup(pth);
469 tmp = strchr(tmp, ':');
472 *tmp++ = '\0'; /* ':' -> '\0' */
474 break; /* :<empty> */
481 static void exe_n_cwd_tab_completion(char *command, int type)
487 char **paths = path1;
491 char *pfind = strrchr(command, '/');
492 /* char dirbuf[MAX_LINELEN]; */
493 #define dirbuf (S.exe_n_cwd_tab_completion__dirbuf)
496 path1[0] = (char*)".";
499 /* no dir, if flags==EXE_ONLY - get paths, else "." */
500 npaths = path_parse(&paths, type);
503 /* dirbuf = ".../.../.../" */
504 safe_strncpy(dirbuf, command, (pfind - command) + 2);
505 #if ENABLE_FEATURE_USERNAME_COMPLETION
506 if (dirbuf[0] == '~') /* ~/... or ~user/... */
507 username_tab_completion(dirbuf, dirbuf);
510 /* point to 'l' in "..../last_component" */
514 for (i = 0; i < npaths; i++) {
515 dir = opendir(paths[i]);
516 if (!dir) /* Don't print an error */
519 while ((next = readdir(dir)) != NULL) {
521 const char *str_found = next->d_name;
524 if (strncmp(str_found, pfind, strlen(pfind)))
526 /* not see .name without .match */
527 if (*str_found == '.' && *pfind == 0) {
528 if (NOT_LONE_CHAR(paths[i], '/') || str_found[1])
530 str_found = ""; /* only "/" */
532 found = concat_path_file(paths[i], str_found);
533 /* hmm, remover in progress? */
534 if (lstat(found, &st) < 0)
536 /* find with dirs? */
537 if (paths[i] != dirbuf)
538 strcpy(found, next->d_name); /* only name */
540 len1 = strlen(found);
541 found = xrealloc(found, len1 + 2);
543 found[len1+1] = '\0';
545 if (S_ISDIR(st.st_mode)) {
546 /* name is directory */
547 if (found[len1-1] != '/') {
551 /* not put found file if search only dirs for cd */
552 if (type == FIND_DIR_ONLY)
555 /* Add it to the list */
563 if (paths != path1) {
564 free(paths[0]); /* allocated memory only in first member */
570 #define QUOT (UCHAR_MAX+1)
572 #define collapse_pos(is, in) do { \
573 memmove(int_buf+(is), int_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
574 memmove(pos_buf+(is), pos_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
577 static int find_match(char *matchBuf, int *len_with_quotes)
582 /* int16_t int_buf[MAX_LINELEN + 1]; */
583 /* int16_t pos_buf[MAX_LINELEN + 1]; */
584 #define int_buf (S.find_match__int_buf)
585 #define pos_buf (S.find_match__pos_buf)
587 /* set to integer dimension characters and own positions */
589 int_buf[i] = (unsigned char)matchBuf[i];
590 if (int_buf[i] == 0) {
591 pos_buf[i] = -1; /* indicator end line */
597 /* mask \+symbol and convert '\t' to ' ' */
598 for (i = j = 0; matchBuf[i]; i++, j++)
599 if (matchBuf[i] == '\\') {
600 collapse_pos(j, j + 1);
603 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
604 if (matchBuf[i] == '\t') /* algorithm equivalent */
605 int_buf[j] = ' ' | QUOT;
608 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
609 else if (matchBuf[i] == '\t')
613 /* mask "symbols" or 'symbols' */
615 for (i = 0; int_buf[i]; i++) {
617 if (c == '\'' || c == '"') {
626 } else if (c2 != 0 && c != '$')
630 /* skip commands with arguments if line has commands delimiters */
631 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
632 for (i = 0; int_buf[i]; i++) {
635 j = i ? int_buf[i - 1] : -1;
637 if (c == ';' || c == '&' || c == '|') {
638 command_mode = 1 + (c == c2);
640 if (j == '>' || j == '<')
642 } else if (c == '|' && j == '>')
646 collapse_pos(0, i + command_mode);
647 i = -1; /* hack incremet */
650 /* collapse `command...` */
651 for (i = 0; int_buf[i]; i++)
652 if (int_buf[i] == '`') {
653 for (j = i + 1; int_buf[j]; j++)
654 if (int_buf[j] == '`') {
655 collapse_pos(i, j + 1);
660 /* not found close ` - command mode, collapse all previous */
661 collapse_pos(0, i + 1);
664 i--; /* hack incremet */
667 /* collapse (command...(command...)...) or {command...{command...}...} */
668 c = 0; /* "recursive" level */
670 for (i = 0; int_buf[i]; i++)
671 if (int_buf[i] == '(' || int_buf[i] == '{') {
672 if (int_buf[i] == '(')
676 collapse_pos(0, i + 1);
677 i = -1; /* hack incremet */
679 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
680 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
681 if (int_buf[i] == ')')
685 collapse_pos(0, i + 1);
686 i = -1; /* hack incremet */
689 /* skip first not quote space */
690 for (i = 0; int_buf[i]; i++)
691 if (int_buf[i] != ' ')
696 /* set find mode for completion */
697 command_mode = FIND_EXE_ONLY;
698 for (i = 0; int_buf[i]; i++)
699 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
700 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
701 && matchBuf[pos_buf[0]] == 'c'
702 && matchBuf[pos_buf[1]] == 'd'
704 command_mode = FIND_DIR_ONLY;
706 command_mode = FIND_FILE_ONLY;
710 for (i = 0; int_buf[i]; i++)
713 for (--i; i >= 0; i--) {
715 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
716 collapse_pos(0, i + 1);
720 /* skip first not quoted '\'' or '"' */
721 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++)
723 /* collapse quote or unquote // or /~ */
724 while ((int_buf[i] & ~QUOT) == '/'
725 && ((int_buf[i+1] & ~QUOT) == '/' || (int_buf[i+1] & ~QUOT) == '~')
730 /* set only match and destroy quotes */
732 for (c = 0; pos_buf[i] >= 0; i++) {
733 matchBuf[c++] = matchBuf[pos_buf[i]];
737 /* old length matchBuf with quotes symbols */
738 *len_with_quotes = j ? j - pos_buf[0] : 0;
746 * display by column (original idea from ls applet,
747 * very optimized by me :)
749 static void showfiles(void)
752 int column_width = 0;
753 int nfiles = num_matches;
757 /* find the longest file name- use that as the column width */
758 for (row = 0; row < nrows; row++) {
759 l = strlen(matches[row]);
760 if (column_width < l)
763 column_width += 2; /* min space for columns */
764 ncols = cmdedit_termw / column_width;
769 nrows++; /* round up fractionals */
773 for (row = 0; row < nrows; row++) {
777 for (nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
778 printf("%s%-*s", matches[n],
779 (int)(column_width - strlen(matches[n])), "");
785 static char *add_quote_for_spec_chars(char *found)
788 char *s = xmalloc((strlen(found) + 1) * 2);
791 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
799 static int match_compare(const void *a, const void *b)
801 return strcmp(*(char**)a, *(char**)b);
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(matches, num_matches, sizeof(char*), match_compare);
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)
962 for (hi = state->cnt_history; hi > 0;) {
964 free(state->history[hi]);
967 fp = fopen(fromfile, "r");
969 for (hi = 0; hi < MAX_HISTORY;) {
970 char *hl = xmalloc_getline(fp);
976 if (l >= MAX_LINELEN)
977 hl[MAX_LINELEN-1] = '\0';
978 if (l == 0 || hl[0] == ' ') {
982 state->history[hi++] = hl;
986 state->cur_history = state->cnt_history = hi;
989 /* state->flags is already checked to be nonzero */
990 static void save_history(const char *tofile)
994 fp = fopen(tofile, "w");
998 for (i = 0; i < state->cnt_history; i++) {
999 fprintf(fp, "%s\n", state->history[i]);
1005 #define load_history(a) ((void)0)
1006 #define save_history(a) ((void)0)
1007 #endif /* FEATURE_COMMAND_SAVEHISTORY */
1009 static void remember_in_history(const char *str)
1013 if (!(state->flags & DO_HISTORY))
1016 i = state->cnt_history;
1017 free(state->history[MAX_HISTORY]);
1018 state->history[MAX_HISTORY] = NULL;
1019 /* After max history, remove the oldest command */
1020 if (i >= MAX_HISTORY) {
1021 free(state->history[0]);
1022 for (i = 0; i < MAX_HISTORY-1; i++)
1023 state->history[i] = state->history[i+1];
1025 // Maybe "if (!i || strcmp(history[i-1], command) != 0) ..."
1026 // (i.e. do not save dups?)
1027 state->history[i++] = xstrdup(str);
1028 state->cur_history = i;
1029 state->cnt_history = i;
1030 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1031 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1032 save_history(state->hist_file);
1034 USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;)
1037 #else /* MAX_HISTORY == 0 */
1038 #define remember_in_history(a) ((void)0)
1039 #endif /* MAX_HISTORY */
1043 * This function is used to grab a character buffer
1044 * from the input file descriptor and allows you to
1045 * a string with full command editing (sort of like
1048 * The following standard commands are not implemented:
1049 * ESC-b -- Move back one word
1050 * ESC-f -- Move forward one word
1051 * ESC-d -- Delete back one word
1052 * ESC-h -- Delete forward one word
1053 * CTL-t -- Transpose two characters
1055 * Minimalist vi-style command line editing available if configured.
1056 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
1059 #if ENABLE_FEATURE_EDITING_VI
1061 vi_Word_motion(char *command, int eat)
1063 while (cursor < command_len && !isspace(command[cursor]))
1065 if (eat) while (cursor < command_len && isspace(command[cursor]))
1070 vi_word_motion(char *command, int eat)
1072 if (isalnum(command[cursor]) || command[cursor] == '_') {
1073 while (cursor < command_len
1074 && (isalnum(command[cursor+1]) || command[cursor+1] == '_'))
1076 } else if (ispunct(command[cursor])) {
1077 while (cursor < command_len && ispunct(command[cursor+1]))
1081 if (cursor < command_len)
1084 if (eat && cursor < command_len && isspace(command[cursor]))
1085 while (cursor < command_len && isspace(command[cursor]))
1090 vi_End_motion(char *command)
1093 while (cursor < command_len && isspace(command[cursor]))
1095 while (cursor < command_len-1 && !isspace(command[cursor+1]))
1100 vi_end_motion(char *command)
1102 if (cursor >= command_len-1)
1105 while (cursor < command_len-1 && isspace(command[cursor]))
1107 if (cursor >= command_len-1)
1109 if (isalnum(command[cursor]) || command[cursor] == '_') {
1110 while (cursor < command_len-1
1111 && (isalnum(command[cursor+1]) || command[cursor+1] == '_')
1115 } else if (ispunct(command[cursor])) {
1116 while (cursor < command_len-1 && ispunct(command[cursor+1]))
1122 vi_Back_motion(char *command)
1124 while (cursor > 0 && isspace(command[cursor-1]))
1126 while (cursor > 0 && !isspace(command[cursor-1]))
1131 vi_back_motion(char *command)
1136 while (cursor > 0 && isspace(command[cursor]))
1140 if (isalnum(command[cursor]) || command[cursor] == '_') {
1142 && (isalnum(command[cursor-1]) || command[cursor-1] == '_')
1146 } else if (ispunct(command[cursor])) {
1147 while (cursor > 0 && ispunct(command[cursor-1]))
1155 * read_line_input and its helpers
1158 #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
1159 static void parse_and_put_prompt(const char *prmt_ptr)
1161 cmdedit_prompt = prmt_ptr;
1162 cmdedit_prmt_len = strlen(prmt_ptr);
1166 static void parse_and_put_prompt(const char *prmt_ptr)
1169 size_t cur_prmt_len = 0;
1170 char flg_not_length = '[';
1171 char *prmt_mem_ptr = xzalloc(1);
1172 char *cwd_buf = xrealloc_getcwd_or_warn(NULL);
1177 cmdedit_prmt_len = 0;
1180 cwd_buf = (char *)bb_msg_unknown;
1183 cbuf[1] = '\0'; /* never changes */
1186 char *free_me = NULL;
1191 const char *cp = prmt_ptr;
1194 c = bb_process_escape_sequence(&prmt_ptr);
1195 if (prmt_ptr == cp) {
1201 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1203 pbuf = user_buf ? user_buf : (char*)"";
1207 pbuf = free_me = safe_gethostname();
1208 *strchrnul(pbuf, '.') = '\0';
1211 c = (geteuid() == 0 ? '#' : '$');
1213 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1215 /* /home/user[/something] -> ~[/something] */
1217 l = strlen(home_pwd_buf);
1219 && strncmp(home_pwd_buf, cwd_buf, l) == 0
1220 && (cwd_buf[l]=='/' || cwd_buf[l]=='\0')
1221 && strlen(cwd_buf + l) < PATH_MAX
1223 pbuf = free_me = xasprintf("~%s", cwd_buf + l);
1229 cp = strrchr(pbuf, '/');
1230 if (cp != NULL && cp != pbuf)
1231 pbuf += (cp-pbuf) + 1;
1234 pbuf = free_me = xasprintf("%d", num_ok_lines);
1236 case 'e': case 'E': /* \e \E = \033 */
1239 case 'x': case 'X': {
1241 for (l = 0; l < 3;) {
1243 buf2[l++] = *prmt_ptr;
1245 h = strtoul(buf2, &pbuf, 16);
1246 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
1252 c = (char)strtoul(buf2, NULL, 16);
1259 if (c == flg_not_length) {
1260 flg_not_length = (flg_not_length == '[' ? ']' : '[');
1268 cur_prmt_len = strlen(pbuf);
1269 prmt_len += cur_prmt_len;
1270 if (flg_not_length != ']')
1271 cmdedit_prmt_len += cur_prmt_len;
1272 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
1276 if (cwd_buf != (char *)bb_msg_unknown)
1278 cmdedit_prompt = prmt_mem_ptr;
1283 static void cmdedit_setwidth(unsigned w, int redraw_flg)
1287 /* new y for current cursor */
1288 int new_y = (cursor + cmdedit_prmt_len) / w;
1290 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), command_len - cursor);
1295 static void win_changed(int nsig)
1298 get_terminal_width_height(0, &width, NULL);
1299 cmdedit_setwidth(width, nsig /* - just a yes/no flag */);
1300 if (nsig == SIGWINCH)
1301 signal(SIGWINCH, win_changed); /* rearm ourself */
1305 * The emacs and vi modes share much of the code in the big
1306 * command loop. Commands entered when in vi's command mode (aka
1307 * "escape mode") get an extra bit added to distinguish them --
1308 * this keeps them from being self-inserted. This clutters the
1309 * big switch a bit, but keeps all the code in one place.
1314 /* leave out the "vi-mode"-only case labels if vi editing isn't
1316 #define vi_case(caselabel) USE_FEATURE_EDITING(case caselabel)
1318 /* convert uppercase ascii to equivalent control char, for readability */
1320 #define CTRL(a) ((a) & ~0x40)
1323 * -1 on read errors or EOF, or on bare Ctrl-D,
1324 * 0 on ctrl-C (the line entered is still returned in 'command'),
1325 * >0 length of input string, including terminating '\n'
1327 int read_line_input(const char *prompt, char *command, int maxsize, line_input_t *st)
1329 #if ENABLE_FEATURE_TAB_COMPLETION
1330 smallint lastWasTab = FALSE;
1334 smallint break_out = 0;
1335 #if ENABLE_FEATURE_EDITING_VI
1336 smallint vi_cmdmode = 0;
1339 struct termios initial_settings;
1340 struct termios new_settings;
1344 if (tcgetattr(STDIN_FILENO, &initial_settings) < 0
1345 || !(initial_settings.c_lflag & ECHO)
1347 /* Happens when e.g. stty -echo was run before */
1349 parse_and_put_prompt(prompt);
1351 if (fgets(command, maxsize, stdin) == NULL)
1352 len = -1; /* EOF or error */
1354 len = strlen(command);
1359 // FIXME: audit & improve this
1360 if (maxsize > MAX_LINELEN)
1361 maxsize = MAX_LINELEN;
1363 /* With null flags, no other fields are ever used */
1364 state = st ? st : (line_input_t*) &const_int_0;
1365 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1366 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1367 load_history(state->hist_file);
1370 /* prepare before init handlers */
1371 cmdedit_y = 0; /* quasireal y, not true if line > xt*yt */
1373 command_ps = command;
1376 new_settings = initial_settings;
1377 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1378 /* Turn off echoing and CTRL-C, so we can trap it */
1379 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1380 /* Hmm, in linux c_cc[] is not parsed if ICANON is off */
1381 new_settings.c_cc[VMIN] = 1;
1382 new_settings.c_cc[VTIME] = 0;
1383 /* Turn off CTRL-C, so we can trap it */
1384 #ifndef _POSIX_VDISABLE
1385 #define _POSIX_VDISABLE '\0'
1387 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1388 tcsetattr(STDIN_FILENO, TCSANOW, &new_settings);
1390 /* Now initialize things */
1391 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
1392 win_changed(0); /* do initial resizing */
1393 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1395 struct passwd *entry;
1397 entry = getpwuid(geteuid());
1399 user_buf = xstrdup(entry->pw_name);
1400 home_pwd_buf = xstrdup(entry->pw_dir);
1404 /* Print out the command prompt */
1405 parse_and_put_prompt(prompt);
1410 if (nonblock_safe_read(STDIN_FILENO, &c, 1) < 1) {
1411 /* if we can't read input then exit */
1412 goto prepare_to_die;
1417 #if ENABLE_FEATURE_EDITING_VI
1433 /* Control-a -- Beginning of line */
1434 input_backward(cursor);
1439 vi_case('\x7f'|vbit:) /* DEL */
1440 /* Control-b -- Move back one character */
1444 vi_case(CTRL('C')|vbit:)
1445 /* Control-c -- stop gathering input */
1448 break_out = -1; /* "do not append '\n'" */
1451 /* Control-d -- Delete one character, or exit
1452 * if the len=0 and no chars to delete */
1453 if (command_len == 0) {
1456 /* to control stopped jobs */
1457 break_out = command_len = -1;
1465 /* Control-e -- End of line */
1471 /* Control-f -- Move forward one character */
1476 case '\x7f': /* DEL */
1477 /* Control-h and DEL */
1481 #if ENABLE_FEATURE_TAB_COMPLETION
1483 input_tab(&lastWasTab);
1488 /* Control-k -- clear to end of line */
1489 command[cursor] = 0;
1490 command_len = cursor;
1494 vi_case(CTRL('L')|vbit:)
1495 /* Control-l -- clear screen */
1497 redraw(0, command_len - cursor);
1502 vi_case(CTRL('N')|vbit:)
1504 /* Control-n -- Get next command in history */
1505 if (get_next_history())
1509 vi_case(CTRL('P')|vbit:)
1511 /* Control-p -- Get previous command from history */
1512 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1513 get_previous_history();
1521 vi_case(CTRL('U')|vbit:)
1522 /* Control-U -- Clear line before cursor */
1524 strcpy(command, command + cursor);
1525 command_len -= cursor;
1526 redraw(cmdedit_y, command_len);
1530 vi_case(CTRL('W')|vbit:)
1531 /* Control-W -- Remove the last word */
1532 while (cursor > 0 && isspace(command[cursor-1]))
1534 while (cursor > 0 && !isspace(command[cursor-1]))
1538 #if ENABLE_FEATURE_EDITING_VI
1543 input_backward(cursor);
1564 vi_Word_motion(command, 1);
1567 vi_word_motion(command, 1);
1570 vi_End_motion(command);
1573 vi_end_motion(command);
1576 vi_Back_motion(command);
1579 vi_back_motion(command);
1594 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1595 goto prepare_to_die;
1596 if (c == (prevc & 0xff)) {
1598 input_backward(cursor);
1608 case 'w': /* "dw", "cw" */
1609 vi_word_motion(command, vi_cmdmode);
1611 case 'W': /* 'dW', 'cW' */
1612 vi_Word_motion(command, vi_cmdmode);
1614 case 'e': /* 'de', 'ce' */
1615 vi_end_motion(command);
1618 case 'E': /* 'dE', 'cE' */
1619 vi_End_motion(command);
1624 input_backward(cursor - sc);
1625 while (nc-- > cursor)
1628 case 'b': /* "db", "cb" */
1629 case 'B': /* implemented as B */
1631 vi_back_motion(command);
1633 vi_Back_motion(command);
1634 while (sc-- > cursor)
1637 case ' ': /* "d ", "c " */
1640 case '$': /* "d$", "c$" */
1642 while (cursor < command_len)
1655 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1656 goto prepare_to_die;
1660 *(command + cursor) = c;
1665 #endif /* FEATURE_COMMAND_EDITING_VI */
1667 case '\x1b': /* ESC */
1669 #if ENABLE_FEATURE_EDITING_VI
1670 if (state->flags & VI_MODE) {
1671 /* ESC: insert mode --> command mode */
1677 /* escape sequence follows */
1678 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1679 goto prepare_to_die;
1680 /* different vt100 emulations */
1681 if (c == '[' || c == 'O') {
1684 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1685 goto prepare_to_die;
1687 if (c >= '1' && c <= '9') {
1688 unsigned char dummy;
1690 if (safe_read(STDIN_FILENO, &dummy, 1) < 1)
1691 goto prepare_to_die;
1697 #if ENABLE_FEATURE_TAB_COMPLETION
1698 case '\t': /* Alt-Tab */
1699 input_tab(&lastWasTab);
1704 /* Up Arrow -- Get previous command from history */
1705 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1706 get_previous_history();
1712 /* Down Arrow -- Get next command in history */
1713 if (!get_next_history())
1716 /* Rewrite the line with the selected history item */
1717 /* change command */
1718 command_len = strlen(strcpy(command, state->history[state->cur_history]));
1719 /* redraw and go to eol (bol, in vi */
1720 redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
1724 /* Right Arrow -- Move forward one character */
1728 /* Left Arrow -- Move back one character */
1735 case '1': // vt100? linux vt? or what?
1736 case '7': // vt100? linux vt? or what?
1737 case 'H': /* xterm's <Home> */
1738 input_backward(cursor);
1740 case '4': // vt100? linux vt? or what?
1741 case '8': // vt100? linux vt? or what?
1742 case 'F': /* xterm's <End> */
1751 default: /* If it's regular input, do the normal thing */
1753 /* Control-V -- force insert of next char */
1754 if (c == CTRL('V')) {
1755 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1756 goto prepare_to_die;
1763 #if ENABLE_FEATURE_EDITING_VI
1764 if (vi_cmdmode) /* Don't self-insert */
1767 if (command_len >= (maxsize - 2)) /* Need to leave space for enter */
1771 if (cursor == (command_len - 1)) { /* Append if at the end of the line */
1772 command[cursor] = c;
1773 command[cursor+1] = '\0';
1774 cmdedit_set_out_char(' ');
1775 } else { /* Insert otherwise */
1778 memmove(command + sc + 1, command + sc, command_len - sc);
1781 /* rewrite from cursor */
1783 /* to prev x pos + 1 */
1784 input_backward(cursor - sc);
1788 if (break_out) /* Enter is the command terminator, no more input. */
1791 #if ENABLE_FEATURE_TAB_COMPLETION
1797 if (command_len > 0)
1798 remember_in_history(command);
1800 if (break_out > 0) {
1801 command[command_len++] = '\n';
1802 command[command_len] = '\0';
1805 #if ENABLE_FEATURE_TAB_COMPLETION
1806 free_tab_completion_data();
1809 /* restore initial_settings */
1810 tcsetattr(STDIN_FILENO, TCSANOW, &initial_settings);
1811 /* restore SIGWINCH handler */
1812 signal(SIGWINCH, previous_SIGWINCH_handler);
1820 line_input_t *new_line_input_t(int flags)
1822 line_input_t *n = xzalloc(sizeof(*n));
1829 #undef read_line_input
1830 int read_line_input(const char* prompt, char* command, int maxsize)
1832 fputs(prompt, stdout);
1834 fgets(command, maxsize, stdin);
1835 return strlen(command);
1838 #endif /* FEATURE_COMMAND_EDITING */
1849 const char *applet_name = "debug stuff usage";
1851 int main(int argc, char **argv)
1853 char buff[MAX_LINELEN];
1855 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1856 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:"
1857 "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] "
1858 "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1863 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1864 setlocale(LC_ALL, "");
1868 l = read_line_input(prompt, buff);
1869 if (l <= 0 || buff[l-1] != '\n')
1872 printf("*** read_line_input() returned line =%s=\n", buff);
1874 printf("*** read_line_input() detect ^D\n");