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
31 #include <sys/ioctl.h>
35 /* FIXME: obsolete CONFIG item? */
36 #define ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT 0
41 #define ENABLE_FEATURE_EDITING 0
42 #define ENABLE_FEATURE_TAB_COMPLETION 0
43 #define ENABLE_FEATURE_USERNAME_COMPLETION 0
44 #define ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT 0
45 #define ENABLE_FEATURE_CLEAN_UP 0
50 /* Entire file (except TESTing part) sits inside this #if */
51 #if ENABLE_FEATURE_EDITING
53 #if ENABLE_LOCALE_SUPPORT
54 #define Isprint(c) isprint(c)
56 #define Isprint(c) ((c) >= ' ' && (c) != ((unsigned char)'\233'))
59 #define ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR \
60 (ENABLE_FEATURE_USERNAME_COMPLETION || ENABLE_FEATURE_EDITING_FANCY_PROMPT)
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 char *user_buf = (char*)"";
85 static char *home_pwd_buf = (char*)"";
88 #if ENABLE_FEATURE_TAB_COMPLETION
93 /* Put 'command_ps[cursor]', cursor++.
94 * Advance cursor on screen. If we reached right margin, scroll text up
95 * and remove terminal margin effect by printing 'next_char' */
96 static void cmdedit_set_out_char(int next_char)
98 int c = (unsigned char)command_ps[cursor];
101 /* erase character after end of input string */
104 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
105 /* Display non-printable characters in reverse */
113 printf("\033[7m%c\033[0m", c);
117 if (initial_settings.c_lflag & ECHO)
120 if (++cmdedit_x >= cmdedit_termw) {
121 /* terminal is scrolled down */
124 /* destroy "(auto)margin" */
128 // Huh? What if command_ps[cursor] == '\0' (we are at the end already?)
132 /* Move to end of line (by printing all chars till the end) */
133 static void input_end(void)
135 while (cursor < command_len)
136 cmdedit_set_out_char(' ');
139 /* Go to the next line */
140 static void goto_new_line(void)
148 static void out1str(const char *s)
154 static void beep(void)
159 /* Move back one character */
160 /* (optimized for slow terminals) */
161 static void input_backward(unsigned num)
171 if (cmdedit_x >= num) {
174 printf("\b\b\b\b" + (4-num));
177 printf("\033[%uD", num);
181 /* Need to go one or more lines up */
183 count_y = 1 + (num / cmdedit_termw);
184 cmdedit_y -= count_y;
185 cmdedit_x = cmdedit_termw * count_y - num;
186 /* go to 1st column; go up; go to correct column */
187 printf("\r" "\033[%dA" "\033[%dC", count_y, cmdedit_x);
190 static void put_prompt(void)
192 out1str(cmdedit_prompt);
193 cmdedit_x = cmdedit_prmt_len;
195 // Huh? what if cmdedit_prmt_len >= width?
196 cmdedit_y = 0; /* new quasireal y */
199 /* draw prompt, editor line, and clear tail */
200 static void redraw(int y, int back_cursor)
202 if (y > 0) /* up to start y */
203 printf("\033[%dA", y);
206 input_end(); /* rewrite */
207 printf("\033[J"); /* erase after cursor */
208 input_backward(back_cursor);
211 #if ENABLE_FEATURE_EDITING_VI
212 #define DELBUFSIZ 128
213 static char *delbuf; /* a (malloced) place to store deleted characters */
215 static char newdelflag; /* whether delbuf should be reused yet */
218 /* Delete the char in front of the cursor, optionally saving it
219 * for later putback */
220 static void input_delete(int save)
224 if (j == command_len)
227 #if ENABLE_FEATURE_EDITING_VI
231 delbuf = malloc(DELBUFSIZ);
232 /* safe if malloc fails */
236 if (delbuf && (delp - delbuf < DELBUFSIZ))
237 *delp++ = command_ps[j];
241 strcpy(command_ps + j, command_ps + j + 1);
243 input_end(); /* rewrite new line */
244 cmdedit_set_out_char(' '); /* erase char */
245 input_backward(cursor - j); /* back to old pos cursor */
248 #if ENABLE_FEATURE_EDITING_VI
249 static void put(void)
252 int j = delp - delbuf;
257 /* open hole and then fill it */
258 memmove(command_ps + cursor + j, command_ps + cursor, command_len - cursor + 1);
259 strncpy(command_ps + cursor, delbuf, j);
261 input_end(); /* rewrite new line */
262 input_backward(cursor - ocursor - j + 1); /* at end of new text */
266 /* Delete the char in back of the cursor */
267 static void input_backspace(void)
275 /* Move forward one character */
276 static void input_forward(void)
278 if (cursor < command_len)
279 cmdedit_set_out_char(command_ps[cursor + 1]);
283 #if ENABLE_FEATURE_TAB_COMPLETION
285 static char **matches;
286 static unsigned num_matches;
288 static void free_tab_completion_data(void)
292 free(matches[--num_matches]);
298 static void add_match(char *matched)
300 int nm = num_matches;
303 matches = xrealloc(matches, nm1 * sizeof(char *));
304 matches[nm] = matched;
308 #if ENABLE_FEATURE_USERNAME_COMPLETION
309 static void username_tab_completion(char *ud, char *with_shash_flg)
311 struct passwd *entry;
314 ud++; /* ~user/... to user/... */
315 userlen = strlen(ud);
317 if (with_shash_flg) { /* "~/..." or "~user/..." */
318 char *sav_ud = ud - 1;
322 if (*ud == '/') { /* "~/..." */
326 temp = strchr(ud, '/');
327 *temp = 0; /* ~user\0 */
328 entry = getpwnam(ud);
329 *temp = '/'; /* restore ~user/... */
332 home = entry->pw_dir;
335 if ((userlen + strlen(home) + 1) < BUFSIZ) {
336 char temp2[BUFSIZ]; /* argument size */
339 sprintf(temp2, "%s%s", home, ud);
340 strcpy(sav_ud, temp2);
347 while ((entry = getpwent()) != NULL) {
348 /* Null usernames should result in all users as possible completions. */
349 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
350 add_match(xasprintf("~%s/", entry->pw_name));
357 #endif /* FEATURE_COMMAND_USERNAME_COMPLETION */
365 static int path_parse(char ***p, int flags)
372 /* if not setenv PATH variable, to search cur dir "." */
373 if (flags != FIND_EXE_ONLY)
376 if (state->flags & WITH_PATH_LOOKUP)
377 pth = state->path_lookup;
379 pth = getenv("PATH");
380 /* PATH=<empty> or PATH=:<empty> */
381 if (!pth || !pth[0] || LONE_CHAR(pth, ':'))
385 npth = 1; /* path component count */
387 tmp = strchr(tmp, ':');
391 break; /* :<empty> */
395 res = xmalloc(npth * sizeof(char*));
396 res[0] = tmp = xstrdup(pth);
399 tmp = strchr(tmp, ':');
402 *tmp++ = '\0'; /* ':' -> '\0' */
404 break; /* :<empty> */
411 static void exe_n_cwd_tab_completion(char *command, int type)
418 char **paths = path1;
422 char *pfind = strrchr(command, '/');
425 path1[0] = (char*)".";
428 /* no dir, if flags==EXE_ONLY - get paths, else "." */
429 npaths = path_parse(&paths, type);
432 /* dirbuf = ".../.../.../" */
433 safe_strncpy(dirbuf, command, (pfind - command) + 2);
434 #if ENABLE_FEATURE_USERNAME_COMPLETION
435 if (dirbuf[0] == '~') /* ~/... or ~user/... */
436 username_tab_completion(dirbuf, dirbuf);
439 /* point to 'l' in "..../last_component" */
443 for (i = 0; i < npaths; i++) {
444 dir = opendir(paths[i]);
445 if (!dir) /* Don't print an error */
448 while ((next = readdir(dir)) != NULL) {
450 const char *str_found = next->d_name;
453 if (strncmp(str_found, pfind, strlen(pfind)))
455 /* not see .name without .match */
456 if (*str_found == '.' && *pfind == 0) {
457 if (NOT_LONE_CHAR(paths[i], '/') || str_found[1])
459 str_found = ""; /* only "/" */
461 found = concat_path_file(paths[i], str_found);
462 /* hmm, remover in progress? */
463 if (stat(found, &st) < 0)
465 /* find with dirs? */
466 if (paths[i] != dirbuf)
467 strcpy(found, next->d_name); /* only name */
469 len1 = strlen(found);
470 found = xrealloc(found, len1 + 2);
472 found[len1+1] = '\0';
474 if (S_ISDIR(st.st_mode)) {
475 /* name is directory */
476 if (found[len1-1] != '/') {
480 /* not put found file if search only dirs for cd */
481 if (type == FIND_DIR_ONLY)
484 /* Add it to the list */
492 if (paths != path1) {
493 free(paths[0]); /* allocated memory only in first member */
498 #define QUOT (UCHAR_MAX+1)
500 #define collapse_pos(is, in) { \
501 memmove(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
502 memmove(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
504 static int find_match(char *matchBuf, int *len_with_quotes)
509 int int_buf[BUFSIZ + 1];
510 int pos_buf[BUFSIZ + 1];
512 /* set to integer dimension characters and own positions */
514 int_buf[i] = (unsigned char)matchBuf[i];
515 if (int_buf[i] == 0) {
516 pos_buf[i] = -1; /* indicator end line */
522 /* mask \+symbol and convert '\t' to ' ' */
523 for (i = j = 0; matchBuf[i]; i++, j++)
524 if (matchBuf[i] == '\\') {
525 collapse_pos(j, j + 1);
528 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
529 if (matchBuf[i] == '\t') /* algorithm equivalent */
530 int_buf[j] = ' ' | QUOT;
533 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
534 else if (matchBuf[i] == '\t')
538 /* mask "symbols" or 'symbols' */
540 for (i = 0; int_buf[i]; i++) {
542 if (c == '\'' || c == '"') {
551 } else if (c2 != 0 && c != '$')
555 /* skip commands with arguments if line has commands delimiters */
556 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
557 for (i = 0; int_buf[i]; i++) {
560 j = i ? int_buf[i - 1] : -1;
562 if (c == ';' || c == '&' || c == '|') {
563 command_mode = 1 + (c == c2);
565 if (j == '>' || j == '<')
567 } else if (c == '|' && j == '>')
571 collapse_pos(0, i + command_mode);
572 i = -1; /* hack incremet */
575 /* collapse `command...` */
576 for (i = 0; int_buf[i]; i++)
577 if (int_buf[i] == '`') {
578 for (j = i + 1; int_buf[j]; j++)
579 if (int_buf[j] == '`') {
580 collapse_pos(i, j + 1);
585 /* not found close ` - command mode, collapse all previous */
586 collapse_pos(0, i + 1);
589 i--; /* hack incremet */
592 /* collapse (command...(command...)...) or {command...{command...}...} */
593 c = 0; /* "recursive" level */
595 for (i = 0; int_buf[i]; i++)
596 if (int_buf[i] == '(' || int_buf[i] == '{') {
597 if (int_buf[i] == '(')
601 collapse_pos(0, i + 1);
602 i = -1; /* hack incremet */
604 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
605 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
606 if (int_buf[i] == ')')
610 collapse_pos(0, i + 1);
611 i = -1; /* hack incremet */
614 /* skip first not quote space */
615 for (i = 0; int_buf[i]; i++)
616 if (int_buf[i] != ' ')
621 /* set find mode for completion */
622 command_mode = FIND_EXE_ONLY;
623 for (i = 0; int_buf[i]; i++)
624 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
625 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
626 && matchBuf[pos_buf[0]]=='c'
627 && matchBuf[pos_buf[1]]=='d'
629 command_mode = FIND_DIR_ONLY;
631 command_mode = FIND_FILE_ONLY;
635 for (i = 0; int_buf[i]; i++)
638 for (--i; i >= 0; i--) {
640 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
641 collapse_pos(0, i + 1);
645 /* skip first not quoted '\'' or '"' */
646 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++)
648 /* collapse quote or unquote // or /~ */
649 while ((int_buf[i] & ~QUOT) == '/'
650 && ((int_buf[i+1] & ~QUOT) == '/' || (int_buf[i+1] & ~QUOT) == '~')
655 /* set only match and destroy quotes */
657 for (c = 0; pos_buf[i] >= 0; i++) {
658 matchBuf[c++] = matchBuf[pos_buf[i]];
662 /* old lenght matchBuf with quotes symbols */
663 *len_with_quotes = j ? j - pos_buf[0] : 0;
669 * display by column (original idea from ls applet,
670 * very optimized by me :)
672 static void showfiles(void)
675 int column_width = 0;
676 int nfiles = num_matches;
680 /* find the longest file name- use that as the column width */
681 for (row = 0; row < nrows; row++) {
682 l = strlen(matches[row]);
683 if (column_width < l)
686 column_width += 2; /* min space for columns */
687 ncols = cmdedit_termw / column_width;
692 nrows++; /* round up fractionals */
696 for (row = 0; row < nrows; row++) {
700 for (nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
701 printf("%s%-*s", matches[n],
702 (int)(column_width - strlen(matches[n])), "");
704 printf("%s\n", matches[n]);
708 static char *add_quote_for_spec_chars(char *found)
711 char *s = xmalloc((strlen(found) + 1) * 2);
714 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
722 static int match_compare(const void *a, const void *b)
724 return strcmp(*(char**)a, *(char**)b);
727 /* Do TAB completion */
728 static void input_tab(int *lastWasTab)
730 if (!(state->flags & TAB_COMPLETION))
736 char matchBuf[BUFSIZ];
740 *lastWasTab = TRUE; /* flop trigger */
742 /* Make a local copy of the string -- up
743 * to the position of the cursor */
744 tmp = strncpy(matchBuf, command_ps, cursor);
747 find_type = find_match(matchBuf, &recalc_pos);
749 /* Free up any memory already allocated */
750 free_tab_completion_data();
752 #if ENABLE_FEATURE_USERNAME_COMPLETION
753 /* If the word starts with `~' and there is no slash in the word,
754 * then try completing this word as a username. */
755 if (state->flags & USERNAME_COMPLETION)
756 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
757 username_tab_completion(matchBuf, NULL);
759 /* Try to match any executable in our path and everything
760 * in the current working directory */
762 exe_n_cwd_tab_completion(matchBuf, find_type);
763 /* Sort, then remove any duplicates found */
766 qsort(matches, num_matches, sizeof(char*), match_compare);
767 for (i = 0; i < num_matches - 1; ++i) {
768 if (matches[i] && matches[i+1]) { /* paranoia */
769 if (strcmp(matches[i], matches[i+1]) == 0) {
771 matches[i] = NULL; /* paranoia */
773 matches[n++] = matches[i];
777 matches[n] = matches[i];
780 /* Did we find exactly one match? */
781 if (!matches || num_matches > 1) {
784 return; /* not found */
785 /* find minimal match */
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) < BUFSIZ) {
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);
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 (state->flags & SAVE_HISTORY)
954 save_history(state->hist_file);
955 USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;)
958 #else /* MAX_HISTORY == 0 */
959 #define remember_in_history(a) ((void)0)
960 #endif /* MAX_HISTORY */
964 * This function is used to grab a character buffer
965 * from the input file descriptor and allows you to
966 * a string with full command editing (sort of like
969 * The following standard commands are not implemented:
970 * ESC-b -- Move back one word
971 * ESC-f -- Move forward one word
972 * ESC-d -- Delete back one word
973 * ESC-h -- Delete forward one word
974 * CTL-t -- Transpose two characters
976 * Minimalist vi-style command line editing available if configured.
977 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
980 #if ENABLE_FEATURE_EDITING_VI
982 vi_Word_motion(char *command, int eat)
984 while (cursor < command_len && !isspace(command[cursor]))
986 if (eat) while (cursor < command_len && isspace(command[cursor]))
991 vi_word_motion(char *command, int eat)
993 if (isalnum(command[cursor]) || command[cursor] == '_') {
994 while (cursor < command_len
995 && (isalnum(command[cursor+1]) || command[cursor+1] == '_'))
997 } else if (ispunct(command[cursor])) {
998 while (cursor < command_len && ispunct(command[cursor+1]))
1002 if (cursor < command_len)
1005 if (eat && cursor < command_len && isspace(command[cursor]))
1006 while (cursor < command_len && isspace(command[cursor]))
1011 vi_End_motion(char *command)
1014 while (cursor < command_len && isspace(command[cursor]))
1016 while (cursor < command_len-1 && !isspace(command[cursor+1]))
1021 vi_end_motion(char *command)
1023 if (cursor >= command_len-1)
1026 while (cursor < command_len-1 && isspace(command[cursor]))
1028 if (cursor >= command_len-1)
1030 if (isalnum(command[cursor]) || command[cursor] == '_') {
1031 while (cursor < command_len-1
1032 && (isalnum(command[cursor+1]) || command[cursor+1] == '_')
1036 } else if (ispunct(command[cursor])) {
1037 while (cursor < command_len-1 && ispunct(command[cursor+1]))
1043 vi_Back_motion(char *command)
1045 while (cursor > 0 && isspace(command[cursor-1]))
1047 while (cursor > 0 && !isspace(command[cursor-1]))
1052 vi_back_motion(char *command)
1057 while (cursor > 0 && isspace(command[cursor]))
1061 if (isalnum(command[cursor]) || command[cursor] == '_') {
1063 && (isalnum(command[cursor-1]) || command[cursor-1] == '_')
1067 } else if (ispunct(command[cursor])) {
1068 while (cursor > 0 && ispunct(command[cursor-1]))
1076 * read_line_input and its helpers
1079 #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
1080 static void parse_prompt(const char *prmt_ptr)
1082 cmdedit_prompt = prmt_ptr;
1083 cmdedit_prmt_len = strlen(prmt_ptr);
1087 static void parse_prompt(const char *prmt_ptr)
1090 size_t cur_prmt_len = 0;
1091 char flg_not_length = '[';
1092 char *prmt_mem_ptr = xzalloc(1);
1093 char *pwd_buf = xgetcwd(0);
1094 char buf2[PATH_MAX + 1];
1099 cmdedit_prmt_len = 0;
1102 pwd_buf = (char *)bb_msg_unknown;
1110 const char *cp = prmt_ptr;
1113 c = bb_process_escape_sequence(&prmt_ptr);
1114 if (prmt_ptr == cp) {
1119 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1125 pbuf = hostname_buf;
1127 pbuf = xzalloc(256);
1128 if (gethostname(pbuf, 255) < 0) {
1131 char *s = strchr(pbuf, '.');
1135 hostname_buf = pbuf;
1139 c = (geteuid() == 0 ? '#' : '$');
1141 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1144 l = strlen(home_pwd_buf);
1145 if (home_pwd_buf[0] != 0
1146 && strncmp(home_pwd_buf, pbuf, l) == 0
1147 && (pbuf[l]=='/' || pbuf[l]=='\0')
1148 && strlen(pwd_buf+l)<PATH_MAX
1152 strcpy(pbuf+1, pwd_buf+l);
1158 cp = strrchr(pbuf,'/');
1159 if (cp != NULL && cp != pbuf)
1160 pbuf += (cp-pbuf) + 1;
1164 snprintf(buf2, sizeof(buf2), "%d", num_ok_lines);
1166 case 'e': case 'E': /* \e \E = \033 */
1170 for (l = 0; l < 3;) {
1172 buf2[l++] = *prmt_ptr;
1174 h = strtol(buf2, &pbuf, 16);
1175 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
1182 c = (char)strtol(buf2, NULL, 16);
1188 if (c == flg_not_length) {
1189 flg_not_length = flg_not_length == '[' ? ']' : '[';
1198 cur_prmt_len = strlen(pbuf);
1199 prmt_len += cur_prmt_len;
1200 if (flg_not_length != ']')
1201 cmdedit_prmt_len += cur_prmt_len;
1202 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
1204 if (pwd_buf != (char *)bb_msg_unknown)
1206 cmdedit_prompt = prmt_mem_ptr;
1211 #define setTermSettings(fd, argp) tcsetattr(fd, TCSANOW, argp)
1212 #define getTermSettings(fd, argp) tcgetattr(fd, argp);
1214 static sighandler_t previous_SIGWINCH_handler;
1216 static void cmdedit_setwidth(unsigned w, int redraw_flg)
1220 /* new y for current cursor */
1221 int new_y = (cursor + cmdedit_prmt_len) / w;
1223 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), command_len - cursor);
1228 static void win_changed(int nsig)
1231 get_terminal_width_height(0, &width, NULL);
1232 cmdedit_setwidth(width, nsig /* - just a yes/no flag */);
1233 if (nsig == SIGWINCH)
1234 signal(SIGWINCH, win_changed); /* rearm ourself */
1238 * The emacs and vi modes share much of the code in the big
1239 * command loop. Commands entered when in vi's command mode (aka
1240 * "escape mode") get an extra bit added to distinguish them --
1241 * this keeps them from being self-inserted. This clutters the
1242 * big switch a bit, but keeps all the code in one place.
1247 /* leave out the "vi-mode"-only case labels if vi editing isn't
1249 #define vi_case(caselabel) USE_FEATURE_EDITING(case caselabel)
1251 /* convert uppercase ascii to equivalent control char, for readability */
1253 #define CTRL(a) ((a) & ~0x40)
1255 int read_line_input(const char* prompt, char* command, int maxsize, line_input_t *st)
1257 int lastWasTab = FALSE;
1260 smallint break_out = 0;
1261 #if ENABLE_FEATURE_EDITING_VI
1262 smallint vi_cmdmode = 0;
1266 // FIXME: audit & improve this
1267 if (maxsize > BUFSIZ)
1270 /* With null flags, no other fields are ever used */
1271 state = st ? st : (line_input_t*) &const_int_0;
1272 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1273 if (state->flags & SAVE_HISTORY)
1274 load_history(state->hist_file);
1277 /* prepare before init handlers */
1278 cmdedit_y = 0; /* quasireal y, not true if line > xt*yt */
1280 command_ps = command;
1283 getTermSettings(0, (void *) &initial_settings);
1284 memcpy(&new_settings, &initial_settings, sizeof(new_settings));
1285 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1286 /* Turn off echoing and CTRL-C, so we can trap it */
1287 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1288 /* Hmm, in linux c_cc[] is not parsed if ICANON is off */
1289 new_settings.c_cc[VMIN] = 1;
1290 new_settings.c_cc[VTIME] = 0;
1291 /* Turn off CTRL-C, so we can trap it */
1292 #ifndef _POSIX_VDISABLE
1293 #define _POSIX_VDISABLE '\0'
1295 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1296 setTermSettings(0, (void *) &new_settings);
1298 /* Now initialize things */
1299 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
1300 win_changed(0); /* do initial resizing */
1301 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1303 struct passwd *entry;
1305 entry = getpwuid(geteuid());
1307 user_buf = xstrdup(entry->pw_name);
1308 home_pwd_buf = xstrdup(entry->pw_dir);
1312 #if ENABLE_FEATURE_TAB_COMPLETION
1316 /* Print out the command prompt */
1317 parse_prompt(prompt);
1322 if (safe_read(0, &c, 1) < 1) {
1323 /* if we can't read input then exit */
1324 goto prepare_to_die;
1329 #if ENABLE_FEATURE_EDITING_VI
1343 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1346 /* Control-a -- Beginning of line */
1347 input_backward(cursor);
1352 vi_case('\x7f'|vbit:) /* DEL */
1353 /* Control-b -- Move back one character */
1358 vi_case(CTRL('C')|vbit:)
1359 /* Control-c -- stop gathering input */
1362 break_out = -1; /* "do not append '\n'" */
1365 /* Control-d -- Delete one character, or exit
1366 * if the len=0 and no chars to delete */
1367 if (command_len == 0) {
1370 /* to control stopped jobs */
1371 break_out = command_len = -1;
1377 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1380 /* Control-e -- End of line */
1386 /* Control-f -- Move forward one character */
1392 case '\x7f': /* DEL */
1393 /* Control-h and DEL */
1398 input_tab(&lastWasTab);
1401 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1403 /* Control-k -- clear to end of line */
1404 command[cursor] = 0;
1405 command_len = cursor;
1409 vi_case(CTRL('L')|vbit:)
1410 /* Control-l -- clear screen */
1412 redraw(0, command_len - cursor);
1418 vi_case(CTRL('N')|vbit:)
1420 /* Control-n -- Get next command in history */
1421 if (get_next_history())
1425 vi_case(CTRL('P')|vbit:)
1427 /* Control-p -- Get previous command from history */
1428 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1429 get_previous_history();
1436 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1438 vi_case(CTRL('U')|vbit:)
1439 /* Control-U -- Clear line before cursor */
1441 strcpy(command, command + cursor);
1442 command_len -= cursor;
1443 redraw(cmdedit_y, command_len);
1448 vi_case(CTRL('W')|vbit:)
1449 /* Control-W -- Remove the last word */
1450 while (cursor > 0 && isspace(command[cursor-1]))
1452 while (cursor > 0 && !isspace(command[cursor-1]))
1456 #if ENABLE_FEATURE_EDITING_VI
1461 input_backward(cursor);
1482 vi_Word_motion(command, 1);
1485 vi_word_motion(command, 1);
1488 vi_End_motion(command);
1491 vi_end_motion(command);
1494 vi_Back_motion(command);
1497 vi_back_motion(command);
1512 if (safe_read(0, &c, 1) < 1)
1513 goto prepare_to_die;
1514 if (c == (prevc & 0xff)) {
1516 input_backward(cursor);
1526 case 'w': /* "dw", "cw" */
1527 vi_word_motion(command, vi_cmdmode);
1529 case 'W': /* 'dW', 'cW' */
1530 vi_Word_motion(command, vi_cmdmode);
1532 case 'e': /* 'de', 'ce' */
1533 vi_end_motion(command);
1536 case 'E': /* 'dE', 'cE' */
1537 vi_End_motion(command);
1542 input_backward(cursor - sc);
1543 while (nc-- > cursor)
1546 case 'b': /* "db", "cb" */
1547 case 'B': /* implemented as B */
1549 vi_back_motion(command);
1551 vi_Back_motion(command);
1552 while (sc-- > cursor)
1555 case ' ': /* "d ", "c " */
1558 case '$': /* "d$", "c$" */
1560 while (cursor < command_len)
1573 if (safe_read(0, &c, 1) < 1)
1574 goto prepare_to_die;
1578 *(command + cursor) = c;
1583 #endif /* FEATURE_COMMAND_EDITING_VI */
1585 case '\x1b': /* ESC */
1587 #if ENABLE_FEATURE_EDITING_VI
1588 if (state->flags & VI_MODE) {
1589 /* ESC: insert mode --> command mode */
1595 /* escape sequence follows */
1596 if (safe_read(0, &c, 1) < 1)
1597 goto prepare_to_die;
1598 /* different vt100 emulations */
1599 if (c == '[' || c == 'O') {
1602 if (safe_read(0, &c, 1) < 1)
1603 goto prepare_to_die;
1605 if (c >= '1' && c <= '9') {
1606 unsigned char dummy;
1608 if (safe_read(0, &dummy, 1) < 1)
1609 goto prepare_to_die;
1615 #if ENABLE_FEATURE_TAB_COMPLETION
1616 case '\t': /* Alt-Tab */
1617 input_tab(&lastWasTab);
1622 /* Up Arrow -- Get previous command from history */
1623 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1624 get_previous_history();
1630 /* Down Arrow -- Get next command in history */
1631 if (!get_next_history())
1634 /* Rewrite the line with the selected history item */
1635 /* change command */
1636 command_len = strlen(strcpy(command, state->history[state->cur_history]));
1637 /* redraw and go to eol (bol, in vi */
1638 redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
1642 /* Right Arrow -- Move forward one character */
1646 /* Left Arrow -- Move back one character */
1656 input_backward(cursor);
1669 default: /* If it's regular input, do the normal thing */
1670 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1671 /* Control-V -- Add non-printable symbol */
1672 if (c == CTRL('V')) {
1673 if (safe_read(0, &c, 1) < 1)
1674 goto prepare_to_die;
1682 #if ENABLE_FEATURE_EDITING_VI
1683 if (vi_cmdmode) /* Don't self-insert */
1686 if (!Isprint(c)) /* Skip non-printable characters */
1689 if (command_len >= (maxsize - 2)) /* Need to leave space for enter */
1693 if (cursor == (command_len - 1)) { /* Append if at the end of the line */
1694 command[cursor] = c;
1695 command[cursor+1] = '\0';
1696 cmdedit_set_out_char(' ');
1697 } else { /* Insert otherwise */
1700 memmove(command + sc + 1, command + sc, command_len - sc);
1703 /* rewrite from cursor */
1705 /* to prev x pos + 1 */
1706 input_backward(cursor - sc);
1710 if (break_out) /* Enter is the command terminator, no more input. */
1717 if (command_len > 0)
1718 remember_in_history(command);
1720 if (break_out > 0) {
1721 command[command_len++] = '\n';
1722 command[command_len] = '\0';
1725 #if ENABLE_FEATURE_CLEAN_UP && ENABLE_FEATURE_TAB_COMPLETION
1726 free_tab_completion_data();
1729 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1730 free((char*)cmdedit_prompt);
1732 /* restore initial_settings */
1733 setTermSettings(STDIN_FILENO, (void *) &initial_settings);
1734 /* restore SIGWINCH handler */
1735 signal(SIGWINCH, previous_SIGWINCH_handler);
1740 line_input_t *new_line_input_t(int flags)
1742 line_input_t *n = xzalloc(sizeof(*n));
1749 #undef read_line_input
1750 int read_line_input(const char* prompt, char* command, int maxsize)
1752 fputs(prompt, stdout);
1754 fgets(command, maxsize, stdin);
1755 return strlen(command);
1758 #endif /* FEATURE_COMMAND_EDITING */
1769 const char *applet_name = "debug stuff usage";
1771 int main(int argc, char **argv)
1775 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1776 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:"
1777 "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] "
1778 "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1783 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1784 setlocale(LC_ALL, "");
1788 l = read_line_input(prompt, buff);
1789 if (l <= 0 || buff[l-1] != '\n')
1792 printf("*** read_line_input() returned line =%s=\n", buff);
1794 printf("*** read_line_input() detect ^D\n");