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
39 #include <sys/ioctl.h>
46 #include "../shell/cmdedit.h"
49 #ifdef CONFIG_LOCALE_SUPPORT
50 #define Isprint(c) isprint((c))
52 #define Isprint(c) ( (c) >= ' ' && (c) != ((unsigned char)'\233') )
57 /* pretect redefined for test */
58 #undef CONFIG_FEATURE_COMMAND_EDITING
59 #undef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
60 #undef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
61 #undef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
62 #undef CONFIG_FEATURE_CLEAN_UP
64 #define CONFIG_FEATURE_COMMAND_EDITING
65 #define CONFIG_FEATURE_COMMAND_TAB_COMPLETION
66 #define CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
67 #define CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
68 #define CONFIG_FEATURE_CLEAN_UP
72 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
77 #ifdef CONFIG_FEATURE_COMMAND_EDITING
79 #if defined(CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION) || defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
80 #define CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
83 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
85 #endif /* advanced FEATURES */
88 /* Maximum length of the linked list for the command line history */
89 #ifndef CONFIG_FEATURE_COMMAND_HISTORY
90 #define MAX_HISTORY 15
92 #define MAX_HISTORY CONFIG_FEATURE_COMMAND_HISTORY
96 #warning cmdedit: You set MAX_HISTORY < 1. The history algorithm switched off.
98 static char *history[MAX_HISTORY+1]; /* history + current */
99 /* saved history lines */
100 static int n_history;
101 /* current pointer to history line */
102 static int cur_history;
106 #define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
107 #define getTermSettings(fd,argp) tcgetattr(fd, argp);
109 /* Current termio and the previous termio before starting sh */
110 static struct termios initial_settings, new_settings;
114 volatile int cmdedit_termw = 80; /* actual terminal width */
116 volatile int handlers_sets = 0; /* Set next bites: */
119 SET_ATEXIT = 1, /* when atexit() has been called
120 and get euid,uid,gid to fast compare */
121 SET_WCHG_HANDLERS = 2, /* winchg signal handler */
122 SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */
126 static int cmdedit_x; /* real x terminal position */
127 static int cmdedit_y; /* pseudoreal y terminal position */
128 static int cmdedit_prmt_len; /* lenght prompt without colores string */
130 static int cursor; /* required global for signal handler */
131 static int len; /* --- "" - - "" - -"- --""-- --""--- */
132 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
134 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
137 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
139 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
140 static char *user_buf = "";
141 static char *home_pwd_buf = "";
145 #ifdef CONFIG_FEATURE_SH_FANCY_PROMPT
146 static char *hostname_buf;
147 static int num_ok_lines = 1;
151 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
153 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
160 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
162 static void cmdedit_setwidth(int w, int redraw_flg);
164 static void win_changed(int nsig)
166 static sighandler_t previous_SIGWINCH_handler; /* for reset */
168 /* emulate || signal call */
169 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
171 get_terminal_width_height(0, &width, NULL);
172 cmdedit_setwidth(width, nsig == SIGWINCH);
174 /* Unix not all standart in recall signal */
176 if (nsig == -SIGWINCH) /* save previous handler */
177 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
178 else if (nsig == SIGWINCH) /* signaled called handler */
179 signal(SIGWINCH, win_changed); /* set for next call */
181 /* set previous handler */
182 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
185 static void cmdedit_reset_term(void)
187 if ((handlers_sets & SET_RESET_TERM) != 0) {
188 /* sparc and other have broken termios support: use old termio handling. */
189 setTermSettings(STDIN_FILENO, (void *) &initial_settings);
190 handlers_sets &= ~SET_RESET_TERM;
192 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
193 /* reset SIGWINCH handler to previous (default) */
195 handlers_sets &= ~SET_WCHG_HANDLERS;
201 /* special for recount position for scroll and remove terminal margin effect */
202 static void cmdedit_set_out_char(int next_char)
205 int c = (int)((unsigned char) command_ps[cursor]);
208 c = ' '; /* destroy end char? */
209 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
210 if (!Isprint(c)) { /* Inverse put non-printable characters */
217 printf("\033[7m%c\033[0m", c);
221 if (++cmdedit_x >= cmdedit_termw) {
222 /* terminal is scrolled down */
228 /* destroy "(auto)margin" */
235 /* Move to end line. Bonus: rewrite line from cursor */
236 static void input_end(void)
239 cmdedit_set_out_char(0);
242 /* Go to the next line */
243 static void goto_new_line(void)
251 static inline void out1str(const char *s)
257 static inline void beep(void)
262 /* Move back one character */
263 /* special for slow terminal */
264 static void input_backward(int num)
268 cursor -= num; /* new cursor (in command, not terminal) */
270 if (cmdedit_x >= num) { /* no to up line */
277 printf("\033[%dD", num);
282 putchar('\r'); /* back to first terminal pos. */
283 num -= cmdedit_x; /* set previous backward */
285 count_y = 1 + num / cmdedit_termw;
286 printf("\033[%dA", count_y);
287 cmdedit_y -= count_y;
288 /* require forward after uping */
289 cmdedit_x = cmdedit_termw * count_y - num;
290 printf("\033[%dC", cmdedit_x); /* set term cursor */
294 static void put_prompt(void)
296 out1str(cmdedit_prompt);
297 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
299 cmdedit_y = 0; /* new quasireal y */
302 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
303 static void parse_prompt(const char *prmt_ptr)
305 cmdedit_prompt = prmt_ptr;
306 cmdedit_prmt_len = strlen(prmt_ptr);
310 static void parse_prompt(const char *prmt_ptr)
313 size_t cur_prmt_len = 0;
314 char flg_not_length = '[';
315 char *prmt_mem_ptr = xcalloc(1, 1);
316 char *pwd_buf = xgetcwd(0);
317 char buf2[PATH_MAX + 1];
323 pwd_buf=(char *)bb_msg_unknown;
331 const char *cp = prmt_ptr;
334 c = bb_process_escape_sequence(&prmt_ptr);
340 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
348 pbuf = xcalloc(256, 1);
349 if (gethostname(pbuf, 255) < 0) {
352 char *s = strchr(pbuf, '.');
361 c = my_euid == 0 ? '#' : '$';
363 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
366 l = strlen(home_pwd_buf);
367 if (home_pwd_buf[0] != 0 &&
368 strncmp(home_pwd_buf, pbuf, l) == 0 &&
369 (pbuf[l]=='/' || pbuf[l]=='\0') &&
370 strlen(pwd_buf+l)<PATH_MAX) {
373 strcpy(pbuf+1, pwd_buf+l);
379 cp = strrchr(pbuf,'/');
380 if ( (cp != NULL) && (cp != pbuf) )
384 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
386 case 'e': case 'E': /* \e \E = \033 */
390 for (l = 0; l < 3;) {
392 buf2[l++] = *prmt_ptr;
394 h = strtol(buf2, &pbuf, 16);
395 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
402 c = (char)strtol(buf2, 0, 16);
408 if (c == flg_not_length) {
409 flg_not_length = flg_not_length == '[' ? ']' : '[';
418 cur_prmt_len = strlen(pbuf);
419 prmt_len += cur_prmt_len;
420 if (flg_not_length != ']')
421 cmdedit_prmt_len += cur_prmt_len;
422 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
424 if(pwd_buf!=(char *)bb_msg_unknown)
426 cmdedit_prompt = prmt_mem_ptr;
432 /* draw prompt, editor line, and clear tail */
433 static void redraw(int y, int back_cursor)
435 if (y > 0) /* up to start y */
436 printf("\033[%dA", y);
439 input_end(); /* rewrite */
440 printf("\033[J"); /* destroy tail after cursor */
441 input_backward(back_cursor);
444 #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
445 static char delbuf[BUFSIZ]; /* a place to store deleted characters */
446 static char *delp = delbuf;
447 static int 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
465 if (delp - delbuf < BUFSIZ)
466 *delp++ = command_ps[j];
470 strcpy(command_ps + j, command_ps + j + 1);
472 input_end(); /* rewrite new line */
473 cmdedit_set_out_char(0); /* destroy end char */
474 input_backward(cursor - j); /* back to old pos cursor */
477 #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
478 static void put(void)
480 int ocursor, j = delp - delbuf;
484 /* open hole and then fill it */
485 memmove(command_ps + cursor + j, command_ps + cursor, len - cursor + 1);
486 strncpy(command_ps + cursor, delbuf, j);
488 input_end(); /* rewrite new line */
489 input_backward(cursor-ocursor-j+1); /* at end of new text */
493 /* Delete the char in back of the cursor */
494 static void input_backspace(void)
503 /* Move forward one character */
504 static void input_forward(void)
507 cmdedit_set_out_char(command_ps[cursor + 1]);
510 static void cmdedit_setwidth(int w, int redraw_flg)
512 cmdedit_termw = cmdedit_prmt_len + 2;
513 if (w <= cmdedit_termw) {
514 cmdedit_termw = cmdedit_termw % w;
516 if (w > cmdedit_termw) {
520 /* new y for current cursor */
521 int new_y = (cursor + cmdedit_prmt_len) / w;
524 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
530 static void cmdedit_init(void)
532 cmdedit_prmt_len = 0;
533 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
534 /* emulate usage handler to set handler and call yours work */
535 win_changed(-SIGWINCH);
536 handlers_sets |= SET_WCHG_HANDLERS;
539 if ((handlers_sets & SET_ATEXIT) == 0) {
540 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
541 struct passwd *entry;
544 entry = getpwuid(my_euid);
546 user_buf = bb_xstrdup(entry->pw_name);
547 home_pwd_buf = bb_xstrdup(entry->pw_dir);
551 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
553 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
558 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
559 handlers_sets |= SET_ATEXIT;
560 atexit(cmdedit_reset_term); /* be sure to do this only once */
564 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
566 static char **matches;
567 static int num_matches;
568 static char *add_char_to_match;
570 static void add_match(char *matched, int add_char)
572 int nm = num_matches;
575 matches = xrealloc(matches, nm1 * sizeof(char *));
576 add_char_to_match = xrealloc(add_char_to_match, nm1);
577 matches[nm] = matched;
578 add_char_to_match[nm] = (char)add_char;
582 static int is_execute(const struct stat *st)
584 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
585 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
586 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
587 (st->st_mode & S_IXOTH)) return TRUE;
591 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
593 static void username_tab_completion(char *ud, char *with_shash_flg)
595 struct passwd *entry;
598 ud++; /* ~user/... to user/... */
599 userlen = strlen(ud);
601 if (with_shash_flg) { /* "~/..." or "~user/..." */
602 char *sav_ud = ud - 1;
606 if (*ud == '/') { /* "~/..." */
610 temp = strchr(ud, '/');
611 *temp = 0; /* ~user\0 */
612 entry = getpwnam(ud);
613 *temp = '/'; /* restore ~user/... */
616 home = entry->pw_dir;
619 if ((userlen + strlen(home) + 1) < BUFSIZ) {
620 char temp2[BUFSIZ]; /* argument size */
623 sprintf(temp2, "%s%s", home, ud);
624 strcpy(sav_ud, temp2);
631 while ((entry = getpwent()) != NULL) {
632 /* Null usernames should result in all users as possible completions. */
633 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
634 add_match(bb_xasprintf("~%s", entry->pw_name), '/');
641 #endif /* CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION */
650 const char *cmdedit_path_lookup;
652 #define cmdedit_path_lookup getenv("PATH")
655 static int path_parse(char ***p, int flags)
661 /* if not setenv PATH variable, to search cur dir "." */
662 if (flags != FIND_EXE_ONLY || (pth = cmdedit_path_lookup) == 0 ||
663 /* PATH=<empty> or PATH=:<empty> */
664 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
672 npth++; /* count words is + 1 count ':' */
673 tmp = strchr(tmp, ':');
676 break; /* :<empty> */
681 *p = xmalloc(npth * sizeof(char *));
684 (*p)[0] = bb_xstrdup(tmp);
685 npth = 1; /* count words is + 1 count ':' */
688 tmp = strchr(tmp, ':');
690 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
692 break; /* :<empty> */
695 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
701 static char *add_quote_for_spec_chars(char *found, int add)
704 char *s = xmalloc((strlen(found) + 1) * 2);
707 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
717 static void exe_n_cwd_tab_completion(char *command, int type)
724 char **paths = path1;
728 char *pfind = strrchr(command, '/');
733 /* no dir, if flags==EXE_ONLY - get paths, else "." */
734 npaths = path_parse(&paths, type);
738 /* save for change */
739 strcpy(dirbuf, command);
741 dirbuf[(pfind - command) + 1] = 0;
742 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
743 if (dirbuf[0] == '~') /* ~/... or ~user/... */
744 username_tab_completion(dirbuf, dirbuf);
746 /* "strip" dirname in command */
750 npaths = 1; /* only 1 dir */
753 for (i = 0; i < npaths; i++) {
755 dir = opendir(paths[i]);
756 if (!dir) /* Don't print an error */
759 while ((next = readdir(dir)) != NULL) {
760 char *str_found = next->d_name;
764 if (strncmp(str_found, pfind, strlen(pfind)))
766 /* not see .name without .match */
767 if (*str_found == '.' && *pfind == 0) {
768 if (*paths[i] == '/' && paths[i][1] == 0
769 && str_found[1] == 0) str_found = ""; /* only "/" */
773 found = concat_path_file(paths[i], str_found);
774 /* hmm, remover in progress? */
775 if (stat(found, &st) < 0)
777 /* find with dirs ? */
778 if (paths[i] != dirbuf)
779 strcpy(found, next->d_name); /* only name */
780 if (S_ISDIR(st.st_mode)) {
781 /* name is directory */
782 char *e = found + strlen(found) - 1;
788 /* not put found file if search only dirs for cd */
789 if (type == FIND_DIR_ONLY)
791 if (type == FIND_FILE_ONLY ||
792 (type == FIND_EXE_ONLY && is_execute(&st)))
795 /* Add it to the list */
796 add_match(found, add_chr);
803 if (paths != path1) {
804 free(paths[0]); /* allocated memory only in first member */
810 #define QUOT (UCHAR_MAX+1)
812 #define collapse_pos(is, in) { \
813 memmove(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
814 memmove(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
816 static int find_match(char *matchBuf, int *len_with_quotes)
821 int int_buf[BUFSIZ + 1];
822 int pos_buf[BUFSIZ + 1];
824 /* set to integer dimension characters and own positions */
826 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
827 if (int_buf[i] == 0) {
828 pos_buf[i] = -1; /* indicator end line */
834 /* mask \+symbol and convert '\t' to ' ' */
835 for (i = j = 0; matchBuf[i]; i++, j++)
836 if (matchBuf[i] == '\\') {
837 collapse_pos(j, j + 1);
840 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
841 if (matchBuf[i] == '\t') /* algorithm equivalent */
842 int_buf[j] = ' ' | QUOT;
845 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
846 else if (matchBuf[i] == '\t')
850 /* mask "symbols" or 'symbols' */
852 for (i = 0; int_buf[i]; i++) {
854 if (c == '\'' || c == '"') {
863 } else if (c2 != 0 && c != '$')
867 /* skip commands with arguments if line have commands delimiters */
868 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
869 for (i = 0; int_buf[i]; i++) {
872 j = i ? int_buf[i - 1] : -1;
874 if (c == ';' || c == '&' || c == '|') {
875 command_mode = 1 + (c == c2);
877 if (j == '>' || j == '<')
879 } else if (c == '|' && j == '>')
883 collapse_pos(0, i + command_mode);
884 i = -1; /* hack incremet */
887 /* collapse `command...` */
888 for (i = 0; int_buf[i]; i++)
889 if (int_buf[i] == '`') {
890 for (j = i + 1; int_buf[j]; j++)
891 if (int_buf[j] == '`') {
892 collapse_pos(i, j + 1);
897 /* not found close ` - command mode, collapse all previous */
898 collapse_pos(0, i + 1);
901 i--; /* hack incremet */
904 /* collapse (command...(command...)...) or {command...{command...}...} */
905 c = 0; /* "recursive" level */
907 for (i = 0; int_buf[i]; i++)
908 if (int_buf[i] == '(' || int_buf[i] == '{') {
909 if (int_buf[i] == '(')
913 collapse_pos(0, i + 1);
914 i = -1; /* hack incremet */
916 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
917 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
918 if (int_buf[i] == ')')
922 collapse_pos(0, i + 1);
923 i = -1; /* hack incremet */
926 /* skip first not quote space */
927 for (i = 0; int_buf[i]; i++)
928 if (int_buf[i] != ' ')
933 /* set find mode for completion */
934 command_mode = FIND_EXE_ONLY;
935 for (i = 0; int_buf[i]; i++)
936 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
937 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
938 && matchBuf[pos_buf[0]]=='c'
939 && matchBuf[pos_buf[1]]=='d' )
940 command_mode = FIND_DIR_ONLY;
942 command_mode = FIND_FILE_ONLY;
947 for (i = 0; int_buf[i]; i++);
949 for (--i; i >= 0; i--) {
951 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
952 collapse_pos(0, i + 1);
956 /* skip first not quoted '\'' or '"' */
957 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
958 /* collapse quote or unquote // or /~ */
959 while ((int_buf[i] & ~QUOT) == '/' &&
960 ((int_buf[i + 1] & ~QUOT) == '/'
961 || (int_buf[i + 1] & ~QUOT) == '~')) {
965 /* set only match and destroy quotes */
967 for (c = 0; pos_buf[i] >= 0; i++) {
968 matchBuf[c++] = matchBuf[pos_buf[i]];
972 /* old lenght matchBuf with quotes symbols */
973 *len_with_quotes = j ? j - pos_buf[0] : 0;
979 display by column original ideas from ls applet,
980 very optimize by my :)
982 static void showfiles(void)
985 int column_width = 0;
986 int nfiles = num_matches;
991 /* find the longest file name- use that as the column width */
992 for (row = 0; row < nrows; row++) {
993 l = strlen(matches[row]);
994 if(add_char_to_match[row])
996 if (column_width < l)
999 column_width += 2; /* min space for columns */
1000 ncols = cmdedit_termw / column_width;
1005 nrows++; /* round up fractionals */
1010 for (row = 0; row < nrows; row++) {
1015 for(nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
1016 str_add_chr[0] = add_char_to_match[n];
1017 acol = str_add_chr[0] ? column_width - 1 : column_width;
1018 printf("%s%s", matches[n], str_add_chr);
1019 l = strlen(matches[n]);
1025 str_add_chr[0] = add_char_to_match[n];
1026 printf("%s%s\n", matches[n], str_add_chr);
1031 static void input_tab(int *lastWasTab)
1033 /* Do TAB completion */
1034 if (lastWasTab == 0) { /* free all memory */
1036 while (num_matches > 0)
1037 free(matches[--num_matches]);
1039 matches = (char **) NULL;
1040 free(add_char_to_match);
1041 add_char_to_match = NULL;
1045 if (! *lastWasTab) {
1049 char matchBuf[BUFSIZ];
1053 *lastWasTab = TRUE; /* flop trigger */
1055 /* Make a local copy of the string -- up
1056 * to the position of the cursor */
1057 tmp = strncpy(matchBuf, command_ps, cursor);
1060 find_type = find_match(matchBuf, &recalc_pos);
1062 /* Free up any memory already allocated */
1065 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
1066 /* If the word starts with `~' and there is no slash in the word,
1067 * then try completing this word as a username. */
1069 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1070 username_tab_completion(matchBuf, NULL);
1073 /* Try to match any executable in our path and everything
1074 * in the current working directory that matches. */
1075 exe_n_cwd_tab_completion(matchBuf, find_type);
1076 /* Remove duplicate found and sort */
1081 for(i=0; i<(n-1); i++)
1082 for(j=i+1; j<n; j++)
1083 if(matches[i]!=NULL && matches[j]!=NULL) {
1084 srt = strcmp(matches[i], matches[j]);
1088 } else if(srt > 0) {
1090 matches[i] = matches[j];
1092 srt = add_char_to_match[i];
1093 add_char_to_match[i] = add_char_to_match[j];
1094 add_char_to_match[j] = srt;
1101 matches[n]=matches[i];
1102 add_char_to_match[n]=add_char_to_match[i];
1107 /* Did we find exactly one match? */
1108 if (!matches || num_matches > 1) {
1112 return; /* not found */
1113 /* find minimal match */
1114 tmp1 = bb_xstrdup(matches[0]);
1115 for (tmp = tmp1; *tmp; tmp++)
1116 for (len_found = 1; len_found < num_matches; len_found++)
1117 if (matches[len_found][(tmp - tmp1)] != *tmp) {
1121 if (*tmp1 == 0) { /* have unique */
1125 tmp = add_quote_for_spec_chars(tmp1, 0);
1127 } else { /* one match */
1128 tmp = add_quote_for_spec_chars(matches[0], add_char_to_match[0]);
1129 /* for next completion current found */
1130 *lastWasTab = FALSE;
1132 len_found = strlen(tmp);
1133 /* have space to placed match? */
1134 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1136 /* before word for match */
1137 command_ps[cursor - recalc_pos] = 0;
1138 /* save tail line */
1139 strcpy(matchBuf, command_ps + cursor);
1141 strcat(command_ps, tmp);
1143 strcat(command_ps, matchBuf);
1144 /* back to begin word for match */
1145 input_backward(recalc_pos);
1147 recalc_pos = cursor + len_found;
1149 len = strlen(command_ps);
1150 /* write out the matched command */
1151 redraw(cmdedit_y, len - recalc_pos);
1155 /* Ok -- the last char was a TAB. Since they
1156 * just hit TAB again, print a list of all the
1157 * available choices... */
1158 if (matches && num_matches > 0) {
1159 int sav_cursor = cursor; /* change goto_new_line() */
1161 /* Go to the next line */
1164 redraw(0, len - sav_cursor);
1168 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
1170 #if MAX_HISTORY >= 1
1171 static void get_previous_history(void)
1173 if(command_ps[0] != 0 || history[cur_history] == 0) {
1174 free(history[cur_history]);
1175 history[cur_history] = bb_xstrdup(command_ps);
1180 static int get_next_history(void)
1182 int ch = cur_history;
1184 if (ch < n_history) {
1185 get_previous_history(); /* save the current history line */
1186 return (cur_history = ch+1);
1193 #ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY
1194 extern void load_history ( const char *fromfile )
1201 for(hi = n_history; hi > 0; ) {
1203 free ( history [hi] );
1206 if (( fp = fopen ( fromfile, "r" ))) {
1208 for ( hi = 0; hi < MAX_HISTORY; ) {
1209 char * hl = bb_get_chomped_line_from_file(fp);
1217 if(l == 0 || hl[0] == ' ') {
1221 history [hi++] = hl;
1225 cur_history = n_history = hi;
1228 extern void save_history ( const char *tofile )
1230 FILE *fp = fopen ( tofile, "w" );
1235 for ( i = 0; i < n_history; i++ ) {
1236 fprintf(fp, "%s\n", history [i]);
1252 * This function is used to grab a character buffer
1253 * from the input file descriptor and allows you to
1254 * a string with full command editing (sort of like
1257 * The following standard commands are not implemented:
1258 * ESC-b -- Move back one word
1259 * ESC-f -- Move forward one word
1260 * ESC-d -- Delete back one word
1261 * ESC-h -- Delete forward one word
1262 * CTL-t -- Transpose two characters
1264 * Minimalist vi-style command line editing available if configured.
1265 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
1269 #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
1272 void setvimode ( int viflag )
1278 vi_Word_motion(char *command, int eat)
1280 while (cursor < len && !isspace(command[cursor]))
1282 if (eat) while (cursor < len && isspace(command[cursor]))
1287 vi_word_motion(char *command, int eat)
1289 if (isalnum(command[cursor]) || command[cursor] == '_') {
1290 while (cursor < len &&
1291 (isalnum(command[cursor+1]) ||
1292 command[cursor+1] == '_'))
1294 } else if (ispunct(command[cursor])) {
1295 while (cursor < len &&
1296 (ispunct(command[cursor+1])))
1303 if (eat && cursor < len && isspace(command[cursor]))
1304 while (cursor < len && isspace(command[cursor]))
1309 vi_End_motion(char *command)
1312 while (cursor < len && isspace(command[cursor]))
1314 while (cursor < len-1 && !isspace(command[cursor+1]))
1319 vi_end_motion(char *command)
1321 if (cursor >= len-1)
1324 while (cursor < len-1 && isspace(command[cursor]))
1326 if (cursor >= len-1)
1328 if (isalnum(command[cursor]) || command[cursor] == '_') {
1329 while (cursor < len-1 &&
1330 (isalnum(command[cursor+1]) ||
1331 command[cursor+1] == '_'))
1333 } else if (ispunct(command[cursor])) {
1334 while (cursor < len-1 &&
1335 (ispunct(command[cursor+1])))
1341 vi_Back_motion(char *command)
1343 while (cursor > 0 && isspace(command[cursor-1]))
1345 while (cursor > 0 && !isspace(command[cursor-1]))
1350 vi_back_motion(char *command)
1355 while (cursor > 0 && isspace(command[cursor]))
1359 if (isalnum(command[cursor]) || command[cursor] == '_') {
1360 while (cursor > 0 &&
1361 (isalnum(command[cursor-1]) ||
1362 command[cursor-1] == '_'))
1364 } else if (ispunct(command[cursor])) {
1365 while (cursor > 0 &&
1366 (ispunct(command[cursor-1])))
1373 * the normal emacs mode and vi's insert mode are the same.
1374 * commands entered when in vi command mode ("escape mode") get
1375 * an extra bit added to distinguish them. this lets them share
1376 * much of the code in the big switch and while loop. i
1377 * experimented with an ugly macro to make the case labels for
1378 * these cases go away entirely when vi mode isn't configured, in
1379 * hopes of letting the jump tables get smaller:
1380 * #define vcase(caselabel) caselabel
1383 * case vcase(VICMD('0'):)
1384 * but it didn't seem to make any difference in code size,
1385 * and the macro-ized code was too ugly.
1388 #define VI_cmdbit 0x100
1389 #define VICMD(somecmd) ((somecmd)|VI_cmdbit)
1391 /* convert uppercase ascii to equivalent control char, for readability */
1392 #define CNTRL(uc_char) ((uc_char) - 0x40)
1395 int cmdedit_read_input(char *prompt, char command[BUFSIZ])
1399 int lastWasTab = FALSE;
1401 #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
1402 unsigned int ic, prevc;
1405 /* prepare before init handlers */
1406 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1408 command_ps = command;
1410 getTermSettings(0, (void *) &initial_settings);
1411 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1412 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1413 /* Turn off echoing and CTRL-C, so we can trap it */
1414 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1415 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1416 new_settings.c_cc[VMIN] = 1;
1417 new_settings.c_cc[VTIME] = 0;
1418 /* Turn off CTRL-C, so we can trap it */
1419 # ifndef _POSIX_VDISABLE
1420 # define _POSIX_VDISABLE '\0'
1422 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1425 setTermSettings(0, (void *) &new_settings);
1426 handlers_sets |= SET_RESET_TERM;
1428 /* Now initialize things */
1430 /* Print out the command prompt */
1431 parse_prompt(prompt);
1435 fflush(stdout); /* buffered out to fast */
1437 if (safe_read(0, &c, 1) < 1)
1438 /* if we can't read input then exit */
1439 goto prepare_to_die;
1441 #ifdef CONFIG_FEATURE_COMMAND_EDITING_VI
1461 /* Control-a -- Beginning of line */
1462 input_backward(cursor);
1468 /* Control-b -- Move back one character */
1472 case VICMD(CNTRL('C')):
1473 /* Control-c -- stop gathering input */
1482 break_out = -1; /* to control traps */
1486 /* Control-d -- Delete one character, or exit
1487 * if the len=0 and no chars to delete */
1491 #if !defined(CONFIG_ASH)
1494 /* cmdedit_reset_term() called in atexit */
1497 /* to control stopped jobs */
1498 len = break_out = -1;
1507 /* Control-e -- End of line */
1513 /* Control-f -- Move forward one character */
1518 /* Control-h and DEL */
1522 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1523 input_tab(&lastWasTab);
1527 /* Control-k -- clear to end of line */
1528 *(command + cursor) = 0;
1533 case VICMD(CNTRL('L')):
1534 /* Control-l -- clear screen */
1536 redraw(0, len-cursor);
1538 #if MAX_HISTORY >= 1
1540 case VICMD(CNTRL('N')):
1542 /* Control-n -- Get next command in history */
1543 if (get_next_history())
1547 case VICMD(CNTRL('P')):
1549 /* Control-p -- Get previous command from history */
1550 if (cur_history > 0) {
1551 get_previous_history();
1559 case VICMD(CNTRL('U')):
1560 /* Control-U -- Clear line before cursor */
1562 strcpy(command, command + cursor);
1563 redraw(cmdedit_y, len -= cursor);
1567 case VICMD(CNTRL('W')):
1568 /* Control-W -- Remove the last word */
1569 while (cursor > 0 && isspace(command[cursor-1]))
1571 while (cursor > 0 &&!isspace(command[cursor-1]))
1574 #if CONFIG_FEATURE_COMMAND_EDITING_VI
1579 input_backward(cursor);
1600 vi_Word_motion(command, 1);
1603 vi_word_motion(command, 1);
1606 vi_End_motion(command);
1609 vi_end_motion(command);
1612 vi_Back_motion(command);
1615 vi_back_motion(command);
1631 if (safe_read(0, &c, 1) < 1)
1632 goto prepare_to_die;
1633 if (c == (prevc & 0xff)) {
1635 input_backward(cursor);
1645 case 'w': /* "dw", "cw" */
1646 vi_word_motion(command, vi_cmdmode);
1648 case 'W': /* 'dW', 'cW' */
1649 vi_Word_motion(command, vi_cmdmode);
1651 case 'e': /* 'de', 'ce' */
1652 vi_end_motion(command);
1655 case 'E': /* 'dE', 'cE' */
1656 vi_End_motion(command);
1661 input_backward(cursor - sc);
1662 while (nc-- > cursor)
1665 case 'b': /* "db", "cb" */
1666 case 'B': /* implemented as B */
1668 vi_back_motion(command);
1670 vi_Back_motion(command);
1671 while (sc-- > cursor)
1674 case ' ': /* "d ", "c " */
1677 case '$': /* "d$", "c$" */
1679 while (cursor < len)
1692 if (safe_read(0, &c, 1) < 1)
1693 goto prepare_to_die;
1697 *(command + cursor) = c;
1702 #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') {
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);
1792 default: /* If it's regular input, do the normal thing */
1793 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1794 /* Control-V -- Add non-printable symbol */
1795 if (c == CNTRL('V')) {
1796 if (safe_read(0, &c, 1) < 1)
1797 goto prepare_to_die;
1805 #if CONFIG_FEATURE_COMMAND_EDITING_VI
1806 if (vi_cmdmode) /* don't self-insert */
1809 if (!Isprint(c)) /* Skip non-printable characters */
1813 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1818 if (cursor == (len - 1)) { /* Append if at the end of the line */
1819 *(command + cursor) = c;
1820 *(command + cursor + 1) = 0;
1821 cmdedit_set_out_char(0);
1822 } else { /* Insert otherwise */
1825 memmove(command + sc + 1, command + sc, len - sc);
1826 *(command + sc) = c;
1828 /* rewrite from cursor */
1830 /* to prev x pos + 1 */
1831 input_backward(cursor - sc);
1836 if (break_out) /* Enter is the command terminator, no more input. */
1843 setTermSettings(0, (void *) &initial_settings);
1844 handlers_sets &= ~SET_RESET_TERM;
1846 #if MAX_HISTORY >= 1
1847 /* Handle command history log */
1848 /* cleanup may be saved current command line */
1849 if (len> 0) { /* no put empty line */
1852 free(history[MAX_HISTORY]);
1853 history[MAX_HISTORY] = 0;
1854 /* After max history, remove the oldest command */
1855 if (i >= MAX_HISTORY) {
1857 for(i = 0; i < (MAX_HISTORY-1); i++)
1858 history[i] = history[i+1];
1860 history[i++] = bb_xstrdup(command);
1863 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1867 #else /* MAX_HISTORY < 1 */
1868 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1869 if (len > 0) { /* no put empty line */
1873 #endif /* MAX_HISTORY >= 1 */
1874 if (break_out > 0) {
1875 command[len++] = '\n'; /* set '\n' */
1878 #if defined(CONFIG_FEATURE_CLEAN_UP) && defined(CONFIG_FEATURE_COMMAND_TAB_COMPLETION)
1879 input_tab(0); /* strong free */
1881 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1882 free(cmdedit_prompt);
1884 cmdedit_reset_term();
1890 #endif /* CONFIG_FEATURE_COMMAND_EDITING */
1895 const char *bb_applet_name = "debug stuff usage";
1897 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1901 int main(int argc, char **argv)
1905 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1906 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1907 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1908 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1913 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1914 setlocale(LC_ALL, "");
1918 l = cmdedit_read_input(prompt, buff);
1919 if(l > 0 && buff[l-1] == '\n') {
1921 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1926 printf("*** cmdedit_read_input() detect ^D\n");