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 /* This is longer by 5 bytes on x86.
250 * Also gets mysteriously
251 * miscompiled for some ARM users.
252 * printf(("\b\b\b\b" + 4) - num);
260 printf("\033[%uD", num);
264 /* Need to go one or more lines up */
266 count_y = 1 + (num / cmdedit_termw);
267 cmdedit_y -= count_y;
268 cmdedit_x = cmdedit_termw * count_y - num;
269 /* go to 1st column; go up; go to correct column */
270 printf("\r" "\033[%dA" "\033[%dC", count_y, cmdedit_x);
273 static void put_prompt(void)
275 out1str(cmdedit_prompt);
276 cmdedit_x = cmdedit_prmt_len;
278 // Huh? what if cmdedit_prmt_len >= width?
279 cmdedit_y = 0; /* new quasireal y */
282 /* draw prompt, editor line, and clear tail */
283 static void redraw(int y, int back_cursor)
285 if (y > 0) /* up to start y */
286 printf("\033[%dA", y);
289 input_end(); /* rewrite */
290 printf("\033[J"); /* erase after cursor */
291 input_backward(back_cursor);
294 /* Delete the char in front of the cursor, optionally saving it
295 * for later putback */
296 static void input_delete(int save)
300 if (j == command_len)
303 #if ENABLE_FEATURE_EDITING_VI
309 if ((delptr - delbuf) < DELBUFSIZ)
310 *delptr++ = command_ps[j];
314 strcpy(command_ps + j, command_ps + j + 1);
316 input_end(); /* rewrite new line */
317 cmdedit_set_out_char(' '); /* erase char */
318 input_backward(cursor - j); /* back to old pos cursor */
321 #if ENABLE_FEATURE_EDITING_VI
322 static void put(void)
325 int j = delptr - delbuf;
330 /* open hole and then fill it */
331 memmove(command_ps + cursor + j, command_ps + cursor, command_len - cursor + 1);
332 strncpy(command_ps + cursor, delbuf, j);
334 input_end(); /* rewrite new line */
335 input_backward(cursor - ocursor - j + 1); /* at end of new text */
339 /* Delete the char in back of the cursor */
340 static void input_backspace(void)
348 /* Move forward one character */
349 static void input_forward(void)
351 if (cursor < command_len)
352 cmdedit_set_out_char(command_ps[cursor + 1]);
355 #if ENABLE_FEATURE_TAB_COMPLETION
357 static void free_tab_completion_data(void)
361 free(matches[--num_matches]);
367 static void add_match(char *matched)
369 int nm = num_matches;
372 matches = xrealloc(matches, nm1 * sizeof(char *));
373 matches[nm] = matched;
377 #if ENABLE_FEATURE_USERNAME_COMPLETION
378 static void username_tab_completion(char *ud, char *with_shash_flg)
380 struct passwd *entry;
383 ud++; /* ~user/... to user/... */
384 userlen = strlen(ud);
386 if (with_shash_flg) { /* "~/..." or "~user/..." */
387 char *sav_ud = ud - 1;
390 if (*ud == '/') { /* "~/..." */
395 temp = strchr(ud, '/');
396 *temp = '\0'; /* ~user\0 */
397 entry = getpwnam(ud);
398 *temp = '/'; /* restore ~user/... */
401 home = entry->pw_dir;
404 if ((userlen + strlen(home) + 1) < MAX_LINELEN) {
406 sprintf(sav_ud, "%s%s", home, ud);
411 /* Using _r function to avoid pulling in static buffers */
414 struct passwd *result;
417 while (!getpwent_r(&pwd, line_buff, sizeof(line_buff), &result)) {
418 /* Null usernames should result in all users as possible completions. */
419 if (/*!userlen || */ strncmp(ud, pwd.pw_name, userlen) == 0) {
420 add_match(xasprintf("~%s/", pwd.pw_name));
426 #endif /* FEATURE_COMMAND_USERNAME_COMPLETION */
434 static int path_parse(char ***p, int flags)
441 /* if not setenv PATH variable, to search cur dir "." */
442 if (flags != FIND_EXE_ONLY)
445 if (state->flags & WITH_PATH_LOOKUP)
446 pth = state->path_lookup;
448 pth = getenv("PATH");
449 /* PATH=<empty> or PATH=:<empty> */
450 if (!pth || !pth[0] || LONE_CHAR(pth, ':'))
454 npth = 1; /* path component count */
456 tmp = strchr(tmp, ':');
460 break; /* :<empty> */
464 res = xmalloc(npth * sizeof(char*));
465 res[0] = tmp = xstrdup(pth);
468 tmp = strchr(tmp, ':');
471 *tmp++ = '\0'; /* ':' -> '\0' */
473 break; /* :<empty> */
480 static void exe_n_cwd_tab_completion(char *command, int type)
486 char **paths = path1;
490 char *pfind = strrchr(command, '/');
491 /* char dirbuf[MAX_LINELEN]; */
492 #define dirbuf (S.exe_n_cwd_tab_completion__dirbuf)
495 path1[0] = (char*)".";
498 /* no dir, if flags==EXE_ONLY - get paths, else "." */
499 npaths = path_parse(&paths, type);
502 /* dirbuf = ".../.../.../" */
503 safe_strncpy(dirbuf, command, (pfind - command) + 2);
504 #if ENABLE_FEATURE_USERNAME_COMPLETION
505 if (dirbuf[0] == '~') /* ~/... or ~user/... */
506 username_tab_completion(dirbuf, dirbuf);
509 /* point to 'l' in "..../last_component" */
513 for (i = 0; i < npaths; i++) {
514 dir = opendir(paths[i]);
515 if (!dir) /* Don't print an error */
518 while ((next = readdir(dir)) != NULL) {
520 const char *str_found = next->d_name;
523 if (strncmp(str_found, pfind, strlen(pfind)))
525 /* not see .name without .match */
526 if (*str_found == '.' && *pfind == 0) {
527 if (NOT_LONE_CHAR(paths[i], '/') || str_found[1])
529 str_found = ""; /* only "/" */
531 found = concat_path_file(paths[i], str_found);
532 /* hmm, remover in progress? */
533 if (lstat(found, &st) < 0)
535 /* find with dirs? */
536 if (paths[i] != dirbuf)
537 strcpy(found, next->d_name); /* only name */
539 len1 = strlen(found);
540 found = xrealloc(found, len1 + 2);
542 found[len1+1] = '\0';
544 if (S_ISDIR(st.st_mode)) {
545 /* name is directory */
546 if (found[len1-1] != '/') {
550 /* not put found file if search only dirs for cd */
551 if (type == FIND_DIR_ONLY)
554 /* Add it to the list */
562 if (paths != path1) {
563 free(paths[0]); /* allocated memory only in first member */
569 #define QUOT (UCHAR_MAX+1)
571 #define collapse_pos(is, in) do { \
572 memmove(int_buf+(is), int_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
573 memmove(pos_buf+(is), pos_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
576 static int find_match(char *matchBuf, int *len_with_quotes)
581 /* int16_t int_buf[MAX_LINELEN + 1]; */
582 /* int16_t pos_buf[MAX_LINELEN + 1]; */
583 #define int_buf (S.find_match__int_buf)
584 #define pos_buf (S.find_match__pos_buf)
586 /* set to integer dimension characters and own positions */
588 int_buf[i] = (unsigned char)matchBuf[i];
589 if (int_buf[i] == 0) {
590 pos_buf[i] = -1; /* indicator end line */
596 /* mask \+symbol and convert '\t' to ' ' */
597 for (i = j = 0; matchBuf[i]; i++, j++)
598 if (matchBuf[i] == '\\') {
599 collapse_pos(j, j + 1);
602 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
603 if (matchBuf[i] == '\t') /* algorithm equivalent */
604 int_buf[j] = ' ' | QUOT;
607 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
608 else if (matchBuf[i] == '\t')
612 /* mask "symbols" or 'symbols' */
614 for (i = 0; int_buf[i]; i++) {
616 if (c == '\'' || c == '"') {
625 } else if (c2 != 0 && c != '$')
629 /* skip commands with arguments if line has commands delimiters */
630 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
631 for (i = 0; int_buf[i]; i++) {
634 j = i ? int_buf[i - 1] : -1;
636 if (c == ';' || c == '&' || c == '|') {
637 command_mode = 1 + (c == c2);
639 if (j == '>' || j == '<')
641 } else if (c == '|' && j == '>')
645 collapse_pos(0, i + command_mode);
646 i = -1; /* hack incremet */
649 /* collapse `command...` */
650 for (i = 0; int_buf[i]; i++)
651 if (int_buf[i] == '`') {
652 for (j = i + 1; int_buf[j]; j++)
653 if (int_buf[j] == '`') {
654 collapse_pos(i, j + 1);
659 /* not found close ` - command mode, collapse all previous */
660 collapse_pos(0, i + 1);
663 i--; /* hack incremet */
666 /* collapse (command...(command...)...) or {command...{command...}...} */
667 c = 0; /* "recursive" level */
669 for (i = 0; int_buf[i]; i++)
670 if (int_buf[i] == '(' || int_buf[i] == '{') {
671 if (int_buf[i] == '(')
675 collapse_pos(0, i + 1);
676 i = -1; /* hack incremet */
678 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
679 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
680 if (int_buf[i] == ')')
684 collapse_pos(0, i + 1);
685 i = -1; /* hack incremet */
688 /* skip first not quote space */
689 for (i = 0; int_buf[i]; i++)
690 if (int_buf[i] != ' ')
695 /* set find mode for completion */
696 command_mode = FIND_EXE_ONLY;
697 for (i = 0; int_buf[i]; i++)
698 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
699 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
700 && matchBuf[pos_buf[0]] == 'c'
701 && matchBuf[pos_buf[1]] == 'd'
703 command_mode = FIND_DIR_ONLY;
705 command_mode = FIND_FILE_ONLY;
709 for (i = 0; int_buf[i]; i++)
712 for (--i; i >= 0; i--) {
714 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
715 collapse_pos(0, i + 1);
719 /* skip first not quoted '\'' or '"' */
720 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++)
722 /* collapse quote or unquote // or /~ */
723 while ((int_buf[i] & ~QUOT) == '/'
724 && ((int_buf[i+1] & ~QUOT) == '/' || (int_buf[i+1] & ~QUOT) == '~')
729 /* set only match and destroy quotes */
731 for (c = 0; pos_buf[i] >= 0; i++) {
732 matchBuf[c++] = matchBuf[pos_buf[i]];
736 /* old length matchBuf with quotes symbols */
737 *len_with_quotes = j ? j - pos_buf[0] : 0;
745 * display by column (original idea from ls applet,
746 * very optimized by me :)
748 static void showfiles(void)
751 int column_width = 0;
752 int nfiles = num_matches;
756 /* find the longest file name- use that as the column width */
757 for (row = 0; row < nrows; row++) {
758 l = strlen(matches[row]);
759 if (column_width < l)
762 column_width += 2; /* min space for columns */
763 ncols = cmdedit_termw / column_width;
768 nrows++; /* round up fractionals */
772 for (row = 0; row < nrows; row++) {
776 for (nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
777 printf("%s%-*s", matches[n],
778 (int)(column_width - strlen(matches[n])), "");
784 static char *add_quote_for_spec_chars(char *found)
787 char *s = xmalloc((strlen(found) + 1) * 2);
790 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
798 static int match_compare(const void *a, const void *b)
800 return strcmp(*(char**)a, *(char**)b);
803 /* Do TAB completion */
804 static void input_tab(smallint *lastWasTab)
806 if (!(state->flags & TAB_COMPLETION))
812 /* char matchBuf[MAX_LINELEN]; */
813 #define matchBuf (S.input_tab__matchBuf)
817 *lastWasTab = TRUE; /* flop trigger */
819 /* Make a local copy of the string -- up
820 * to the position of the cursor */
821 tmp = strncpy(matchBuf, command_ps, cursor);
824 find_type = find_match(matchBuf, &recalc_pos);
826 /* Free up any memory already allocated */
827 free_tab_completion_data();
829 #if ENABLE_FEATURE_USERNAME_COMPLETION
830 /* If the word starts with `~' and there is no slash in the word,
831 * then try completing this word as a username. */
832 if (state->flags & USERNAME_COMPLETION)
833 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
834 username_tab_completion(matchBuf, NULL);
836 /* Try to match any executable in our path and everything
837 * in the current working directory */
839 exe_n_cwd_tab_completion(matchBuf, find_type);
840 /* Sort, then remove any duplicates found */
843 qsort(matches, num_matches, sizeof(char*), match_compare);
844 for (i = 0; i < num_matches - 1; ++i) {
845 if (matches[i] && matches[i+1]) { /* paranoia */
846 if (strcmp(matches[i], matches[i+1]) == 0) {
848 matches[i] = NULL; /* paranoia */
850 matches[n++] = matches[i];
854 matches[n] = matches[i];
857 /* Did we find exactly one match? */
858 if (!matches || num_matches > 1) {
861 return; /* not found */
862 /* find minimal match */
863 tmp1 = xstrdup(matches[0]);
864 for (tmp = tmp1; *tmp; tmp++)
865 for (len_found = 1; len_found < num_matches; len_found++)
866 if (matches[len_found][(tmp - tmp1)] != *tmp) {
870 if (*tmp1 == '\0') { /* have unique */
874 tmp = add_quote_for_spec_chars(tmp1);
876 } else { /* one match */
877 tmp = add_quote_for_spec_chars(matches[0]);
878 /* for next completion current found */
881 len_found = strlen(tmp);
882 if (tmp[len_found-1] != '/') {
883 tmp[len_found] = ' ';
884 tmp[len_found+1] = '\0';
887 len_found = strlen(tmp);
888 /* have space to placed match? */
889 if ((len_found - strlen(matchBuf) + command_len) < MAX_LINELEN) {
890 /* before word for match */
891 command_ps[cursor - recalc_pos] = '\0';
893 strcpy(matchBuf, command_ps + cursor);
895 strcat(command_ps, tmp);
897 strcat(command_ps, matchBuf);
898 /* back to begin word for match */
899 input_backward(recalc_pos);
901 recalc_pos = cursor + len_found;
903 command_len = strlen(command_ps);
904 /* write out the matched command */
905 redraw(cmdedit_y, command_len - recalc_pos);
910 /* Ok -- the last char was a TAB. Since they
911 * just hit TAB again, print a list of all the
912 * available choices... */
913 if (matches && num_matches > 0) {
914 int sav_cursor = cursor; /* change goto_new_line() */
916 /* Go to the next line */
919 redraw(0, command_len - sav_cursor);
924 #endif /* FEATURE_COMMAND_TAB_COMPLETION */
929 /* state->flags is already checked to be nonzero */
930 static void get_previous_history(void)
932 if (command_ps[0] != '\0' || state->history[state->cur_history] == NULL) {
933 free(state->history[state->cur_history]);
934 state->history[state->cur_history] = xstrdup(command_ps);
936 state->cur_history--;
939 static int get_next_history(void)
941 if (state->flags & DO_HISTORY) {
942 int ch = state->cur_history;
943 if (ch < state->cnt_history) {
944 get_previous_history(); /* save the current history line */
945 state->cur_history = ch + 1;
946 return state->cur_history;
953 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
954 /* state->flags is already checked to be nonzero */
955 static void load_history(const char *fromfile)
961 for (hi = state->cnt_history; hi > 0;) {
963 free(state->history[hi]);
966 fp = fopen(fromfile, "r");
968 for (hi = 0; hi < MAX_HISTORY;) {
969 char *hl = xmalloc_getline(fp);
975 if (l >= MAX_LINELEN)
976 hl[MAX_LINELEN-1] = '\0';
977 if (l == 0 || hl[0] == ' ') {
981 state->history[hi++] = hl;
985 state->cur_history = state->cnt_history = hi;
988 /* state->flags is already checked to be nonzero */
989 static void save_history(const char *tofile)
993 fp = fopen(tofile, "w");
997 for (i = 0; i < state->cnt_history; i++) {
998 fprintf(fp, "%s\n", state->history[i]);
1004 #define load_history(a) ((void)0)
1005 #define save_history(a) ((void)0)
1006 #endif /* FEATURE_COMMAND_SAVEHISTORY */
1008 static void remember_in_history(const char *str)
1012 if (!(state->flags & DO_HISTORY))
1015 i = state->cnt_history;
1016 free(state->history[MAX_HISTORY]);
1017 state->history[MAX_HISTORY] = NULL;
1018 /* After max history, remove the oldest command */
1019 if (i >= MAX_HISTORY) {
1020 free(state->history[0]);
1021 for (i = 0; i < MAX_HISTORY-1; i++)
1022 state->history[i] = state->history[i+1];
1024 // Maybe "if (!i || strcmp(history[i-1], command) != 0) ..."
1025 // (i.e. do not save dups?)
1026 state->history[i++] = xstrdup(str);
1027 state->cur_history = i;
1028 state->cnt_history = i;
1029 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1030 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1031 save_history(state->hist_file);
1033 USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;)
1036 #else /* MAX_HISTORY == 0 */
1037 #define remember_in_history(a) ((void)0)
1038 #endif /* MAX_HISTORY */
1042 * This function is used to grab a character buffer
1043 * from the input file descriptor and allows you to
1044 * a string with full command editing (sort of like
1047 * The following standard commands are not implemented:
1048 * ESC-b -- Move back one word
1049 * ESC-f -- Move forward one word
1050 * ESC-d -- Delete back one word
1051 * ESC-h -- Delete forward one word
1052 * CTL-t -- Transpose two characters
1054 * Minimalist vi-style command line editing available if configured.
1055 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
1058 #if ENABLE_FEATURE_EDITING_VI
1060 vi_Word_motion(char *command, int eat)
1062 while (cursor < command_len && !isspace(command[cursor]))
1064 if (eat) while (cursor < command_len && isspace(command[cursor]))
1069 vi_word_motion(char *command, int eat)
1071 if (isalnum(command[cursor]) || command[cursor] == '_') {
1072 while (cursor < command_len
1073 && (isalnum(command[cursor+1]) || command[cursor+1] == '_'))
1075 } else if (ispunct(command[cursor])) {
1076 while (cursor < command_len && ispunct(command[cursor+1]))
1080 if (cursor < command_len)
1083 if (eat && cursor < command_len && isspace(command[cursor]))
1084 while (cursor < command_len && isspace(command[cursor]))
1089 vi_End_motion(char *command)
1092 while (cursor < command_len && isspace(command[cursor]))
1094 while (cursor < command_len-1 && !isspace(command[cursor+1]))
1099 vi_end_motion(char *command)
1101 if (cursor >= command_len-1)
1104 while (cursor < command_len-1 && isspace(command[cursor]))
1106 if (cursor >= command_len-1)
1108 if (isalnum(command[cursor]) || command[cursor] == '_') {
1109 while (cursor < command_len-1
1110 && (isalnum(command[cursor+1]) || command[cursor+1] == '_')
1114 } else if (ispunct(command[cursor])) {
1115 while (cursor < command_len-1 && ispunct(command[cursor+1]))
1121 vi_Back_motion(char *command)
1123 while (cursor > 0 && isspace(command[cursor-1]))
1125 while (cursor > 0 && !isspace(command[cursor-1]))
1130 vi_back_motion(char *command)
1135 while (cursor > 0 && isspace(command[cursor]))
1139 if (isalnum(command[cursor]) || command[cursor] == '_') {
1141 && (isalnum(command[cursor-1]) || command[cursor-1] == '_')
1145 } else if (ispunct(command[cursor])) {
1146 while (cursor > 0 && ispunct(command[cursor-1]))
1154 * read_line_input and its helpers
1157 #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
1158 static void parse_and_put_prompt(const char *prmt_ptr)
1160 cmdedit_prompt = prmt_ptr;
1161 cmdedit_prmt_len = strlen(prmt_ptr);
1165 static void parse_and_put_prompt(const char *prmt_ptr)
1168 size_t cur_prmt_len = 0;
1169 char flg_not_length = '[';
1170 char *prmt_mem_ptr = xzalloc(1);
1171 char *cwd_buf = xrealloc_getcwd_or_warn(NULL);
1176 cmdedit_prmt_len = 0;
1179 cwd_buf = (char *)bb_msg_unknown;
1182 cbuf[1] = '\0'; /* never changes */
1185 char *free_me = NULL;
1190 const char *cp = prmt_ptr;
1193 c = bb_process_escape_sequence(&prmt_ptr);
1194 if (prmt_ptr == cp) {
1200 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1202 pbuf = user_buf ? user_buf : (char*)"";
1206 pbuf = free_me = xzalloc(256);
1207 if (gethostname(pbuf, 255) < 0) {
1211 *strchrnul(pbuf, '.') = '\0';
1214 c = (geteuid() == 0 ? '#' : '$');
1216 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1218 /* /home/user[/something] -> ~[/something] */
1220 l = strlen(home_pwd_buf);
1222 && strncmp(home_pwd_buf, cwd_buf, l) == 0
1223 && (cwd_buf[l]=='/' || cwd_buf[l]=='\0')
1224 && strlen(cwd_buf + l) < PATH_MAX
1226 pbuf = free_me = xasprintf("~%s", cwd_buf + l);
1232 cp = strrchr(pbuf, '/');
1233 if (cp != NULL && cp != pbuf)
1234 pbuf += (cp-pbuf) + 1;
1237 pbuf = free_me = xasprintf("%d", num_ok_lines);
1239 case 'e': case 'E': /* \e \E = \033 */
1242 case 'x': case 'X': {
1244 for (l = 0; l < 3;) {
1246 buf2[l++] = *prmt_ptr;
1248 h = strtoul(buf2, &pbuf, 16);
1249 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
1255 c = (char)strtoul(buf2, NULL, 16);
1262 if (c == flg_not_length) {
1263 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);
1279 if (cwd_buf != (char *)bb_msg_unknown)
1281 cmdedit_prompt = prmt_mem_ptr;
1286 static void cmdedit_setwidth(unsigned w, int redraw_flg)
1290 /* new y for current cursor */
1291 int new_y = (cursor + cmdedit_prmt_len) / w;
1293 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), command_len - cursor);
1298 static void win_changed(int nsig)
1301 get_terminal_width_height(0, &width, NULL);
1302 cmdedit_setwidth(width, nsig /* - just a yes/no flag */);
1303 if (nsig == SIGWINCH)
1304 signal(SIGWINCH, win_changed); /* rearm ourself */
1308 * The emacs and vi modes share much of the code in the big
1309 * command loop. Commands entered when in vi's command mode (aka
1310 * "escape mode") get an extra bit added to distinguish them --
1311 * this keeps them from being self-inserted. This clutters the
1312 * big switch a bit, but keeps all the code in one place.
1317 /* leave out the "vi-mode"-only case labels if vi editing isn't
1319 #define vi_case(caselabel) USE_FEATURE_EDITING(case caselabel)
1321 /* convert uppercase ascii to equivalent control char, for readability */
1323 #define CTRL(a) ((a) & ~0x40)
1326 * -1 on read errors or EOF, or on bare Ctrl-D,
1327 * 0 on ctrl-C (the line entered is still returned in 'command'),
1328 * >0 length of input string, including terminating '\n'
1330 int read_line_input(const char *prompt, char *command, int maxsize, line_input_t *st)
1332 #if ENABLE_FEATURE_TAB_COMPLETION
1333 smallint lastWasTab = FALSE;
1337 smallint break_out = 0;
1338 #if ENABLE_FEATURE_EDITING_VI
1339 smallint vi_cmdmode = 0;
1342 struct termios initial_settings;
1343 struct termios new_settings;
1347 if (tcgetattr(STDIN_FILENO, &initial_settings) < 0
1348 || !(initial_settings.c_lflag & ECHO)
1350 /* Happens when e.g. stty -echo was run before */
1352 parse_and_put_prompt(prompt);
1354 if (fgets(command, maxsize, stdin) == NULL)
1355 len = -1; /* EOF or error */
1357 len = strlen(command);
1362 // FIXME: audit & improve this
1363 if (maxsize > MAX_LINELEN)
1364 maxsize = MAX_LINELEN;
1366 /* With null flags, no other fields are ever used */
1367 state = st ? st : (line_input_t*) &const_int_0;
1368 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1369 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1370 load_history(state->hist_file);
1373 /* prepare before init handlers */
1374 cmdedit_y = 0; /* quasireal y, not true if line > xt*yt */
1376 command_ps = command;
1379 new_settings = initial_settings;
1380 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1381 /* Turn off echoing and CTRL-C, so we can trap it */
1382 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1383 /* Hmm, in linux c_cc[] is not parsed if ICANON is off */
1384 new_settings.c_cc[VMIN] = 1;
1385 new_settings.c_cc[VTIME] = 0;
1386 /* Turn off CTRL-C, so we can trap it */
1387 #ifndef _POSIX_VDISABLE
1388 #define _POSIX_VDISABLE '\0'
1390 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1391 tcsetattr(STDIN_FILENO, TCSANOW, &new_settings);
1393 /* Now initialize things */
1394 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
1395 win_changed(0); /* do initial resizing */
1396 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1398 struct passwd *entry;
1400 entry = getpwuid(geteuid());
1402 user_buf = xstrdup(entry->pw_name);
1403 home_pwd_buf = xstrdup(entry->pw_dir);
1407 /* Print out the command prompt */
1408 parse_and_put_prompt(prompt);
1413 if (nonblock_safe_read(STDIN_FILENO, &c, 1) < 1) {
1414 /* if we can't read input then exit */
1415 goto prepare_to_die;
1420 #if ENABLE_FEATURE_EDITING_VI
1436 /* Control-a -- Beginning of line */
1437 input_backward(cursor);
1442 vi_case('\x7f'|vbit:) /* DEL */
1443 /* Control-b -- Move back one character */
1447 vi_case(CTRL('C')|vbit:)
1448 /* Control-c -- stop gathering input */
1451 break_out = -1; /* "do not append '\n'" */
1454 /* Control-d -- Delete one character, or exit
1455 * if the len=0 and no chars to delete */
1456 if (command_len == 0) {
1459 /* to control stopped jobs */
1460 break_out = command_len = -1;
1468 /* Control-e -- End of line */
1474 /* Control-f -- Move forward one character */
1479 case '\x7f': /* DEL */
1480 /* Control-h and DEL */
1484 #if ENABLE_FEATURE_TAB_COMPLETION
1486 input_tab(&lastWasTab);
1491 /* Control-k -- clear to end of line */
1492 command[cursor] = 0;
1493 command_len = cursor;
1497 vi_case(CTRL('L')|vbit:)
1498 /* Control-l -- clear screen */
1500 redraw(0, command_len - cursor);
1505 vi_case(CTRL('N')|vbit:)
1507 /* Control-n -- Get next command in history */
1508 if (get_next_history())
1512 vi_case(CTRL('P')|vbit:)
1514 /* Control-p -- Get previous command from history */
1515 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1516 get_previous_history();
1524 vi_case(CTRL('U')|vbit:)
1525 /* Control-U -- Clear line before cursor */
1527 strcpy(command, command + cursor);
1528 command_len -= cursor;
1529 redraw(cmdedit_y, command_len);
1533 vi_case(CTRL('W')|vbit:)
1534 /* Control-W -- Remove the last word */
1535 while (cursor > 0 && isspace(command[cursor-1]))
1537 while (cursor > 0 && !isspace(command[cursor-1]))
1541 #if ENABLE_FEATURE_EDITING_VI
1546 input_backward(cursor);
1567 vi_Word_motion(command, 1);
1570 vi_word_motion(command, 1);
1573 vi_End_motion(command);
1576 vi_end_motion(command);
1579 vi_Back_motion(command);
1582 vi_back_motion(command);
1597 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1598 goto prepare_to_die;
1599 if (c == (prevc & 0xff)) {
1601 input_backward(cursor);
1611 case 'w': /* "dw", "cw" */
1612 vi_word_motion(command, vi_cmdmode);
1614 case 'W': /* 'dW', 'cW' */
1615 vi_Word_motion(command, vi_cmdmode);
1617 case 'e': /* 'de', 'ce' */
1618 vi_end_motion(command);
1621 case 'E': /* 'dE', 'cE' */
1622 vi_End_motion(command);
1627 input_backward(cursor - sc);
1628 while (nc-- > cursor)
1631 case 'b': /* "db", "cb" */
1632 case 'B': /* implemented as B */
1634 vi_back_motion(command);
1636 vi_Back_motion(command);
1637 while (sc-- > cursor)
1640 case ' ': /* "d ", "c " */
1643 case '$': /* "d$", "c$" */
1645 while (cursor < command_len)
1658 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1659 goto prepare_to_die;
1663 *(command + cursor) = c;
1668 #endif /* FEATURE_COMMAND_EDITING_VI */
1670 case '\x1b': /* ESC */
1672 #if ENABLE_FEATURE_EDITING_VI
1673 if (state->flags & VI_MODE) {
1674 /* ESC: insert mode --> command mode */
1680 /* escape sequence follows */
1681 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1682 goto prepare_to_die;
1683 /* different vt100 emulations */
1684 if (c == '[' || c == 'O') {
1687 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1688 goto prepare_to_die;
1690 if (c >= '1' && c <= '9') {
1691 unsigned char dummy;
1693 if (safe_read(STDIN_FILENO, &dummy, 1) < 1)
1694 goto prepare_to_die;
1700 #if ENABLE_FEATURE_TAB_COMPLETION
1701 case '\t': /* Alt-Tab */
1702 input_tab(&lastWasTab);
1707 /* Up Arrow -- Get previous command from history */
1708 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1709 get_previous_history();
1715 /* Down Arrow -- Get next command in history */
1716 if (!get_next_history())
1719 /* Rewrite the line with the selected history item */
1720 /* change command */
1721 command_len = strlen(strcpy(command, state->history[state->cur_history]));
1722 /* redraw and go to eol (bol, in vi */
1723 redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
1727 /* Right Arrow -- Move forward one character */
1731 /* Left Arrow -- Move back one character */
1738 case '1': // vt100? linux vt? or what?
1739 case '7': // vt100? linux vt? or what?
1740 case 'H': /* xterm's <Home> */
1741 input_backward(cursor);
1743 case '4': // vt100? linux vt? or what?
1744 case '8': // vt100? linux vt? or what?
1745 case 'F': /* xterm's <End> */
1754 default: /* If it's regular input, do the normal thing */
1756 /* Control-V -- force insert of next char */
1757 if (c == CTRL('V')) {
1758 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1759 goto prepare_to_die;
1766 #if ENABLE_FEATURE_EDITING_VI
1767 if (vi_cmdmode) /* Don't self-insert */
1770 if (command_len >= (maxsize - 2)) /* Need to leave space for enter */
1774 if (cursor == (command_len - 1)) { /* Append if at the end of the line */
1775 command[cursor] = c;
1776 command[cursor+1] = '\0';
1777 cmdedit_set_out_char(' ');
1778 } else { /* Insert otherwise */
1781 memmove(command + sc + 1, command + sc, command_len - sc);
1784 /* rewrite from cursor */
1786 /* to prev x pos + 1 */
1787 input_backward(cursor - sc);
1791 if (break_out) /* Enter is the command terminator, no more input. */
1794 #if ENABLE_FEATURE_TAB_COMPLETION
1800 if (command_len > 0)
1801 remember_in_history(command);
1803 if (break_out > 0) {
1804 command[command_len++] = '\n';
1805 command[command_len] = '\0';
1808 #if ENABLE_FEATURE_TAB_COMPLETION
1809 free_tab_completion_data();
1812 /* restore initial_settings */
1813 tcsetattr(STDIN_FILENO, TCSANOW, &initial_settings);
1814 /* restore SIGWINCH handler */
1815 signal(SIGWINCH, previous_SIGWINCH_handler);
1823 line_input_t *new_line_input_t(int flags)
1825 line_input_t *n = xzalloc(sizeof(*n));
1832 #undef read_line_input
1833 int read_line_input(const char* prompt, char* command, int maxsize)
1835 fputs(prompt, stdout);
1837 fgets(command, maxsize, stdin);
1838 return strlen(command);
1841 #endif /* FEATURE_COMMAND_EDITING */
1852 const char *applet_name = "debug stuff usage";
1854 int main(int argc, char **argv)
1856 char buff[MAX_LINELEN];
1858 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1859 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:"
1860 "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] "
1861 "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1866 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1867 setlocale(LC_ALL, "");
1871 l = read_line_input(prompt, buff);
1872 if (l <= 0 || buff[l-1] != '\n')
1875 printf("*** read_line_input() returned line =%s=\n", buff);
1877 printf("*** read_line_input() detect ^D\n");