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.
21 Terminal key codes are not extensive, and more will probably
22 need to be added. This version was created on Debian GNU/Linux 2.x.
23 Delete, Backspace, Home, End, and the arrow keys were tested
24 to work in an Xterm and console. Ctrl-A also works as Home.
25 Ctrl-E also works as End.
27 Small bugs (simple effect):
28 - not true viewing if terminal size (x*y symbols) less
29 size (prompt + editor`s line + 2 symbols)
30 - not true viewing if length prompt less terminal width
40 #include <sys/ioctl.h>
45 #include "../shell/cmdedit.h"
48 #ifdef CONFIG_LOCALE_SUPPORT
49 #define Isprint(c) isprint((c))
51 #define Isprint(c) ( (c) >= ' ' && (c) != ((unsigned char)'\233') )
56 /* pretect redefined for test */
57 #undef CONFIG_FEATURE_COMMAND_EDITING
58 #undef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
59 #undef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
60 #undef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
61 #undef CONFIG_FEATURE_CLEAN_UP
63 #define CONFIG_FEATURE_COMMAND_EDITING
64 #define CONFIG_FEATURE_COMMAND_TAB_COMPLETION
65 #define CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
66 #define CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
67 #define CONFIG_FEATURE_CLEAN_UP
71 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
76 #ifdef CONFIG_FEATURE_COMMAND_EDITING
78 #if defined(CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION) || defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
79 #define CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
82 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
84 #endif /* advanced FEATURES */
87 /* Maximum length of the linked list for the command line history */
88 #ifndef CONFIG_FEATURE_COMMAND_HISTORY
89 #define MAX_HISTORY 15
91 #define MAX_HISTORY CONFIG_FEATURE_COMMAND_HISTORY
95 #warning cmdedit: You set MAX_HISTORY < 1. The history algorithm switched off.
97 static char *history[MAX_HISTORY+1]; /* history + current */
98 /* saved history lines */
100 /* current pointer to history line */
101 static int cur_history;
105 #define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
106 #define getTermSettings(fd,argp) tcgetattr(fd, argp);
108 /* Current termio and the previous termio before starting sh */
109 static struct termios initial_settings, new_settings;
113 volatile int cmdedit_termw = 80; /* actual terminal width */
115 volatile int handlers_sets = 0; /* Set next bites: */
118 SET_ATEXIT = 1, /* when atexit() has been called
119 and get euid,uid,gid to fast compare */
120 SET_WCHG_HANDLERS = 2, /* winchg signal handler */
121 SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */
125 static int cmdedit_x; /* real x terminal position */
126 static int cmdedit_y; /* pseudoreal y terminal position */
127 static int cmdedit_prmt_len; /* lenght prompt without colores string */
129 static int cursor; /* required global for signal handler */
130 static int len; /* --- "" - - "" - -"- --""-- --""--- */
131 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
133 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
136 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
138 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
139 static char *user_buf = "";
140 static char *home_pwd_buf = "";
144 #ifdef CONFIG_FEATURE_SH_FANCY_PROMPT
145 static char *hostname_buf;
146 static int num_ok_lines = 1;
150 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
152 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
159 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
161 static void cmdedit_setwidth(int w, int redraw_flg);
163 static void win_changed(int nsig)
165 static sighandler_t previous_SIGWINCH_handler; /* for reset */
167 /* emulate || signal call */
168 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
170 get_terminal_width_height(0, &width, NULL);
171 cmdedit_setwidth(width, nsig == SIGWINCH);
173 /* Unix not all standart in recall signal */
175 if (nsig == -SIGWINCH) /* save previous handler */
176 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
177 else if (nsig == SIGWINCH) /* signaled called handler */
178 signal(SIGWINCH, win_changed); /* set for next call */
180 /* set previous handler */
181 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
184 static void cmdedit_reset_term(void)
186 if ((handlers_sets & SET_RESET_TERM) != 0) {
187 /* sparc and other have broken termios support: use old termio handling. */
188 setTermSettings(STDIN_FILENO, (void *) &initial_settings);
189 handlers_sets &= ~SET_RESET_TERM;
191 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
192 /* reset SIGWINCH handler to previous (default) */
194 handlers_sets &= ~SET_WCHG_HANDLERS;
200 /* special for recount position for scroll and remove terminal margin effect */
201 static void cmdedit_set_out_char(int next_char)
204 int c = (int)((unsigned char) command_ps[cursor]);
207 c = ' '; /* destroy end char? */
208 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
209 if (!Isprint(c)) { /* Inverse put non-printable characters */
216 printf("\033[7m%c\033[0m", c);
220 if (++cmdedit_x >= cmdedit_termw) {
221 /* terminal is scrolled down */
227 /* destroy "(auto)margin" */
234 /* Move to end line. Bonus: rewrite line from cursor */
235 static void input_end(void)
238 cmdedit_set_out_char(0);
241 /* Go to the next line */
242 static void goto_new_line(void)
250 static inline void out1str(const char *s)
256 static inline void beep(void)
261 /* Move back one character */
262 /* special for slow terminal */
263 static void input_backward(int num)
267 cursor -= num; /* new cursor (in command, not terminal) */
269 if (cmdedit_x >= num) { /* no to up line */
276 printf("\033[%dD", num);
281 putchar('\r'); /* back to first terminal pos. */
282 num -= cmdedit_x; /* set previous backward */
284 count_y = 1 + num / cmdedit_termw;
285 printf("\033[%dA", count_y);
286 cmdedit_y -= count_y;
287 /* require forward after uping */
288 cmdedit_x = cmdedit_termw * count_y - num;
289 printf("\033[%dC", cmdedit_x); /* set term cursor */
293 static void put_prompt(void)
295 out1str(cmdedit_prompt);
296 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
298 cmdedit_y = 0; /* new quasireal y */
301 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
302 static void parse_prompt(const char *prmt_ptr)
304 cmdedit_prompt = prmt_ptr;
305 cmdedit_prmt_len = strlen(prmt_ptr);
309 static void parse_prompt(const char *prmt_ptr)
312 size_t cur_prmt_len = 0;
313 char flg_not_length = '[';
314 char *prmt_mem_ptr = xcalloc(1, 1);
315 char *pwd_buf = xgetcwd(0);
316 char buf2[PATH_MAX + 1];
322 pwd_buf=(char *)bb_msg_unknown;
330 const char *cp = prmt_ptr;
333 c = bb_process_escape_sequence(&prmt_ptr);
339 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
347 pbuf = xcalloc(256, 1);
348 if (gethostname(pbuf, 255) < 0) {
351 char *s = strchr(pbuf, '.');
360 c = my_euid == 0 ? '#' : '$';
362 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
365 l = strlen(home_pwd_buf);
366 if (home_pwd_buf[0] != 0 &&
367 strncmp(home_pwd_buf, pbuf, l) == 0 &&
368 (pbuf[l]=='/' || pbuf[l]=='\0') &&
369 strlen(pwd_buf+l)<PATH_MAX) {
372 strcpy(pbuf+1, pwd_buf+l);
378 cp = strrchr(pbuf,'/');
379 if ( (cp != NULL) && (cp != pbuf) )
383 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
385 case 'e': case 'E': /* \e \E = \033 */
389 for (l = 0; l < 3;) {
391 buf2[l++] = *prmt_ptr;
393 h = strtol(buf2, &pbuf, 16);
394 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
401 c = (char)strtol(buf2, 0, 16);
407 if (c == flg_not_length) {
408 flg_not_length = flg_not_length == '[' ? ']' : '[';
417 cur_prmt_len = strlen(pbuf);
418 prmt_len += cur_prmt_len;
419 if (flg_not_length != ']')
420 cmdedit_prmt_len += cur_prmt_len;
421 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
423 if(pwd_buf!=(char *)bb_msg_unknown)
425 cmdedit_prompt = prmt_mem_ptr;
431 /* draw prompt, editor line, and clear tail */
432 static void redraw(int y, int back_cursor)
434 if (y > 0) /* up to start y */
435 printf("\033[%dA", y);
438 input_end(); /* rewrite */
439 printf("\033[J"); /* destroy tail after cursor */
440 input_backward(back_cursor);
443 #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
444 #define DELBUFSIZ 128
445 static char *delbuf; /* a (malloced) place to store deleted characters */
447 static char newdelflag; /* whether delbuf should be reused yet */
450 /* Delete the char in front of the cursor, optionally saving it
451 * for later putback */
452 static void input_delete(int save)
459 #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
463 delbuf = malloc(DELBUFSIZ);
464 /* safe if malloc fails */
468 if (delbuf && (delp - delbuf < DELBUFSIZ))
469 *delp++ = command_ps[j];
473 strcpy(command_ps + j, command_ps + j + 1);
475 input_end(); /* rewrite new line */
476 cmdedit_set_out_char(0); /* destroy end char */
477 input_backward(cursor - j); /* back to old pos cursor */
480 #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
481 static void put(void)
483 int ocursor, j = delp - delbuf;
487 /* open hole and then fill it */
488 memmove(command_ps + cursor + j, command_ps + cursor, len - cursor + 1);
489 strncpy(command_ps + cursor, delbuf, j);
491 input_end(); /* rewrite new line */
492 input_backward(cursor-ocursor-j+1); /* at end of new text */
496 /* Delete the char in back of the cursor */
497 static void input_backspace(void)
506 /* Move forward one character */
507 static void input_forward(void)
510 cmdedit_set_out_char(command_ps[cursor + 1]);
513 static void cmdedit_setwidth(int w, int redraw_flg)
515 cmdedit_termw = cmdedit_prmt_len + 2;
516 if (w <= cmdedit_termw) {
517 cmdedit_termw = cmdedit_termw % w;
519 if (w > cmdedit_termw) {
523 /* new y for current cursor */
524 int new_y = (cursor + cmdedit_prmt_len) / w;
527 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
533 static void cmdedit_init(void)
535 cmdedit_prmt_len = 0;
536 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
537 /* emulate usage handler to set handler and call yours work */
538 win_changed(-SIGWINCH);
539 handlers_sets |= SET_WCHG_HANDLERS;
542 if ((handlers_sets & SET_ATEXIT) == 0) {
543 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
544 struct passwd *entry;
547 entry = getpwuid(my_euid);
549 user_buf = bb_xstrdup(entry->pw_name);
550 home_pwd_buf = bb_xstrdup(entry->pw_dir);
554 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
556 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
561 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
562 handlers_sets |= SET_ATEXIT;
563 atexit(cmdedit_reset_term); /* be sure to do this only once */
567 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
569 static char **matches;
570 static int num_matches;
571 static char *add_char_to_match;
573 static void add_match(char *matched, int add_char)
575 int nm = num_matches;
578 matches = xrealloc(matches, nm1 * sizeof(char *));
579 add_char_to_match = xrealloc(add_char_to_match, nm1);
580 matches[nm] = matched;
581 add_char_to_match[nm] = (char)add_char;
585 static int is_execute(const struct stat *st)
587 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
588 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
589 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
590 (st->st_mode & S_IXOTH)) return TRUE;
594 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
596 static void username_tab_completion(char *ud, char *with_shash_flg)
598 struct passwd *entry;
601 ud++; /* ~user/... to user/... */
602 userlen = strlen(ud);
604 if (with_shash_flg) { /* "~/..." or "~user/..." */
605 char *sav_ud = ud - 1;
609 if (*ud == '/') { /* "~/..." */
613 temp = strchr(ud, '/');
614 *temp = 0; /* ~user\0 */
615 entry = getpwnam(ud);
616 *temp = '/'; /* restore ~user/... */
619 home = entry->pw_dir;
622 if ((userlen + strlen(home) + 1) < BUFSIZ) {
623 char temp2[BUFSIZ]; /* argument size */
626 sprintf(temp2, "%s%s", home, ud);
627 strcpy(sav_ud, temp2);
634 while ((entry = getpwent()) != NULL) {
635 /* Null usernames should result in all users as possible completions. */
636 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
637 add_match(bb_xasprintf("~%s", entry->pw_name), '/');
644 #endif /* CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION */
653 const char *cmdedit_path_lookup;
655 #define cmdedit_path_lookup getenv("PATH")
658 static int path_parse(char ***p, int flags)
664 /* if not setenv PATH variable, to search cur dir "." */
665 if (flags != FIND_EXE_ONLY || (pth = cmdedit_path_lookup) == 0 ||
666 /* PATH=<empty> or PATH=:<empty> */
667 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
675 npth++; /* count words is + 1 count ':' */
676 tmp = strchr(tmp, ':');
679 break; /* :<empty> */
684 *p = xmalloc(npth * sizeof(char *));
687 (*p)[0] = bb_xstrdup(tmp);
688 npth = 1; /* count words is + 1 count ':' */
691 tmp = strchr(tmp, ':');
693 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
695 break; /* :<empty> */
698 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
704 static char *add_quote_for_spec_chars(char *found, int add)
707 char *s = xmalloc((strlen(found) + 1) * 2);
710 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
720 static void exe_n_cwd_tab_completion(char *command, int type)
727 char **paths = path1;
731 char *pfind = strrchr(command, '/');
736 /* no dir, if flags==EXE_ONLY - get paths, else "." */
737 npaths = path_parse(&paths, type);
741 /* save for change */
742 strcpy(dirbuf, command);
744 dirbuf[(pfind - command) + 1] = 0;
745 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
746 if (dirbuf[0] == '~') /* ~/... or ~user/... */
747 username_tab_completion(dirbuf, dirbuf);
749 /* "strip" dirname in command */
753 npaths = 1; /* only 1 dir */
756 for (i = 0; i < npaths; i++) {
758 dir = opendir(paths[i]);
759 if (!dir) /* Don't print an error */
762 while ((next = readdir(dir)) != NULL) {
763 char *str_found = next->d_name;
767 if (strncmp(str_found, pfind, strlen(pfind)))
769 /* not see .name without .match */
770 if (*str_found == '.' && *pfind == 0) {
771 if (*paths[i] == '/' && paths[i][1] == 0
772 && str_found[1] == 0) str_found = ""; /* only "/" */
776 found = concat_path_file(paths[i], str_found);
777 /* hmm, remover in progress? */
778 if (stat(found, &st) < 0)
780 /* find with dirs ? */
781 if (paths[i] != dirbuf)
782 strcpy(found, next->d_name); /* only name */
783 if (S_ISDIR(st.st_mode)) {
784 /* name is directory */
785 char *e = found + strlen(found) - 1;
791 /* not put found file if search only dirs for cd */
792 if (type == FIND_DIR_ONLY)
794 if (type == FIND_FILE_ONLY ||
795 (type == FIND_EXE_ONLY && is_execute(&st)))
798 /* Add it to the list */
799 add_match(found, add_chr);
806 if (paths != path1) {
807 free(paths[0]); /* allocated memory only in first member */
813 #define QUOT (UCHAR_MAX+1)
815 #define collapse_pos(is, in) { \
816 memmove(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
817 memmove(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
819 static int find_match(char *matchBuf, int *len_with_quotes)
824 int int_buf[BUFSIZ + 1];
825 int pos_buf[BUFSIZ + 1];
827 /* set to integer dimension characters and own positions */
829 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
830 if (int_buf[i] == 0) {
831 pos_buf[i] = -1; /* indicator end line */
837 /* mask \+symbol and convert '\t' to ' ' */
838 for (i = j = 0; matchBuf[i]; i++, j++)
839 if (matchBuf[i] == '\\') {
840 collapse_pos(j, j + 1);
843 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
844 if (matchBuf[i] == '\t') /* algorithm equivalent */
845 int_buf[j] = ' ' | QUOT;
848 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
849 else if (matchBuf[i] == '\t')
853 /* mask "symbols" or 'symbols' */
855 for (i = 0; int_buf[i]; i++) {
857 if (c == '\'' || c == '"') {
866 } else if (c2 != 0 && c != '$')
870 /* skip commands with arguments if line have commands delimiters */
871 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
872 for (i = 0; int_buf[i]; i++) {
875 j = i ? int_buf[i - 1] : -1;
877 if (c == ';' || c == '&' || c == '|') {
878 command_mode = 1 + (c == c2);
880 if (j == '>' || j == '<')
882 } else if (c == '|' && j == '>')
886 collapse_pos(0, i + command_mode);
887 i = -1; /* hack incremet */
890 /* collapse `command...` */
891 for (i = 0; int_buf[i]; i++)
892 if (int_buf[i] == '`') {
893 for (j = i + 1; int_buf[j]; j++)
894 if (int_buf[j] == '`') {
895 collapse_pos(i, j + 1);
900 /* not found close ` - command mode, collapse all previous */
901 collapse_pos(0, i + 1);
904 i--; /* hack incremet */
907 /* collapse (command...(command...)...) or {command...{command...}...} */
908 c = 0; /* "recursive" level */
910 for (i = 0; int_buf[i]; i++)
911 if (int_buf[i] == '(' || int_buf[i] == '{') {
912 if (int_buf[i] == '(')
916 collapse_pos(0, i + 1);
917 i = -1; /* hack incremet */
919 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
920 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
921 if (int_buf[i] == ')')
925 collapse_pos(0, i + 1);
926 i = -1; /* hack incremet */
929 /* skip first not quote space */
930 for (i = 0; int_buf[i]; i++)
931 if (int_buf[i] != ' ')
936 /* set find mode for completion */
937 command_mode = FIND_EXE_ONLY;
938 for (i = 0; int_buf[i]; i++)
939 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
940 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
941 && matchBuf[pos_buf[0]]=='c'
942 && matchBuf[pos_buf[1]]=='d' )
943 command_mode = FIND_DIR_ONLY;
945 command_mode = FIND_FILE_ONLY;
950 for (i = 0; int_buf[i]; i++);
952 for (--i; i >= 0; i--) {
954 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
955 collapse_pos(0, i + 1);
959 /* skip first not quoted '\'' or '"' */
960 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
961 /* collapse quote or unquote // or /~ */
962 while ((int_buf[i] & ~QUOT) == '/' &&
963 ((int_buf[i + 1] & ~QUOT) == '/'
964 || (int_buf[i + 1] & ~QUOT) == '~')) {
968 /* set only match and destroy quotes */
970 for (c = 0; pos_buf[i] >= 0; i++) {
971 matchBuf[c++] = matchBuf[pos_buf[i]];
975 /* old lenght matchBuf with quotes symbols */
976 *len_with_quotes = j ? j - pos_buf[0] : 0;
982 display by column original ideas from ls applet,
983 very optimize by my :)
985 static void showfiles(void)
988 int column_width = 0;
989 int nfiles = num_matches;
994 /* find the longest file name- use that as the column width */
995 for (row = 0; row < nrows; row++) {
996 l = strlen(matches[row]);
997 if(add_char_to_match[row])
999 if (column_width < l)
1002 column_width += 2; /* min space for columns */
1003 ncols = cmdedit_termw / column_width;
1008 nrows++; /* round up fractionals */
1013 for (row = 0; row < nrows; row++) {
1018 for(nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
1019 str_add_chr[0] = add_char_to_match[n];
1020 acol = str_add_chr[0] ? column_width - 1 : column_width;
1021 printf("%s%s", matches[n], str_add_chr);
1022 l = strlen(matches[n]);
1028 str_add_chr[0] = add_char_to_match[n];
1029 printf("%s%s\n", matches[n], str_add_chr);
1034 static void input_tab(int *lastWasTab)
1036 /* Do TAB completion */
1037 if (lastWasTab == 0) { /* free all memory */
1039 while (num_matches > 0)
1040 free(matches[--num_matches]);
1042 matches = (char **) NULL;
1043 free(add_char_to_match);
1044 add_char_to_match = NULL;
1048 if (! *lastWasTab) {
1052 char matchBuf[BUFSIZ];
1056 *lastWasTab = TRUE; /* flop trigger */
1058 /* Make a local copy of the string -- up
1059 * to the position of the cursor */
1060 tmp = strncpy(matchBuf, command_ps, cursor);
1063 find_type = find_match(matchBuf, &recalc_pos);
1065 /* Free up any memory already allocated */
1068 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
1069 /* If the word starts with `~' and there is no slash in the word,
1070 * then try completing this word as a username. */
1072 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1073 username_tab_completion(matchBuf, NULL);
1076 /* Try to match any executable in our path and everything
1077 * in the current working directory that matches. */
1078 exe_n_cwd_tab_completion(matchBuf, find_type);
1079 /* Remove duplicate found and sort */
1084 for(i=0; i<(n-1); i++)
1085 for(j=i+1; j<n; j++)
1086 if(matches[i]!=NULL && matches[j]!=NULL) {
1087 srt = strcmp(matches[i], matches[j]);
1091 } else if(srt > 0) {
1093 matches[i] = matches[j];
1095 srt = add_char_to_match[i];
1096 add_char_to_match[i] = add_char_to_match[j];
1097 add_char_to_match[j] = srt;
1104 matches[n]=matches[i];
1105 add_char_to_match[n]=add_char_to_match[i];
1110 /* Did we find exactly one match? */
1111 if (!matches || num_matches > 1) {
1115 return; /* not found */
1116 /* find minimal match */
1117 tmp1 = bb_xstrdup(matches[0]);
1118 for (tmp = tmp1; *tmp; tmp++)
1119 for (len_found = 1; len_found < num_matches; len_found++)
1120 if (matches[len_found][(tmp - tmp1)] != *tmp) {
1124 if (*tmp1 == 0) { /* have unique */
1128 tmp = add_quote_for_spec_chars(tmp1, 0);
1130 } else { /* one match */
1131 tmp = add_quote_for_spec_chars(matches[0], add_char_to_match[0]);
1132 /* for next completion current found */
1133 *lastWasTab = FALSE;
1135 len_found = strlen(tmp);
1136 /* have space to placed match? */
1137 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1139 /* before word for match */
1140 command_ps[cursor - recalc_pos] = 0;
1141 /* save tail line */
1142 strcpy(matchBuf, command_ps + cursor);
1144 strcat(command_ps, tmp);
1146 strcat(command_ps, matchBuf);
1147 /* back to begin word for match */
1148 input_backward(recalc_pos);
1150 recalc_pos = cursor + len_found;
1152 len = strlen(command_ps);
1153 /* write out the matched command */
1154 redraw(cmdedit_y, len - recalc_pos);
1158 /* Ok -- the last char was a TAB. Since they
1159 * just hit TAB again, print a list of all the
1160 * available choices... */
1161 if (matches && num_matches > 0) {
1162 int sav_cursor = cursor; /* change goto_new_line() */
1164 /* Go to the next line */
1167 redraw(0, len - sav_cursor);
1171 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
1173 #if MAX_HISTORY >= 1
1174 static void get_previous_history(void)
1176 if(command_ps[0] != 0 || history[cur_history] == 0) {
1177 free(history[cur_history]);
1178 history[cur_history] = bb_xstrdup(command_ps);
1183 static int get_next_history(void)
1185 int ch = cur_history;
1187 if (ch < n_history) {
1188 get_previous_history(); /* save the current history line */
1189 return (cur_history = ch+1);
1196 #ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY
1197 void load_history ( const char *fromfile )
1204 for(hi = n_history; hi > 0; ) {
1206 free ( history [hi] );
1209 if (( fp = fopen ( fromfile, "r" ))) {
1211 for ( hi = 0; hi < MAX_HISTORY; ) {
1212 char * hl = bb_get_chomped_line_from_file(fp);
1220 if(l == 0 || hl[0] == ' ') {
1224 history [hi++] = hl;
1228 cur_history = n_history = hi;
1231 void save_history ( const char *tofile )
1233 FILE *fp = fopen ( tofile, "w" );
1238 for ( i = 0; i < n_history; i++ ) {
1239 fprintf(fp, "%s\n", history [i]);
1255 * This function is used to grab a character buffer
1256 * from the input file descriptor and allows you to
1257 * a string with full command editing (sort of like
1260 * The following standard commands are not implemented:
1261 * ESC-b -- Move back one word
1262 * ESC-f -- Move forward one word
1263 * ESC-d -- Delete back one word
1264 * ESC-h -- Delete forward one word
1265 * CTL-t -- Transpose two characters
1267 * Minimalist vi-style command line editing available if configured.
1268 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
1272 #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
1275 void setvimode ( int viflag )
1281 vi_Word_motion(char *command, int eat)
1283 while (cursor < len && !isspace(command[cursor]))
1285 if (eat) while (cursor < len && isspace(command[cursor]))
1290 vi_word_motion(char *command, int eat)
1292 if (isalnum(command[cursor]) || command[cursor] == '_') {
1293 while (cursor < len &&
1294 (isalnum(command[cursor+1]) ||
1295 command[cursor+1] == '_'))
1297 } else if (ispunct(command[cursor])) {
1298 while (cursor < len &&
1299 (ispunct(command[cursor+1])))
1306 if (eat && cursor < len && isspace(command[cursor]))
1307 while (cursor < len && isspace(command[cursor]))
1312 vi_End_motion(char *command)
1315 while (cursor < len && isspace(command[cursor]))
1317 while (cursor < len-1 && !isspace(command[cursor+1]))
1322 vi_end_motion(char *command)
1324 if (cursor >= len-1)
1327 while (cursor < len-1 && isspace(command[cursor]))
1329 if (cursor >= len-1)
1331 if (isalnum(command[cursor]) || command[cursor] == '_') {
1332 while (cursor < len-1 &&
1333 (isalnum(command[cursor+1]) ||
1334 command[cursor+1] == '_'))
1336 } else if (ispunct(command[cursor])) {
1337 while (cursor < len-1 &&
1338 (ispunct(command[cursor+1])))
1344 vi_Back_motion(char *command)
1346 while (cursor > 0 && isspace(command[cursor-1]))
1348 while (cursor > 0 && !isspace(command[cursor-1]))
1353 vi_back_motion(char *command)
1358 while (cursor > 0 && isspace(command[cursor]))
1362 if (isalnum(command[cursor]) || command[cursor] == '_') {
1363 while (cursor > 0 &&
1364 (isalnum(command[cursor-1]) ||
1365 command[cursor-1] == '_'))
1367 } else if (ispunct(command[cursor])) {
1368 while (cursor > 0 &&
1369 (ispunct(command[cursor-1])))
1376 * the emacs and vi modes share much of the code in the big
1377 * command loop. commands entered when in vi's command mode (aka
1378 * "escape mode") get an extra bit added to distinguish them --
1379 * this keeps them from being self-inserted. this clutters the
1380 * big switch a bit, but keeps all the code in one place.
1385 /* leave out the "vi-mode"-only case labels if vi editing isn't
1387 #define vi_case(caselabel) USE_FEATURE_COMMAND_EDITING(caselabel)
1389 /* convert uppercase ascii to equivalent control char, for readability */
1390 #define CNTRL(uc_char) ((uc_char) - 0x40)
1393 int cmdedit_read_input(char *prompt, char command[BUFSIZ])
1397 int lastWasTab = FALSE;
1400 #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
1404 /* prepare before init handlers */
1405 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1407 command_ps = command;
1409 getTermSettings(0, (void *) &initial_settings);
1410 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1411 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1412 /* Turn off echoing and CTRL-C, so we can trap it */
1413 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1414 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1415 new_settings.c_cc[VMIN] = 1;
1416 new_settings.c_cc[VTIME] = 0;
1417 /* Turn off CTRL-C, so we can trap it */
1418 # ifndef _POSIX_VDISABLE
1419 # define _POSIX_VDISABLE '\0'
1421 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1424 setTermSettings(0, (void *) &new_settings);
1425 handlers_sets |= SET_RESET_TERM;
1427 /* Now initialize things */
1429 /* Print out the command prompt */
1430 parse_prompt(prompt);
1434 fflush(stdout); /* buffered out to fast */
1436 if (safe_read(0, &c, 1) < 1)
1437 /* if we can't read input then exit */
1438 goto prepare_to_die;
1442 #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
1451 vi_case( case '\n'|vbit: )
1452 vi_case( case '\r'|vbit: )
1458 vi_case( case '0'|vbit: )
1459 /* Control-a -- Beginning of line */
1460 input_backward(cursor);
1463 vi_case( case 'h'|vbit: )
1464 vi_case( case '\b'|vbit: )
1465 vi_case( case DEL|vbit: )
1466 /* Control-b -- Move back one character */
1470 vi_case( case CNTRL('C')|vbit: )
1471 /* Control-c -- stop gathering input */
1480 break_out = -1; /* to control traps */
1484 /* Control-d -- Delete one character, or exit
1485 * if the len=0 and no chars to delete */
1489 #if !defined(CONFIG_ASH)
1492 /* cmdedit_reset_term() called in atexit */
1495 /* to control stopped jobs */
1496 len = break_out = -1;
1504 vi_case( case '$'|vbit: )
1505 /* Control-e -- End of line */
1509 vi_case( case 'l'|vbit: )
1510 vi_case( case ' '|vbit: )
1511 /* Control-f -- Move forward one character */
1516 /* Control-h and DEL */
1520 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1521 input_tab(&lastWasTab);
1525 /* Control-k -- clear to end of line */
1526 *(command + cursor) = 0;
1531 vi_case( case CNTRL('L')|vbit: )
1532 /* Control-l -- clear screen */
1534 redraw(0, len-cursor);
1536 #if MAX_HISTORY >= 1
1538 vi_case( case CNTRL('N')|vbit: )
1539 vi_case( case 'j'|vbit: )
1540 /* Control-n -- Get next command in history */
1541 if (get_next_history())
1545 vi_case( case CNTRL('P')|vbit: )
1546 vi_case( case 'k'|vbit: )
1547 /* Control-p -- Get previous command from history */
1548 if (cur_history > 0) {
1549 get_previous_history();
1557 vi_case( case CNTRL('U')|vbit: )
1558 /* Control-U -- Clear line before cursor */
1560 strcpy(command, command + cursor);
1561 redraw(cmdedit_y, len -= cursor);
1565 vi_case( case CNTRL('W')|vbit: )
1566 /* Control-W -- Remove the last word */
1567 while (cursor > 0 && isspace(command[cursor-1]))
1569 while (cursor > 0 &&!isspace(command[cursor-1]))
1572 #if CONFIG_FEATURE_COMMAND_EDITING_VI
1577 input_backward(cursor);
1598 vi_Word_motion(command, 1);
1601 vi_word_motion(command, 1);
1604 vi_End_motion(command);
1607 vi_end_motion(command);
1610 vi_Back_motion(command);
1613 vi_back_motion(command);
1629 if (safe_read(0, &c, 1) < 1)
1630 goto prepare_to_die;
1631 if (c == (prevc & 0xff)) {
1633 input_backward(cursor);
1643 case 'w': /* "dw", "cw" */
1644 vi_word_motion(command, vi_cmdmode);
1646 case 'W': /* 'dW', 'cW' */
1647 vi_Word_motion(command, vi_cmdmode);
1649 case 'e': /* 'de', 'ce' */
1650 vi_end_motion(command);
1653 case 'E': /* 'dE', 'cE' */
1654 vi_End_motion(command);
1659 input_backward(cursor - sc);
1660 while (nc-- > cursor)
1663 case 'b': /* "db", "cb" */
1664 case 'B': /* implemented as B */
1666 vi_back_motion(command);
1668 vi_Back_motion(command);
1669 while (sc-- > cursor)
1672 case ' ': /* "d ", "c " */
1675 case '$': /* "d$", "c$" */
1677 while (cursor < len)
1690 if (safe_read(0, &c, 1) < 1)
1691 goto prepare_to_die;
1695 *(command + cursor) = c;
1700 #endif /* CONFIG_FEATURE_COMMAND_EDITING_VI */
1704 #if CONFIG_FEATURE_COMMAND_EDITING_VI
1706 /* ESC: insert mode --> command mode */
1712 /* escape sequence follows */
1713 if (safe_read(0, &c, 1) < 1)
1714 goto prepare_to_die;
1715 /* different vt100 emulations */
1716 if (c == '[' || c == 'O') {
1717 vi_case( case '['|vbit: )
1718 vi_case( case 'O'|vbit: )
1719 if (safe_read(0, &c, 1) < 1)
1720 goto prepare_to_die;
1722 if (c >= '1' && c <= '9') {
1723 unsigned char dummy;
1725 if (safe_read(0, &dummy, 1) < 1)
1726 goto prepare_to_die;
1731 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1732 case '\t': /* Alt-Tab */
1734 input_tab(&lastWasTab);
1737 #if MAX_HISTORY >= 1
1739 /* Up Arrow -- Get previous command from history */
1740 if (cur_history > 0) {
1741 get_previous_history();
1748 /* Down Arrow -- Get next command in history */
1749 if (!get_next_history())
1751 /* Rewrite the line with the selected history item */
1753 /* change command */
1754 len = strlen(strcpy(command, history[cur_history]));
1755 /* redraw and go to eol (bol, in vi */
1756 #if CONFIG_FEATURE_COMMAND_EDITING_VI
1757 redraw(cmdedit_y, vi_mode ? 9999:0);
1759 redraw(cmdedit_y, 0);
1764 /* Right Arrow -- Move forward one character */
1768 /* Left Arrow -- Move back one character */
1778 input_backward(cursor);
1791 default: /* If it's regular input, do the normal thing */
1792 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1793 /* Control-V -- Add non-printable symbol */
1794 if (c == CNTRL('V')) {
1795 if (safe_read(0, &c, 1) < 1)
1796 goto prepare_to_die;
1804 #if CONFIG_FEATURE_COMMAND_EDITING_VI
1805 if (vi_cmdmode) /* don't self-insert */
1808 if (!Isprint(c)) /* Skip non-printable characters */
1812 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1817 if (cursor == (len - 1)) { /* Append if at the end of the line */
1818 *(command + cursor) = c;
1819 *(command + cursor + 1) = 0;
1820 cmdedit_set_out_char(0);
1821 } else { /* Insert otherwise */
1824 memmove(command + sc + 1, command + sc, len - sc);
1825 *(command + sc) = c;
1827 /* rewrite from cursor */
1829 /* to prev x pos + 1 */
1830 input_backward(cursor - sc);
1835 if (break_out) /* Enter is the command terminator, no more input. */
1842 setTermSettings(0, (void *) &initial_settings);
1843 handlers_sets &= ~SET_RESET_TERM;
1845 #if MAX_HISTORY >= 1
1846 /* Handle command history log */
1847 /* cleanup may be saved current command line */
1848 if (len> 0) { /* no put empty line */
1851 free(history[MAX_HISTORY]);
1852 history[MAX_HISTORY] = 0;
1853 /* After max history, remove the oldest command */
1854 if (i >= MAX_HISTORY) {
1856 for(i = 0; i < (MAX_HISTORY-1); i++)
1857 history[i] = history[i+1];
1859 history[i++] = bb_xstrdup(command);
1862 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1866 #else /* MAX_HISTORY < 1 */
1867 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1868 if (len > 0) { /* no put empty line */
1872 #endif /* MAX_HISTORY >= 1 */
1873 if (break_out > 0) {
1874 command[len++] = '\n'; /* set '\n' */
1877 #if defined(CONFIG_FEATURE_CLEAN_UP) && defined(CONFIG_FEATURE_COMMAND_TAB_COMPLETION)
1878 input_tab(0); /* strong free */
1880 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1881 free(cmdedit_prompt);
1883 cmdedit_reset_term();
1889 #endif /* CONFIG_FEATURE_COMMAND_EDITING */
1894 const char *bb_applet_name = "debug stuff usage";
1896 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1900 int main(int argc, char **argv)
1904 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1905 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1906 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1907 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1912 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1913 setlocale(LC_ALL, "");
1917 l = cmdedit_read_input(prompt, buff);
1918 if(l > 0 && buff[l-1] == '\n') {
1920 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1925 printf("*** cmdedit_read_input() detect ^D\n");