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
44 #define ENABLE_FEATURE_CLEAN_UP 0
49 /* Entire file (except TESTing part) sits inside this #if */
50 #if ENABLE_FEATURE_EDITING
52 #if ENABLE_LOCALE_SUPPORT
53 #define Isprint(c) isprint(c)
55 #define Isprint(c) ((c) >= ' ' && (c) != ((unsigned char)'\233'))
58 #define ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR \
59 (ENABLE_FEATURE_USERNAME_COMPLETION || ENABLE_FEATURE_EDITING_FANCY_PROMPT)
61 enum { MAX_LINELEN = CONFIG_FEATURE_EDITING_MAX_LEN };
63 static line_input_t *state;
65 static struct termios initial_settings, new_settings;
67 static volatile unsigned cmdedit_termw = 80; /* actual terminal width */
69 static int cmdedit_x; /* real x terminal position */
70 static int cmdedit_y; /* pseudoreal y terminal position */
71 static int cmdedit_prmt_len; /* length of prompt (without colors etc) */
73 static unsigned cursor;
74 static unsigned command_len;
75 static char *command_ps;
76 static const char *cmdedit_prompt;
78 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
79 static char *hostname_buf;
80 static int num_ok_lines = 1;
83 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
84 static const char null_str[] = "";
85 static char *user_buf;
86 static char *home_pwd_buf = (char*)null_str;
89 /* Put 'command_ps[cursor]', cursor++.
90 * Advance cursor on screen. If we reached right margin, scroll text up
91 * and remove terminal margin effect by printing 'next_char' */
92 static void cmdedit_set_out_char(int next_char)
94 int c = (unsigned char)command_ps[cursor];
97 /* erase character after end of input string */
100 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
101 /* Display non-printable characters in reverse */
109 printf("\033[7m%c\033[0m", c);
113 if (initial_settings.c_lflag & ECHO)
116 if (++cmdedit_x >= cmdedit_termw) {
117 /* terminal is scrolled down */
120 /* destroy "(auto)margin" */
121 bb_putchar(next_char);
124 // Huh? What if command_ps[cursor] == '\0' (we are at the end already?)
128 /* Move to end of line (by printing all chars till the end) */
129 static void input_end(void)
131 while (cursor < command_len)
132 cmdedit_set_out_char(' ');
135 /* Go to the next line */
136 static void goto_new_line(void)
144 static void out1str(const char *s)
150 static void beep(void)
155 /* Move back one character */
156 /* (optimized for slow terminals) */
157 static void input_backward(unsigned num)
167 if (cmdedit_x >= num) {
170 printf("\b\b\b\b" + (4-num));
173 printf("\033[%uD", num);
177 /* Need to go one or more lines up */
179 count_y = 1 + (num / cmdedit_termw);
180 cmdedit_y -= count_y;
181 cmdedit_x = cmdedit_termw * count_y - num;
182 /* go to 1st column; go up; go to correct column */
183 printf("\r" "\033[%dA" "\033[%dC", count_y, cmdedit_x);
186 static void put_prompt(void)
188 out1str(cmdedit_prompt);
189 cmdedit_x = cmdedit_prmt_len;
191 // Huh? what if cmdedit_prmt_len >= width?
192 cmdedit_y = 0; /* new quasireal y */
195 /* draw prompt, editor line, and clear tail */
196 static void redraw(int y, int back_cursor)
198 if (y > 0) /* up to start y */
199 printf("\033[%dA", y);
202 input_end(); /* rewrite */
203 printf("\033[J"); /* erase after cursor */
204 input_backward(back_cursor);
207 #if ENABLE_FEATURE_EDITING_VI
208 #define DELBUFSIZ 128
209 static char *delbuf; /* a (malloced) place to store deleted characters */
211 static char newdelflag; /* whether delbuf should be reused yet */
214 /* Delete the char in front of the cursor, optionally saving it
215 * for later putback */
216 static void input_delete(int save)
220 if (j == command_len)
223 #if ENABLE_FEATURE_EDITING_VI
227 delbuf = malloc(DELBUFSIZ);
228 /* safe if malloc fails */
232 if (delbuf && (delp - delbuf < DELBUFSIZ))
233 *delp++ = command_ps[j];
237 strcpy(command_ps + j, command_ps + j + 1);
239 input_end(); /* rewrite new line */
240 cmdedit_set_out_char(' '); /* erase char */
241 input_backward(cursor - j); /* back to old pos cursor */
244 #if ENABLE_FEATURE_EDITING_VI
245 static void put(void)
248 int j = delp - delbuf;
253 /* open hole and then fill it */
254 memmove(command_ps + cursor + j, command_ps + cursor, command_len - cursor + 1);
255 strncpy(command_ps + cursor, delbuf, j);
257 input_end(); /* rewrite new line */
258 input_backward(cursor - ocursor - j + 1); /* at end of new text */
262 /* Delete the char in back of the cursor */
263 static void input_backspace(void)
271 /* Move forward one character */
272 static void input_forward(void)
274 if (cursor < command_len)
275 cmdedit_set_out_char(command_ps[cursor + 1]);
279 #if ENABLE_FEATURE_TAB_COMPLETION
281 static char **matches;
282 static unsigned num_matches;
284 static void free_tab_completion_data(void)
288 free(matches[--num_matches]);
294 static void add_match(char *matched)
296 int nm = num_matches;
299 matches = xrealloc(matches, nm1 * sizeof(char *));
300 matches[nm] = matched;
304 #if ENABLE_FEATURE_USERNAME_COMPLETION
305 static void username_tab_completion(char *ud, char *with_shash_flg)
307 struct passwd *entry;
310 ud++; /* ~user/... to user/... */
311 userlen = strlen(ud);
313 if (with_shash_flg) { /* "~/..." or "~user/..." */
314 char *sav_ud = ud - 1;
318 if (*ud == '/') { /* "~/..." */
322 temp = strchr(ud, '/');
323 *temp = 0; /* ~user\0 */
324 entry = getpwnam(ud);
325 *temp = '/'; /* restore ~user/... */
328 home = entry->pw_dir;
331 if ((userlen + strlen(home) + 1) < MAX_LINELEN) {
332 char temp2[MAX_LINELEN]; /* argument size */
335 sprintf(temp2, "%s%s", home, ud);
336 strcpy(sav_ud, temp2);
341 /* Using _r function to avoid pulling in static buffers */
344 struct passwd *result;
347 while (!getpwent_r(&pwd, line_buff, sizeof(line_buff), &result)) {
348 /* Null usernames should result in all users as possible completions. */
349 if (/*!userlen || */ strncmp(ud, pwd.pw_name, userlen) == 0) {
350 add_match(xasprintf("~%s/", pwd.pw_name));
356 #endif /* FEATURE_COMMAND_USERNAME_COMPLETION */
364 static int path_parse(char ***p, int flags)
371 /* if not setenv PATH variable, to search cur dir "." */
372 if (flags != FIND_EXE_ONLY)
375 if (state->flags & WITH_PATH_LOOKUP)
376 pth = state->path_lookup;
378 pth = getenv("PATH");
379 /* PATH=<empty> or PATH=:<empty> */
380 if (!pth || !pth[0] || LONE_CHAR(pth, ':'))
384 npth = 1; /* path component count */
386 tmp = strchr(tmp, ':');
390 break; /* :<empty> */
394 res = xmalloc(npth * sizeof(char*));
395 res[0] = tmp = xstrdup(pth);
398 tmp = strchr(tmp, ':');
401 *tmp++ = '\0'; /* ':' -> '\0' */
403 break; /* :<empty> */
410 static void exe_n_cwd_tab_completion(char *command, int type)
414 char dirbuf[MAX_LINELEN];
417 char **paths = path1;
421 char *pfind = strrchr(command, '/');
424 path1[0] = (char*)".";
427 /* no dir, if flags==EXE_ONLY - get paths, else "." */
428 npaths = path_parse(&paths, type);
431 /* dirbuf = ".../.../.../" */
432 safe_strncpy(dirbuf, command, (pfind - command) + 2);
433 #if ENABLE_FEATURE_USERNAME_COMPLETION
434 if (dirbuf[0] == '~') /* ~/... or ~user/... */
435 username_tab_completion(dirbuf, dirbuf);
438 /* point to 'l' in "..../last_component" */
442 for (i = 0; i < npaths; i++) {
443 dir = opendir(paths[i]);
444 if (!dir) /* Don't print an error */
447 while ((next = readdir(dir)) != NULL) {
449 const char *str_found = next->d_name;
452 if (strncmp(str_found, pfind, strlen(pfind)))
454 /* not see .name without .match */
455 if (*str_found == '.' && *pfind == 0) {
456 if (NOT_LONE_CHAR(paths[i], '/') || str_found[1])
458 str_found = ""; /* only "/" */
460 found = concat_path_file(paths[i], str_found);
461 /* hmm, remover in progress? */
462 if (stat(found, &st) < 0)
464 /* find with dirs? */
465 if (paths[i] != dirbuf)
466 strcpy(found, next->d_name); /* only name */
468 len1 = strlen(found);
469 found = xrealloc(found, len1 + 2);
471 found[len1+1] = '\0';
473 if (S_ISDIR(st.st_mode)) {
474 /* name is directory */
475 if (found[len1-1] != '/') {
479 /* not put found file if search only dirs for cd */
480 if (type == FIND_DIR_ONLY)
483 /* Add it to the list */
491 if (paths != path1) {
492 free(paths[0]); /* allocated memory only in first member */
497 #define QUOT (UCHAR_MAX+1)
499 #define collapse_pos(is, in) { \
500 memmove(int_buf+(is), int_buf+(in), (MAX_LINELEN+1-(is)-(in))*sizeof(int)); \
501 memmove(pos_buf+(is), pos_buf+(in), (MAX_LINELEN+1-(is)-(in))*sizeof(int)); }
503 static int find_match(char *matchBuf, int *len_with_quotes)
508 int int_buf[MAX_LINELEN + 1];
509 int pos_buf[MAX_LINELEN + 1];
511 /* set to integer dimension characters and own positions */
513 int_buf[i] = (unsigned char)matchBuf[i];
514 if (int_buf[i] == 0) {
515 pos_buf[i] = -1; /* indicator end line */
521 /* mask \+symbol and convert '\t' to ' ' */
522 for (i = j = 0; matchBuf[i]; i++, j++)
523 if (matchBuf[i] == '\\') {
524 collapse_pos(j, j + 1);
527 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
528 if (matchBuf[i] == '\t') /* algorithm equivalent */
529 int_buf[j] = ' ' | QUOT;
532 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
533 else if (matchBuf[i] == '\t')
537 /* mask "symbols" or 'symbols' */
539 for (i = 0; int_buf[i]; i++) {
541 if (c == '\'' || c == '"') {
550 } else if (c2 != 0 && c != '$')
554 /* skip commands with arguments if line has commands delimiters */
555 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
556 for (i = 0; int_buf[i]; i++) {
559 j = i ? int_buf[i - 1] : -1;
561 if (c == ';' || c == '&' || c == '|') {
562 command_mode = 1 + (c == c2);
564 if (j == '>' || j == '<')
566 } else if (c == '|' && j == '>')
570 collapse_pos(0, i + command_mode);
571 i = -1; /* hack incremet */
574 /* collapse `command...` */
575 for (i = 0; int_buf[i]; i++)
576 if (int_buf[i] == '`') {
577 for (j = i + 1; int_buf[j]; j++)
578 if (int_buf[j] == '`') {
579 collapse_pos(i, j + 1);
584 /* not found close ` - command mode, collapse all previous */
585 collapse_pos(0, i + 1);
588 i--; /* hack incremet */
591 /* collapse (command...(command...)...) or {command...{command...}...} */
592 c = 0; /* "recursive" level */
594 for (i = 0; int_buf[i]; i++)
595 if (int_buf[i] == '(' || int_buf[i] == '{') {
596 if (int_buf[i] == '(')
600 collapse_pos(0, i + 1);
601 i = -1; /* hack incremet */
603 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
604 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
605 if (int_buf[i] == ')')
609 collapse_pos(0, i + 1);
610 i = -1; /* hack incremet */
613 /* skip first not quote space */
614 for (i = 0; int_buf[i]; i++)
615 if (int_buf[i] != ' ')
620 /* set find mode for completion */
621 command_mode = FIND_EXE_ONLY;
622 for (i = 0; int_buf[i]; i++)
623 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
624 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
625 && matchBuf[pos_buf[0]]=='c'
626 && matchBuf[pos_buf[1]]=='d'
628 command_mode = FIND_DIR_ONLY;
630 command_mode = FIND_FILE_ONLY;
634 for (i = 0; int_buf[i]; i++)
637 for (--i; i >= 0; i--) {
639 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
640 collapse_pos(0, i + 1);
644 /* skip first not quoted '\'' or '"' */
645 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++)
647 /* collapse quote or unquote // or /~ */
648 while ((int_buf[i] & ~QUOT) == '/'
649 && ((int_buf[i+1] & ~QUOT) == '/' || (int_buf[i+1] & ~QUOT) == '~')
654 /* set only match and destroy quotes */
656 for (c = 0; pos_buf[i] >= 0; i++) {
657 matchBuf[c++] = matchBuf[pos_buf[i]];
661 /* old lenght matchBuf with quotes symbols */
662 *len_with_quotes = j ? j - pos_buf[0] : 0;
668 * display by column (original idea from ls applet,
669 * very optimized by me :)
671 static void showfiles(void)
674 int column_width = 0;
675 int nfiles = num_matches;
679 /* find the longest file name- use that as the column width */
680 for (row = 0; row < nrows; row++) {
681 l = strlen(matches[row]);
682 if (column_width < l)
685 column_width += 2; /* min space for columns */
686 ncols = cmdedit_termw / column_width;
691 nrows++; /* round up fractionals */
695 for (row = 0; row < nrows; row++) {
699 for (nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
700 printf("%s%-*s", matches[n],
701 (int)(column_width - strlen(matches[n])), "");
703 printf("%s\n", matches[n]);
707 static char *add_quote_for_spec_chars(char *found)
710 char *s = xmalloc((strlen(found) + 1) * 2);
713 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
721 static int match_compare(const void *a, const void *b)
723 return strcmp(*(char**)a, *(char**)b);
726 /* Do TAB completion */
727 static void input_tab(int *lastWasTab)
729 if (!(state->flags & TAB_COMPLETION))
735 char matchBuf[MAX_LINELEN];
739 *lastWasTab = TRUE; /* flop trigger */
741 /* Make a local copy of the string -- up
742 * to the position of the cursor */
743 tmp = strncpy(matchBuf, command_ps, cursor);
746 find_type = find_match(matchBuf, &recalc_pos);
748 /* Free up any memory already allocated */
749 free_tab_completion_data();
751 #if ENABLE_FEATURE_USERNAME_COMPLETION
752 /* If the word starts with `~' and there is no slash in the word,
753 * then try completing this word as a username. */
754 if (state->flags & USERNAME_COMPLETION)
755 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
756 username_tab_completion(matchBuf, NULL);
758 /* Try to match any executable in our path and everything
759 * in the current working directory */
761 exe_n_cwd_tab_completion(matchBuf, find_type);
762 /* Sort, then remove any duplicates found */
765 qsort(matches, num_matches, sizeof(char*), match_compare);
766 for (i = 0; i < num_matches - 1; ++i) {
767 if (matches[i] && matches[i+1]) { /* paranoia */
768 if (strcmp(matches[i], matches[i+1]) == 0) {
770 matches[i] = NULL; /* paranoia */
772 matches[n++] = matches[i];
776 matches[n] = matches[i];
779 /* Did we find exactly one match? */
780 if (!matches || num_matches > 1) {
783 return; /* not found */
784 /* find minimal match */
785 // ash: yet another failure in trying to achieve "we don't die on OOM"
786 tmp1 = xstrdup(matches[0]);
787 for (tmp = tmp1; *tmp; tmp++)
788 for (len_found = 1; len_found < num_matches; len_found++)
789 if (matches[len_found][(tmp - tmp1)] != *tmp) {
793 if (*tmp1 == '\0') { /* have unique */
797 tmp = add_quote_for_spec_chars(tmp1);
799 } else { /* one match */
800 tmp = add_quote_for_spec_chars(matches[0]);
801 /* for next completion current found */
804 len_found = strlen(tmp);
805 if (tmp[len_found-1] != '/') {
806 tmp[len_found] = ' ';
807 tmp[len_found+1] = '\0';
810 len_found = strlen(tmp);
811 /* have space to placed match? */
812 if ((len_found - strlen(matchBuf) + command_len) < MAX_LINELEN) {
813 /* before word for match */
814 command_ps[cursor - recalc_pos] = 0;
816 strcpy(matchBuf, command_ps + cursor);
818 strcat(command_ps, tmp);
820 strcat(command_ps, matchBuf);
821 /* back to begin word for match */
822 input_backward(recalc_pos);
824 recalc_pos = cursor + len_found;
826 command_len = strlen(command_ps);
827 /* write out the matched command */
828 redraw(cmdedit_y, command_len - recalc_pos);
832 /* Ok -- the last char was a TAB. Since they
833 * just hit TAB again, print a list of all the
834 * available choices... */
835 if (matches && num_matches > 0) {
836 int sav_cursor = cursor; /* change goto_new_line() */
838 /* Go to the next line */
841 redraw(0, command_len - sav_cursor);
847 #define input_tab(a) ((void)0)
848 #endif /* FEATURE_COMMAND_TAB_COMPLETION */
853 /* state->flags is already checked to be nonzero */
854 static void get_previous_history(void)
856 if (command_ps[0] != '\0' || state->history[state->cur_history] == NULL) {
857 free(state->history[state->cur_history]);
858 state->history[state->cur_history] = xstrdup(command_ps);
860 state->cur_history--;
863 static int get_next_history(void)
865 if (state->flags & DO_HISTORY) {
866 int ch = state->cur_history;
867 if (ch < state->cnt_history) {
868 get_previous_history(); /* save the current history line */
869 state->cur_history = ch + 1;
870 return state->cur_history;
877 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
878 /* state->flags is already checked to be nonzero */
879 static void load_history(const char *fromfile)
885 for (hi = state->cnt_history; hi > 0;) {
887 free(state->history[hi]);
890 fp = fopen(fromfile, "r");
892 for (hi = 0; hi < MAX_HISTORY;) {
893 char *hl = xmalloc_getline(fp);
899 if (l >= MAX_LINELEN)
900 hl[MAX_LINELEN-1] = '\0';
901 if (l == 0 || hl[0] == ' ') {
905 state->history[hi++] = hl;
909 state->cur_history = state->cnt_history = hi;
912 /* state->flags is already checked to be nonzero */
913 static void save_history(const char *tofile)
917 fp = fopen(tofile, "w");
921 for (i = 0; i < state->cnt_history; i++) {
922 fprintf(fp, "%s\n", state->history[i]);
928 #define load_history(a) ((void)0)
929 #define save_history(a) ((void)0)
930 #endif /* FEATURE_COMMAND_SAVEHISTORY */
932 static void remember_in_history(const char *str)
936 if (!(state->flags & DO_HISTORY))
939 i = state->cnt_history;
940 free(state->history[MAX_HISTORY]);
941 state->history[MAX_HISTORY] = NULL;
942 /* After max history, remove the oldest command */
943 if (i >= MAX_HISTORY) {
944 free(state->history[0]);
945 for (i = 0; i < MAX_HISTORY-1; i++)
946 state->history[i] = state->history[i+1];
948 // Maybe "if (!i || strcmp(history[i-1], command) != 0) ..."
949 // (i.e. do not save dups?)
950 state->history[i++] = xstrdup(str);
951 state->cur_history = i;
952 state->cnt_history = i;
953 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
954 if ((state->flags & SAVE_HISTORY) && state->hist_file)
955 save_history(state->hist_file);
957 USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;)
960 #else /* MAX_HISTORY == 0 */
961 #define remember_in_history(a) ((void)0)
962 #endif /* MAX_HISTORY */
966 * This function is used to grab a character buffer
967 * from the input file descriptor and allows you to
968 * a string with full command editing (sort of like
971 * The following standard commands are not implemented:
972 * ESC-b -- Move back one word
973 * ESC-f -- Move forward one word
974 * ESC-d -- Delete back one word
975 * ESC-h -- Delete forward one word
976 * CTL-t -- Transpose two characters
978 * Minimalist vi-style command line editing available if configured.
979 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
982 #if ENABLE_FEATURE_EDITING_VI
984 vi_Word_motion(char *command, int eat)
986 while (cursor < command_len && !isspace(command[cursor]))
988 if (eat) while (cursor < command_len && isspace(command[cursor]))
993 vi_word_motion(char *command, int eat)
995 if (isalnum(command[cursor]) || command[cursor] == '_') {
996 while (cursor < command_len
997 && (isalnum(command[cursor+1]) || command[cursor+1] == '_'))
999 } else if (ispunct(command[cursor])) {
1000 while (cursor < command_len && ispunct(command[cursor+1]))
1004 if (cursor < command_len)
1007 if (eat && cursor < command_len && isspace(command[cursor]))
1008 while (cursor < command_len && isspace(command[cursor]))
1013 vi_End_motion(char *command)
1016 while (cursor < command_len && isspace(command[cursor]))
1018 while (cursor < command_len-1 && !isspace(command[cursor+1]))
1023 vi_end_motion(char *command)
1025 if (cursor >= command_len-1)
1028 while (cursor < command_len-1 && isspace(command[cursor]))
1030 if (cursor >= command_len-1)
1032 if (isalnum(command[cursor]) || command[cursor] == '_') {
1033 while (cursor < command_len-1
1034 && (isalnum(command[cursor+1]) || command[cursor+1] == '_')
1038 } else if (ispunct(command[cursor])) {
1039 while (cursor < command_len-1 && ispunct(command[cursor+1]))
1045 vi_Back_motion(char *command)
1047 while (cursor > 0 && isspace(command[cursor-1]))
1049 while (cursor > 0 && !isspace(command[cursor-1]))
1054 vi_back_motion(char *command)
1059 while (cursor > 0 && isspace(command[cursor]))
1063 if (isalnum(command[cursor]) || command[cursor] == '_') {
1065 && (isalnum(command[cursor-1]) || command[cursor-1] == '_')
1069 } else if (ispunct(command[cursor])) {
1070 while (cursor > 0 && ispunct(command[cursor-1]))
1078 * read_line_input and its helpers
1081 #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
1082 static void parse_prompt(const char *prmt_ptr)
1084 cmdedit_prompt = prmt_ptr;
1085 cmdedit_prmt_len = strlen(prmt_ptr);
1089 static void parse_prompt(const char *prmt_ptr)
1092 size_t cur_prmt_len = 0;
1093 char flg_not_length = '[';
1094 char *prmt_mem_ptr = xzalloc(1);
1095 char *pwd_buf = xrealloc_getcwd_or_warn(NULL);
1096 char buf2[PATH_MAX + 1];
1101 cmdedit_prmt_len = 0;
1104 pwd_buf = (char *)bb_msg_unknown;
1112 const char *cp = prmt_ptr;
1115 c = bb_process_escape_sequence(&prmt_ptr);
1116 if (prmt_ptr == cp) {
1121 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1123 pbuf = user_buf ? user_buf : (char*)"";
1127 pbuf = hostname_buf;
1129 pbuf = xzalloc(256);
1130 if (gethostname(pbuf, 255) < 0) {
1133 char *s = strchr(pbuf, '.');
1137 hostname_buf = pbuf;
1141 c = (geteuid() == 0 ? '#' : '$');
1143 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1146 l = strlen(home_pwd_buf);
1148 && strncmp(home_pwd_buf, pbuf, l) == 0
1149 && (pbuf[l]=='/' || pbuf[l]=='\0')
1150 && strlen(pwd_buf+l)<PATH_MAX
1154 strcpy(pbuf+1, pwd_buf+l);
1160 cp = strrchr(pbuf, '/');
1161 if (cp != NULL && cp != pbuf)
1162 pbuf += (cp-pbuf) + 1;
1166 snprintf(buf2, sizeof(buf2), "%d", num_ok_lines);
1168 case 'e': case 'E': /* \e \E = \033 */
1172 for (l = 0; l < 3;) {
1174 buf2[l++] = *prmt_ptr;
1176 h = strtol(buf2, &pbuf, 16);
1177 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
1184 c = (char)strtol(buf2, NULL, 16);
1190 if (c == flg_not_length) {
1191 flg_not_length = flg_not_length == '[' ? ']' : '[';
1200 cur_prmt_len = strlen(pbuf);
1201 prmt_len += cur_prmt_len;
1202 if (flg_not_length != ']')
1203 cmdedit_prmt_len += cur_prmt_len;
1204 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
1206 if (pwd_buf != (char *)bb_msg_unknown)
1208 cmdedit_prompt = prmt_mem_ptr;
1213 #define setTermSettings(fd, argp) tcsetattr(fd, TCSANOW, argp)
1214 #define getTermSettings(fd, argp) tcgetattr(fd, argp);
1216 static sighandler_t previous_SIGWINCH_handler;
1218 static void cmdedit_setwidth(unsigned w, int redraw_flg)
1222 /* new y for current cursor */
1223 int new_y = (cursor + cmdedit_prmt_len) / w;
1225 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), command_len - cursor);
1230 static void win_changed(int nsig)
1233 get_terminal_width_height(0, &width, NULL);
1234 cmdedit_setwidth(width, nsig /* - just a yes/no flag */);
1235 if (nsig == SIGWINCH)
1236 signal(SIGWINCH, win_changed); /* rearm ourself */
1240 * The emacs and vi modes share much of the code in the big
1241 * command loop. Commands entered when in vi's command mode (aka
1242 * "escape mode") get an extra bit added to distinguish them --
1243 * this keeps them from being self-inserted. This clutters the
1244 * big switch a bit, but keeps all the code in one place.
1249 /* leave out the "vi-mode"-only case labels if vi editing isn't
1251 #define vi_case(caselabel) USE_FEATURE_EDITING(case caselabel)
1253 /* convert uppercase ascii to equivalent control char, for readability */
1255 #define CTRL(a) ((a) & ~0x40)
1258 * -1 on read errors or EOF, or on bare Ctrl-D.
1260 * >0 length of input string, including terminating '\n'
1262 int read_line_input(const char* prompt, char* command, int maxsize, line_input_t *st)
1264 int lastWasTab = FALSE;
1267 smallint break_out = 0;
1268 #if ENABLE_FEATURE_EDITING_VI
1269 smallint vi_cmdmode = 0;
1273 // FIXME: audit & improve this
1274 if (maxsize > MAX_LINELEN)
1275 maxsize = MAX_LINELEN;
1277 /* With null flags, no other fields are ever used */
1278 state = st ? st : (line_input_t*) &const_int_0;
1279 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1280 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1281 load_history(state->hist_file);
1284 /* prepare before init handlers */
1285 cmdedit_y = 0; /* quasireal y, not true if line > xt*yt */
1287 command_ps = command;
1290 getTermSettings(0, (void *) &initial_settings);
1291 memcpy(&new_settings, &initial_settings, sizeof(new_settings));
1292 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1293 /* Turn off echoing and CTRL-C, so we can trap it */
1294 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1295 /* Hmm, in linux c_cc[] is not parsed if ICANON is off */
1296 new_settings.c_cc[VMIN] = 1;
1297 new_settings.c_cc[VTIME] = 0;
1298 /* Turn off CTRL-C, so we can trap it */
1299 #ifndef _POSIX_VDISABLE
1300 #define _POSIX_VDISABLE '\0'
1302 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1303 setTermSettings(0, (void *) &new_settings);
1305 /* Now initialize things */
1306 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
1307 win_changed(0); /* do initial resizing */
1308 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1310 struct passwd *entry;
1312 entry = getpwuid(geteuid());
1314 /* If we enter read_line_input for the Nth time,
1315 * they may be already allocated! Need to free. */
1317 if (home_pwd_buf != null_str)
1319 user_buf = xstrdup(entry->pw_name);
1320 home_pwd_buf = xstrdup(entry->pw_dir);
1321 /* They are not freed on exit (too small to bother) */
1325 /* Print out the command prompt */
1326 parse_prompt(prompt);
1331 if (safe_read(0, &c, 1) < 1) {
1332 /* if we can't read input then exit */
1333 goto prepare_to_die;
1338 #if ENABLE_FEATURE_EDITING_VI
1352 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1355 /* Control-a -- Beginning of line */
1356 input_backward(cursor);
1361 vi_case('\x7f'|vbit:) /* DEL */
1362 /* Control-b -- Move back one character */
1367 vi_case(CTRL('C')|vbit:)
1368 /* Control-c -- stop gathering input */
1371 break_out = -1; /* "do not append '\n'" */
1374 /* Control-d -- Delete one character, or exit
1375 * if the len=0 and no chars to delete */
1376 if (command_len == 0) {
1379 /* to control stopped jobs */
1380 break_out = command_len = -1;
1386 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1389 /* Control-e -- End of line */
1395 /* Control-f -- Move forward one character */
1401 case '\x7f': /* DEL */
1402 /* Control-h and DEL */
1407 input_tab(&lastWasTab);
1410 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1412 /* Control-k -- clear to end of line */
1413 command[cursor] = 0;
1414 command_len = cursor;
1418 vi_case(CTRL('L')|vbit:)
1419 /* Control-l -- clear screen */
1421 redraw(0, command_len - cursor);
1427 vi_case(CTRL('N')|vbit:)
1429 /* Control-n -- Get next command in history */
1430 if (get_next_history())
1434 vi_case(CTRL('P')|vbit:)
1436 /* Control-p -- Get previous command from history */
1437 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1438 get_previous_history();
1445 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1447 vi_case(CTRL('U')|vbit:)
1448 /* Control-U -- Clear line before cursor */
1450 strcpy(command, command + cursor);
1451 command_len -= cursor;
1452 redraw(cmdedit_y, command_len);
1457 vi_case(CTRL('W')|vbit:)
1458 /* Control-W -- Remove the last word */
1459 while (cursor > 0 && isspace(command[cursor-1]))
1461 while (cursor > 0 && !isspace(command[cursor-1]))
1465 #if ENABLE_FEATURE_EDITING_VI
1470 input_backward(cursor);
1491 vi_Word_motion(command, 1);
1494 vi_word_motion(command, 1);
1497 vi_End_motion(command);
1500 vi_end_motion(command);
1503 vi_Back_motion(command);
1506 vi_back_motion(command);
1521 if (safe_read(0, &c, 1) < 1)
1522 goto prepare_to_die;
1523 if (c == (prevc & 0xff)) {
1525 input_backward(cursor);
1535 case 'w': /* "dw", "cw" */
1536 vi_word_motion(command, vi_cmdmode);
1538 case 'W': /* 'dW', 'cW' */
1539 vi_Word_motion(command, vi_cmdmode);
1541 case 'e': /* 'de', 'ce' */
1542 vi_end_motion(command);
1545 case 'E': /* 'dE', 'cE' */
1546 vi_End_motion(command);
1551 input_backward(cursor - sc);
1552 while (nc-- > cursor)
1555 case 'b': /* "db", "cb" */
1556 case 'B': /* implemented as B */
1558 vi_back_motion(command);
1560 vi_Back_motion(command);
1561 while (sc-- > cursor)
1564 case ' ': /* "d ", "c " */
1567 case '$': /* "d$", "c$" */
1569 while (cursor < command_len)
1582 if (safe_read(0, &c, 1) < 1)
1583 goto prepare_to_die;
1587 *(command + cursor) = c;
1592 #endif /* FEATURE_COMMAND_EDITING_VI */
1594 case '\x1b': /* ESC */
1596 #if ENABLE_FEATURE_EDITING_VI
1597 if (state->flags & VI_MODE) {
1598 /* ESC: insert mode --> command mode */
1604 /* escape sequence follows */
1605 if (safe_read(0, &c, 1) < 1)
1606 goto prepare_to_die;
1607 /* different vt100 emulations */
1608 if (c == '[' || c == 'O') {
1611 if (safe_read(0, &c, 1) < 1)
1612 goto prepare_to_die;
1614 if (c >= '1' && c <= '9') {
1615 unsigned char dummy;
1617 if (safe_read(0, &dummy, 1) < 1)
1618 goto prepare_to_die;
1624 #if ENABLE_FEATURE_TAB_COMPLETION
1625 case '\t': /* Alt-Tab */
1626 input_tab(&lastWasTab);
1631 /* Up Arrow -- Get previous command from history */
1632 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1633 get_previous_history();
1639 /* Down Arrow -- Get next command in history */
1640 if (!get_next_history())
1643 /* Rewrite the line with the selected history item */
1644 /* change command */
1645 command_len = strlen(strcpy(command, state->history[state->cur_history]));
1646 /* redraw and go to eol (bol, in vi */
1647 redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
1651 /* Right Arrow -- Move forward one character */
1655 /* Left Arrow -- Move back one character */
1662 case '1': // vt100? linux vt? or what?
1663 case '7': // vt100? linux vt? or what?
1664 case 'H': /* xterm's <Home> */
1665 input_backward(cursor);
1667 case '4': // vt100? linux vt? or what?
1668 case '8': // vt100? linux vt? or what?
1669 case 'F': /* xterm's <End> */
1678 default: /* If it's regular input, do the normal thing */
1679 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1680 /* Control-V -- Add non-printable symbol */
1681 if (c == CTRL('V')) {
1682 if (safe_read(0, &c, 1) < 1)
1683 goto prepare_to_die;
1691 #if ENABLE_FEATURE_EDITING_VI
1692 if (vi_cmdmode) /* Don't self-insert */
1695 if (!Isprint(c)) /* Skip non-printable characters */
1698 if (command_len >= (maxsize - 2)) /* Need to leave space for enter */
1702 if (cursor == (command_len - 1)) { /* Append if at the end of the line */
1703 command[cursor] = c;
1704 command[cursor+1] = '\0';
1705 cmdedit_set_out_char(' ');
1706 } else { /* Insert otherwise */
1709 memmove(command + sc + 1, command + sc, command_len - sc);
1712 /* rewrite from cursor */
1714 /* to prev x pos + 1 */
1715 input_backward(cursor - sc);
1719 if (break_out) /* Enter is the command terminator, no more input. */
1726 if (command_len > 0)
1727 remember_in_history(command);
1729 if (break_out > 0) {
1730 command[command_len++] = '\n';
1731 command[command_len] = '\0';
1734 #if ENABLE_FEATURE_CLEAN_UP && ENABLE_FEATURE_TAB_COMPLETION
1735 free_tab_completion_data();
1738 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1739 free((char*)cmdedit_prompt);
1741 /* restore initial_settings */
1742 setTermSettings(STDIN_FILENO, (void *) &initial_settings);
1743 /* restore SIGWINCH handler */
1744 signal(SIGWINCH, previous_SIGWINCH_handler);
1749 line_input_t *new_line_input_t(int flags)
1751 line_input_t *n = xzalloc(sizeof(*n));
1758 #undef read_line_input
1759 int read_line_input(const char* prompt, char* command, int maxsize)
1761 fputs(prompt, stdout);
1763 fgets(command, maxsize, stdin);
1764 return strlen(command);
1767 #endif /* FEATURE_COMMAND_EDITING */
1778 const char *applet_name = "debug stuff usage";
1780 int main(int argc, char **argv)
1782 char buff[MAX_LINELEN];
1784 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1785 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:"
1786 "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] "
1787 "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1792 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1793 setlocale(LC_ALL, "");
1797 l = read_line_input(prompt, buff);
1798 if (l <= 0 || buff[l-1] != '\n')
1801 printf("*** read_line_input() returned line =%s=\n", buff);
1803 printf("*** read_line_input() detect ^D\n");