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
95 int num_ok_lines; /* = 1; */
98 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
100 char *home_pwd_buf; /* = (char*)null_str; */
103 #if ENABLE_FEATURE_TAB_COMPLETION
105 unsigned num_matches;
108 #if ENABLE_FEATURE_EDITING_VI
109 #define DELBUFSIZ 128
111 smallint newdelflag; /* whether delbuf should be reused yet */
112 char delbuf[DELBUFSIZ]; /* a place to store deleted characters */
115 /* Formerly these were big buffers on stack: */
116 #if ENABLE_FEATURE_TAB_COMPLETION
117 char exe_n_cwd_tab_completion__dirbuf[MAX_LINELEN];
118 char input_tab__matchBuf[MAX_LINELEN];
119 int16_t find_match__int_buf[MAX_LINELEN + 1]; /* need to have 9 bits at least */
120 int16_t find_match__pos_buf[MAX_LINELEN + 1];
124 /* Make it reside in writable memory, yet make compiler understand
125 * that it is not going to change. */
126 static struct statics *const ptr_to_statics __attribute__ ((section (".data")));
128 #define S (*ptr_to_statics)
129 #define state (S.state )
130 #define cmdedit_termw (S.cmdedit_termw )
131 #define previous_SIGWINCH_handler (S.previous_SIGWINCH_handler)
132 #define cmdedit_x (S.cmdedit_x )
133 #define cmdedit_y (S.cmdedit_y )
134 #define cmdedit_prmt_len (S.cmdedit_prmt_len)
135 #define cursor (S.cursor )
136 #define command_len (S.command_len )
137 #define command_ps (S.command_ps )
138 #define cmdedit_prompt (S.cmdedit_prompt )
139 #define hostname_buf (S.hostname_buf )
140 #define num_ok_lines (S.num_ok_lines )
141 #define user_buf (S.user_buf )
142 #define home_pwd_buf (S.home_pwd_buf )
143 #define matches (S.matches )
144 #define num_matches (S.num_matches )
145 #define delptr (S.delptr )
146 #define newdelflag (S.newdelflag )
147 #define delbuf (S.delbuf )
149 #define INIT_S() do { \
150 (*(struct statics**)&ptr_to_statics) = xzalloc(sizeof(S)); \
151 cmdedit_termw = 80; \
152 USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines = 1;) \
153 USE_FEATURE_GETUSERNAME_AND_HOMEDIR(home_pwd_buf = (char*)null_str;) \
155 static void deinit_S(void)
157 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
159 /* This one is allocated only if FANCY_PROMPT is on
160 * (otherwise it points to verbatim prompt (NOT malloced) */
161 free((char*)cmdedit_prompt);
163 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
165 if (home_pwd_buf != null_str)
168 free(ptr_to_statics);
170 #define DEINIT_S() deinit_S()
172 /* Put 'command_ps[cursor]', cursor++.
173 * Advance cursor on screen. If we reached right margin, scroll text up
174 * and remove terminal margin effect by printing 'next_char' */
175 static void cmdedit_set_out_char(int next_char)
177 int c = (unsigned char)command_ps[cursor];
180 /* erase character after end of input string */
183 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
184 /* Display non-printable characters in reverse */
192 printf("\033[7m%c\033[0m", c);
198 if (++cmdedit_x >= cmdedit_termw) {
199 /* terminal is scrolled down */
202 /* destroy "(auto)margin" */
203 bb_putchar(next_char);
206 // Huh? What if command_ps[cursor] == '\0' (we are at the end already?)
210 /* Move to end of line (by printing all chars till the end) */
211 static void input_end(void)
213 while (cursor < command_len)
214 cmdedit_set_out_char(' ');
217 /* Go to the next line */
218 static void goto_new_line(void)
226 static void out1str(const char *s)
232 static void beep(void)
237 /* Move back one character */
238 /* (optimized for slow terminals) */
239 static void input_backward(unsigned num)
249 if (cmdedit_x >= num) {
252 printf("\b\b\b\b" + (4-num));
255 printf("\033[%uD", num);
259 /* Need to go one or more lines up */
261 count_y = 1 + (num / cmdedit_termw);
262 cmdedit_y -= count_y;
263 cmdedit_x = cmdedit_termw * count_y - num;
264 /* go to 1st column; go up; go to correct column */
265 printf("\r" "\033[%dA" "\033[%dC", count_y, cmdedit_x);
268 static void put_prompt(void)
270 out1str(cmdedit_prompt);
271 cmdedit_x = cmdedit_prmt_len;
273 // Huh? what if cmdedit_prmt_len >= width?
274 cmdedit_y = 0; /* new quasireal y */
277 /* draw prompt, editor line, and clear tail */
278 static void redraw(int y, int back_cursor)
280 if (y > 0) /* up to start y */
281 printf("\033[%dA", y);
284 input_end(); /* rewrite */
285 printf("\033[J"); /* erase after cursor */
286 input_backward(back_cursor);
289 /* Delete the char in front of the cursor, optionally saving it
290 * for later putback */
291 static void input_delete(int save)
295 if (j == command_len)
298 #if ENABLE_FEATURE_EDITING_VI
304 if ((delptr - delbuf) < DELBUFSIZ)
305 *delptr++ = command_ps[j];
309 strcpy(command_ps + j, command_ps + j + 1);
311 input_end(); /* rewrite new line */
312 cmdedit_set_out_char(' '); /* erase char */
313 input_backward(cursor - j); /* back to old pos cursor */
316 #if ENABLE_FEATURE_EDITING_VI
317 static void put(void)
320 int j = delptr - delbuf;
325 /* open hole and then fill it */
326 memmove(command_ps + cursor + j, command_ps + cursor, command_len - cursor + 1);
327 strncpy(command_ps + cursor, delbuf, j);
329 input_end(); /* rewrite new line */
330 input_backward(cursor - ocursor - j + 1); /* at end of new text */
334 /* Delete the char in back of the cursor */
335 static void input_backspace(void)
343 /* Move forward one character */
344 static void input_forward(void)
346 if (cursor < command_len)
347 cmdedit_set_out_char(command_ps[cursor + 1]);
350 #if ENABLE_FEATURE_TAB_COMPLETION
352 static void free_tab_completion_data(void)
356 free(matches[--num_matches]);
362 static void add_match(char *matched)
364 int nm = num_matches;
367 matches = xrealloc(matches, nm1 * sizeof(char *));
368 matches[nm] = matched;
372 #if ENABLE_FEATURE_USERNAME_COMPLETION
373 static void username_tab_completion(char *ud, char *with_shash_flg)
375 struct passwd *entry;
378 ud++; /* ~user/... to user/... */
379 userlen = strlen(ud);
381 if (with_shash_flg) { /* "~/..." or "~user/..." */
382 char *sav_ud = ud - 1;
386 if (*ud == '/') { /* "~/..." */
390 temp = strchr(ud, '/');
391 *temp = 0; /* ~user\0 */
392 entry = getpwnam(ud);
393 *temp = '/'; /* restore ~user/... */
396 home = entry->pw_dir;
399 if ((userlen + strlen(home) + 1) < MAX_LINELEN) {
401 sprintf(sav_ud, "%s%s", home, ud);
406 /* Using _r function to avoid pulling in static buffers */
409 struct passwd *result;
412 while (!getpwent_r(&pwd, line_buff, sizeof(line_buff), &result)) {
413 /* Null usernames should result in all users as possible completions. */
414 if (/*!userlen || */ strncmp(ud, pwd.pw_name, userlen) == 0) {
415 add_match(xasprintf("~%s/", pwd.pw_name));
421 #endif /* FEATURE_COMMAND_USERNAME_COMPLETION */
429 static int path_parse(char ***p, int flags)
436 /* if not setenv PATH variable, to search cur dir "." */
437 if (flags != FIND_EXE_ONLY)
440 if (state->flags & WITH_PATH_LOOKUP)
441 pth = state->path_lookup;
443 pth = getenv("PATH");
444 /* PATH=<empty> or PATH=:<empty> */
445 if (!pth || !pth[0] || LONE_CHAR(pth, ':'))
449 npth = 1; /* path component count */
451 tmp = strchr(tmp, ':');
455 break; /* :<empty> */
459 res = xmalloc(npth * sizeof(char*));
460 res[0] = tmp = xstrdup(pth);
463 tmp = strchr(tmp, ':');
466 *tmp++ = '\0'; /* ':' -> '\0' */
468 break; /* :<empty> */
475 static void exe_n_cwd_tab_completion(char *command, int type)
481 char **paths = path1;
485 char *pfind = strrchr(command, '/');
486 /* char dirbuf[MAX_LINELEN]; */
487 #define dirbuf (S.exe_n_cwd_tab_completion__dirbuf)
490 path1[0] = (char*)".";
493 /* no dir, if flags==EXE_ONLY - get paths, else "." */
494 npaths = path_parse(&paths, type);
497 /* dirbuf = ".../.../.../" */
498 safe_strncpy(dirbuf, command, (pfind - command) + 2);
499 #if ENABLE_FEATURE_USERNAME_COMPLETION
500 if (dirbuf[0] == '~') /* ~/... or ~user/... */
501 username_tab_completion(dirbuf, dirbuf);
504 /* point to 'l' in "..../last_component" */
508 for (i = 0; i < npaths; i++) {
509 dir = opendir(paths[i]);
510 if (!dir) /* Don't print an error */
513 while ((next = readdir(dir)) != NULL) {
515 const char *str_found = next->d_name;
518 if (strncmp(str_found, pfind, strlen(pfind)))
520 /* not see .name without .match */
521 if (*str_found == '.' && *pfind == 0) {
522 if (NOT_LONE_CHAR(paths[i], '/') || str_found[1])
524 str_found = ""; /* only "/" */
526 found = concat_path_file(paths[i], str_found);
527 /* hmm, remover in progress? */
528 if (stat(found, &st) < 0)
530 /* find with dirs? */
531 if (paths[i] != dirbuf)
532 strcpy(found, next->d_name); /* only name */
534 len1 = strlen(found);
535 found = xrealloc(found, len1 + 2);
537 found[len1+1] = '\0';
539 if (S_ISDIR(st.st_mode)) {
540 /* name is directory */
541 if (found[len1-1] != '/') {
545 /* not put found file if search only dirs for cd */
546 if (type == FIND_DIR_ONLY)
549 /* Add it to the list */
557 if (paths != path1) {
558 free(paths[0]); /* allocated memory only in first member */
564 #define QUOT (UCHAR_MAX+1)
566 #define collapse_pos(is, in) do { \
567 memmove(int_buf+(is), int_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
568 memmove(pos_buf+(is), pos_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
571 static int find_match(char *matchBuf, int *len_with_quotes)
576 /* int16_t int_buf[MAX_LINELEN + 1]; */
577 /* int16_t pos_buf[MAX_LINELEN + 1]; */
578 #define int_buf (S.find_match__int_buf)
579 #define pos_buf (S.find_match__pos_buf)
581 /* set to integer dimension characters and own positions */
583 int_buf[i] = (unsigned char)matchBuf[i];
584 if (int_buf[i] == 0) {
585 pos_buf[i] = -1; /* indicator end line */
591 /* mask \+symbol and convert '\t' to ' ' */
592 for (i = j = 0; matchBuf[i]; i++, j++)
593 if (matchBuf[i] == '\\') {
594 collapse_pos(j, j + 1);
597 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
598 if (matchBuf[i] == '\t') /* algorithm equivalent */
599 int_buf[j] = ' ' | QUOT;
602 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
603 else if (matchBuf[i] == '\t')
607 /* mask "symbols" or 'symbols' */
609 for (i = 0; int_buf[i]; i++) {
611 if (c == '\'' || c == '"') {
620 } else if (c2 != 0 && c != '$')
624 /* skip commands with arguments if line has commands delimiters */
625 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
626 for (i = 0; int_buf[i]; i++) {
629 j = i ? int_buf[i - 1] : -1;
631 if (c == ';' || c == '&' || c == '|') {
632 command_mode = 1 + (c == c2);
634 if (j == '>' || j == '<')
636 } else if (c == '|' && j == '>')
640 collapse_pos(0, i + command_mode);
641 i = -1; /* hack incremet */
644 /* collapse `command...` */
645 for (i = 0; int_buf[i]; i++)
646 if (int_buf[i] == '`') {
647 for (j = i + 1; int_buf[j]; j++)
648 if (int_buf[j] == '`') {
649 collapse_pos(i, j + 1);
654 /* not found close ` - command mode, collapse all previous */
655 collapse_pos(0, i + 1);
658 i--; /* hack incremet */
661 /* collapse (command...(command...)...) or {command...{command...}...} */
662 c = 0; /* "recursive" level */
664 for (i = 0; int_buf[i]; i++)
665 if (int_buf[i] == '(' || int_buf[i] == '{') {
666 if (int_buf[i] == '(')
670 collapse_pos(0, i + 1);
671 i = -1; /* hack incremet */
673 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
674 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
675 if (int_buf[i] == ')')
679 collapse_pos(0, i + 1);
680 i = -1; /* hack incremet */
683 /* skip first not quote space */
684 for (i = 0; int_buf[i]; i++)
685 if (int_buf[i] != ' ')
690 /* set find mode for completion */
691 command_mode = FIND_EXE_ONLY;
692 for (i = 0; int_buf[i]; i++)
693 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
694 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
695 && matchBuf[pos_buf[0]] == 'c'
696 && matchBuf[pos_buf[1]] == 'd'
698 command_mode = FIND_DIR_ONLY;
700 command_mode = FIND_FILE_ONLY;
704 for (i = 0; int_buf[i]; i++)
707 for (--i; i >= 0; i--) {
709 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
710 collapse_pos(0, i + 1);
714 /* skip first not quoted '\'' or '"' */
715 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++)
717 /* collapse quote or unquote // or /~ */
718 while ((int_buf[i] & ~QUOT) == '/'
719 && ((int_buf[i+1] & ~QUOT) == '/' || (int_buf[i+1] & ~QUOT) == '~')
724 /* set only match and destroy quotes */
726 for (c = 0; pos_buf[i] >= 0; i++) {
727 matchBuf[c++] = matchBuf[pos_buf[i]];
731 /* old length matchBuf with quotes symbols */
732 *len_with_quotes = j ? j - pos_buf[0] : 0;
740 * display by column (original idea from ls applet,
741 * very optimized by me :)
743 static void showfiles(void)
746 int column_width = 0;
747 int nfiles = num_matches;
751 /* find the longest file name- use that as the column width */
752 for (row = 0; row < nrows; row++) {
753 l = strlen(matches[row]);
754 if (column_width < l)
757 column_width += 2; /* min space for columns */
758 ncols = cmdedit_termw / column_width;
763 nrows++; /* round up fractionals */
767 for (row = 0; row < nrows; row++) {
771 for (nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
772 printf("%s%-*s", matches[n],
773 (int)(column_width - strlen(matches[n])), "");
779 static char *add_quote_for_spec_chars(char *found)
782 char *s = xmalloc((strlen(found) + 1) * 2);
785 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
793 static int match_compare(const void *a, const void *b)
795 return strcmp(*(char**)a, *(char**)b);
798 /* Do TAB completion */
799 static void input_tab(smallint *lastWasTab)
801 if (!(state->flags & TAB_COMPLETION))
807 /* char matchBuf[MAX_LINELEN]; */
808 #define matchBuf (S.input_tab__matchBuf)
812 *lastWasTab = TRUE; /* flop trigger */
814 /* Make a local copy of the string -- up
815 * to the position of the cursor */
816 tmp = strncpy(matchBuf, command_ps, cursor);
819 find_type = find_match(matchBuf, &recalc_pos);
821 /* Free up any memory already allocated */
822 free_tab_completion_data();
824 #if ENABLE_FEATURE_USERNAME_COMPLETION
825 /* If the word starts with `~' and there is no slash in the word,
826 * then try completing this word as a username. */
827 if (state->flags & USERNAME_COMPLETION)
828 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
829 username_tab_completion(matchBuf, NULL);
831 /* Try to match any executable in our path and everything
832 * in the current working directory */
834 exe_n_cwd_tab_completion(matchBuf, find_type);
835 /* Sort, then remove any duplicates found */
838 qsort(matches, num_matches, sizeof(char*), match_compare);
839 for (i = 0; i < num_matches - 1; ++i) {
840 if (matches[i] && matches[i+1]) { /* paranoia */
841 if (strcmp(matches[i], matches[i+1]) == 0) {
843 matches[i] = NULL; /* paranoia */
845 matches[n++] = matches[i];
849 matches[n] = matches[i];
852 /* Did we find exactly one match? */
853 if (!matches || num_matches > 1) {
856 return; /* not found */
857 /* find minimal match */
858 tmp1 = xstrdup(matches[0]);
859 for (tmp = tmp1; *tmp; tmp++)
860 for (len_found = 1; len_found < num_matches; len_found++)
861 if (matches[len_found][(tmp - tmp1)] != *tmp) {
865 if (*tmp1 == '\0') { /* have unique */
869 tmp = add_quote_for_spec_chars(tmp1);
871 } else { /* one match */
872 tmp = add_quote_for_spec_chars(matches[0]);
873 /* for next completion current found */
876 len_found = strlen(tmp);
877 if (tmp[len_found-1] != '/') {
878 tmp[len_found] = ' ';
879 tmp[len_found+1] = '\0';
882 len_found = strlen(tmp);
883 /* have space to placed match? */
884 if ((len_found - strlen(matchBuf) + command_len) < MAX_LINELEN) {
885 /* before word for match */
886 command_ps[cursor - recalc_pos] = '\0';
888 strcpy(matchBuf, command_ps + cursor);
890 strcat(command_ps, tmp);
892 strcat(command_ps, matchBuf);
893 /* back to begin word for match */
894 input_backward(recalc_pos);
896 recalc_pos = cursor + len_found;
898 command_len = strlen(command_ps);
899 /* write out the matched command */
900 redraw(cmdedit_y, command_len - recalc_pos);
905 /* Ok -- the last char was a TAB. Since they
906 * just hit TAB again, print a list of all the
907 * available choices... */
908 if (matches && num_matches > 0) {
909 int sav_cursor = cursor; /* change goto_new_line() */
911 /* Go to the next line */
914 redraw(0, command_len - sav_cursor);
919 #endif /* FEATURE_COMMAND_TAB_COMPLETION */
924 /* state->flags is already checked to be nonzero */
925 static void get_previous_history(void)
927 if (command_ps[0] != '\0' || state->history[state->cur_history] == NULL) {
928 free(state->history[state->cur_history]);
929 state->history[state->cur_history] = xstrdup(command_ps);
931 state->cur_history--;
934 static int get_next_history(void)
936 if (state->flags & DO_HISTORY) {
937 int ch = state->cur_history;
938 if (ch < state->cnt_history) {
939 get_previous_history(); /* save the current history line */
940 state->cur_history = ch + 1;
941 return state->cur_history;
948 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
949 /* state->flags is already checked to be nonzero */
950 static void load_history(const char *fromfile)
956 for (hi = state->cnt_history; hi > 0;) {
958 free(state->history[hi]);
961 fp = fopen(fromfile, "r");
963 for (hi = 0; hi < MAX_HISTORY;) {
964 char *hl = xmalloc_getline(fp);
970 if (l >= MAX_LINELEN)
971 hl[MAX_LINELEN-1] = '\0';
972 if (l == 0 || hl[0] == ' ') {
976 state->history[hi++] = hl;
980 state->cur_history = state->cnt_history = hi;
983 /* state->flags is already checked to be nonzero */
984 static void save_history(const char *tofile)
988 fp = fopen(tofile, "w");
992 for (i = 0; i < state->cnt_history; i++) {
993 fprintf(fp, "%s\n", state->history[i]);
999 #define load_history(a) ((void)0)
1000 #define save_history(a) ((void)0)
1001 #endif /* FEATURE_COMMAND_SAVEHISTORY */
1003 static void remember_in_history(const char *str)
1007 if (!(state->flags & DO_HISTORY))
1010 i = state->cnt_history;
1011 free(state->history[MAX_HISTORY]);
1012 state->history[MAX_HISTORY] = NULL;
1013 /* After max history, remove the oldest command */
1014 if (i >= MAX_HISTORY) {
1015 free(state->history[0]);
1016 for (i = 0; i < MAX_HISTORY-1; i++)
1017 state->history[i] = state->history[i+1];
1019 // Maybe "if (!i || strcmp(history[i-1], command) != 0) ..."
1020 // (i.e. do not save dups?)
1021 state->history[i++] = xstrdup(str);
1022 state->cur_history = i;
1023 state->cnt_history = i;
1024 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1025 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1026 save_history(state->hist_file);
1028 USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;)
1031 #else /* MAX_HISTORY == 0 */
1032 #define remember_in_history(a) ((void)0)
1033 #endif /* MAX_HISTORY */
1037 * This function is used to grab a character buffer
1038 * from the input file descriptor and allows you to
1039 * a string with full command editing (sort of like
1042 * The following standard commands are not implemented:
1043 * ESC-b -- Move back one word
1044 * ESC-f -- Move forward one word
1045 * ESC-d -- Delete back one word
1046 * ESC-h -- Delete forward one word
1047 * CTL-t -- Transpose two characters
1049 * Minimalist vi-style command line editing available if configured.
1050 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
1053 #if ENABLE_FEATURE_EDITING_VI
1055 vi_Word_motion(char *command, int eat)
1057 while (cursor < command_len && !isspace(command[cursor]))
1059 if (eat) while (cursor < command_len && isspace(command[cursor]))
1064 vi_word_motion(char *command, int eat)
1066 if (isalnum(command[cursor]) || command[cursor] == '_') {
1067 while (cursor < command_len
1068 && (isalnum(command[cursor+1]) || command[cursor+1] == '_'))
1070 } else if (ispunct(command[cursor])) {
1071 while (cursor < command_len && ispunct(command[cursor+1]))
1075 if (cursor < command_len)
1078 if (eat && cursor < command_len && isspace(command[cursor]))
1079 while (cursor < command_len && isspace(command[cursor]))
1084 vi_End_motion(char *command)
1087 while (cursor < command_len && isspace(command[cursor]))
1089 while (cursor < command_len-1 && !isspace(command[cursor+1]))
1094 vi_end_motion(char *command)
1096 if (cursor >= command_len-1)
1099 while (cursor < command_len-1 && isspace(command[cursor]))
1101 if (cursor >= command_len-1)
1103 if (isalnum(command[cursor]) || command[cursor] == '_') {
1104 while (cursor < command_len-1
1105 && (isalnum(command[cursor+1]) || command[cursor+1] == '_')
1109 } else if (ispunct(command[cursor])) {
1110 while (cursor < command_len-1 && ispunct(command[cursor+1]))
1116 vi_Back_motion(char *command)
1118 while (cursor > 0 && isspace(command[cursor-1]))
1120 while (cursor > 0 && !isspace(command[cursor-1]))
1125 vi_back_motion(char *command)
1130 while (cursor > 0 && isspace(command[cursor]))
1134 if (isalnum(command[cursor]) || command[cursor] == '_') {
1136 && (isalnum(command[cursor-1]) || command[cursor-1] == '_')
1140 } else if (ispunct(command[cursor])) {
1141 while (cursor > 0 && ispunct(command[cursor-1]))
1149 * read_line_input and its helpers
1152 #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
1153 static void parse_and_put_prompt(const char *prmt_ptr)
1155 cmdedit_prompt = prmt_ptr;
1156 cmdedit_prmt_len = strlen(prmt_ptr);
1160 static void parse_and_put_prompt(const char *prmt_ptr)
1163 size_t cur_prmt_len = 0;
1164 char flg_not_length = '[';
1165 char *prmt_mem_ptr = xzalloc(1);
1166 char *pwd_buf = xrealloc_getcwd_or_warn(NULL);
1167 char buf2[PATH_MAX + 1];
1172 cmdedit_prmt_len = 0;
1175 pwd_buf = (char *)bb_msg_unknown;
1183 const char *cp = prmt_ptr;
1186 c = bb_process_escape_sequence(&prmt_ptr);
1187 if (prmt_ptr == cp) {
1192 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1194 pbuf = user_buf ? user_buf : (char*)"";
1198 pbuf = hostname_buf;
1200 pbuf = xzalloc(256);
1201 if (gethostname(pbuf, 255) < 0) {
1204 char *s = strchr(pbuf, '.');
1208 hostname_buf = pbuf;
1212 c = (geteuid() == 0 ? '#' : '$');
1214 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1217 l = strlen(home_pwd_buf);
1219 && strncmp(home_pwd_buf, pbuf, l) == 0
1220 && (pbuf[l]=='/' || pbuf[l]=='\0')
1221 && strlen(pwd_buf+l) < PATH_MAX
1225 strcpy(pbuf+1, pwd_buf+l);
1231 cp = strrchr(pbuf, '/');
1232 if (cp != NULL && cp != pbuf)
1233 pbuf += (cp-pbuf) + 1;
1237 snprintf(buf2, sizeof(buf2), "%d", num_ok_lines);
1239 case 'e': case 'E': /* \e \E = \033 */
1243 for (l = 0; l < 3;) {
1245 buf2[l++] = *prmt_ptr;
1247 h = strtol(buf2, &pbuf, 16);
1248 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
1255 c = (char)strtol(buf2, NULL, 16);
1261 if (c == flg_not_length) {
1262 flg_not_length = (flg_not_length == '[' ? ']' : '[');
1271 cur_prmt_len = strlen(pbuf);
1272 prmt_len += cur_prmt_len;
1273 if (flg_not_length != ']')
1274 cmdedit_prmt_len += cur_prmt_len;
1275 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
1277 if (pwd_buf != (char *)bb_msg_unknown)
1279 cmdedit_prompt = prmt_mem_ptr;
1284 static void cmdedit_setwidth(unsigned w, int redraw_flg)
1288 /* new y for current cursor */
1289 int new_y = (cursor + cmdedit_prmt_len) / w;
1291 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), command_len - cursor);
1296 static void win_changed(int nsig)
1299 get_terminal_width_height(0, &width, NULL);
1300 cmdedit_setwidth(width, nsig /* - just a yes/no flag */);
1301 if (nsig == SIGWINCH)
1302 signal(SIGWINCH, win_changed); /* rearm ourself */
1306 * The emacs and vi modes share much of the code in the big
1307 * command loop. Commands entered when in vi's command mode (aka
1308 * "escape mode") get an extra bit added to distinguish them --
1309 * this keeps them from being self-inserted. This clutters the
1310 * big switch a bit, but keeps all the code in one place.
1315 /* leave out the "vi-mode"-only case labels if vi editing isn't
1317 #define vi_case(caselabel) USE_FEATURE_EDITING(case caselabel)
1319 /* convert uppercase ascii to equivalent control char, for readability */
1321 #define CTRL(a) ((a) & ~0x40)
1324 * -1 on read errors or EOF, or on bare Ctrl-D.
1326 * >0 length of input string, including terminating '\n'
1328 int read_line_input(const char *prompt, char *command, int maxsize, line_input_t *st)
1330 #if ENABLE_FEATURE_TAB_COMPLETION
1331 smallint lastWasTab = FALSE;
1335 smallint break_out = 0;
1336 #if ENABLE_FEATURE_EDITING_VI
1337 smallint vi_cmdmode = 0;
1340 struct termios initial_settings;
1341 struct termios new_settings;
1345 if (tcgetattr(STDIN_FILENO, &initial_settings) < 0
1346 || !(initial_settings.c_lflag & ECHO)
1348 /* Happens when e.g. stty -echo was run before */
1350 parse_and_put_prompt(prompt);
1352 fgets(command, maxsize, stdin);
1353 len = strlen(command);
1358 // FIXME: audit & improve this
1359 if (maxsize > MAX_LINELEN)
1360 maxsize = MAX_LINELEN;
1362 /* With null flags, no other fields are ever used */
1363 state = st ? st : (line_input_t*) &const_int_0;
1364 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1365 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1366 load_history(state->hist_file);
1369 /* prepare before init handlers */
1370 cmdedit_y = 0; /* quasireal y, not true if line > xt*yt */
1372 command_ps = command;
1375 new_settings = initial_settings;
1376 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1377 /* Turn off echoing and CTRL-C, so we can trap it */
1378 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1379 /* Hmm, in linux c_cc[] is not parsed if ICANON is off */
1380 new_settings.c_cc[VMIN] = 1;
1381 new_settings.c_cc[VTIME] = 0;
1382 /* Turn off CTRL-C, so we can trap it */
1383 #ifndef _POSIX_VDISABLE
1384 #define _POSIX_VDISABLE '\0'
1386 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1387 tcsetattr(STDIN_FILENO, TCSANOW, &new_settings);
1389 /* Now initialize things */
1390 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
1391 win_changed(0); /* do initial resizing */
1392 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1394 struct passwd *entry;
1396 entry = getpwuid(geteuid());
1398 user_buf = xstrdup(entry->pw_name);
1399 home_pwd_buf = xstrdup(entry->pw_dir);
1400 /* They are not freed on exit (too small to bother) */
1404 /* Print out the command prompt */
1405 parse_and_put_prompt(prompt);
1410 if (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
1431 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1434 /* Control-a -- Beginning of line */
1435 input_backward(cursor);
1440 vi_case('\x7f'|vbit:) /* DEL */
1441 /* Control-b -- Move back one character */
1446 vi_case(CTRL('C')|vbit:)
1447 /* Control-c -- stop gathering input */
1450 break_out = -1; /* "do not append '\n'" */
1453 /* Control-d -- Delete one character, or exit
1454 * if the len=0 and no chars to delete */
1455 if (command_len == 0) {
1458 /* to control stopped jobs */
1459 break_out = command_len = -1;
1465 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1468 /* Control-e -- End of line */
1474 /* Control-f -- Move forward one character */
1480 case '\x7f': /* DEL */
1481 /* Control-h and DEL */
1485 #if ENABLE_FEATURE_TAB_COMPLETION
1487 input_tab(&lastWasTab);
1491 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1493 /* Control-k -- clear to end of line */
1494 command[cursor] = 0;
1495 command_len = cursor;
1499 vi_case(CTRL('L')|vbit:)
1500 /* Control-l -- clear screen */
1502 redraw(0, command_len - cursor);
1508 vi_case(CTRL('N')|vbit:)
1510 /* Control-n -- Get next command in history */
1511 if (get_next_history())
1515 vi_case(CTRL('P')|vbit:)
1517 /* Control-p -- Get previous command from history */
1518 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1519 get_previous_history();
1526 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1528 vi_case(CTRL('U')|vbit:)
1529 /* Control-U -- Clear line before cursor */
1531 strcpy(command, command + cursor);
1532 command_len -= cursor;
1533 redraw(cmdedit_y, command_len);
1538 vi_case(CTRL('W')|vbit:)
1539 /* Control-W -- Remove the last word */
1540 while (cursor > 0 && isspace(command[cursor-1]))
1542 while (cursor > 0 && !isspace(command[cursor-1]))
1546 #if ENABLE_FEATURE_EDITING_VI
1551 input_backward(cursor);
1572 vi_Word_motion(command, 1);
1575 vi_word_motion(command, 1);
1578 vi_End_motion(command);
1581 vi_end_motion(command);
1584 vi_Back_motion(command);
1587 vi_back_motion(command);
1602 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1603 goto prepare_to_die;
1604 if (c == (prevc & 0xff)) {
1606 input_backward(cursor);
1616 case 'w': /* "dw", "cw" */
1617 vi_word_motion(command, vi_cmdmode);
1619 case 'W': /* 'dW', 'cW' */
1620 vi_Word_motion(command, vi_cmdmode);
1622 case 'e': /* 'de', 'ce' */
1623 vi_end_motion(command);
1626 case 'E': /* 'dE', 'cE' */
1627 vi_End_motion(command);
1632 input_backward(cursor - sc);
1633 while (nc-- > cursor)
1636 case 'b': /* "db", "cb" */
1637 case 'B': /* implemented as B */
1639 vi_back_motion(command);
1641 vi_Back_motion(command);
1642 while (sc-- > cursor)
1645 case ' ': /* "d ", "c " */
1648 case '$': /* "d$", "c$" */
1650 while (cursor < command_len)
1663 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1664 goto prepare_to_die;
1668 *(command + cursor) = c;
1673 #endif /* FEATURE_COMMAND_EDITING_VI */
1675 case '\x1b': /* ESC */
1677 #if ENABLE_FEATURE_EDITING_VI
1678 if (state->flags & VI_MODE) {
1679 /* ESC: insert mode --> command mode */
1685 /* escape sequence follows */
1686 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1687 goto prepare_to_die;
1688 /* different vt100 emulations */
1689 if (c == '[' || c == 'O') {
1692 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1693 goto prepare_to_die;
1695 if (c >= '1' && c <= '9') {
1696 unsigned char dummy;
1698 if (safe_read(STDIN_FILENO, &dummy, 1) < 1)
1699 goto prepare_to_die;
1705 #if ENABLE_FEATURE_TAB_COMPLETION
1706 case '\t': /* Alt-Tab */
1707 input_tab(&lastWasTab);
1712 /* Up Arrow -- Get previous command from history */
1713 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1714 get_previous_history();
1720 /* Down Arrow -- Get next command in history */
1721 if (!get_next_history())
1724 /* Rewrite the line with the selected history item */
1725 /* change command */
1726 command_len = strlen(strcpy(command, state->history[state->cur_history]));
1727 /* redraw and go to eol (bol, in vi */
1728 redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
1732 /* Right Arrow -- Move forward one character */
1736 /* Left Arrow -- Move back one character */
1743 case '1': // vt100? linux vt? or what?
1744 case '7': // vt100? linux vt? or what?
1745 case 'H': /* xterm's <Home> */
1746 input_backward(cursor);
1748 case '4': // vt100? linux vt? or what?
1749 case '8': // vt100? linux vt? or what?
1750 case 'F': /* xterm's <End> */
1759 default: /* If it's regular input, do the normal thing */
1760 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1761 /* Control-V -- Add non-printable symbol */
1762 if (c == CTRL('V')) {
1763 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1764 goto prepare_to_die;
1772 #if ENABLE_FEATURE_EDITING_VI
1773 if (vi_cmdmode) /* Don't self-insert */
1776 if (!Isprint(c)) /* Skip non-printable characters */
1779 if (command_len >= (maxsize - 2)) /* Need to leave space for enter */
1783 if (cursor == (command_len - 1)) { /* Append if at the end of the line */
1784 command[cursor] = c;
1785 command[cursor+1] = '\0';
1786 cmdedit_set_out_char(' ');
1787 } else { /* Insert otherwise */
1790 memmove(command + sc + 1, command + sc, command_len - sc);
1793 /* rewrite from cursor */
1795 /* to prev x pos + 1 */
1796 input_backward(cursor - sc);
1800 if (break_out) /* Enter is the command terminator, no more input. */
1803 #if ENABLE_FEATURE_TAB_COMPLETION
1809 if (command_len > 0)
1810 remember_in_history(command);
1812 if (break_out > 0) {
1813 command[command_len++] = '\n';
1814 command[command_len] = '\0';
1817 #if ENABLE_FEATURE_TAB_COMPLETION
1818 free_tab_completion_data();
1821 /* restore initial_settings */
1822 tcsetattr(STDIN_FILENO, TCSANOW, &initial_settings);
1823 /* restore SIGWINCH handler */
1824 signal(SIGWINCH, previous_SIGWINCH_handler);
1832 line_input_t *new_line_input_t(int flags)
1834 line_input_t *n = xzalloc(sizeof(*n));
1841 #undef read_line_input
1842 int read_line_input(const char* prompt, char* command, int maxsize)
1844 fputs(prompt, stdout);
1846 fgets(command, maxsize, stdin);
1847 return strlen(command);
1850 #endif /* FEATURE_COMMAND_EDITING */
1861 const char *applet_name = "debug stuff usage";
1863 int main(int argc, char **argv)
1865 char buff[MAX_LINELEN];
1867 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1868 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:"
1869 "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] "
1870 "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1875 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1876 setlocale(LC_ALL, "");
1880 l = read_line_input(prompt, buff);
1881 if (l <= 0 || buff[l-1] != '\n')
1884 printf("*** read_line_input() returned line =%s=\n", buff);
1886 printf("*** read_line_input() detect ^D\n");