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()
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 #if !ENABLE_FEATURE_EDITING_VI
297 static void input_delete(void)
298 #define input_delete(save) input_delete()
300 static void input_delete(int save)
305 if (j == command_len)
308 #if ENABLE_FEATURE_EDITING_VI
314 if ((delptr - delbuf) < DELBUFSIZ)
315 *delptr++ = command_ps[j];
319 strcpy(command_ps + j, command_ps + j + 1);
321 input_end(); /* rewrite new line */
322 cmdedit_set_out_char(' '); /* erase char */
323 input_backward(cursor - j); /* back to old pos cursor */
326 #if ENABLE_FEATURE_EDITING_VI
327 static void put(void)
330 int j = delptr - delbuf;
335 /* open hole and then fill it */
336 memmove(command_ps + cursor + j, command_ps + cursor, command_len - cursor + 1);
337 strncpy(command_ps + cursor, delbuf, j);
339 input_end(); /* rewrite new line */
340 input_backward(cursor - ocursor - j + 1); /* at end of new text */
344 /* Delete the char in back of the cursor */
345 static void input_backspace(void)
353 /* Move forward one character */
354 static void input_forward(void)
356 if (cursor < command_len)
357 cmdedit_set_out_char(command_ps[cursor + 1]);
360 #if ENABLE_FEATURE_TAB_COMPLETION
362 static void free_tab_completion_data(void)
366 free(matches[--num_matches]);
372 static void add_match(char *matched)
374 int nm = num_matches;
377 matches = xrealloc(matches, nm1 * sizeof(char *));
378 matches[nm] = matched;
382 #if ENABLE_FEATURE_USERNAME_COMPLETION
383 static void username_tab_completion(char *ud, char *with_shash_flg)
385 struct passwd *entry;
388 ud++; /* ~user/... to user/... */
389 userlen = strlen(ud);
391 if (with_shash_flg) { /* "~/..." or "~user/..." */
392 char *sav_ud = ud - 1;
395 if (*ud == '/') { /* "~/..." */
400 temp = strchr(ud, '/');
401 *temp = '\0'; /* ~user\0 */
402 entry = getpwnam(ud);
403 *temp = '/'; /* restore ~user/... */
406 home = entry->pw_dir;
409 if ((userlen + strlen(home) + 1) < MAX_LINELEN) {
411 sprintf(sav_ud, "%s%s", home, ud);
416 /* Using _r function to avoid pulling in static buffers */
419 struct passwd *result;
422 while (!getpwent_r(&pwd, line_buff, sizeof(line_buff), &result)) {
423 /* Null usernames should result in all users as possible completions. */
424 if (/*!userlen || */ strncmp(ud, pwd.pw_name, userlen) == 0) {
425 add_match(xasprintf("~%s/", pwd.pw_name));
431 #endif /* FEATURE_COMMAND_USERNAME_COMPLETION */
439 static int path_parse(char ***p, int flags)
446 /* if not setenv PATH variable, to search cur dir "." */
447 if (flags != FIND_EXE_ONLY)
450 if (state->flags & WITH_PATH_LOOKUP)
451 pth = state->path_lookup;
453 pth = getenv("PATH");
454 /* PATH=<empty> or PATH=:<empty> */
455 if (!pth || !pth[0] || LONE_CHAR(pth, ':'))
459 npth = 1; /* path component count */
461 tmp = strchr(tmp, ':');
465 break; /* :<empty> */
469 res = xmalloc(npth * sizeof(char*));
470 res[0] = tmp = xstrdup(pth);
473 tmp = strchr(tmp, ':');
476 *tmp++ = '\0'; /* ':' -> '\0' */
478 break; /* :<empty> */
485 static void exe_n_cwd_tab_completion(char *command, int type)
491 char **paths = path1;
495 char *pfind = strrchr(command, '/');
496 /* char dirbuf[MAX_LINELEN]; */
497 #define dirbuf (S.exe_n_cwd_tab_completion__dirbuf)
500 path1[0] = (char*)".";
503 /* no dir, if flags==EXE_ONLY - get paths, else "." */
504 npaths = path_parse(&paths, type);
507 /* dirbuf = ".../.../.../" */
508 safe_strncpy(dirbuf, command, (pfind - command) + 2);
509 #if ENABLE_FEATURE_USERNAME_COMPLETION
510 if (dirbuf[0] == '~') /* ~/... or ~user/... */
511 username_tab_completion(dirbuf, dirbuf);
514 /* point to 'l' in "..../last_component" */
518 for (i = 0; i < npaths; i++) {
519 dir = opendir(paths[i]);
521 continue; /* don't print an error */
523 while ((next = readdir(dir)) != NULL) {
525 const char *str_found = next->d_name;
528 if (strncmp(str_found, pfind, strlen(pfind)))
530 /* not see .name without .match */
531 if (*str_found == '.' && *pfind == '\0') {
532 if (NOT_LONE_CHAR(paths[i], '/') || str_found[1])
534 str_found = ""; /* only "/" */
536 found = concat_path_file(paths[i], str_found);
537 /* hmm, remove in progress? */
538 /* NB: stat() first so that we see is it a directory;
539 * but if that fails, use lstat() so that
540 * we still match dangling links */
541 if (stat(found, &st) && lstat(found, &st))
543 /* find with dirs? */
544 if (paths[i] != dirbuf)
545 strcpy(found, next->d_name); /* only name */
547 len1 = strlen(found);
548 found = xrealloc(found, len1 + 2);
550 found[len1+1] = '\0';
552 if (S_ISDIR(st.st_mode)) {
553 /* name is a directory */
554 if (found[len1-1] != '/') {
558 /* not put found file if search only dirs for cd */
559 if (type == FIND_DIR_ONLY)
562 /* Add it to the list */
570 if (paths != path1) {
571 free(paths[0]); /* allocated memory is only in first member */
577 #define QUOT (UCHAR_MAX+1)
579 #define collapse_pos(is, in) do { \
580 memmove(int_buf+(is), int_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
581 memmove(pos_buf+(is), pos_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \
584 static int find_match(char *matchBuf, int *len_with_quotes)
589 /* int16_t int_buf[MAX_LINELEN + 1]; */
590 /* int16_t pos_buf[MAX_LINELEN + 1]; */
591 #define int_buf (S.find_match__int_buf)
592 #define pos_buf (S.find_match__pos_buf)
594 /* set to integer dimension characters and own positions */
596 int_buf[i] = (unsigned char)matchBuf[i];
597 if (int_buf[i] == 0) {
598 pos_buf[i] = -1; /* indicator end line */
604 /* mask \+symbol and convert '\t' to ' ' */
605 for (i = j = 0; matchBuf[i]; i++, j++)
606 if (matchBuf[i] == '\\') {
607 collapse_pos(j, j + 1);
610 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
611 if (matchBuf[i] == '\t') /* algorithm equivalent */
612 int_buf[j] = ' ' | QUOT;
615 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
616 else if (matchBuf[i] == '\t')
620 /* mask "symbols" or 'symbols' */
622 for (i = 0; int_buf[i]; i++) {
624 if (c == '\'' || c == '"') {
633 } else if (c2 != 0 && c != '$')
637 /* skip commands with arguments if line has commands delimiters */
638 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
639 for (i = 0; int_buf[i]; i++) {
642 j = i ? int_buf[i - 1] : -1;
644 if (c == ';' || c == '&' || c == '|') {
645 command_mode = 1 + (c == c2);
647 if (j == '>' || j == '<')
649 } else if (c == '|' && j == '>')
653 collapse_pos(0, i + command_mode);
654 i = -1; /* hack incremet */
657 /* collapse `command...` */
658 for (i = 0; int_buf[i]; i++)
659 if (int_buf[i] == '`') {
660 for (j = i + 1; int_buf[j]; j++)
661 if (int_buf[j] == '`') {
662 collapse_pos(i, j + 1);
667 /* not found close ` - command mode, collapse all previous */
668 collapse_pos(0, i + 1);
671 i--; /* hack incremet */
674 /* collapse (command...(command...)...) or {command...{command...}...} */
675 c = 0; /* "recursive" level */
677 for (i = 0; int_buf[i]; i++)
678 if (int_buf[i] == '(' || int_buf[i] == '{') {
679 if (int_buf[i] == '(')
683 collapse_pos(0, i + 1);
684 i = -1; /* hack incremet */
686 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
687 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
688 if (int_buf[i] == ')')
692 collapse_pos(0, i + 1);
693 i = -1; /* hack incremet */
696 /* skip first not quote space */
697 for (i = 0; int_buf[i]; i++)
698 if (int_buf[i] != ' ')
703 /* set find mode for completion */
704 command_mode = FIND_EXE_ONLY;
705 for (i = 0; int_buf[i]; i++)
706 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
707 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
708 && matchBuf[pos_buf[0]] == 'c'
709 && matchBuf[pos_buf[1]] == 'd'
711 command_mode = FIND_DIR_ONLY;
713 command_mode = FIND_FILE_ONLY;
717 for (i = 0; int_buf[i]; i++)
720 for (--i; i >= 0; i--) {
722 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
723 collapse_pos(0, i + 1);
727 /* skip first not quoted '\'' or '"' */
728 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++)
730 /* collapse quote or unquote // or /~ */
731 while ((int_buf[i] & ~QUOT) == '/'
732 && ((int_buf[i+1] & ~QUOT) == '/' || (int_buf[i+1] & ~QUOT) == '~')
737 /* set only match and destroy quotes */
739 for (c = 0; pos_buf[i] >= 0; i++) {
740 matchBuf[c++] = matchBuf[pos_buf[i]];
744 /* old length matchBuf with quotes symbols */
745 *len_with_quotes = j ? j - pos_buf[0] : 0;
753 * display by column (original idea from ls applet,
754 * very optimized by me :)
756 static void showfiles(void)
759 int column_width = 0;
760 int nfiles = num_matches;
764 /* find the longest file name- use that as the column width */
765 for (row = 0; row < nrows; row++) {
766 l = strlen(matches[row]);
767 if (column_width < l)
770 column_width += 2; /* min space for columns */
771 ncols = cmdedit_termw / column_width;
776 nrows++; /* round up fractionals */
780 for (row = 0; row < nrows; row++) {
784 for (nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
785 printf("%s%-*s", matches[n],
786 (int)(column_width - strlen(matches[n])), "");
792 static char *add_quote_for_spec_chars(char *found)
795 char *s = xmalloc((strlen(found) + 1) * 2);
798 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
806 /* Do TAB completion */
807 static void input_tab(smallint *lastWasTab)
809 if (!(state->flags & TAB_COMPLETION))
815 /* char matchBuf[MAX_LINELEN]; */
816 #define matchBuf (S.input_tab__matchBuf)
820 *lastWasTab = TRUE; /* flop trigger */
822 /* Make a local copy of the string -- up
823 * to the position of the cursor */
824 tmp = strncpy(matchBuf, command_ps, cursor);
827 find_type = find_match(matchBuf, &recalc_pos);
829 /* Free up any memory already allocated */
830 free_tab_completion_data();
832 #if ENABLE_FEATURE_USERNAME_COMPLETION
833 /* If the word starts with `~' and there is no slash in the word,
834 * then try completing this word as a username. */
835 if (state->flags & USERNAME_COMPLETION)
836 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
837 username_tab_completion(matchBuf, NULL);
839 /* Try to match any executable in our path and everything
840 * in the current working directory */
842 exe_n_cwd_tab_completion(matchBuf, find_type);
843 /* Sort, then remove any duplicates found */
846 qsort_string_vector(matches, num_matches);
847 for (i = 0; i < num_matches - 1; ++i) {
848 if (matches[i] && matches[i+1]) { /* paranoia */
849 if (strcmp(matches[i], matches[i+1]) == 0) {
851 matches[i] = NULL; /* paranoia */
853 matches[n++] = matches[i];
857 matches[n] = matches[i];
860 /* Did we find exactly one match? */
861 if (!matches || num_matches > 1) {
864 return; /* not found */
865 /* find minimal match */
866 tmp1 = xstrdup(matches[0]);
867 for (tmp = tmp1; *tmp; tmp++)
868 for (len_found = 1; len_found < num_matches; len_found++)
869 if (matches[len_found][(tmp - tmp1)] != *tmp) {
873 if (*tmp1 == '\0') { /* have unique */
877 tmp = add_quote_for_spec_chars(tmp1);
879 } else { /* one match */
880 tmp = add_quote_for_spec_chars(matches[0]);
881 /* for next completion current found */
884 len_found = strlen(tmp);
885 if (tmp[len_found-1] != '/') {
886 tmp[len_found] = ' ';
887 tmp[len_found+1] = '\0';
890 len_found = strlen(tmp);
891 /* have space to placed match? */
892 if ((len_found - strlen(matchBuf) + command_len) < MAX_LINELEN) {
893 /* before word for match */
894 command_ps[cursor - recalc_pos] = '\0';
896 strcpy(matchBuf, command_ps + cursor);
898 strcat(command_ps, tmp);
900 strcat(command_ps, matchBuf);
901 /* back to begin word for match */
902 input_backward(recalc_pos);
904 recalc_pos = cursor + len_found;
906 command_len = strlen(command_ps);
907 /* write out the matched command */
908 redraw(cmdedit_y, command_len - recalc_pos);
913 /* Ok -- the last char was a TAB. Since they
914 * just hit TAB again, print a list of all the
915 * available choices... */
916 if (matches && num_matches > 0) {
917 int sav_cursor = cursor; /* change goto_new_line() */
919 /* Go to the next line */
922 redraw(0, command_len - sav_cursor);
927 #endif /* FEATURE_COMMAND_TAB_COMPLETION */
932 /* state->flags is already checked to be nonzero */
933 static void get_previous_history(void)
935 if (command_ps[0] != '\0' || state->history[state->cur_history] == NULL) {
936 free(state->history[state->cur_history]);
937 state->history[state->cur_history] = xstrdup(command_ps);
939 state->cur_history--;
942 static int get_next_history(void)
944 if (state->flags & DO_HISTORY) {
945 int ch = state->cur_history;
946 if (ch < state->cnt_history) {
947 get_previous_history(); /* save the current history line */
948 state->cur_history = ch + 1;
949 return state->cur_history;
956 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
957 /* state->flags is already checked to be nonzero */
958 static void load_history(const char *fromfile)
963 /* NB: do not trash old history if file can't be opened */
965 fp = fopen(fromfile, "r");
967 /* clean up old history */
968 for (hi = state->cnt_history; hi > 0;) {
970 free(state->history[hi]);
973 for (hi = 0; hi < MAX_HISTORY;) {
974 char *hl = xmalloc_fgetline(fp);
980 if (l >= MAX_LINELEN)
981 hl[MAX_LINELEN-1] = '\0';
982 if (l == 0 || hl[0] == ' ') {
986 state->history[hi++] = hl;
989 state->cur_history = state->cnt_history = hi;
993 /* state->flags is already checked to be nonzero */
994 static void save_history(const char *tofile)
998 fp = fopen(tofile, "w");
1002 for (i = 0; i < state->cnt_history; i++) {
1003 fprintf(fp, "%s\n", state->history[i]);
1009 #define load_history(a) ((void)0)
1010 #define save_history(a) ((void)0)
1011 #endif /* FEATURE_COMMAND_SAVEHISTORY */
1013 static void remember_in_history(const char *str)
1017 if (!(state->flags & DO_HISTORY))
1020 i = state->cnt_history;
1021 free(state->history[MAX_HISTORY]);
1022 state->history[MAX_HISTORY] = NULL;
1023 /* After max history, remove the oldest command */
1024 if (i >= MAX_HISTORY) {
1025 free(state->history[0]);
1026 for (i = 0; i < MAX_HISTORY-1; i++)
1027 state->history[i] = state->history[i+1];
1029 // Maybe "if (!i || strcmp(history[i-1], command) != 0) ..."
1030 // (i.e. do not save dups?)
1031 state->history[i++] = xstrdup(str);
1032 state->cur_history = i;
1033 state->cnt_history = i;
1034 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1035 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1036 save_history(state->hist_file);
1038 USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;)
1041 #else /* MAX_HISTORY == 0 */
1042 #define remember_in_history(a) ((void)0)
1043 #endif /* MAX_HISTORY */
1047 * This function is used to grab a character buffer
1048 * from the input file descriptor and allows you to
1049 * a string with full command editing (sort of like
1052 * The following standard commands are not implemented:
1053 * ESC-b -- Move back one word
1054 * ESC-f -- Move forward one word
1055 * ESC-d -- Delete back one word
1056 * ESC-h -- Delete forward one word
1057 * CTL-t -- Transpose two characters
1059 * Minimalist vi-style command line editing available if configured.
1060 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
1063 #if ENABLE_FEATURE_EDITING_VI
1065 vi_Word_motion(char *command, int eat)
1067 while (cursor < command_len && !isspace(command[cursor]))
1069 if (eat) while (cursor < command_len && isspace(command[cursor]))
1074 vi_word_motion(char *command, int eat)
1076 if (isalnum(command[cursor]) || command[cursor] == '_') {
1077 while (cursor < command_len
1078 && (isalnum(command[cursor+1]) || command[cursor+1] == '_'))
1080 } else if (ispunct(command[cursor])) {
1081 while (cursor < command_len && ispunct(command[cursor+1]))
1085 if (cursor < command_len)
1088 if (eat && cursor < command_len && isspace(command[cursor]))
1089 while (cursor < command_len && isspace(command[cursor]))
1094 vi_End_motion(char *command)
1097 while (cursor < command_len && isspace(command[cursor]))
1099 while (cursor < command_len-1 && !isspace(command[cursor+1]))
1104 vi_end_motion(char *command)
1106 if (cursor >= command_len-1)
1109 while (cursor < command_len-1 && isspace(command[cursor]))
1111 if (cursor >= command_len-1)
1113 if (isalnum(command[cursor]) || command[cursor] == '_') {
1114 while (cursor < command_len-1
1115 && (isalnum(command[cursor+1]) || command[cursor+1] == '_')
1119 } else if (ispunct(command[cursor])) {
1120 while (cursor < command_len-1 && ispunct(command[cursor+1]))
1126 vi_Back_motion(char *command)
1128 while (cursor > 0 && isspace(command[cursor-1]))
1130 while (cursor > 0 && !isspace(command[cursor-1]))
1135 vi_back_motion(char *command)
1140 while (cursor > 0 && isspace(command[cursor]))
1144 if (isalnum(command[cursor]) || command[cursor] == '_') {
1146 && (isalnum(command[cursor-1]) || command[cursor-1] == '_')
1150 } else if (ispunct(command[cursor])) {
1151 while (cursor > 0 && ispunct(command[cursor-1]))
1159 * read_line_input and its helpers
1162 #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
1163 static void parse_and_put_prompt(const char *prmt_ptr)
1165 cmdedit_prompt = prmt_ptr;
1166 cmdedit_prmt_len = strlen(prmt_ptr);
1170 static void parse_and_put_prompt(const char *prmt_ptr)
1173 size_t cur_prmt_len = 0;
1174 char flg_not_length = '[';
1175 char *prmt_mem_ptr = xzalloc(1);
1176 char *cwd_buf = xrealloc_getcwd_or_warn(NULL);
1181 cmdedit_prmt_len = 0;
1184 cwd_buf = (char *)bb_msg_unknown;
1187 cbuf[1] = '\0'; /* never changes */
1190 char *free_me = NULL;
1195 const char *cp = prmt_ptr;
1198 c = bb_process_escape_sequence(&prmt_ptr);
1199 if (prmt_ptr == cp) {
1205 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1207 pbuf = user_buf ? user_buf : (char*)"";
1211 pbuf = free_me = safe_gethostname();
1212 *strchrnul(pbuf, '.') = '\0';
1215 c = (geteuid() == 0 ? '#' : '$');
1217 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1219 /* /home/user[/something] -> ~[/something] */
1221 l = strlen(home_pwd_buf);
1223 && strncmp(home_pwd_buf, cwd_buf, l) == 0
1224 && (cwd_buf[l]=='/' || cwd_buf[l]=='\0')
1225 && strlen(cwd_buf + l) < PATH_MAX
1227 pbuf = free_me = xasprintf("~%s", cwd_buf + l);
1233 cp = strrchr(pbuf, '/');
1234 if (cp != NULL && cp != pbuf)
1235 pbuf += (cp-pbuf) + 1;
1238 pbuf = free_me = xasprintf("%d", num_ok_lines);
1240 case 'e': case 'E': /* \e \E = \033 */
1243 case 'x': case 'X': {
1245 for (l = 0; l < 3;) {
1247 buf2[l++] = *prmt_ptr;
1249 h = strtoul(buf2, &pbuf, 16);
1250 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
1256 c = (char)strtoul(buf2, NULL, 16);
1263 if (c == flg_not_length) {
1264 flg_not_length = (flg_not_length == '[' ? ']' : '[');
1272 cur_prmt_len = strlen(pbuf);
1273 prmt_len += cur_prmt_len;
1274 if (flg_not_length != ']')
1275 cmdedit_prmt_len += cur_prmt_len;
1276 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
1280 if (cwd_buf != (char *)bb_msg_unknown)
1282 cmdedit_prompt = prmt_mem_ptr;
1287 static void cmdedit_setwidth(unsigned w, int redraw_flg)
1291 /* new y for current cursor */
1292 int new_y = (cursor + cmdedit_prmt_len) / w;
1294 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), command_len - cursor);
1299 static void win_changed(int nsig)
1302 get_terminal_width_height(0, &width, NULL);
1303 cmdedit_setwidth(width, nsig /* - just a yes/no flag */);
1304 if (nsig == SIGWINCH)
1305 signal(SIGWINCH, win_changed); /* rearm ourself */
1309 * The emacs and vi modes share much of the code in the big
1310 * command loop. Commands entered when in vi's command mode (aka
1311 * "escape mode") get an extra bit added to distinguish them --
1312 * this keeps them from being self-inserted. This clutters the
1313 * big switch a bit, but keeps all the code in one place.
1318 /* leave out the "vi-mode"-only case labels if vi editing isn't
1320 #define vi_case(caselabel) USE_FEATURE_EDITING(case caselabel)
1322 /* convert uppercase ascii to equivalent control char, for readability */
1324 #define CTRL(a) ((a) & ~0x40)
1327 * -1 on read errors or EOF, or on bare Ctrl-D,
1328 * 0 on ctrl-C (the line entered is still returned in 'command'),
1329 * >0 length of input string, including terminating '\n'
1331 int read_line_input(const char *prompt, char *command, int maxsize, line_input_t *st)
1333 #if ENABLE_FEATURE_TAB_COMPLETION
1334 smallint lastWasTab = FALSE;
1338 smallint break_out = 0;
1339 #if ENABLE_FEATURE_EDITING_VI
1340 smallint vi_cmdmode = 0;
1343 struct termios initial_settings;
1344 struct termios new_settings;
1348 if (tcgetattr(STDIN_FILENO, &initial_settings) < 0
1349 || !(initial_settings.c_lflag & ECHO)
1351 /* Happens when e.g. stty -echo was run before */
1353 parse_and_put_prompt(prompt);
1355 if (fgets(command, maxsize, stdin) == NULL)
1356 len = -1; /* EOF or error */
1358 len = strlen(command);
1363 // FIXME: audit & improve this
1364 if (maxsize > MAX_LINELEN)
1365 maxsize = MAX_LINELEN;
1367 /* With null flags, no other fields are ever used */
1368 state = st ? st : (line_input_t*) &const_int_0;
1369 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1370 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1371 load_history(state->hist_file);
1374 /* prepare before init handlers */
1375 cmdedit_y = 0; /* quasireal y, not true if line > xt*yt */
1377 command_ps = command;
1380 new_settings = initial_settings;
1381 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1382 /* Turn off echoing and CTRL-C, so we can trap it */
1383 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1384 /* Hmm, in linux c_cc[] is not parsed if ICANON is off */
1385 new_settings.c_cc[VMIN] = 1;
1386 new_settings.c_cc[VTIME] = 0;
1387 /* Turn off CTRL-C, so we can trap it */
1388 #ifndef _POSIX_VDISABLE
1389 #define _POSIX_VDISABLE '\0'
1391 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1392 tcsetattr(STDIN_FILENO, TCSANOW, &new_settings);
1394 /* Now initialize things */
1395 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
1396 win_changed(0); /* do initial resizing */
1397 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1399 struct passwd *entry;
1401 entry = getpwuid(geteuid());
1403 user_buf = xstrdup(entry->pw_name);
1404 home_pwd_buf = xstrdup(entry->pw_dir);
1408 /* Print out the command prompt */
1409 parse_and_put_prompt(prompt);
1414 if (nonblock_safe_read(STDIN_FILENO, &c, 1) < 1) {
1415 /* if we can't read input then exit */
1416 goto prepare_to_die;
1421 #if ENABLE_FEATURE_EDITING_VI
1437 /* Control-a -- Beginning of line */
1438 input_backward(cursor);
1443 vi_case('\x7f'|vbit:) /* DEL */
1444 /* Control-b -- Move back one character */
1448 vi_case(CTRL('C')|vbit:)
1449 /* Control-c -- stop gathering input */
1452 break_out = -1; /* "do not append '\n'" */
1455 /* Control-d -- Delete one character, or exit
1456 * if the len=0 and no chars to delete */
1457 if (command_len == 0) {
1460 /* to control stopped jobs */
1461 break_out = command_len = -1;
1469 /* Control-e -- End of line */
1475 /* 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);
1492 /* Control-k -- clear to end of line */
1493 command[cursor] = 0;
1494 command_len = cursor;
1498 vi_case(CTRL('L')|vbit:)
1499 /* Control-l -- clear screen */
1501 redraw(0, command_len - cursor);
1506 vi_case(CTRL('N')|vbit:)
1508 /* Control-n -- Get next command in history */
1509 if (get_next_history())
1513 vi_case(CTRL('P')|vbit:)
1515 /* Control-p -- Get previous command from history */
1516 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1517 get_previous_history();
1525 vi_case(CTRL('U')|vbit:)
1526 /* Control-U -- Clear line before cursor */
1528 strcpy(command, command + cursor);
1529 command_len -= cursor;
1530 redraw(cmdedit_y, command_len);
1534 vi_case(CTRL('W')|vbit:)
1535 /* Control-W -- Remove the last word */
1536 while (cursor > 0 && isspace(command[cursor-1]))
1538 while (cursor > 0 && !isspace(command[cursor-1]))
1542 #if ENABLE_FEATURE_EDITING_VI
1547 input_backward(cursor);
1568 vi_Word_motion(command, 1);
1571 vi_word_motion(command, 1);
1574 vi_End_motion(command);
1577 vi_end_motion(command);
1580 vi_Back_motion(command);
1583 vi_back_motion(command);
1598 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1599 goto prepare_to_die;
1600 if (c == (prevc & 0xff)) {
1602 input_backward(cursor);
1612 case 'w': /* "dw", "cw" */
1613 vi_word_motion(command, vi_cmdmode);
1615 case 'W': /* 'dW', 'cW' */
1616 vi_Word_motion(command, vi_cmdmode);
1618 case 'e': /* 'de', 'ce' */
1619 vi_end_motion(command);
1622 case 'E': /* 'dE', 'cE' */
1623 vi_End_motion(command);
1628 input_backward(cursor - sc);
1629 while (nc-- > cursor)
1632 case 'b': /* "db", "cb" */
1633 case 'B': /* implemented as B */
1635 vi_back_motion(command);
1637 vi_Back_motion(command);
1638 while (sc-- > cursor)
1641 case ' ': /* "d ", "c " */
1644 case '$': /* "d$", "c$" */
1646 while (cursor < command_len)
1659 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1660 goto prepare_to_die;
1664 *(command + cursor) = c;
1669 #endif /* FEATURE_COMMAND_EDITING_VI */
1671 case '\x1b': /* ESC */
1673 #if ENABLE_FEATURE_EDITING_VI
1674 if (state->flags & VI_MODE) {
1675 /* ESC: insert mode --> command mode */
1681 /* escape sequence follows */
1682 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1683 goto prepare_to_die;
1684 /* different vt100 emulations */
1685 if (c == '[' || c == 'O') {
1688 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1689 goto prepare_to_die;
1691 if (c >= '1' && c <= '9') {
1692 unsigned char dummy;
1694 if (safe_read(STDIN_FILENO, &dummy, 1) < 1)
1695 goto prepare_to_die;
1701 #if ENABLE_FEATURE_TAB_COMPLETION
1702 case '\t': /* Alt-Tab */
1703 input_tab(&lastWasTab);
1708 /* Up Arrow -- Get previous command from history */
1709 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1710 get_previous_history();
1716 /* Down Arrow -- Get next command in history */
1717 if (!get_next_history())
1720 /* Rewrite the line with the selected history item */
1721 /* change command */
1722 command_len = strlen(strcpy(command, state->history[state->cur_history]));
1723 /* redraw and go to eol (bol, in vi */
1724 redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
1728 /* Right Arrow -- Move forward one character */
1732 /* Left Arrow -- Move back one character */
1739 case '1': // vt100? linux vt? or what?
1740 case '7': // vt100? linux vt? or what?
1741 case 'H': /* xterm's <Home> */
1742 input_backward(cursor);
1744 case '4': // vt100? linux vt? or what?
1745 case '8': // vt100? linux vt? or what?
1746 case 'F': /* xterm's <End> */
1755 default: /* If it's regular input, do the normal thing */
1757 /* Control-V -- force insert of next char */
1758 if (c == CTRL('V')) {
1759 if (safe_read(STDIN_FILENO, &c, 1) < 1)
1760 goto prepare_to_die;
1767 #if ENABLE_FEATURE_EDITING_VI
1768 if (vi_cmdmode) /* Don't self-insert */
1771 if (command_len >= (maxsize - 2)) /* Need to leave space for enter */
1775 if (cursor == (command_len - 1)) { /* Append if at the end of the line */
1776 command[cursor] = c;
1777 command[cursor+1] = '\0';
1778 cmdedit_set_out_char(' ');
1779 } else { /* Insert otherwise */
1782 memmove(command + sc + 1, command + sc, command_len - sc);
1785 /* rewrite from cursor */
1787 /* to prev x pos + 1 */
1788 input_backward(cursor - sc);
1792 if (break_out) /* Enter is the command terminator, no more input. */
1795 #if ENABLE_FEATURE_TAB_COMPLETION
1801 if (command_len > 0)
1802 remember_in_history(command);
1804 if (break_out > 0) {
1805 command[command_len++] = '\n';
1806 command[command_len] = '\0';
1809 #if ENABLE_FEATURE_TAB_COMPLETION
1810 free_tab_completion_data();
1813 /* restore initial_settings */
1814 tcsetattr(STDIN_FILENO, TCSANOW, &initial_settings);
1815 /* restore SIGWINCH handler */
1816 signal(SIGWINCH, previous_SIGWINCH_handler);
1824 line_input_t *new_line_input_t(int flags)
1826 line_input_t *n = xzalloc(sizeof(*n));
1833 #undef read_line_input
1834 int read_line_input(const char* prompt, char* command, int maxsize)
1836 fputs(prompt, stdout);
1838 fgets(command, maxsize, stdin);
1839 return strlen(command);
1842 #endif /* FEATURE_COMMAND_EDITING */
1853 const char *applet_name = "debug stuff usage";
1855 int main(int argc, char **argv)
1857 char buff[MAX_LINELEN];
1859 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1860 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:"
1861 "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] "
1862 "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1867 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1868 setlocale(LC_ALL, "");
1872 l = read_line_input(prompt, buff);
1873 if (l <= 0 || buff[l-1] != '\n')
1876 printf("*** read_line_input() returned line =%s=\n", buff);
1878 printf("*** read_line_input() detect ^D\n");