1 /* vi: set sw=4 ts=4: */
3 * Termios command line History and Editting.
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 #ifdef CONFIG_LOCALE_SUPPORT
47 #define Isprint(c) isprint((c))
49 #define Isprint(c) ( (c) >= ' ' && (c) != ((unsigned char)'\233') )
58 /* pretect redefined for test */
59 #undef CONFIG_FEATURE_COMMAND_EDITING
60 #undef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
61 #undef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
62 #undef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
63 #undef CONFIG_FEATURE_CLEAN_UP
65 #define CONFIG_FEATURE_COMMAND_EDITING
66 #define CONFIG_FEATURE_COMMAND_TAB_COMPLETION
67 #define CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
68 #define CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
69 #define CONFIG_FEATURE_CLEAN_UP
75 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
80 #ifdef CONFIG_FEATURE_COMMAND_EDITING
82 #ifndef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
83 #undef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
86 #if defined(CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION) || defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
87 #define CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
90 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
96 #endif /* advanced FEATURES */
99 /* Maximum length of the linked list for the command line history */
100 #ifndef CONFIG_FEATURE_COMMAND_HISTORY
101 #define MAX_HISTORY 15
103 #define MAX_HISTORY CONFIG_FEATURE_COMMAND_HISTORY
107 #warning cmdedit: You set MAX_HISTORY < 1. The history algorithm switched off.
109 static char *history[MAX_HISTORY+1]; /* history + current */
110 /* saved history lines */
111 static int n_history;
112 /* current pointer to history line */
113 static int cur_history;
117 #define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
118 #define getTermSettings(fd,argp) tcgetattr(fd, argp);
120 /* Current termio and the previous termio before starting sh */
121 static struct termios initial_settings, new_settings;
125 volatile int cmdedit_termw = 80; /* actual terminal width */
127 volatile int handlers_sets = 0; /* Set next bites: */
130 SET_ATEXIT = 1, /* when atexit() has been called
131 and get euid,uid,gid to fast compare */
132 SET_WCHG_HANDLERS = 2, /* winchg signal handler */
133 SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */
137 static int cmdedit_x; /* real x terminal position */
138 static int cmdedit_y; /* pseudoreal y terminal position */
139 static int cmdedit_prmt_len; /* lenght prompt without colores string */
141 static int cursor; /* required global for signal handler */
142 static int len; /* --- "" - - "" - -"- --""-- --""--- */
143 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
145 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
148 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
150 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
151 static char *user_buf = "";
152 static char *home_pwd_buf = "";
156 #ifdef CONFIG_FEATURE_SH_FANCY_PROMPT
157 static char *hostname_buf;
158 static int num_ok_lines = 1;
162 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
164 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
171 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
173 static void cmdedit_setwidth(int w, int redraw_flg);
175 static void win_changed(int nsig)
177 static sighandler_t previous_SIGWINCH_handler; /* for reset */
179 /* emulate || signal call */
180 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
182 get_terminal_width_height(0, &width, NULL);
183 cmdedit_setwidth(width, nsig == SIGWINCH);
185 /* Unix not all standart in recall signal */
187 if (nsig == -SIGWINCH) /* save previous handler */
188 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
189 else if (nsig == SIGWINCH) /* signaled called handler */
190 signal(SIGWINCH, win_changed); /* set for next call */
192 /* set previous handler */
193 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
196 static void cmdedit_reset_term(void)
198 if ((handlers_sets & SET_RESET_TERM) != 0) {
199 /* sparc and other have broken termios support: use old termio handling. */
200 setTermSettings(fileno(stdin), (void *) &initial_settings);
201 handlers_sets &= ~SET_RESET_TERM;
203 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
204 /* reset SIGWINCH handler to previous (default) */
206 handlers_sets &= ~SET_WCHG_HANDLERS;
212 /* special for recount position for scroll and remove terminal margin effect */
213 static void cmdedit_set_out_char(int next_char)
216 int c = (int)((unsigned char) command_ps[cursor]);
219 c = ' '; /* destroy end char? */
220 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
221 if (!Isprint(c)) { /* Inverse put non-printable characters */
228 printf("\033[7m%c\033[0m", c);
232 if (++cmdedit_x >= cmdedit_termw) {
233 /* terminal is scrolled down */
239 /* destroy "(auto)margin" */
246 /* Move to end line. Bonus: rewrite line from cursor */
247 static void input_end(void)
250 cmdedit_set_out_char(0);
253 /* Go to the next line */
254 static void goto_new_line(void)
262 static inline void out1str(const char *s)
268 static inline void beep(void)
273 /* Move back one charactor */
274 /* special for slow terminal */
275 static void input_backward(int num)
279 cursor -= num; /* new cursor (in command, not terminal) */
281 if (cmdedit_x >= num) { /* no to up line */
288 printf("\033[%dD", num);
293 putchar('\r'); /* back to first terminal pos. */
294 num -= cmdedit_x; /* set previous backward */
296 count_y = 1 + num / cmdedit_termw;
297 printf("\033[%dA", count_y);
298 cmdedit_y -= count_y;
299 /* require forward after uping */
300 cmdedit_x = cmdedit_termw * count_y - num;
301 printf("\033[%dC", cmdedit_x); /* set term cursor */
305 static void put_prompt(void)
307 out1str(cmdedit_prompt);
308 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
310 cmdedit_y = 0; /* new quasireal y */
313 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
314 static void parse_prompt(const char *prmt_ptr)
316 cmdedit_prompt = prmt_ptr;
317 cmdedit_prmt_len = strlen(prmt_ptr);
321 static void parse_prompt(const char *prmt_ptr)
325 char flg_not_length = '[';
326 char *prmt_mem_ptr = xcalloc(1, 1);
327 char *pwd_buf = xgetcwd(0);
328 char buf2[PATH_MAX + 1];
334 pwd_buf=(char *)bb_msg_unknown;
342 const char *cp = prmt_ptr;
345 c = bb_process_escape_sequence(&prmt_ptr);
351 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
359 pbuf = xcalloc(256, 1);
360 if (gethostname(pbuf, 255) < 0) {
363 char *s = strchr(pbuf, '.');
372 c = my_euid == 0 ? '#' : '$';
374 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
377 l = strlen(home_pwd_buf);
378 if (home_pwd_buf[0] != 0 &&
379 strncmp(home_pwd_buf, pbuf, l) == 0 &&
380 (pbuf[l]=='/' || pbuf[l]=='\0') &&
381 strlen(pwd_buf+l)<PATH_MAX) {
384 strcpy(pbuf+1, pwd_buf+l);
390 cp = strrchr(pbuf,'/');
391 if ( (cp != NULL) && (cp != pbuf) )
395 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
397 case 'e': case 'E': /* \e \E = \033 */
401 for (l = 0; l < 3;) {
403 buf2[l++] = *prmt_ptr;
405 h = strtol(buf2, &pbuf, 16);
406 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
413 c = (char)strtol(buf2, 0, 16);
419 if (c == flg_not_length) {
420 flg_not_length = flg_not_length == '[' ? ']' : '[';
429 prmt_len += strlen(pbuf);
430 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
431 if (flg_not_length == ']')
434 if(pwd_buf!=(char *)bb_msg_unknown)
436 cmdedit_prompt = prmt_mem_ptr;
437 cmdedit_prmt_len = prmt_len - sub_len;
443 /* draw promt, editor line, and clear tail */
444 static void redraw(int y, int back_cursor)
446 if (y > 0) /* up to start y */
447 printf("\033[%dA", y);
450 input_end(); /* rewrite */
451 printf("\033[J"); /* destroy tail after cursor */
452 input_backward(back_cursor);
455 /* Delete the char in front of the cursor */
456 static void input_delete(void)
463 strcpy(command_ps + j, command_ps + j + 1);
465 input_end(); /* rewtite new line */
466 cmdedit_set_out_char(0); /* destroy end char */
467 input_backward(cursor - j); /* back to old pos cursor */
470 /* Delete the char in back of the cursor */
471 static void input_backspace(void)
480 /* Move forward one charactor */
481 static void input_forward(void)
484 cmdedit_set_out_char(command_ps[cursor + 1]);
488 static void cmdedit_setwidth(int w, int redraw_flg)
490 cmdedit_termw = cmdedit_prmt_len + 2;
491 if (w <= cmdedit_termw) {
492 cmdedit_termw = cmdedit_termw % w;
494 if (w > cmdedit_termw) {
498 /* new y for current cursor */
499 int new_y = (cursor + cmdedit_prmt_len) / w;
502 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
508 static void cmdedit_init(void)
510 cmdedit_prmt_len = 0;
511 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
512 /* emulate usage handler to set handler and call yours work */
513 win_changed(-SIGWINCH);
514 handlers_sets |= SET_WCHG_HANDLERS;
517 if ((handlers_sets & SET_ATEXIT) == 0) {
518 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
519 struct passwd *entry;
522 entry = getpwuid(my_euid);
524 user_buf = bb_xstrdup(entry->pw_name);
525 home_pwd_buf = bb_xstrdup(entry->pw_dir);
529 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
531 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
536 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
537 handlers_sets |= SET_ATEXIT;
538 atexit(cmdedit_reset_term); /* be sure to do this only once */
542 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
544 static int is_execute(const struct stat *st)
546 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
547 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
548 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
549 (st->st_mode & S_IXOTH)) return TRUE;
553 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
555 static char **username_tab_completion(char *ud, int *num_matches)
557 struct passwd *entry;
562 ud++; /* ~user/... to user/... */
563 userlen = strlen(ud);
565 if (num_matches == 0) { /* "~/..." or "~user/..." */
566 char *sav_ud = ud - 1;
569 if (*ud == '/') { /* "~/..." */
573 temp = strchr(ud, '/');
574 *temp = 0; /* ~user\0 */
575 entry = getpwnam(ud);
576 *temp = '/'; /* restore ~user/... */
579 home = entry->pw_dir;
582 if ((userlen + strlen(home) + 1) < BUFSIZ) {
583 char temp2[BUFSIZ]; /* argument size */
586 sprintf(temp2, "%s%s", home, ud);
587 strcpy(sav_ud, temp2);
590 return 0; /* void, result save to argument :-) */
593 char **matches = (char **) NULL;
598 while ((entry = getpwent()) != NULL) {
599 /* Null usernames should result in all users as possible completions. */
600 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
602 bb_xasprintf(&temp, "~%s/", entry->pw_name);
603 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
605 matches[nm++] = temp;
614 #endif /* CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION */
622 static int path_parse(char ***p, int flags)
628 /* if not setenv PATH variable, to search cur dir "." */
629 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
630 /* PATH=<empty> or PATH=:<empty> */
631 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
639 npth++; /* count words is + 1 count ':' */
640 tmp = strchr(tmp, ':');
643 break; /* :<empty> */
648 *p = xmalloc(npth * sizeof(char *));
651 (*p)[0] = bb_xstrdup(tmp);
652 npth = 1; /* count words is + 1 count ':' */
655 tmp = strchr(tmp, ':');
657 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
659 break; /* :<empty> */
662 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
668 static char *add_quote_for_spec_chars(char *found)
671 char *s = xmalloc((strlen(found) + 1) * 2);
674 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
682 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
690 int nm = *num_matches;
693 char **paths = path1;
697 char *pfind = strrchr(command, '/');
702 /* no dir, if flags==EXE_ONLY - get paths, else "." */
703 npaths = path_parse(&paths, type);
707 /* save for change */
708 strcpy(dirbuf, command);
710 dirbuf[(pfind - command) + 1] = 0;
711 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
712 if (dirbuf[0] == '~') /* ~/... or ~user/... */
713 username_tab_completion(dirbuf, 0);
715 /* "strip" dirname in command */
719 npaths = 1; /* only 1 dir */
722 for (i = 0; i < npaths; i++) {
724 dir = opendir(paths[i]);
725 if (!dir) /* Don't print an error */
728 while ((next = readdir(dir)) != NULL) {
729 char *str_found = next->d_name;
732 if (strncmp(str_found, pfind, strlen(pfind)))
734 /* not see .name without .match */
735 if (*str_found == '.' && *pfind == 0) {
736 if (*paths[i] == '/' && paths[i][1] == 0
737 && str_found[1] == 0) str_found = ""; /* only "/" */
741 found = concat_path_file(paths[i], str_found);
742 /* hmm, remover in progress? */
743 if (stat(found, &st) < 0)
745 /* find with dirs ? */
746 if (paths[i] != dirbuf)
747 strcpy(found, next->d_name); /* only name */
748 if (S_ISDIR(st.st_mode)) {
749 /* name is directory */
751 found = concat_path_file(found, "");
753 str_found = add_quote_for_spec_chars(found);
755 /* not put found file if search only dirs for cd */
756 if (type == FIND_DIR_ONLY)
758 str_found = add_quote_for_spec_chars(found);
759 if (type == FIND_FILE_ONLY ||
760 (type == FIND_EXE_ONLY && is_execute(&st)))
761 strcat(str_found, " ");
763 /* Add it to the list */
764 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
766 matches[nm++] = str_found;
772 if (paths != path1) {
773 free(paths[0]); /* allocated memory only in first member */
780 static int match_compare(const void *a, const void *b)
782 return strcmp(*(char **) a, *(char **) b);
787 #define QUOT (UCHAR_MAX+1)
789 #define collapse_pos(is, in) { \
790 memcpy(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
791 memcpy(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
793 static int find_match(char *matchBuf, int *len_with_quotes)
798 int int_buf[BUFSIZ + 1];
799 int pos_buf[BUFSIZ + 1];
801 /* set to integer dimension characters and own positions */
803 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
804 if (int_buf[i] == 0) {
805 pos_buf[i] = -1; /* indicator end line */
811 /* mask \+symbol and convert '\t' to ' ' */
812 for (i = j = 0; matchBuf[i]; i++, j++)
813 if (matchBuf[i] == '\\') {
814 collapse_pos(j, j + 1);
817 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
818 if (matchBuf[i] == '\t') /* algorithm equivalent */
819 int_buf[j] = ' ' | QUOT;
822 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
823 else if (matchBuf[i] == '\t')
827 /* mask "symbols" or 'symbols' */
829 for (i = 0; int_buf[i]; i++) {
831 if (c == '\'' || c == '"') {
840 } else if (c2 != 0 && c != '$')
844 /* skip commands with arguments if line have commands delimiters */
845 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
846 for (i = 0; int_buf[i]; i++) {
849 j = i ? int_buf[i - 1] : -1;
851 if (c == ';' || c == '&' || c == '|') {
852 command_mode = 1 + (c == c2);
854 if (j == '>' || j == '<')
856 } else if (c == '|' && j == '>')
860 collapse_pos(0, i + command_mode);
861 i = -1; /* hack incremet */
864 /* collapse `command...` */
865 for (i = 0; int_buf[i]; i++)
866 if (int_buf[i] == '`') {
867 for (j = i + 1; int_buf[j]; j++)
868 if (int_buf[j] == '`') {
869 collapse_pos(i, j + 1);
874 /* not found close ` - command mode, collapse all previous */
875 collapse_pos(0, i + 1);
878 i--; /* hack incremet */
881 /* collapse (command...(command...)...) or {command...{command...}...} */
882 c = 0; /* "recursive" level */
884 for (i = 0; int_buf[i]; i++)
885 if (int_buf[i] == '(' || int_buf[i] == '{') {
886 if (int_buf[i] == '(')
890 collapse_pos(0, i + 1);
891 i = -1; /* hack incremet */
893 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
894 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
895 if (int_buf[i] == ')')
899 collapse_pos(0, i + 1);
900 i = -1; /* hack incremet */
903 /* skip first not quote space */
904 for (i = 0; int_buf[i]; i++)
905 if (int_buf[i] != ' ')
910 /* set find mode for completion */
911 command_mode = FIND_EXE_ONLY;
912 for (i = 0; int_buf[i]; i++)
913 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
914 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
915 && matchBuf[pos_buf[0]]=='c'
916 && matchBuf[pos_buf[1]]=='d' )
917 command_mode = FIND_DIR_ONLY;
919 command_mode = FIND_FILE_ONLY;
924 for (i = 0; int_buf[i]; i++);
926 for (--i; i >= 0; i--) {
928 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
929 collapse_pos(0, i + 1);
933 /* skip first not quoted '\'' or '"' */
934 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
935 /* collapse quote or unquote // or /~ */
936 while ((int_buf[i] & ~QUOT) == '/' &&
937 ((int_buf[i + 1] & ~QUOT) == '/'
938 || (int_buf[i + 1] & ~QUOT) == '~')) {
942 /* set only match and destroy quotes */
944 for (c = 0; pos_buf[i] >= 0; i++) {
945 matchBuf[c++] = matchBuf[pos_buf[i]];
949 /* old lenght matchBuf with quotes symbols */
950 *len_with_quotes = j ? j - pos_buf[0] : 0;
956 display by column original ideas from ls applet,
957 very optimize by my :)
959 static void showfiles(char **matches, int nfiles)
962 int column_width = 0;
965 /* find the longest file name- use that as the column width */
966 for (row = 0; row < nrows; row++) {
967 int l = strlen(matches[row]);
969 if (column_width < l)
972 column_width += 2; /* min space for columns */
973 ncols = cmdedit_termw / column_width;
978 nrows++; /* round up fractionals */
979 column_width = -column_width; /* for printf("%-Ns", ...); */
983 for (row = 0; row < nrows; row++) {
987 for(nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++)
988 printf("%*s", column_width, matches[n]);
989 printf("%s\n", matches[n]);
994 static void input_tab(int *lastWasTab)
996 /* Do TAB completion */
997 static int num_matches;
998 static char **matches;
1000 if (lastWasTab == 0) { /* free all memory */
1002 while (num_matches > 0)
1003 free(matches[--num_matches]);
1005 matches = (char **) NULL;
1009 if (! *lastWasTab) {
1013 char matchBuf[BUFSIZ];
1017 *lastWasTab = TRUE; /* flop trigger */
1019 /* Make a local copy of the string -- up
1020 * to the position of the cursor */
1021 tmp = strncpy(matchBuf, command_ps, cursor);
1024 find_type = find_match(matchBuf, &recalc_pos);
1026 /* Free up any memory already allocated */
1029 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
1030 /* If the word starts with `~' and there is no slash in the word,
1031 * then try completing this word as a username. */
1033 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1034 matches = username_tab_completion(matchBuf, &num_matches);
1036 /* Try to match any executable in our path and everything
1037 * in the current working directory that matches. */
1040 exe_n_cwd_tab_completion(matchBuf,
1041 &num_matches, find_type);
1042 /* Remove duplicate found */
1046 for(i=0; i<(num_matches-1); i++)
1047 for(j=i+1; j<num_matches; j++)
1048 if(matches[i]!=0 && matches[j]!=0 &&
1049 strcmp(matches[i], matches[j])==0) {
1057 if(!strcmp(matches[i], "./"))
1059 else if(!strcmp(matches[i], "../"))
1061 matches[num_matches++]=matches[i];
1064 /* Did we find exactly one match? */
1065 if (!matches || num_matches > 1) {
1070 return; /* not found */
1072 qsort(matches, num_matches, sizeof(char *), match_compare);
1074 /* find minimal match */
1075 tmp = bb_xstrdup(matches[0]);
1076 for (tmp1 = tmp; *tmp1; tmp1++)
1077 for (len_found = 1; len_found < num_matches; len_found++)
1078 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1082 if (*tmp == 0) { /* have unique */
1086 } else { /* one match */
1088 /* for next completion current found */
1089 *lastWasTab = FALSE;
1092 len_found = strlen(tmp);
1093 /* have space to placed match? */
1094 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1096 /* before word for match */
1097 command_ps[cursor - recalc_pos] = 0;
1098 /* save tail line */
1099 strcpy(matchBuf, command_ps + cursor);
1101 strcat(command_ps, tmp);
1103 strcat(command_ps, matchBuf);
1104 /* back to begin word for match */
1105 input_backward(recalc_pos);
1107 recalc_pos = cursor + len_found;
1109 len = strlen(command_ps);
1110 /* write out the matched command */
1111 redraw(cmdedit_y, len - recalc_pos);
1113 if (tmp != matches[0])
1116 /* Ok -- the last char was a TAB. Since they
1117 * just hit TAB again, print a list of all the
1118 * available choices... */
1119 if (matches && num_matches > 0) {
1120 int sav_cursor = cursor; /* change goto_new_line() */
1122 /* Go to the next line */
1124 showfiles(matches, num_matches);
1125 redraw(0, len - sav_cursor);
1129 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
1131 #if MAX_HISTORY >= 1
1132 static void get_previous_history(void)
1134 if(command_ps[0] != 0 || history[cur_history] == 0) {
1135 free(history[cur_history]);
1136 history[cur_history] = bb_xstrdup(command_ps);
1141 static int get_next_history(void)
1143 int ch = cur_history;
1145 if (ch < n_history) {
1146 get_previous_history(); /* save the current history line */
1147 return (cur_history = ch+1);
1154 #ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY
1155 extern void load_history ( const char *fromfile )
1162 for(hi = n_history; hi > 0; ) {
1164 free ( history [hi] );
1167 if (( fp = fopen ( fromfile, "r" ))) {
1169 for ( hi = 0; hi < MAX_HISTORY; ) {
1170 char * hl = bb_get_chomped_line_from_file(fp);
1178 if(l == 0 || hl[0] == ' ') {
1182 history [hi++] = hl;
1186 cur_history = n_history = hi;
1189 extern void save_history ( const char *tofile )
1191 FILE *fp = fopen ( tofile, "w" );
1196 for ( i = 0; i < n_history; i++ ) {
1197 fprintf(fp, "%s\n", history [i]);
1213 * This function is used to grab a character buffer
1214 * from the input file descriptor and allows you to
1215 * a string with full command editing (sortof like
1218 * The following standard commands are not implemented:
1219 * ESC-b -- Move back one word
1220 * ESC-f -- Move forward one word
1221 * ESC-d -- Delete back one word
1222 * ESC-h -- Delete forward one word
1223 * CTL-t -- Transpose two characters
1225 * Furthermore, the "vi" command editing keys are not implemented.
1230 int cmdedit_read_input(char *prompt, char command[BUFSIZ])
1234 int lastWasTab = FALSE;
1235 unsigned char c = 0;
1237 /* prepare before init handlers */
1238 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1240 command_ps = command;
1242 getTermSettings(0, (void *) &initial_settings);
1243 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1244 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1245 /* Turn off echoing and CTRL-C, so we can trap it */
1246 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1247 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1248 new_settings.c_cc[VMIN] = 1;
1249 new_settings.c_cc[VTIME] = 0;
1250 /* Turn off CTRL-C, so we can trap it */
1251 # ifndef _POSIX_VDISABLE
1252 # define _POSIX_VDISABLE '\0'
1254 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1257 setTermSettings(0, (void *) &new_settings);
1258 handlers_sets |= SET_RESET_TERM;
1260 /* Now initialize things */
1262 /* Print out the command prompt */
1263 parse_prompt(prompt);
1267 fflush(stdout); /* buffered out to fast */
1269 if (safe_read(0, &c, 1) < 1)
1270 /* if we can't read input then exit */
1271 goto prepare_to_die;
1281 /* Control-a -- Beginning of line */
1282 input_backward(cursor);
1285 /* Control-b -- Move back one character */
1289 /* Control-c -- stop gathering input */
1297 /* Control-d -- Delete one character, or exit
1298 * if the len=0 and no chars to delete */
1301 #if !defined(CONFIG_ASH)
1304 /* cmdedit_reset_term() called in atexit */
1307 break_out = -1; /* for control stoped jobs */
1315 /* Control-e -- End of line */
1319 /* Control-f -- Move forward one character */
1324 /* Control-h and DEL */
1328 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1329 input_tab(&lastWasTab);
1333 /* Control-k -- clear to end of line */
1334 *(command + cursor) = 0;
1339 /* Control-l -- clear screen */
1341 redraw(0, len-cursor);
1343 #if MAX_HISTORY >= 1
1345 /* Control-n -- Get next command in history */
1346 if (get_next_history())
1350 /* Control-p -- Get previous command from history */
1351 if (cur_history > 0) {
1352 get_previous_history();
1360 /* Control-U -- Clear line before cursor */
1362 strcpy(command, command + cursor);
1363 redraw(cmdedit_y, len -= cursor);
1367 /* Control-W -- Remove the last word */
1368 while (cursor > 0 && isspace(command[cursor-1]))
1370 while (cursor > 0 &&!isspace(command[cursor-1]))
1374 /* escape sequence follows */
1375 if (safe_read(0, &c, 1) < 1)
1376 goto prepare_to_die;
1377 /* different vt100 emulations */
1378 if (c == '[' || c == 'O') {
1379 if (safe_read(0, &c, 1) < 1)
1380 goto prepare_to_die;
1383 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1384 case '\t': /* Alt-Tab */
1386 input_tab(&lastWasTab);
1389 #if MAX_HISTORY >= 1
1391 /* Up Arrow -- Get previous command from history */
1392 if (cur_history > 0) {
1393 get_previous_history();
1400 /* Down Arrow -- Get next command in history */
1401 if (!get_next_history())
1403 /* Rewrite the line with the selected history item */
1405 /* change command */
1406 len = strlen(strcpy(command, history[cur_history]));
1407 /* redraw and go to end line */
1408 redraw(cmdedit_y, 0);
1412 /* Right Arrow -- Move forward one character */
1416 /* Left Arrow -- Move back one character */
1426 input_backward(cursor);
1434 if (!(c >= '1' && c <= '9'))
1438 if (c >= '1' && c <= '9')
1440 if (safe_read(0, &c, 1) < 1)
1441 goto prepare_to_die;
1446 default: /* If it's regular input, do the normal thing */
1447 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1448 /* Control-V -- Add non-printable symbol */
1450 if (safe_read(0, &c, 1) < 1)
1451 goto prepare_to_die;
1458 if (!Isprint(c)) /* Skip non-printable characters */
1461 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1466 if (cursor == (len - 1)) { /* Append if at the end of the line */
1467 *(command + cursor) = c;
1468 *(command + cursor + 1) = 0;
1469 cmdedit_set_out_char(0);
1470 } else { /* Insert otherwise */
1473 memmove(command + sc + 1, command + sc, len - sc);
1474 *(command + sc) = c;
1476 /* rewrite from cursor */
1478 /* to prev x pos + 1 */
1479 input_backward(cursor - sc);
1484 if (break_out) /* Enter is the command terminator, no more input. */
1491 setTermSettings(0, (void *) &initial_settings);
1492 handlers_sets &= ~SET_RESET_TERM;
1494 #if MAX_HISTORY >= 1
1495 /* Handle command history log */
1496 /* cleanup may be saved current command line */
1497 free(history[MAX_HISTORY]);
1498 history[MAX_HISTORY] = 0;
1499 if (len) { /* no put empty line */
1501 /* After max history, remove the oldest command */
1502 if (i >= MAX_HISTORY) {
1504 for(i = 0; i < (MAX_HISTORY-1); i++)
1505 history[i] = history[i+1];
1507 history[i++] = bb_xstrdup(command);
1510 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1514 #else /* MAX_HISTORY < 1 */
1515 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1516 if (len) { /* no put empty line */
1520 #endif /* MAX_HISTORY >= 1 */
1521 if (break_out > 0) {
1522 command[len++] = '\n'; /* set '\n' */
1525 #if defined(CONFIG_FEATURE_CLEAN_UP) && defined(CONFIG_FEATURE_COMMAND_TAB_COMPLETION)
1526 input_tab(0); /* strong free */
1528 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1529 free(cmdedit_prompt);
1531 cmdedit_reset_term();
1537 #endif /* CONFIG_FEATURE_COMMAND_EDITING */
1542 const char *bb_applet_name = "debug stuff usage";
1544 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1548 int main(int argc, char **argv)
1552 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1553 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1554 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1555 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1560 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1561 setlocale(LC_ALL, "");
1565 l = cmdedit_read_input(prompt, buff);
1566 if(l > 0 && buff[l-1] == '\n') {
1568 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1573 printf("*** cmdedit_read_input() detect ^D\n");