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. */
77 struct lineedit_statics {
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 /* See lineedit_ptr_hack.c */
124 extern struct lineedit_statics *const lineedit_ptr_to_statics;
126 #define S (*lineedit_ptr_to_statics)
127 #define state (S.state )
128 #define cmdedit_termw (S.cmdedit_termw )
129 #define previous_SIGWINCH_handler (S.previous_SIGWINCH_handler)
130 #define cmdedit_x (S.cmdedit_x )
131 #define cmdedit_y (S.cmdedit_y )
132 #define cmdedit_prmt_len (S.cmdedit_prmt_len)
133 #define cursor (S.cursor )
134 #define command_len (S.command_len )
135 #define command_ps (S.command_ps )
136 #define cmdedit_prompt (S.cmdedit_prompt )
137 #define num_ok_lines (S.num_ok_lines )
138 #define user_buf (S.user_buf )
139 #define home_pwd_buf (S.home_pwd_buf )
140 #define matches (S.matches )
141 #define num_matches (S.num_matches )
142 #define delptr (S.delptr )
143 #define newdelflag (S.newdelflag )
144 #define delbuf (S.delbuf )
146 #define INIT_S() do { \
147 (*(struct lineedit_statics**)&lineedit_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(lineedit_ptr_to_statics);
167 #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 #define HACK_FOR_WRONG_WIDTH 1
174 #if HACK_FOR_WRONG_WIDTH
175 static void cmdedit_set_out_char(void)
176 #define cmdedit_set_out_char(next_char) cmdedit_set_out_char()
178 static void cmdedit_set_out_char(int next_char)
181 int c = (unsigned char)command_ps[cursor];
184 /* erase character after end of input string */
187 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
188 /* Display non-printable characters in reverse */
196 printf("\033[7m%c\033[0m", c);
202 if (++cmdedit_x >= (int)cmdedit_termw) {
203 /* terminal is scrolled down */
206 #if HACK_FOR_WRONG_WIDTH
207 /* This works better if our idea of term width is wrong
208 * and it is actually wider (often happens on serial lines).
209 * Printing CR,LF *forces* cursor to next line.
210 * OTOH if terminal width is correct AND terminal does NOT
211 * have automargin (IOW: it is moving cursor to next line
212 * by itself (which is wrong for VT-10x terminals)),
213 * this will break things: there will be one extra empty line */
214 puts("\r"); /* + implicit '\n' */
216 /* Works ok only if cmdedit_termw is correct */
217 /* destroy "(auto)margin" */
218 bb_putchar(next_char);
222 // Huh? What if command_ps[cursor] == '\0' (we are at the end already?)
226 /* Move to end of line (by printing all chars till the end) */
227 static void input_end(void)
229 while (cursor < command_len)
230 cmdedit_set_out_char(' ');
233 /* Go to the next line */
234 static void goto_new_line(void)
242 static void out1str(const char *s)
248 static void beep(void)
253 /* Move back one character */
254 /* (optimized for slow terminals) */
255 static void input_backward(unsigned num)
265 if ((unsigned)cmdedit_x >= num) {
268 /* This is longer by 5 bytes on x86.
269 * Also gets mysteriously
270 * miscompiled for some ARM users.
271 * printf(("\b\b\b\b" + 4) - num);
279 printf("\033[%uD", num);
283 /* Need to go one or more lines up */
285 count_y = 1 + (num / cmdedit_termw);
286 cmdedit_y -= count_y;
287 cmdedit_x = cmdedit_termw * count_y - num;
288 /* go to 1st column; go up; go to correct column */
289 printf("\r" "\033[%dA" "\033[%dC", count_y, cmdedit_x);
292 static void put_prompt(void)
294 out1str(cmdedit_prompt);
295 cmdedit_x = cmdedit_prmt_len;
297 // Huh? what if cmdedit_prmt_len >= width?
298 cmdedit_y = 0; /* new quasireal y */
301 /* draw prompt, editor line, and clear tail */
302 static void redraw(int y, int back_cursor)
304 if (y > 0) /* up to start y */
305 printf("\033[%dA", y);
308 input_end(); /* rewrite */
309 printf("\033[J"); /* erase after cursor */
310 input_backward(back_cursor);
313 /* Delete the char in front of the cursor, optionally saving it
314 * for later putback */
315 #if !ENABLE_FEATURE_EDITING_VI
316 static void input_delete(void)
317 #define input_delete(save) input_delete()
319 static void input_delete(int save)
324 if (j == (int)command_len)
327 #if ENABLE_FEATURE_EDITING_VI
333 if ((delptr - delbuf) < DELBUFSIZ)
334 *delptr++ = command_ps[j];
338 strcpy(command_ps + j, command_ps + j + 1);
340 input_end(); /* rewrite new line */
341 cmdedit_set_out_char(' '); /* erase char */
342 input_backward(cursor - j); /* back to old pos cursor */
345 #if ENABLE_FEATURE_EDITING_VI
346 static void put(void)
349 int j = delptr - delbuf;
354 /* open hole and then fill it */
355 memmove(command_ps + cursor + j, command_ps + cursor, command_len - cursor + 1);
356 strncpy(command_ps + cursor, delbuf, j);
358 input_end(); /* rewrite new line */
359 input_backward(cursor - ocursor - j + 1); /* at end of new text */
363 /* Delete the char in back of the cursor */
364 static void input_backspace(void)
372 /* Move forward one character */
373 static void input_forward(void)
375 if (cursor < command_len)
376 cmdedit_set_out_char(command_ps[cursor + 1]);
379 #if ENABLE_FEATURE_TAB_COMPLETION
381 static void free_tab_completion_data(void)
385 free(matches[--num_matches]);
391 static void add_match(char *matched)
393 int nm = num_matches;
396 matches = xrealloc(matches, nm1 * sizeof(char *));
397 matches[nm] = matched;
401 #if ENABLE_FEATURE_USERNAME_COMPLETION
402 static void username_tab_completion(char *ud, char *with_shash_flg)
404 struct passwd *entry;
407 ud++; /* ~user/... to user/... */
408 userlen = strlen(ud);
410 if (with_shash_flg) { /* "~/..." or "~user/..." */
411 char *sav_ud = ud - 1;
414 if (*ud == '/') { /* "~/..." */
419 temp = strchr(ud, '/');
420 *temp = '\0'; /* ~user\0 */
421 entry = getpwnam(ud);
422 *temp = '/'; /* restore ~user/... */
425 home = entry->pw_dir;
428 if ((userlen + strlen(home) + 1) < MAX_LINELEN) {
430 sprintf(sav_ud, "%s%s", home, ud);
435 /* Using _r function to avoid pulling in static buffers */
438 struct passwd *result;
441 while (!getpwent_r(&pwd, line_buff, sizeof(line_buff), &result)) {
442 /* Null usernames should result in all users as possible completions. */
443 if (/*!userlen || */ strncmp(ud, pwd.pw_name, userlen) == 0) {
444 add_match(xasprintf("~%s/", pwd.pw_name));
450 #endif /* FEATURE_COMMAND_USERNAME_COMPLETION */
458 static int path_parse(char ***p, int flags)
465 /* if not setenv PATH variable, to search cur dir "." */
466 if (flags != FIND_EXE_ONLY)
469 if (state->flags & WITH_PATH_LOOKUP)
470 pth = state->path_lookup;
472 pth = getenv("PATH");
473 /* PATH=<empty> or PATH=:<empty> */
474 if (!pth || !pth[0] || LONE_CHAR(pth, ':'))
478 npth = 1; /* path component count */
480 tmp = strchr(tmp, ':');
484 break; /* :<empty> */
488 res = xmalloc(npth * sizeof(char*));
489 res[0] = tmp = xstrdup(pth);
492 tmp = strchr(tmp, ':');
495 *tmp++ = '\0'; /* ':' -> '\0' */
497 break; /* :<empty> */
504 static void exe_n_cwd_tab_completion(char *command, int type)
510 char **paths = path1;
514 char *pfind = strrchr(command, '/');
515 /* char dirbuf[MAX_LINELEN]; */
516 #define dirbuf (S.exe_n_cwd_tab_completion__dirbuf)
519 path1[0] = (char*)".";
522 /* no dir, if flags==EXE_ONLY - get paths, else "." */
523 npaths = path_parse(&paths, type);
526 /* dirbuf = ".../.../.../" */
527 safe_strncpy(dirbuf, command, (pfind - command) + 2);
528 #if ENABLE_FEATURE_USERNAME_COMPLETION
529 if (dirbuf[0] == '~') /* ~/... or ~user/... */
530 username_tab_completion(dirbuf, dirbuf);
533 /* point to 'l' in "..../last_component" */
537 for (i = 0; i < npaths; i++) {
538 dir = opendir(paths[i]);
540 continue; /* don't print an error */
542 while ((next = readdir(dir)) != NULL) {
544 const char *str_found = next->d_name;
547 if (strncmp(str_found, pfind, strlen(pfind)))
549 /* not see .name without .match */
550 if (*str_found == '.' && *pfind == '\0') {
551 if (NOT_LONE_CHAR(paths[i], '/') || str_found[1])
553 str_found = ""; /* only "/" */
555 found = concat_path_file(paths[i], str_found);
556 /* hmm, remove in progress? */
557 /* NB: stat() first so that we see is it a directory;
558 * but if that fails, use lstat() so that
559 * we still match dangling links */
560 if (stat(found, &st) && lstat(found, &st))
562 /* find with dirs? */
563 if (paths[i] != dirbuf)
564 strcpy(found, next->d_name); /* only name */
566 len1 = strlen(found);
567 found = xrealloc(found, len1 + 2);
569 found[len1+1] = '\0';
571 if (S_ISDIR(st.st_mode)) {
572 /* name is a directory */
573 if (found[len1-1] != '/') {
577 /* not put found file if search only dirs for cd */
578 if (type == FIND_DIR_ONLY)
581 /* Add it to the list */
589 if (paths != path1) {
590 free(paths[0]); /* allocated memory is only in first member */
596 #define QUOT (UCHAR_MAX+1)
598 #define collapse_pos(is, in) do { \
599 memmove(int_buf+(is), int_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
600 memmove(pos_buf+(is), pos_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
603 static int find_match(char *matchBuf, int *len_with_quotes)
608 /* int16_t int_buf[MAX_LINELEN + 1]; */
609 /* int16_t pos_buf[MAX_LINELEN + 1]; */
610 #define int_buf (S.find_match__int_buf)
611 #define pos_buf (S.find_match__pos_buf)
613 /* set to integer dimension characters and own positions */
615 int_buf[i] = (unsigned char)matchBuf[i];
616 if (int_buf[i] == 0) {
617 pos_buf[i] = -1; /* indicator end line */
623 /* mask \+symbol and convert '\t' to ' ' */
624 for (i = j = 0; matchBuf[i]; i++, j++)
625 if (matchBuf[i] == '\\') {
626 collapse_pos(j, j + 1);
629 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
630 if (matchBuf[i] == '\t') /* algorithm equivalent */
631 int_buf[j] = ' ' | QUOT;
634 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
635 else if (matchBuf[i] == '\t')
639 /* mask "symbols" or 'symbols' */
641 for (i = 0; int_buf[i]; i++) {
643 if (c == '\'' || c == '"') {
652 } else if (c2 != 0 && c != '$')
656 /* skip commands with arguments if line has commands delimiters */
657 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
658 for (i = 0; int_buf[i]; i++) {
661 j = i ? int_buf[i - 1] : -1;
663 if (c == ';' || c == '&' || c == '|') {
664 command_mode = 1 + (c == c2);
666 if (j == '>' || j == '<')
668 } else if (c == '|' && j == '>')
672 collapse_pos(0, i + command_mode);
673 i = -1; /* hack incremet */
676 /* collapse `command...` */
677 for (i = 0; int_buf[i]; i++)
678 if (int_buf[i] == '`') {
679 for (j = i + 1; int_buf[j]; j++)
680 if (int_buf[j] == '`') {
681 collapse_pos(i, j + 1);
686 /* not found close ` - command mode, collapse all previous */
687 collapse_pos(0, i + 1);
690 i--; /* hack incremet */
693 /* collapse (command...(command...)...) or {command...{command...}...} */
694 c = 0; /* "recursive" level */
696 for (i = 0; int_buf[i]; i++)
697 if (int_buf[i] == '(' || int_buf[i] == '{') {
698 if (int_buf[i] == '(')
702 collapse_pos(0, i + 1);
703 i = -1; /* hack incremet */
705 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
706 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
707 if (int_buf[i] == ')')
711 collapse_pos(0, i + 1);
712 i = -1; /* hack incremet */
715 /* skip first not quote space */
716 for (i = 0; int_buf[i]; i++)
717 if (int_buf[i] != ' ')
722 /* set find mode for completion */
723 command_mode = FIND_EXE_ONLY;
724 for (i = 0; int_buf[i]; i++)
725 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
726 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
727 && matchBuf[pos_buf[0]] == 'c'
728 && matchBuf[pos_buf[1]] == 'd'
730 command_mode = FIND_DIR_ONLY;
732 command_mode = FIND_FILE_ONLY;
736 for (i = 0; int_buf[i]; i++)
739 for (--i; i >= 0; i--) {
741 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
742 collapse_pos(0, i + 1);
746 /* skip first not quoted '\'' or '"' */
747 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++)
749 /* collapse quote or unquote // or /~ */
750 while ((int_buf[i] & ~QUOT) == '/'
751 && ((int_buf[i+1] & ~QUOT) == '/' || (int_buf[i+1] & ~QUOT) == '~')
756 /* set only match and destroy quotes */
758 for (c = 0; pos_buf[i] >= 0; i++) {
759 matchBuf[c++] = matchBuf[pos_buf[i]];
763 /* old length matchBuf with quotes symbols */
764 *len_with_quotes = j ? j - pos_buf[0] : 0;
772 * display by column (original idea from ls applet,
773 * very optimized by me :)
775 static void showfiles(void)
778 int column_width = 0;
779 int nfiles = num_matches;
783 /* find the longest file name- use that as the column width */
784 for (row = 0; row < nrows; row++) {
785 l = strlen(matches[row]);
786 if (column_width < l)
789 column_width += 2; /* min space for columns */
790 ncols = cmdedit_termw / column_width;
795 nrows++; /* round up fractionals */
799 for (row = 0; row < nrows; row++) {
803 for (nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
804 printf("%s%-*s", matches[n],
805 (int)(column_width - strlen(matches[n])), "");
811 static char *add_quote_for_spec_chars(char *found)
814 char *s = xmalloc((strlen(found) + 1) * 2);
817 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
825 /* Do TAB completion */
826 static void input_tab(smallint *lastWasTab)
828 if (!(state->flags & TAB_COMPLETION))
834 /* char matchBuf[MAX_LINELEN]; */
835 #define matchBuf (S.input_tab__matchBuf)
839 *lastWasTab = TRUE; /* flop trigger */
841 /* Make a local copy of the string -- up
842 * to the position of the cursor */
843 tmp = strncpy(matchBuf, command_ps, cursor);
846 find_type = find_match(matchBuf, &recalc_pos);
848 /* Free up any memory already allocated */
849 free_tab_completion_data();
851 #if ENABLE_FEATURE_USERNAME_COMPLETION
852 /* If the word starts with `~' and there is no slash in the word,
853 * then try completing this word as a username. */
854 if (state->flags & USERNAME_COMPLETION)
855 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
856 username_tab_completion(matchBuf, NULL);
858 /* Try to match any executable in our path and everything
859 * in the current working directory */
861 exe_n_cwd_tab_completion(matchBuf, find_type);
862 /* Sort, then remove any duplicates found */
866 qsort_string_vector(matches, num_matches);
867 for (i = 0; i < num_matches - 1; ++i) {
868 if (matches[i] && matches[i+1]) { /* paranoia */
869 if (strcmp(matches[i], matches[i+1]) == 0) {
871 matches[i] = NULL; /* paranoia */
873 matches[n++] = matches[i];
877 matches[n] = matches[i];
880 /* Did we find exactly one match? */
881 if (!matches || num_matches > 1) {
884 return; /* not found */
885 /* find minimal match */
886 tmp1 = xstrdup(matches[0]);
887 for (tmp = tmp1; *tmp; tmp++)
888 for (len_found = 1; len_found < num_matches; len_found++)
889 if (matches[len_found][(tmp - tmp1)] != *tmp) {
893 if (*tmp1 == '\0') { /* have unique */
897 tmp = add_quote_for_spec_chars(tmp1);
899 } else { /* one match */
900 tmp = add_quote_for_spec_chars(matches[0]);
901 /* for next completion current found */
904 len_found = strlen(tmp);
905 if (tmp[len_found-1] != '/') {
906 tmp[len_found] = ' ';
907 tmp[len_found+1] = '\0';
910 len_found = strlen(tmp);
911 /* have space to placed match? */
912 if ((len_found - strlen(matchBuf) + command_len) < MAX_LINELEN) {
913 /* before word for match */
914 command_ps[cursor - recalc_pos] = '\0';
916 strcpy(matchBuf, command_ps + cursor);
918 strcat(command_ps, tmp);
920 strcat(command_ps, matchBuf);
921 /* back to begin word for match */
922 input_backward(recalc_pos);
924 recalc_pos = cursor + len_found;
926 command_len = strlen(command_ps);
927 /* write out the matched command */
928 redraw(cmdedit_y, command_len - recalc_pos);
933 /* Ok -- the last char was a TAB. Since they
934 * just hit TAB again, print a list of all the
935 * available choices... */
936 if (matches && num_matches > 0) {
937 int sav_cursor = cursor; /* change goto_new_line() */
939 /* Go to the next line */
942 redraw(0, command_len - sav_cursor);
947 #endif /* FEATURE_COMMAND_TAB_COMPLETION */
952 /* state->flags is already checked to be nonzero */
953 static void get_previous_history(void)
955 if (command_ps[0] != '\0' || state->history[state->cur_history] == NULL) {
956 free(state->history[state->cur_history]);
957 state->history[state->cur_history] = xstrdup(command_ps);
959 state->cur_history--;
962 static int get_next_history(void)
964 if (state->flags & DO_HISTORY) {
965 int ch = state->cur_history;
966 if (ch < state->cnt_history) {
967 get_previous_history(); /* save the current history line */
968 state->cur_history = ch + 1;
969 return state->cur_history;
976 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
977 /* state->flags is already checked to be nonzero */
978 static void load_history(const char *fromfile)
983 /* NB: do not trash old history if file can't be opened */
985 fp = fopen(fromfile, "r");
987 /* clean up old history */
988 for (hi = state->cnt_history; hi > 0;) {
990 free(state->history[hi]);
993 for (hi = 0; hi < MAX_HISTORY;) {
994 char *hl = xmalloc_fgetline(fp);
1000 if (l >= MAX_LINELEN)
1001 hl[MAX_LINELEN-1] = '\0';
1002 if (l == 0 || hl[0] == ' ') {
1006 state->history[hi++] = hl;
1009 state->cur_history = state->cnt_history = hi;
1013 /* state->flags is already checked to be nonzero */
1014 static void save_history(const char *tofile)
1018 fp = fopen(tofile, "w");
1022 for (i = 0; i < state->cnt_history; i++) {
1023 fprintf(fp, "%s\n", state->history[i]);
1029 #define load_history(a) ((void)0)
1030 #define save_history(a) ((void)0)
1031 #endif /* FEATURE_COMMAND_SAVEHISTORY */
1033 static void remember_in_history(const char *str)
1037 if (!(state->flags & DO_HISTORY))
1040 i = state->cnt_history;
1041 free(state->history[MAX_HISTORY]);
1042 state->history[MAX_HISTORY] = NULL;
1043 /* After max history, remove the oldest command */
1044 if (i >= MAX_HISTORY) {
1045 free(state->history[0]);
1046 for (i = 0; i < MAX_HISTORY-1; i++)
1047 state->history[i] = state->history[i+1];
1049 // Maybe "if (!i || strcmp(history[i-1], command) != 0) ..."
1050 // (i.e. do not save dups?)
1051 state->history[i++] = xstrdup(str);
1052 state->cur_history = i;
1053 state->cnt_history = i;
1054 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1055 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1056 save_history(state->hist_file);
1058 USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;)
1061 #else /* MAX_HISTORY == 0 */
1062 #define remember_in_history(a) ((void)0)
1063 #endif /* MAX_HISTORY */
1067 * This function is used to grab a character buffer
1068 * from the input file descriptor and allows you to
1069 * a string with full command editing (sort of like
1072 * The following standard commands are not implemented:
1073 * ESC-b -- Move back one word
1074 * ESC-f -- Move forward one word
1075 * ESC-d -- Delete back one word
1076 * ESC-h -- Delete forward one word
1077 * CTL-t -- Transpose two characters
1079 * Minimalist vi-style command line editing available if configured.
1080 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
1083 #if ENABLE_FEATURE_EDITING_VI
1085 vi_Word_motion(char *command, int eat)
1087 while (cursor < command_len && !isspace(command[cursor]))
1089 if (eat) while (cursor < command_len && isspace(command[cursor]))
1094 vi_word_motion(char *command, int eat)
1096 if (isalnum(command[cursor]) || command[cursor] == '_') {
1097 while (cursor < command_len
1098 && (isalnum(command[cursor+1]) || command[cursor+1] == '_'))
1100 } else if (ispunct(command[cursor])) {
1101 while (cursor < command_len && ispunct(command[cursor+1]))
1105 if (cursor < command_len)
1108 if (eat && cursor < command_len && isspace(command[cursor]))
1109 while (cursor < command_len && isspace(command[cursor]))
1114 vi_End_motion(char *command)
1117 while (cursor < command_len && isspace(command[cursor]))
1119 while (cursor < command_len-1 && !isspace(command[cursor+1]))
1124 vi_end_motion(char *command)
1126 if (cursor >= command_len-1)
1129 while (cursor < command_len-1 && isspace(command[cursor]))
1131 if (cursor >= command_len-1)
1133 if (isalnum(command[cursor]) || command[cursor] == '_') {
1134 while (cursor < command_len-1
1135 && (isalnum(command[cursor+1]) || command[cursor+1] == '_')
1139 } else if (ispunct(command[cursor])) {
1140 while (cursor < command_len-1 && ispunct(command[cursor+1]))
1146 vi_Back_motion(char *command)
1148 while (cursor > 0 && isspace(command[cursor-1]))
1150 while (cursor > 0 && !isspace(command[cursor-1]))
1155 vi_back_motion(char *command)
1160 while (cursor > 0 && isspace(command[cursor]))
1164 if (isalnum(command[cursor]) || command[cursor] == '_') {
1166 && (isalnum(command[cursor-1]) || command[cursor-1] == '_')
1170 } else if (ispunct(command[cursor])) {
1171 while (cursor > 0 && ispunct(command[cursor-1]))
1179 * read_line_input and its helpers
1182 #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
1183 static void parse_and_put_prompt(const char *prmt_ptr)
1185 cmdedit_prompt = prmt_ptr;
1186 cmdedit_prmt_len = strlen(prmt_ptr);
1190 static void parse_and_put_prompt(const char *prmt_ptr)
1193 size_t cur_prmt_len = 0;
1194 char flg_not_length = '[';
1195 char *prmt_mem_ptr = xzalloc(1);
1196 char *cwd_buf = xrealloc_getcwd_or_warn(NULL);
1201 cmdedit_prmt_len = 0;
1204 cwd_buf = (char *)bb_msg_unknown;
1207 cbuf[1] = '\0'; /* never changes */
1210 char *free_me = NULL;
1215 const char *cp = prmt_ptr;
1218 c = bb_process_escape_sequence(&prmt_ptr);
1219 if (prmt_ptr == cp) {
1225 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1227 pbuf = user_buf ? user_buf : (char*)"";
1231 pbuf = free_me = safe_gethostname();
1232 *strchrnul(pbuf, '.') = '\0';
1235 c = (geteuid() == 0 ? '#' : '$');
1237 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1239 /* /home/user[/something] -> ~[/something] */
1241 l = strlen(home_pwd_buf);
1243 && strncmp(home_pwd_buf, cwd_buf, l) == 0
1244 && (cwd_buf[l]=='/' || cwd_buf[l]=='\0')
1245 && strlen(cwd_buf + l) < PATH_MAX
1247 pbuf = free_me = xasprintf("~%s", cwd_buf + l);
1253 cp = strrchr(pbuf, '/');
1254 if (cp != NULL && cp != pbuf)
1255 pbuf += (cp-pbuf) + 1;
1258 pbuf = free_me = xasprintf("%d", num_ok_lines);
1260 case 'e': case 'E': /* \e \E = \033 */
1263 case 'x': case 'X': {
1265 for (l = 0; l < 3;) {
1267 buf2[l++] = *prmt_ptr;
1269 h = strtoul(buf2, &pbuf, 16);
1270 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
1276 c = (char)strtoul(buf2, NULL, 16);
1283 if (c == flg_not_length) {
1284 flg_not_length = (flg_not_length == '[' ? ']' : '[');
1292 cur_prmt_len = strlen(pbuf);
1293 prmt_len += cur_prmt_len;
1294 if (flg_not_length != ']')
1295 cmdedit_prmt_len += cur_prmt_len;
1296 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
1300 if (cwd_buf != (char *)bb_msg_unknown)
1302 cmdedit_prompt = prmt_mem_ptr;
1307 static void cmdedit_setwidth(unsigned w, int redraw_flg)
1311 /* new y for current cursor */
1312 int new_y = (cursor + cmdedit_prmt_len) / w;
1314 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), command_len - cursor);
1319 static void win_changed(int nsig)
1322 get_terminal_width_height(0, &width, NULL);
1323 cmdedit_setwidth(width, nsig /* - just a yes/no flag */);
1324 if (nsig == SIGWINCH)
1325 signal(SIGWINCH, win_changed); /* rearm ourself */
1329 * The emacs and vi modes share much of the code in the big
1330 * command loop. Commands entered when in vi's command mode (aka
1331 * "escape mode") get an extra bit added to distinguish them --
1332 * this keeps them from being self-inserted. This clutters the
1333 * big switch a bit, but keeps all the code in one place.
1338 /* leave out the "vi-mode"-only case labels if vi editing isn't
1340 #define vi_case(caselabel) USE_FEATURE_EDITING(case caselabel)
1342 /* convert uppercase ascii to equivalent control char, for readability */
1344 #define CTRL(a) ((a) & ~0x40)
1347 * -1 on read errors or EOF, or on bare Ctrl-D,
1348 * 0 on ctrl-C (the line entered is still returned in 'command'),
1349 * >0 length of input string, including terminating '\n'
1351 int read_line_input(const char *prompt, char *command, int maxsize, line_input_t *st)
1353 #if ENABLE_FEATURE_TAB_COMPLETION
1354 smallint lastWasTab = FALSE;
1358 smallint break_out = 0;
1359 #if ENABLE_FEATURE_EDITING_VI
1360 smallint vi_cmdmode = 0;
1363 struct termios initial_settings;
1364 struct termios new_settings;
1368 if (tcgetattr(STDIN_FILENO, &initial_settings) < 0
1369 || !(initial_settings.c_lflag & ECHO)
1371 /* Happens when e.g. stty -echo was run before */
1373 parse_and_put_prompt(prompt);
1375 if (fgets(command, maxsize, stdin) == NULL)
1376 len = -1; /* EOF or error */
1378 len = strlen(command);
1383 // FIXME: audit & improve this
1384 if (maxsize > MAX_LINELEN)
1385 maxsize = MAX_LINELEN;
1387 /* With null flags, no other fields are ever used */
1388 state = st ? st : (line_input_t*) &const_int_0;
1389 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1390 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1391 load_history(state->hist_file);
1394 /* prepare before init handlers */
1395 cmdedit_y = 0; /* quasireal y, not true if line > xt*yt */
1397 command_ps = command;
1400 new_settings = initial_settings;
1401 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1402 /* Turn off echoing and CTRL-C, so we can trap it */
1403 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1404 /* Hmm, in linux c_cc[] is not parsed if ICANON is off */
1405 new_settings.c_cc[VMIN] = 1;
1406 new_settings.c_cc[VTIME] = 0;
1407 /* Turn off CTRL-C, so we can trap it */
1408 #ifndef _POSIX_VDISABLE
1409 #define _POSIX_VDISABLE '\0'
1411 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1412 tcsetattr(STDIN_FILENO, TCSANOW, &new_settings);
1414 /* Now initialize things */
1415 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
1416 win_changed(0); /* do initial resizing */
1417 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1419 struct passwd *entry;
1421 entry = getpwuid(geteuid());
1423 user_buf = xstrdup(entry->pw_name);
1424 home_pwd_buf = xstrdup(entry->pw_dir);
1428 /* Print out the command prompt */
1429 parse_and_put_prompt(prompt);
1434 if (nonblock_safe_read(STDIN_FILENO, &c, 1) < 1) {
1435 /* if we can't read input then exit */
1436 goto prepare_to_die;
1441 #if ENABLE_FEATURE_EDITING_VI
1457 /* Control-a -- Beginning of line */
1458 input_backward(cursor);
1463 vi_case('\x7f'|vbit:) /* DEL */
1464 /* Control-b -- Move back one character */
1468 vi_case(CTRL('C')|vbit:)
1469 /* Control-c -- stop gathering input */
1472 break_out = -1; /* "do not append '\n'" */
1475 /* Control-d -- Delete one character, or exit
1476 * if the len=0 and no chars to delete */
1477 if (command_len == 0) {
1480 /* to control stopped jobs */
1481 break_out = command_len = -1;
1489 /* Control-e -- End of line */
1495 /* Control-f -- Move forward one character */
1500 case '\x7f': /* DEL */
1501 /* Control-h and DEL */
1505 #if ENABLE_FEATURE_TAB_COMPLETION
1507 input_tab(&lastWasTab);
1512 /* Control-k -- clear to end of line */
1513 command[cursor] = 0;
1514 command_len = cursor;
1518 vi_case(CTRL('L')|vbit:)
1519 /* Control-l -- clear screen */
1521 redraw(0, command_len - cursor);
1526 vi_case(CTRL('N')|vbit:)
1528 /* Control-n -- Get next command in history */
1529 if (get_next_history())
1533 vi_case(CTRL('P')|vbit:)
1535 /* Control-p -- Get previous command from history */
1536 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1537 get_previous_history();
1545 vi_case(CTRL('U')|vbit:)
1546 /* Control-U -- Clear line before cursor */
1548 strcpy(command, command + cursor);
1549 command_len -= cursor;
1550 redraw(cmdedit_y, command_len);
1554 vi_case(CTRL('W')|vbit:)
1555 /* Control-W -- Remove the last word */
1556 while (cursor > 0 && isspace(command[cursor-1]))
1558 while (cursor > 0 && !isspace(command[cursor-1]))
1562 #if ENABLE_FEATURE_EDITING_VI
1567 input_backward(cursor);
1588 vi_Word_motion(command, 1);
1591 vi_word_motion(command, 1);
1594 vi_End_motion(command);
1597 vi_end_motion(command);
1600 vi_Back_motion(command);
1603 vi_back_motion(command);
1618 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1619 goto prepare_to_die;
1620 if (c == (prevc & 0xff)) {
1622 input_backward(cursor);
1632 case 'w': /* "dw", "cw" */
1633 vi_word_motion(command, vi_cmdmode);
1635 case 'W': /* 'dW', 'cW' */
1636 vi_Word_motion(command, vi_cmdmode);
1638 case 'e': /* 'de', 'ce' */
1639 vi_end_motion(command);
1642 case 'E': /* 'dE', 'cE' */
1643 vi_End_motion(command);
1648 input_backward(cursor - sc);
1649 while (nc-- > cursor)
1652 case 'b': /* "db", "cb" */
1653 case 'B': /* implemented as B */
1655 vi_back_motion(command);
1657 vi_Back_motion(command);
1658 while (sc-- > cursor)
1661 case ' ': /* "d ", "c " */
1664 case '$': /* "d$", "c$" */
1666 while (cursor < command_len)
1679 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1680 goto prepare_to_die;
1684 *(command + cursor) = c;
1689 #endif /* FEATURE_COMMAND_EDITING_VI */
1691 case '\x1b': /* ESC */
1693 #if ENABLE_FEATURE_EDITING_VI
1694 if (state->flags & VI_MODE) {
1695 /* ESC: insert mode --> command mode */
1701 /* escape sequence follows */
1702 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1703 goto prepare_to_die;
1704 /* different vt100 emulations */
1705 if (c == '[' || c == 'O') {
1708 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1709 goto prepare_to_die;
1711 if (c >= '1' && c <= '9') {
1712 unsigned char dummy;
1714 if (safe_read(STDIN_FILENO, &dummy, 1) < 1)
1715 goto prepare_to_die;
1721 #if ENABLE_FEATURE_TAB_COMPLETION
1722 case '\t': /* Alt-Tab */
1723 input_tab(&lastWasTab);
1728 /* Up Arrow -- Get previous command from history */
1729 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1730 get_previous_history();
1736 /* Down Arrow -- Get next command in history */
1737 if (!get_next_history())
1740 /* Rewrite the line with the selected history item */
1741 /* change command */
1742 command_len = strlen(strcpy(command, state->history[state->cur_history]));
1743 /* redraw and go to eol (bol, in vi */
1744 redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
1748 /* Right Arrow -- Move forward one character */
1752 /* Left Arrow -- Move back one character */
1759 case '1': // vt100? linux vt? or what?
1760 case '7': // vt100? linux vt? or what?
1761 case 'H': /* xterm's <Home> */
1762 input_backward(cursor);
1764 case '4': // vt100? linux vt? or what?
1765 case '8': // vt100? linux vt? or what?
1766 case 'F': /* xterm's <End> */
1775 default: /* If it's regular input, do the normal thing */
1777 /* Control-V -- force insert of next char */
1778 if (c == CTRL('V')) {
1779 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1780 goto prepare_to_die;
1787 #if ENABLE_FEATURE_EDITING_VI
1788 if (vi_cmdmode) /* Don't self-insert */
1791 if ((int)command_len >= (maxsize - 2)) /* Need to leave space for enter */
1795 if (cursor == (command_len - 1)) { /* Append if at the end of the line */
1796 command[cursor] = c;
1797 command[cursor+1] = '\0';
1798 cmdedit_set_out_char(' ');
1799 } else { /* Insert otherwise */
1802 memmove(command + sc + 1, command + sc, command_len - sc);
1805 /* rewrite from cursor */
1807 /* to prev x pos + 1 */
1808 input_backward(cursor - sc);
1812 if (break_out) /* Enter is the command terminator, no more input. */
1815 #if ENABLE_FEATURE_TAB_COMPLETION
1821 if (command_len > 0)
1822 remember_in_history(command);
1824 if (break_out > 0) {
1825 command[command_len++] = '\n';
1826 command[command_len] = '\0';
1829 #if ENABLE_FEATURE_TAB_COMPLETION
1830 free_tab_completion_data();
1833 /* restore initial_settings */
1834 tcsetattr(STDIN_FILENO, TCSANOW, &initial_settings);
1835 /* restore SIGWINCH handler */
1836 signal(SIGWINCH, previous_SIGWINCH_handler);
1844 line_input_t *new_line_input_t(int flags)
1846 line_input_t *n = xzalloc(sizeof(*n));
1853 #undef read_line_input
1854 int read_line_input(const char* prompt, char* command, int maxsize)
1856 fputs(prompt, stdout);
1858 fgets(command, maxsize, stdin);
1859 return strlen(command);
1862 #endif /* FEATURE_COMMAND_EDITING */
1873 const char *applet_name = "debug stuff usage";
1875 int main(int argc, char **argv)
1877 char buff[MAX_LINELEN];
1879 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1880 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:"
1881 "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] "
1882 "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1887 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1888 setlocale(LC_ALL, "");
1892 l = read_line_input(prompt, buff);
1893 if (l <= 0 || buff[l-1] != '\n')
1896 printf("*** read_line_input() returned line =%s=\n", buff);
1898 printf("*** read_line_input() detect ^D\n");