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)); \
149 cmdedit_termw = 80; \
150 USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines = 1;) \
151 USE_FEATURE_GETUSERNAME_AND_HOMEDIR(home_pwd_buf = (char*)null_str;) \
153 static void deinit_S(void)
155 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
156 /* This one is allocated only if FANCY_PROMPT is on
157 * (otherwise it points to verbatim prompt (NOT malloced) */
158 free((char*)cmdedit_prompt);
160 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
162 if (home_pwd_buf != null_str)
165 free(ptr_to_statics);
167 #define DEINIT_S() deinit_S()
169 /* Put 'command_ps[cursor]', cursor++.
170 * Advance cursor on screen. If we reached right margin, scroll text up
171 * and remove terminal margin effect by printing 'next_char' */
172 static void cmdedit_set_out_char(int next_char)
174 int c = (unsigned char)command_ps[cursor];
177 /* erase character after end of input string */
180 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
181 /* Display non-printable characters in reverse */
189 printf("\033[7m%c\033[0m", c);
195 if (++cmdedit_x >= cmdedit_termw) {
196 /* terminal is scrolled down */
199 /* destroy "(auto)margin" */
200 bb_putchar(next_char);
203 // Huh? What if command_ps[cursor] == '\0' (we are at the end already?)
207 /* Move to end of line (by printing all chars till the end) */
208 static void input_end(void)
210 while (cursor < command_len)
211 cmdedit_set_out_char(' ');
214 /* Go to the next line */
215 static void goto_new_line(void)
223 static void out1str(const char *s)
229 static void beep(void)
234 /* Move back one character */
235 /* (optimized for slow terminals) */
236 static void input_backward(unsigned num)
246 if (cmdedit_x >= num) {
249 printf("\b\b\b\b" + (4-num));
252 printf("\033[%uD", num);
256 /* Need to go one or more lines up */
258 count_y = 1 + (num / cmdedit_termw);
259 cmdedit_y -= count_y;
260 cmdedit_x = cmdedit_termw * count_y - num;
261 /* go to 1st column; go up; go to correct column */
262 printf("\r" "\033[%dA" "\033[%dC", count_y, cmdedit_x);
265 static void put_prompt(void)
267 out1str(cmdedit_prompt);
268 cmdedit_x = cmdedit_prmt_len;
270 // Huh? what if cmdedit_prmt_len >= width?
271 cmdedit_y = 0; /* new quasireal y */
274 /* draw prompt, editor line, and clear tail */
275 static void redraw(int y, int back_cursor)
277 if (y > 0) /* up to start y */
278 printf("\033[%dA", y);
281 input_end(); /* rewrite */
282 printf("\033[J"); /* erase after cursor */
283 input_backward(back_cursor);
286 /* Delete the char in front of the cursor, optionally saving it
287 * for later putback */
288 static void input_delete(int save)
292 if (j == command_len)
295 #if ENABLE_FEATURE_EDITING_VI
301 if ((delptr - delbuf) < DELBUFSIZ)
302 *delptr++ = command_ps[j];
306 strcpy(command_ps + j, command_ps + j + 1);
308 input_end(); /* rewrite new line */
309 cmdedit_set_out_char(' '); /* erase char */
310 input_backward(cursor - j); /* back to old pos cursor */
313 #if ENABLE_FEATURE_EDITING_VI
314 static void put(void)
317 int j = delptr - delbuf;
322 /* open hole and then fill it */
323 memmove(command_ps + cursor + j, command_ps + cursor, command_len - cursor + 1);
324 strncpy(command_ps + cursor, delbuf, j);
326 input_end(); /* rewrite new line */
327 input_backward(cursor - ocursor - j + 1); /* at end of new text */
331 /* Delete the char in back of the cursor */
332 static void input_backspace(void)
340 /* Move forward one character */
341 static void input_forward(void)
343 if (cursor < command_len)
344 cmdedit_set_out_char(command_ps[cursor + 1]);
347 #if ENABLE_FEATURE_TAB_COMPLETION
349 static void free_tab_completion_data(void)
353 free(matches[--num_matches]);
359 static void add_match(char *matched)
361 int nm = num_matches;
364 matches = xrealloc(matches, nm1 * sizeof(char *));
365 matches[nm] = matched;
369 #if ENABLE_FEATURE_USERNAME_COMPLETION
370 static void username_tab_completion(char *ud, char *with_shash_flg)
372 struct passwd *entry;
375 ud++; /* ~user/... to user/... */
376 userlen = strlen(ud);
378 if (with_shash_flg) { /* "~/..." or "~user/..." */
379 char *sav_ud = ud - 1;
382 if (*ud == '/') { /* "~/..." */
387 temp = strchr(ud, '/');
388 *temp = '\0'; /* ~user\0 */
389 entry = getpwnam(ud);
390 *temp = '/'; /* restore ~user/... */
393 home = entry->pw_dir;
396 if ((userlen + strlen(home) + 1) < MAX_LINELEN) {
398 sprintf(sav_ud, "%s%s", home, ud);
403 /* Using _r function to avoid pulling in static buffers */
406 struct passwd *result;
409 while (!getpwent_r(&pwd, line_buff, sizeof(line_buff), &result)) {
410 /* Null usernames should result in all users as possible completions. */
411 if (/*!userlen || */ strncmp(ud, pwd.pw_name, userlen) == 0) {
412 add_match(xasprintf("~%s/", pwd.pw_name));
418 #endif /* FEATURE_COMMAND_USERNAME_COMPLETION */
426 static int path_parse(char ***p, int flags)
433 /* if not setenv PATH variable, to search cur dir "." */
434 if (flags != FIND_EXE_ONLY)
437 if (state->flags & WITH_PATH_LOOKUP)
438 pth = state->path_lookup;
440 pth = getenv("PATH");
441 /* PATH=<empty> or PATH=:<empty> */
442 if (!pth || !pth[0] || LONE_CHAR(pth, ':'))
446 npth = 1; /* path component count */
448 tmp = strchr(tmp, ':');
452 break; /* :<empty> */
456 res = xmalloc(npth * sizeof(char*));
457 res[0] = tmp = xstrdup(pth);
460 tmp = strchr(tmp, ':');
463 *tmp++ = '\0'; /* ':' -> '\0' */
465 break; /* :<empty> */
472 static void exe_n_cwd_tab_completion(char *command, int type)
478 char **paths = path1;
482 char *pfind = strrchr(command, '/');
483 /* char dirbuf[MAX_LINELEN]; */
484 #define dirbuf (S.exe_n_cwd_tab_completion__dirbuf)
487 path1[0] = (char*)".";
490 /* no dir, if flags==EXE_ONLY - get paths, else "." */
491 npaths = path_parse(&paths, type);
494 /* dirbuf = ".../.../.../" */
495 safe_strncpy(dirbuf, command, (pfind - command) + 2);
496 #if ENABLE_FEATURE_USERNAME_COMPLETION
497 if (dirbuf[0] == '~') /* ~/... or ~user/... */
498 username_tab_completion(dirbuf, dirbuf);
501 /* point to 'l' in "..../last_component" */
505 for (i = 0; i < npaths; i++) {
506 dir = opendir(paths[i]);
507 if (!dir) /* Don't print an error */
510 while ((next = readdir(dir)) != NULL) {
512 const char *str_found = next->d_name;
515 if (strncmp(str_found, pfind, strlen(pfind)))
517 /* not see .name without .match */
518 if (*str_found == '.' && *pfind == 0) {
519 if (NOT_LONE_CHAR(paths[i], '/') || str_found[1])
521 str_found = ""; /* only "/" */
523 found = concat_path_file(paths[i], str_found);
524 /* hmm, remover in progress? */
525 if (lstat(found, &st) < 0)
527 /* find with dirs? */
528 if (paths[i] != dirbuf)
529 strcpy(found, next->d_name); /* only name */
531 len1 = strlen(found);
532 found = xrealloc(found, len1 + 2);
534 found[len1+1] = '\0';
536 if (S_ISDIR(st.st_mode)) {
537 /* name is directory */
538 if (found[len1-1] != '/') {
542 /* not put found file if search only dirs for cd */
543 if (type == FIND_DIR_ONLY)
546 /* Add it to the list */
554 if (paths != path1) {
555 free(paths[0]); /* allocated memory only in first member */
561 #define QUOT (UCHAR_MAX+1)
563 #define collapse_pos(is, in) do { \
564 memmove(int_buf+(is), int_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
565 memmove(pos_buf+(is), pos_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
568 static int find_match(char *matchBuf, int *len_with_quotes)
573 /* int16_t int_buf[MAX_LINELEN + 1]; */
574 /* int16_t pos_buf[MAX_LINELEN + 1]; */
575 #define int_buf (S.find_match__int_buf)
576 #define pos_buf (S.find_match__pos_buf)
578 /* set to integer dimension characters and own positions */
580 int_buf[i] = (unsigned char)matchBuf[i];
581 if (int_buf[i] == 0) {
582 pos_buf[i] = -1; /* indicator end line */
588 /* mask \+symbol and convert '\t' to ' ' */
589 for (i = j = 0; matchBuf[i]; i++, j++)
590 if (matchBuf[i] == '\\') {
591 collapse_pos(j, j + 1);
594 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
595 if (matchBuf[i] == '\t') /* algorithm equivalent */
596 int_buf[j] = ' ' | QUOT;
599 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
600 else if (matchBuf[i] == '\t')
604 /* mask "symbols" or 'symbols' */
606 for (i = 0; int_buf[i]; i++) {
608 if (c == '\'' || c == '"') {
617 } else if (c2 != 0 && c != '$')
621 /* skip commands with arguments if line has commands delimiters */
622 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
623 for (i = 0; int_buf[i]; i++) {
626 j = i ? int_buf[i - 1] : -1;
628 if (c == ';' || c == '&' || c == '|') {
629 command_mode = 1 + (c == c2);
631 if (j == '>' || j == '<')
633 } else if (c == '|' && j == '>')
637 collapse_pos(0, i + command_mode);
638 i = -1; /* hack incremet */
641 /* collapse `command...` */
642 for (i = 0; int_buf[i]; i++)
643 if (int_buf[i] == '`') {
644 for (j = i + 1; int_buf[j]; j++)
645 if (int_buf[j] == '`') {
646 collapse_pos(i, j + 1);
651 /* not found close ` - command mode, collapse all previous */
652 collapse_pos(0, i + 1);
655 i--; /* hack incremet */
658 /* collapse (command...(command...)...) or {command...{command...}...} */
659 c = 0; /* "recursive" level */
661 for (i = 0; int_buf[i]; i++)
662 if (int_buf[i] == '(' || int_buf[i] == '{') {
663 if (int_buf[i] == '(')
667 collapse_pos(0, i + 1);
668 i = -1; /* hack incremet */
670 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
671 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
672 if (int_buf[i] == ')')
676 collapse_pos(0, i + 1);
677 i = -1; /* hack incremet */
680 /* skip first not quote space */
681 for (i = 0; int_buf[i]; i++)
682 if (int_buf[i] != ' ')
687 /* set find mode for completion */
688 command_mode = FIND_EXE_ONLY;
689 for (i = 0; int_buf[i]; i++)
690 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
691 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
692 && matchBuf[pos_buf[0]] == 'c'
693 && matchBuf[pos_buf[1]] == 'd'
695 command_mode = FIND_DIR_ONLY;
697 command_mode = FIND_FILE_ONLY;
701 for (i = 0; int_buf[i]; i++)
704 for (--i; i >= 0; i--) {
706 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
707 collapse_pos(0, i + 1);
711 /* skip first not quoted '\'' or '"' */
712 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++)
714 /* collapse quote or unquote // or /~ */
715 while ((int_buf[i] & ~QUOT) == '/'
716 && ((int_buf[i+1] & ~QUOT) == '/' || (int_buf[i+1] & ~QUOT) == '~')
721 /* set only match and destroy quotes */
723 for (c = 0; pos_buf[i] >= 0; i++) {
724 matchBuf[c++] = matchBuf[pos_buf[i]];
728 /* old length matchBuf with quotes symbols */
729 *len_with_quotes = j ? j - pos_buf[0] : 0;
737 * display by column (original idea from ls applet,
738 * very optimized by me :)
740 static void showfiles(void)
743 int column_width = 0;
744 int nfiles = num_matches;
748 /* find the longest file name- use that as the column width */
749 for (row = 0; row < nrows; row++) {
750 l = strlen(matches[row]);
751 if (column_width < l)
754 column_width += 2; /* min space for columns */
755 ncols = cmdedit_termw / column_width;
760 nrows++; /* round up fractionals */
764 for (row = 0; row < nrows; row++) {
768 for (nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
769 printf("%s%-*s", matches[n],
770 (int)(column_width - strlen(matches[n])), "");
776 static char *add_quote_for_spec_chars(char *found)
779 char *s = xmalloc((strlen(found) + 1) * 2);
782 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
790 static int match_compare(const void *a, const void *b)
792 return strcmp(*(char**)a, *(char**)b);
795 /* Do TAB completion */
796 static void input_tab(smallint *lastWasTab)
798 if (!(state->flags & TAB_COMPLETION))
804 /* char matchBuf[MAX_LINELEN]; */
805 #define matchBuf (S.input_tab__matchBuf)
809 *lastWasTab = TRUE; /* flop trigger */
811 /* Make a local copy of the string -- up
812 * to the position of the cursor */
813 tmp = strncpy(matchBuf, command_ps, cursor);
816 find_type = find_match(matchBuf, &recalc_pos);
818 /* Free up any memory already allocated */
819 free_tab_completion_data();
821 #if ENABLE_FEATURE_USERNAME_COMPLETION
822 /* If the word starts with `~' and there is no slash in the word,
823 * then try completing this word as a username. */
824 if (state->flags & USERNAME_COMPLETION)
825 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
826 username_tab_completion(matchBuf, NULL);
828 /* Try to match any executable in our path and everything
829 * in the current working directory */
831 exe_n_cwd_tab_completion(matchBuf, find_type);
832 /* Sort, then remove any duplicates found */
835 qsort(matches, num_matches, sizeof(char*), match_compare);
836 for (i = 0; i < num_matches - 1; ++i) {
837 if (matches[i] && matches[i+1]) { /* paranoia */
838 if (strcmp(matches[i], matches[i+1]) == 0) {
840 matches[i] = NULL; /* paranoia */
842 matches[n++] = matches[i];
846 matches[n] = matches[i];
849 /* Did we find exactly one match? */
850 if (!matches || num_matches > 1) {
853 return; /* not found */
854 /* find minimal match */
855 tmp1 = xstrdup(matches[0]);
856 for (tmp = tmp1; *tmp; tmp++)
857 for (len_found = 1; len_found < num_matches; len_found++)
858 if (matches[len_found][(tmp - tmp1)] != *tmp) {
862 if (*tmp1 == '\0') { /* have unique */
866 tmp = add_quote_for_spec_chars(tmp1);
868 } else { /* one match */
869 tmp = add_quote_for_spec_chars(matches[0]);
870 /* for next completion current found */
873 len_found = strlen(tmp);
874 if (tmp[len_found-1] != '/') {
875 tmp[len_found] = ' ';
876 tmp[len_found+1] = '\0';
879 len_found = strlen(tmp);
880 /* have space to placed match? */
881 if ((len_found - strlen(matchBuf) + command_len) < MAX_LINELEN) {
882 /* before word for match */
883 command_ps[cursor - recalc_pos] = '\0';
885 strcpy(matchBuf, command_ps + cursor);
887 strcat(command_ps, tmp);
889 strcat(command_ps, matchBuf);
890 /* back to begin word for match */
891 input_backward(recalc_pos);
893 recalc_pos = cursor + len_found;
895 command_len = strlen(command_ps);
896 /* write out the matched command */
897 redraw(cmdedit_y, command_len - recalc_pos);
902 /* Ok -- the last char was a TAB. Since they
903 * just hit TAB again, print a list of all the
904 * available choices... */
905 if (matches && num_matches > 0) {
906 int sav_cursor = cursor; /* change goto_new_line() */
908 /* Go to the next line */
911 redraw(0, command_len - sav_cursor);
916 #endif /* FEATURE_COMMAND_TAB_COMPLETION */
921 /* state->flags is already checked to be nonzero */
922 static void get_previous_history(void)
924 if (command_ps[0] != '\0' || state->history[state->cur_history] == NULL) {
925 free(state->history[state->cur_history]);
926 state->history[state->cur_history] = xstrdup(command_ps);
928 state->cur_history--;
931 static int get_next_history(void)
933 if (state->flags & DO_HISTORY) {
934 int ch = state->cur_history;
935 if (ch < state->cnt_history) {
936 get_previous_history(); /* save the current history line */
937 state->cur_history = ch + 1;
938 return state->cur_history;
945 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
946 /* state->flags is already checked to be nonzero */
947 static void load_history(const char *fromfile)
953 for (hi = state->cnt_history; hi > 0;) {
955 free(state->history[hi]);
958 fp = fopen(fromfile, "r");
960 for (hi = 0; hi < MAX_HISTORY;) {
961 char *hl = xmalloc_getline(fp);
967 if (l >= MAX_LINELEN)
968 hl[MAX_LINELEN-1] = '\0';
969 if (l == 0 || hl[0] == ' ') {
973 state->history[hi++] = hl;
977 state->cur_history = state->cnt_history = hi;
980 /* state->flags is already checked to be nonzero */
981 static void save_history(const char *tofile)
985 fp = fopen(tofile, "w");
989 for (i = 0; i < state->cnt_history; i++) {
990 fprintf(fp, "%s\n", state->history[i]);
996 #define load_history(a) ((void)0)
997 #define save_history(a) ((void)0)
998 #endif /* FEATURE_COMMAND_SAVEHISTORY */
1000 static void remember_in_history(const char *str)
1004 if (!(state->flags & DO_HISTORY))
1007 i = state->cnt_history;
1008 free(state->history[MAX_HISTORY]);
1009 state->history[MAX_HISTORY] = NULL;
1010 /* After max history, remove the oldest command */
1011 if (i >= MAX_HISTORY) {
1012 free(state->history[0]);
1013 for (i = 0; i < MAX_HISTORY-1; i++)
1014 state->history[i] = state->history[i+1];
1016 // Maybe "if (!i || strcmp(history[i-1], command) != 0) ..."
1017 // (i.e. do not save dups?)
1018 state->history[i++] = xstrdup(str);
1019 state->cur_history = i;
1020 state->cnt_history = i;
1021 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1022 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1023 save_history(state->hist_file);
1025 USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;)
1028 #else /* MAX_HISTORY == 0 */
1029 #define remember_in_history(a) ((void)0)
1030 #endif /* MAX_HISTORY */
1034 * This function is used to grab a character buffer
1035 * from the input file descriptor and allows you to
1036 * a string with full command editing (sort of like
1039 * The following standard commands are not implemented:
1040 * ESC-b -- Move back one word
1041 * ESC-f -- Move forward one word
1042 * ESC-d -- Delete back one word
1043 * ESC-h -- Delete forward one word
1044 * CTL-t -- Transpose two characters
1046 * Minimalist vi-style command line editing available if configured.
1047 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
1050 #if ENABLE_FEATURE_EDITING_VI
1052 vi_Word_motion(char *command, int eat)
1054 while (cursor < command_len && !isspace(command[cursor]))
1056 if (eat) while (cursor < command_len && isspace(command[cursor]))
1061 vi_word_motion(char *command, int eat)
1063 if (isalnum(command[cursor]) || command[cursor] == '_') {
1064 while (cursor < command_len
1065 && (isalnum(command[cursor+1]) || command[cursor+1] == '_'))
1067 } else if (ispunct(command[cursor])) {
1068 while (cursor < command_len && ispunct(command[cursor+1]))
1072 if (cursor < command_len)
1075 if (eat && cursor < command_len && isspace(command[cursor]))
1076 while (cursor < command_len && isspace(command[cursor]))
1081 vi_End_motion(char *command)
1084 while (cursor < command_len && isspace(command[cursor]))
1086 while (cursor < command_len-1 && !isspace(command[cursor+1]))
1091 vi_end_motion(char *command)
1093 if (cursor >= command_len-1)
1096 while (cursor < command_len-1 && isspace(command[cursor]))
1098 if (cursor >= command_len-1)
1100 if (isalnum(command[cursor]) || command[cursor] == '_') {
1101 while (cursor < command_len-1
1102 && (isalnum(command[cursor+1]) || command[cursor+1] == '_')
1106 } else if (ispunct(command[cursor])) {
1107 while (cursor < command_len-1 && ispunct(command[cursor+1]))
1113 vi_Back_motion(char *command)
1115 while (cursor > 0 && isspace(command[cursor-1]))
1117 while (cursor > 0 && !isspace(command[cursor-1]))
1122 vi_back_motion(char *command)
1127 while (cursor > 0 && isspace(command[cursor]))
1131 if (isalnum(command[cursor]) || command[cursor] == '_') {
1133 && (isalnum(command[cursor-1]) || command[cursor-1] == '_')
1137 } else if (ispunct(command[cursor])) {
1138 while (cursor > 0 && ispunct(command[cursor-1]))
1146 * read_line_input and its helpers
1149 #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
1150 static void parse_and_put_prompt(const char *prmt_ptr)
1152 cmdedit_prompt = prmt_ptr;
1153 cmdedit_prmt_len = strlen(prmt_ptr);
1157 static void parse_and_put_prompt(const char *prmt_ptr)
1160 size_t cur_prmt_len = 0;
1161 char flg_not_length = '[';
1162 char *prmt_mem_ptr = xzalloc(1);
1163 char *cwd_buf = xrealloc_getcwd_or_warn(NULL);
1168 cmdedit_prmt_len = 0;
1171 cwd_buf = (char *)bb_msg_unknown;
1174 cbuf[1] = '\0'; /* never changes */
1177 char *free_me = NULL;
1182 const char *cp = prmt_ptr;
1185 c = bb_process_escape_sequence(&prmt_ptr);
1186 if (prmt_ptr == cp) {
1192 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1194 pbuf = user_buf ? user_buf : (char*)"";
1198 pbuf = free_me = xzalloc(256);
1199 if (gethostname(pbuf, 255) < 0) {
1203 *strchrnul(pbuf, '.') = '\0';
1206 c = (geteuid() == 0 ? '#' : '$');
1208 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1210 /* /home/user[/something] -> ~[/something] */
1212 l = strlen(home_pwd_buf);
1214 && strncmp(home_pwd_buf, cwd_buf, l) == 0
1215 && (cwd_buf[l]=='/' || cwd_buf[l]=='\0')
1216 && strlen(cwd_buf + l) < PATH_MAX
1218 pbuf = free_me = xasprintf("~%s", cwd_buf + l);
1224 cp = strrchr(pbuf, '/');
1225 if (cp != NULL && cp != pbuf)
1226 pbuf += (cp-pbuf) + 1;
1229 pbuf = free_me = xasprintf("%d", num_ok_lines);
1231 case 'e': case 'E': /* \e \E = \033 */
1234 case 'x': case 'X': {
1236 for (l = 0; l < 3;) {
1238 buf2[l++] = *prmt_ptr;
1240 h = strtoul(buf2, &pbuf, 16);
1241 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
1247 c = (char)strtoul(buf2, NULL, 16);
1254 if (c == flg_not_length) {
1255 flg_not_length = (flg_not_length == '[' ? ']' : '[');
1263 cur_prmt_len = strlen(pbuf);
1264 prmt_len += cur_prmt_len;
1265 if (flg_not_length != ']')
1266 cmdedit_prmt_len += cur_prmt_len;
1267 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
1271 if (cwd_buf != (char *)bb_msg_unknown)
1273 cmdedit_prompt = prmt_mem_ptr;
1278 static void cmdedit_setwidth(unsigned w, int redraw_flg)
1282 /* new y for current cursor */
1283 int new_y = (cursor + cmdedit_prmt_len) / w;
1285 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), command_len - cursor);
1290 static void win_changed(int nsig)
1293 get_terminal_width_height(0, &width, NULL);
1294 cmdedit_setwidth(width, nsig /* - just a yes/no flag */);
1295 if (nsig == SIGWINCH)
1296 signal(SIGWINCH, win_changed); /* rearm ourself */
1300 * The emacs and vi modes share much of the code in the big
1301 * command loop. Commands entered when in vi's command mode (aka
1302 * "escape mode") get an extra bit added to distinguish them --
1303 * this keeps them from being self-inserted. This clutters the
1304 * big switch a bit, but keeps all the code in one place.
1309 /* leave out the "vi-mode"-only case labels if vi editing isn't
1311 #define vi_case(caselabel) USE_FEATURE_EDITING(case caselabel)
1313 /* convert uppercase ascii to equivalent control char, for readability */
1315 #define CTRL(a) ((a) & ~0x40)
1318 * -1 on read errors or EOF, or on bare Ctrl-D,
1319 * 0 on ctrl-C (the line entered is still returned in 'command'),
1320 * >0 length of input string, including terminating '\n'
1322 int read_line_input(const char *prompt, char *command, int maxsize, line_input_t *st)
1324 #if ENABLE_FEATURE_TAB_COMPLETION
1325 smallint lastWasTab = FALSE;
1329 smallint break_out = 0;
1330 #if ENABLE_FEATURE_EDITING_VI
1331 smallint vi_cmdmode = 0;
1334 struct termios initial_settings;
1335 struct termios new_settings;
1339 if (tcgetattr(STDIN_FILENO, &initial_settings) < 0
1340 || !(initial_settings.c_lflag & ECHO)
1342 /* Happens when e.g. stty -echo was run before */
1344 parse_and_put_prompt(prompt);
1346 if (fgets(command, maxsize, stdin) == NULL)
1347 len = -1; /* EOF or error */
1349 len = strlen(command);
1354 // FIXME: audit & improve this
1355 if (maxsize > MAX_LINELEN)
1356 maxsize = MAX_LINELEN;
1358 /* With null flags, no other fields are ever used */
1359 state = st ? st : (line_input_t*) &const_int_0;
1360 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1361 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1362 load_history(state->hist_file);
1365 /* prepare before init handlers */
1366 cmdedit_y = 0; /* quasireal y, not true if line > xt*yt */
1368 command_ps = command;
1371 new_settings = initial_settings;
1372 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1373 /* Turn off echoing and CTRL-C, so we can trap it */
1374 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1375 /* Hmm, in linux c_cc[] is not parsed if ICANON is off */
1376 new_settings.c_cc[VMIN] = 1;
1377 new_settings.c_cc[VTIME] = 0;
1378 /* Turn off CTRL-C, so we can trap it */
1379 #ifndef _POSIX_VDISABLE
1380 #define _POSIX_VDISABLE '\0'
1382 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1383 tcsetattr(STDIN_FILENO, TCSANOW, &new_settings);
1385 /* Now initialize things */
1386 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
1387 win_changed(0); /* do initial resizing */
1388 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1390 struct passwd *entry;
1392 entry = getpwuid(geteuid());
1394 user_buf = xstrdup(entry->pw_name);
1395 home_pwd_buf = xstrdup(entry->pw_dir);
1399 /* Print out the command prompt */
1400 parse_and_put_prompt(prompt);
1405 if (safe_read(STDIN_FILENO, &c, 1) < 1) {
1406 /* if we can't read input then exit */
1407 goto prepare_to_die;
1412 #if ENABLE_FEATURE_EDITING_VI
1428 /* Control-a -- Beginning of line */
1429 input_backward(cursor);
1434 vi_case('\x7f'|vbit:) /* DEL */
1435 /* Control-b -- Move back one character */
1439 vi_case(CTRL('C')|vbit:)
1440 /* Control-c -- stop gathering input */
1443 break_out = -1; /* "do not append '\n'" */
1446 /* Control-d -- Delete one character, or exit
1447 * if the len=0 and no chars to delete */
1448 if (command_len == 0) {
1451 /* to control stopped jobs */
1452 break_out = command_len = -1;
1460 /* Control-e -- End of line */
1466 /* Control-f -- Move forward one character */
1471 case '\x7f': /* DEL */
1472 /* Control-h and DEL */
1476 #if ENABLE_FEATURE_TAB_COMPLETION
1478 input_tab(&lastWasTab);
1483 /* Control-k -- clear to end of line */
1484 command[cursor] = 0;
1485 command_len = cursor;
1489 vi_case(CTRL('L')|vbit:)
1490 /* Control-l -- clear screen */
1492 redraw(0, command_len - cursor);
1497 vi_case(CTRL('N')|vbit:)
1499 /* Control-n -- Get next command in history */
1500 if (get_next_history())
1504 vi_case(CTRL('P')|vbit:)
1506 /* Control-p -- Get previous command from history */
1507 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1508 get_previous_history();
1516 vi_case(CTRL('U')|vbit:)
1517 /* Control-U -- Clear line before cursor */
1519 strcpy(command, command + cursor);
1520 command_len -= cursor;
1521 redraw(cmdedit_y, command_len);
1525 vi_case(CTRL('W')|vbit:)
1526 /* Control-W -- Remove the last word */
1527 while (cursor > 0 && isspace(command[cursor-1]))
1529 while (cursor > 0 && !isspace(command[cursor-1]))
1533 #if ENABLE_FEATURE_EDITING_VI
1538 input_backward(cursor);
1559 vi_Word_motion(command, 1);
1562 vi_word_motion(command, 1);
1565 vi_End_motion(command);
1568 vi_end_motion(command);
1571 vi_Back_motion(command);
1574 vi_back_motion(command);
1589 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1590 goto prepare_to_die;
1591 if (c == (prevc & 0xff)) {
1593 input_backward(cursor);
1603 case 'w': /* "dw", "cw" */
1604 vi_word_motion(command, vi_cmdmode);
1606 case 'W': /* 'dW', 'cW' */
1607 vi_Word_motion(command, vi_cmdmode);
1609 case 'e': /* 'de', 'ce' */
1610 vi_end_motion(command);
1613 case 'E': /* 'dE', 'cE' */
1614 vi_End_motion(command);
1619 input_backward(cursor - sc);
1620 while (nc-- > cursor)
1623 case 'b': /* "db", "cb" */
1624 case 'B': /* implemented as B */
1626 vi_back_motion(command);
1628 vi_Back_motion(command);
1629 while (sc-- > cursor)
1632 case ' ': /* "d ", "c " */
1635 case '$': /* "d$", "c$" */
1637 while (cursor < command_len)
1650 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1651 goto prepare_to_die;
1655 *(command + cursor) = c;
1660 #endif /* FEATURE_COMMAND_EDITING_VI */
1662 case '\x1b': /* ESC */
1664 #if ENABLE_FEATURE_EDITING_VI
1665 if (state->flags & VI_MODE) {
1666 /* ESC: insert mode --> command mode */
1672 /* escape sequence follows */
1673 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1674 goto prepare_to_die;
1675 /* different vt100 emulations */
1676 if (c == '[' || c == 'O') {
1679 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1680 goto prepare_to_die;
1682 if (c >= '1' && c <= '9') {
1683 unsigned char dummy;
1685 if (safe_read(STDIN_FILENO, &dummy, 1) < 1)
1686 goto prepare_to_die;
1692 #if ENABLE_FEATURE_TAB_COMPLETION
1693 case '\t': /* Alt-Tab */
1694 input_tab(&lastWasTab);
1699 /* Up Arrow -- Get previous command from history */
1700 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1701 get_previous_history();
1707 /* Down Arrow -- Get next command in history */
1708 if (!get_next_history())
1711 /* Rewrite the line with the selected history item */
1712 /* change command */
1713 command_len = strlen(strcpy(command, state->history[state->cur_history]));
1714 /* redraw and go to eol (bol, in vi */
1715 redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
1719 /* Right Arrow -- Move forward one character */
1723 /* Left Arrow -- Move back one character */
1730 case '1': // vt100? linux vt? or what?
1731 case '7': // vt100? linux vt? or what?
1732 case 'H': /* xterm's <Home> */
1733 input_backward(cursor);
1735 case '4': // vt100? linux vt? or what?
1736 case '8': // vt100? linux vt? or what?
1737 case 'F': /* xterm's <End> */
1746 default: /* If it's regular input, do the normal thing */
1748 /* Control-V -- force insert of next char */
1749 if (c == CTRL('V')) {
1750 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1751 goto prepare_to_die;
1758 #if ENABLE_FEATURE_EDITING_VI
1759 if (vi_cmdmode) /* Don't self-insert */
1762 if (command_len >= (maxsize - 2)) /* Need to leave space for enter */
1766 if (cursor == (command_len - 1)) { /* Append if at the end of the line */
1767 command[cursor] = c;
1768 command[cursor+1] = '\0';
1769 cmdedit_set_out_char(' ');
1770 } else { /* Insert otherwise */
1773 memmove(command + sc + 1, command + sc, command_len - sc);
1776 /* rewrite from cursor */
1778 /* to prev x pos + 1 */
1779 input_backward(cursor - sc);
1783 if (break_out) /* Enter is the command terminator, no more input. */
1786 #if ENABLE_FEATURE_TAB_COMPLETION
1792 if (command_len > 0)
1793 remember_in_history(command);
1795 if (break_out > 0) {
1796 command[command_len++] = '\n';
1797 command[command_len] = '\0';
1800 #if ENABLE_FEATURE_TAB_COMPLETION
1801 free_tab_completion_data();
1804 /* restore initial_settings */
1805 tcsetattr(STDIN_FILENO, TCSANOW, &initial_settings);
1806 /* restore SIGWINCH handler */
1807 signal(SIGWINCH, previous_SIGWINCH_handler);
1815 line_input_t *new_line_input_t(int flags)
1817 line_input_t *n = xzalloc(sizeof(*n));
1824 #undef read_line_input
1825 int read_line_input(const char* prompt, char* command, int maxsize)
1827 fputs(prompt, stdout);
1829 fgets(command, maxsize, stdin);
1830 return strlen(command);
1833 #endif /* FEATURE_COMMAND_EDITING */
1844 const char *applet_name = "debug stuff usage";
1846 int main(int argc, char **argv)
1848 char buff[MAX_LINELEN];
1850 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1851 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:"
1852 "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] "
1853 "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1858 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1859 setlocale(LC_ALL, "");
1863 l = read_line_input(prompt, buff);
1864 if (l <= 0 || buff[l-1] != '\n')
1867 printf("*** read_line_input() returned line =%s=\n", buff);
1869 printf("*** read_line_input() detect ^D\n");