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') )
54 /* pretect redefined for test */
55 #undef CONFIG_FEATURE_COMMAND_EDITING
56 #undef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
57 #undef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
58 #undef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
59 #undef CONFIG_FEATURE_CLEAN_UP
61 #define CONFIG_FEATURE_COMMAND_EDITING
62 #define CONFIG_FEATURE_COMMAND_TAB_COMPLETION
63 #define CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
64 #define CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
65 #define CONFIG_FEATURE_CLEAN_UP
69 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
74 #ifdef CONFIG_FEATURE_COMMAND_EDITING
76 #if defined(CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION) || defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
77 #define CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
80 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
86 #endif /* advanced FEATURES */
89 /* Maximum length of the linked list for the command line history */
90 #ifndef CONFIG_FEATURE_COMMAND_HISTORY
91 #define MAX_HISTORY 15
93 #define MAX_HISTORY CONFIG_FEATURE_COMMAND_HISTORY
97 #warning cmdedit: You set MAX_HISTORY < 1. The history algorithm switched off.
99 static char *history[MAX_HISTORY+1]; /* history + current */
100 /* saved history lines */
101 static int n_history;
102 /* current pointer to history line */
103 static int cur_history;
107 #define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
108 #define getTermSettings(fd,argp) tcgetattr(fd, argp);
110 /* Current termio and the previous termio before starting sh */
111 static struct termios initial_settings, new_settings;
115 volatile int cmdedit_termw = 80; /* actual terminal width */
117 volatile int handlers_sets = 0; /* Set next bites: */
120 SET_ATEXIT = 1, /* when atexit() has been called
121 and get euid,uid,gid to fast compare */
122 SET_WCHG_HANDLERS = 2, /* winchg signal handler */
123 SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */
127 static int cmdedit_x; /* real x terminal position */
128 static int cmdedit_y; /* pseudoreal y terminal position */
129 static int cmdedit_prmt_len; /* lenght prompt without colores string */
131 static int cursor; /* required global for signal handler */
132 static int len; /* --- "" - - "" - -"- --""-- --""--- */
133 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
135 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
138 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
140 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
141 static char *user_buf = "";
142 static char *home_pwd_buf = "";
146 #ifdef CONFIG_FEATURE_SH_FANCY_PROMPT
147 static char *hostname_buf;
148 static int num_ok_lines = 1;
152 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
154 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
161 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
163 static void cmdedit_setwidth(int w, int redraw_flg);
165 static void win_changed(int nsig)
167 static sighandler_t previous_SIGWINCH_handler; /* for reset */
169 /* emulate || signal call */
170 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
172 get_terminal_width_height(0, &width, NULL);
173 cmdedit_setwidth(width, nsig == SIGWINCH);
175 /* Unix not all standart in recall signal */
177 if (nsig == -SIGWINCH) /* save previous handler */
178 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
179 else if (nsig == SIGWINCH) /* signaled called handler */
180 signal(SIGWINCH, win_changed); /* set for next call */
182 /* set previous handler */
183 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
186 static void cmdedit_reset_term(void)
188 if ((handlers_sets & SET_RESET_TERM) != 0) {
189 /* sparc and other have broken termios support: use old termio handling. */
190 setTermSettings(fileno(stdin), (void *) &initial_settings);
191 handlers_sets &= ~SET_RESET_TERM;
193 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
194 /* reset SIGWINCH handler to previous (default) */
196 handlers_sets &= ~SET_WCHG_HANDLERS;
202 /* special for recount position for scroll and remove terminal margin effect */
203 static void cmdedit_set_out_char(int next_char)
206 int c = (int)((unsigned char) command_ps[cursor]);
209 c = ' '; /* destroy end char? */
210 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
211 if (!Isprint(c)) { /* Inverse put non-printable characters */
218 printf("\033[7m%c\033[0m", c);
222 if (++cmdedit_x >= cmdedit_termw) {
223 /* terminal is scrolled down */
229 /* destroy "(auto)margin" */
236 /* Move to end line. Bonus: rewrite line from cursor */
237 static void input_end(void)
240 cmdedit_set_out_char(0);
243 /* Go to the next line */
244 static void goto_new_line(void)
252 static inline void out1str(const char *s)
258 static inline void beep(void)
263 /* Move back one charactor */
264 /* special for slow terminal */
265 static void input_backward(int num)
269 cursor -= num; /* new cursor (in command, not terminal) */
271 if (cmdedit_x >= num) { /* no to up line */
278 printf("\033[%dD", num);
283 putchar('\r'); /* back to first terminal pos. */
284 num -= cmdedit_x; /* set previous backward */
286 count_y = 1 + num / cmdedit_termw;
287 printf("\033[%dA", count_y);
288 cmdedit_y -= count_y;
289 /* require forward after uping */
290 cmdedit_x = cmdedit_termw * count_y - num;
291 printf("\033[%dC", cmdedit_x); /* set term cursor */
295 static void put_prompt(void)
297 out1str(cmdedit_prompt);
298 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
300 cmdedit_y = 0; /* new quasireal y */
303 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
304 static void parse_prompt(const char *prmt_ptr)
306 cmdedit_prompt = prmt_ptr;
307 cmdedit_prmt_len = strlen(prmt_ptr);
311 static void parse_prompt(const char *prmt_ptr)
315 char flg_not_length = '[';
316 char *prmt_mem_ptr = xcalloc(1, 1);
317 char *pwd_buf = xgetcwd(0);
318 char buf2[PATH_MAX + 1];
324 pwd_buf=(char *)bb_msg_unknown;
332 const char *cp = prmt_ptr;
335 c = bb_process_escape_sequence(&prmt_ptr);
341 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
349 pbuf = xcalloc(256, 1);
350 if (gethostname(pbuf, 255) < 0) {
353 char *s = strchr(pbuf, '.');
362 c = my_euid == 0 ? '#' : '$';
364 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
367 l = strlen(home_pwd_buf);
368 if (home_pwd_buf[0] != 0 &&
369 strncmp(home_pwd_buf, pbuf, l) == 0 &&
370 (pbuf[l]=='/' || pbuf[l]=='\0') &&
371 strlen(pwd_buf+l)<PATH_MAX) {
374 strcpy(pbuf+1, pwd_buf+l);
380 cp = strrchr(pbuf,'/');
381 if ( (cp != NULL) && (cp != pbuf) )
385 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
387 case 'e': case 'E': /* \e \E = \033 */
391 for (l = 0; l < 3;) {
393 buf2[l++] = *prmt_ptr;
395 h = strtol(buf2, &pbuf, 16);
396 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
403 c = (char)strtol(buf2, 0, 16);
409 if (c == flg_not_length) {
410 flg_not_length = flg_not_length == '[' ? ']' : '[';
419 prmt_len += strlen(pbuf);
420 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
421 if (flg_not_length == ']')
424 if(pwd_buf!=(char *)bb_msg_unknown)
426 cmdedit_prompt = prmt_mem_ptr;
427 cmdedit_prmt_len = prmt_len - sub_len;
433 /* draw promt, editor line, and clear tail */
434 static void redraw(int y, int back_cursor)
436 if (y > 0) /* up to start y */
437 printf("\033[%dA", y);
440 input_end(); /* rewrite */
441 printf("\033[J"); /* destroy tail after cursor */
442 input_backward(back_cursor);
445 /* Delete the char in front of the cursor */
446 static void input_delete(void)
453 strcpy(command_ps + j, command_ps + j + 1);
455 input_end(); /* rewtite new line */
456 cmdedit_set_out_char(0); /* destroy end char */
457 input_backward(cursor - j); /* back to old pos cursor */
460 /* Delete the char in back of the cursor */
461 static void input_backspace(void)
470 /* Move forward one charactor */
471 static void input_forward(void)
474 cmdedit_set_out_char(command_ps[cursor + 1]);
478 static void cmdedit_setwidth(int w, int redraw_flg)
480 cmdedit_termw = cmdedit_prmt_len + 2;
481 if (w <= cmdedit_termw) {
482 cmdedit_termw = cmdedit_termw % w;
484 if (w > cmdedit_termw) {
488 /* new y for current cursor */
489 int new_y = (cursor + cmdedit_prmt_len) / w;
492 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
498 static void cmdedit_init(void)
500 cmdedit_prmt_len = 0;
501 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
502 /* emulate usage handler to set handler and call yours work */
503 win_changed(-SIGWINCH);
504 handlers_sets |= SET_WCHG_HANDLERS;
507 if ((handlers_sets & SET_ATEXIT) == 0) {
508 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
509 struct passwd *entry;
512 entry = getpwuid(my_euid);
514 user_buf = bb_xstrdup(entry->pw_name);
515 home_pwd_buf = bb_xstrdup(entry->pw_dir);
519 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
521 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
526 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
527 handlers_sets |= SET_ATEXIT;
528 atexit(cmdedit_reset_term); /* be sure to do this only once */
532 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
534 static int is_execute(const struct stat *st)
536 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
537 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
538 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
539 (st->st_mode & S_IXOTH)) return TRUE;
543 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
545 static char **username_tab_completion(char *ud, int *num_matches)
547 struct passwd *entry;
552 ud++; /* ~user/... to user/... */
553 userlen = strlen(ud);
555 if (num_matches == 0) { /* "~/..." or "~user/..." */
556 char *sav_ud = ud - 1;
559 if (*ud == '/') { /* "~/..." */
563 temp = strchr(ud, '/');
564 *temp = 0; /* ~user\0 */
565 entry = getpwnam(ud);
566 *temp = '/'; /* restore ~user/... */
569 home = entry->pw_dir;
572 if ((userlen + strlen(home) + 1) < BUFSIZ) {
573 char temp2[BUFSIZ]; /* argument size */
576 sprintf(temp2, "%s%s", home, ud);
577 strcpy(sav_ud, temp2);
580 return 0; /* void, result save to argument :-) */
583 char **matches = (char **) NULL;
588 while ((entry = getpwent()) != NULL) {
589 /* Null usernames should result in all users as possible completions. */
590 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
592 bb_xasprintf(&temp, "~%s/", entry->pw_name);
593 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
595 matches[nm++] = temp;
604 #endif /* CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION */
612 static int path_parse(char ***p, int flags)
618 /* if not setenv PATH variable, to search cur dir "." */
619 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
620 /* PATH=<empty> or PATH=:<empty> */
621 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
629 npth++; /* count words is + 1 count ':' */
630 tmp = strchr(tmp, ':');
633 break; /* :<empty> */
638 *p = xmalloc(npth * sizeof(char *));
641 (*p)[0] = bb_xstrdup(tmp);
642 npth = 1; /* count words is + 1 count ':' */
645 tmp = strchr(tmp, ':');
647 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
649 break; /* :<empty> */
652 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
658 static char *add_quote_for_spec_chars(char *found)
661 char *s = xmalloc((strlen(found) + 1) * 2);
664 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
672 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
680 int nm = *num_matches;
683 char **paths = path1;
687 char *pfind = strrchr(command, '/');
692 /* no dir, if flags==EXE_ONLY - get paths, else "." */
693 npaths = path_parse(&paths, type);
697 /* save for change */
698 strcpy(dirbuf, command);
700 dirbuf[(pfind - command) + 1] = 0;
701 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
702 if (dirbuf[0] == '~') /* ~/... or ~user/... */
703 username_tab_completion(dirbuf, 0);
705 /* "strip" dirname in command */
709 npaths = 1; /* only 1 dir */
712 for (i = 0; i < npaths; i++) {
714 dir = opendir(paths[i]);
715 if (!dir) /* Don't print an error */
718 while ((next = readdir(dir)) != NULL) {
719 char *str_found = next->d_name;
722 if (strncmp(str_found, pfind, strlen(pfind)))
724 /* not see .name without .match */
725 if (*str_found == '.' && *pfind == 0) {
726 if (*paths[i] == '/' && paths[i][1] == 0
727 && str_found[1] == 0) str_found = ""; /* only "/" */
731 found = concat_path_file(paths[i], str_found);
732 /* hmm, remover in progress? */
733 if (stat(found, &st) < 0)
735 /* find with dirs ? */
736 if (paths[i] != dirbuf)
737 strcpy(found, next->d_name); /* only name */
738 if (S_ISDIR(st.st_mode)) {
739 /* name is directory */
741 found = concat_path_file(found, "");
743 str_found = add_quote_for_spec_chars(found);
745 /* not put found file if search only dirs for cd */
746 if (type == FIND_DIR_ONLY)
748 str_found = add_quote_for_spec_chars(found);
749 if (type == FIND_FILE_ONLY ||
750 (type == FIND_EXE_ONLY && is_execute(&st)))
751 strcat(str_found, " ");
753 /* Add it to the list */
754 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
756 matches[nm++] = str_found;
762 if (paths != path1) {
763 free(paths[0]); /* allocated memory only in first member */
770 static int match_compare(const void *a, const void *b)
772 return strcmp(*(char **) a, *(char **) b);
777 #define QUOT (UCHAR_MAX+1)
779 #define collapse_pos(is, in) { \
780 memcpy(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
781 memcpy(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
783 static int find_match(char *matchBuf, int *len_with_quotes)
788 int int_buf[BUFSIZ + 1];
789 int pos_buf[BUFSIZ + 1];
791 /* set to integer dimension characters and own positions */
793 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
794 if (int_buf[i] == 0) {
795 pos_buf[i] = -1; /* indicator end line */
801 /* mask \+symbol and convert '\t' to ' ' */
802 for (i = j = 0; matchBuf[i]; i++, j++)
803 if (matchBuf[i] == '\\') {
804 collapse_pos(j, j + 1);
807 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
808 if (matchBuf[i] == '\t') /* algorithm equivalent */
809 int_buf[j] = ' ' | QUOT;
812 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
813 else if (matchBuf[i] == '\t')
817 /* mask "symbols" or 'symbols' */
819 for (i = 0; int_buf[i]; i++) {
821 if (c == '\'' || c == '"') {
830 } else if (c2 != 0 && c != '$')
834 /* skip commands with arguments if line have commands delimiters */
835 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
836 for (i = 0; int_buf[i]; i++) {
839 j = i ? int_buf[i - 1] : -1;
841 if (c == ';' || c == '&' || c == '|') {
842 command_mode = 1 + (c == c2);
844 if (j == '>' || j == '<')
846 } else if (c == '|' && j == '>')
850 collapse_pos(0, i + command_mode);
851 i = -1; /* hack incremet */
854 /* collapse `command...` */
855 for (i = 0; int_buf[i]; i++)
856 if (int_buf[i] == '`') {
857 for (j = i + 1; int_buf[j]; j++)
858 if (int_buf[j] == '`') {
859 collapse_pos(i, j + 1);
864 /* not found close ` - command mode, collapse all previous */
865 collapse_pos(0, i + 1);
868 i--; /* hack incremet */
871 /* collapse (command...(command...)...) or {command...{command...}...} */
872 c = 0; /* "recursive" level */
874 for (i = 0; int_buf[i]; i++)
875 if (int_buf[i] == '(' || int_buf[i] == '{') {
876 if (int_buf[i] == '(')
880 collapse_pos(0, i + 1);
881 i = -1; /* hack incremet */
883 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
884 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
885 if (int_buf[i] == ')')
889 collapse_pos(0, i + 1);
890 i = -1; /* hack incremet */
893 /* skip first not quote space */
894 for (i = 0; int_buf[i]; i++)
895 if (int_buf[i] != ' ')
900 /* set find mode for completion */
901 command_mode = FIND_EXE_ONLY;
902 for (i = 0; int_buf[i]; i++)
903 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
904 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
905 && matchBuf[pos_buf[0]]=='c'
906 && matchBuf[pos_buf[1]]=='d' )
907 command_mode = FIND_DIR_ONLY;
909 command_mode = FIND_FILE_ONLY;
914 for (i = 0; int_buf[i]; i++);
916 for (--i; i >= 0; i--) {
918 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
919 collapse_pos(0, i + 1);
923 /* skip first not quoted '\'' or '"' */
924 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
925 /* collapse quote or unquote // or /~ */
926 while ((int_buf[i] & ~QUOT) == '/' &&
927 ((int_buf[i + 1] & ~QUOT) == '/'
928 || (int_buf[i + 1] & ~QUOT) == '~')) {
932 /* set only match and destroy quotes */
934 for (c = 0; pos_buf[i] >= 0; i++) {
935 matchBuf[c++] = matchBuf[pos_buf[i]];
939 /* old lenght matchBuf with quotes symbols */
940 *len_with_quotes = j ? j - pos_buf[0] : 0;
946 display by column original ideas from ls applet,
947 very optimize by my :)
949 static void showfiles(char **matches, int nfiles)
952 int column_width = 0;
955 /* find the longest file name- use that as the column width */
956 for (row = 0; row < nrows; row++) {
957 int l = strlen(matches[row]);
959 if (column_width < l)
962 column_width += 2; /* min space for columns */
963 ncols = cmdedit_termw / column_width;
968 nrows++; /* round up fractionals */
969 column_width = -column_width; /* for printf("%-Ns", ...); */
973 for (row = 0; row < nrows; row++) {
977 for(nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++)
978 printf("%*s", column_width, matches[n]);
979 printf("%s\n", matches[n]);
984 static void input_tab(int *lastWasTab)
986 /* Do TAB completion */
987 static int num_matches;
988 static char **matches;
990 if (lastWasTab == 0) { /* free all memory */
992 while (num_matches > 0)
993 free(matches[--num_matches]);
995 matches = (char **) NULL;
1003 char matchBuf[BUFSIZ];
1007 *lastWasTab = TRUE; /* flop trigger */
1009 /* Make a local copy of the string -- up
1010 * to the position of the cursor */
1011 tmp = strncpy(matchBuf, command_ps, cursor);
1014 find_type = find_match(matchBuf, &recalc_pos);
1016 /* Free up any memory already allocated */
1019 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
1020 /* If the word starts with `~' and there is no slash in the word,
1021 * then try completing this word as a username. */
1023 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1024 matches = username_tab_completion(matchBuf, &num_matches);
1026 /* Try to match any executable in our path and everything
1027 * in the current working directory that matches. */
1030 exe_n_cwd_tab_completion(matchBuf,
1031 &num_matches, find_type);
1032 /* Remove duplicate found */
1036 for(i=0; i<(num_matches-1); i++)
1037 for(j=i+1; j<num_matches; j++)
1038 if(matches[i]!=0 && matches[j]!=0 &&
1039 strcmp(matches[i], matches[j])==0) {
1047 if(!strcmp(matches[i], "./"))
1049 else if(!strcmp(matches[i], "../"))
1051 matches[num_matches++]=matches[i];
1054 /* Did we find exactly one match? */
1055 if (!matches || num_matches > 1) {
1060 return; /* not found */
1062 qsort(matches, num_matches, sizeof(char *), match_compare);
1064 /* find minimal match */
1065 tmp = bb_xstrdup(matches[0]);
1066 for (tmp1 = tmp; *tmp1; tmp1++)
1067 for (len_found = 1; len_found < num_matches; len_found++)
1068 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1072 if (*tmp == 0) { /* have unique */
1076 } else { /* one match */
1078 /* for next completion current found */
1079 *lastWasTab = FALSE;
1082 len_found = strlen(tmp);
1083 /* have space to placed match? */
1084 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1086 /* before word for match */
1087 command_ps[cursor - recalc_pos] = 0;
1088 /* save tail line */
1089 strcpy(matchBuf, command_ps + cursor);
1091 strcat(command_ps, tmp);
1093 strcat(command_ps, matchBuf);
1094 /* back to begin word for match */
1095 input_backward(recalc_pos);
1097 recalc_pos = cursor + len_found;
1099 len = strlen(command_ps);
1100 /* write out the matched command */
1101 redraw(cmdedit_y, len - recalc_pos);
1103 if (tmp != matches[0])
1106 /* Ok -- the last char was a TAB. Since they
1107 * just hit TAB again, print a list of all the
1108 * available choices... */
1109 if (matches && num_matches > 0) {
1110 int sav_cursor = cursor; /* change goto_new_line() */
1112 /* Go to the next line */
1114 showfiles(matches, num_matches);
1115 redraw(0, len - sav_cursor);
1119 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
1121 #if MAX_HISTORY >= 1
1122 static void get_previous_history(void)
1124 if(command_ps[0] != 0 || history[cur_history] == 0) {
1125 free(history[cur_history]);
1126 history[cur_history] = bb_xstrdup(command_ps);
1131 static int get_next_history(void)
1133 int ch = cur_history;
1135 if (ch < n_history) {
1136 get_previous_history(); /* save the current history line */
1137 return (cur_history = ch+1);
1144 #ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY
1145 extern void load_history ( const char *fromfile )
1152 for(hi = n_history; hi > 0; ) {
1154 free ( history [hi] );
1157 if (( fp = fopen ( fromfile, "r" ))) {
1159 for ( hi = 0; hi < MAX_HISTORY; ) {
1160 char * hl = bb_get_chomped_line_from_file(fp);
1168 if(l == 0 || hl[0] == ' ') {
1172 history [hi++] = hl;
1176 cur_history = n_history = hi;
1179 extern void save_history ( const char *tofile )
1181 FILE *fp = fopen ( tofile, "w" );
1186 for ( i = 0; i < n_history; i++ ) {
1187 fprintf(fp, "%s\n", history [i]);
1203 * This function is used to grab a character buffer
1204 * from the input file descriptor and allows you to
1205 * a string with full command editing (sortof like
1208 * The following standard commands are not implemented:
1209 * ESC-b -- Move back one word
1210 * ESC-f -- Move forward one word
1211 * ESC-d -- Delete back one word
1212 * ESC-h -- Delete forward one word
1213 * CTL-t -- Transpose two characters
1215 * Furthermore, the "vi" command editing keys are not implemented.
1220 int cmdedit_read_input(char *prompt, char command[BUFSIZ])
1224 int lastWasTab = FALSE;
1225 unsigned char c = 0;
1227 /* prepare before init handlers */
1228 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1230 command_ps = command;
1232 getTermSettings(0, (void *) &initial_settings);
1233 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1234 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1235 /* Turn off echoing and CTRL-C, so we can trap it */
1236 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1237 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1238 new_settings.c_cc[VMIN] = 1;
1239 new_settings.c_cc[VTIME] = 0;
1240 /* Turn off CTRL-C, so we can trap it */
1241 # ifndef _POSIX_VDISABLE
1242 # define _POSIX_VDISABLE '\0'
1244 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1247 setTermSettings(0, (void *) &new_settings);
1248 handlers_sets |= SET_RESET_TERM;
1250 /* Now initialize things */
1252 /* Print out the command prompt */
1253 parse_prompt(prompt);
1257 fflush(stdout); /* buffered out to fast */
1259 if (safe_read(0, &c, 1) < 1)
1260 /* if we can't read input then exit */
1261 goto prepare_to_die;
1271 /* Control-a -- Beginning of line */
1272 input_backward(cursor);
1275 /* Control-b -- Move back one character */
1279 /* Control-c -- stop gathering input */
1287 /* Control-d -- Delete one character, or exit
1288 * if the len=0 and no chars to delete */
1291 #if !defined(CONFIG_ASH)
1294 /* cmdedit_reset_term() called in atexit */
1297 break_out = -1; /* for control stoped jobs */
1305 /* Control-e -- End of line */
1309 /* Control-f -- Move forward one character */
1314 /* Control-h and DEL */
1318 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1319 input_tab(&lastWasTab);
1323 /* Control-k -- clear to end of line */
1324 *(command + cursor) = 0;
1329 /* Control-l -- clear screen */
1331 redraw(0, len-cursor);
1333 #if MAX_HISTORY >= 1
1335 /* Control-n -- Get next command in history */
1336 if (get_next_history())
1340 /* Control-p -- Get previous command from history */
1341 if (cur_history > 0) {
1342 get_previous_history();
1350 /* Control-U -- Clear line before cursor */
1352 strcpy(command, command + cursor);
1353 redraw(cmdedit_y, len -= cursor);
1357 /* Control-W -- Remove the last word */
1358 while (cursor > 0 && isspace(command[cursor-1]))
1360 while (cursor > 0 &&!isspace(command[cursor-1]))
1364 /* escape sequence follows */
1365 if (safe_read(0, &c, 1) < 1)
1366 goto prepare_to_die;
1367 /* different vt100 emulations */
1368 if (c == '[' || c == 'O') {
1369 if (safe_read(0, &c, 1) < 1)
1370 goto prepare_to_die;
1373 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1374 case '\t': /* Alt-Tab */
1376 input_tab(&lastWasTab);
1379 #if MAX_HISTORY >= 1
1381 /* Up Arrow -- Get previous command from history */
1382 if (cur_history > 0) {
1383 get_previous_history();
1390 /* Down Arrow -- Get next command in history */
1391 if (!get_next_history())
1393 /* Rewrite the line with the selected history item */
1395 /* change command */
1396 len = strlen(strcpy(command, history[cur_history]));
1397 /* redraw and go to end line */
1398 redraw(cmdedit_y, 0);
1402 /* Right Arrow -- Move forward one character */
1406 /* Left Arrow -- Move back one character */
1416 input_backward(cursor);
1424 if (!(c >= '1' && c <= '9'))
1428 if (c >= '1' && c <= '9')
1430 if (safe_read(0, &c, 1) < 1)
1431 goto prepare_to_die;
1436 default: /* If it's regular input, do the normal thing */
1437 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1438 /* Control-V -- Add non-printable symbol */
1440 if (safe_read(0, &c, 1) < 1)
1441 goto prepare_to_die;
1448 if (!Isprint(c)) /* Skip non-printable characters */
1451 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1456 if (cursor == (len - 1)) { /* Append if at the end of the line */
1457 *(command + cursor) = c;
1458 *(command + cursor + 1) = 0;
1459 cmdedit_set_out_char(0);
1460 } else { /* Insert otherwise */
1463 memmove(command + sc + 1, command + sc, len - sc);
1464 *(command + sc) = c;
1466 /* rewrite from cursor */
1468 /* to prev x pos + 1 */
1469 input_backward(cursor - sc);
1474 if (break_out) /* Enter is the command terminator, no more input. */
1481 setTermSettings(0, (void *) &initial_settings);
1482 handlers_sets &= ~SET_RESET_TERM;
1484 #if MAX_HISTORY >= 1
1485 /* Handle command history log */
1486 /* cleanup may be saved current command line */
1487 free(history[MAX_HISTORY]);
1488 history[MAX_HISTORY] = 0;
1489 if (len) { /* no put empty line */
1491 /* After max history, remove the oldest command */
1492 if (i >= MAX_HISTORY) {
1494 for(i = 0; i < (MAX_HISTORY-1); i++)
1495 history[i] = history[i+1];
1497 history[i++] = bb_xstrdup(command);
1500 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1504 #else /* MAX_HISTORY < 1 */
1505 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1506 if (len) { /* no put empty line */
1510 #endif /* MAX_HISTORY >= 1 */
1511 if (break_out > 0) {
1512 command[len++] = '\n'; /* set '\n' */
1515 #if defined(CONFIG_FEATURE_CLEAN_UP) && defined(CONFIG_FEATURE_COMMAND_TAB_COMPLETION)
1516 input_tab(0); /* strong free */
1518 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1519 free(cmdedit_prompt);
1521 cmdedit_reset_term();
1527 #endif /* CONFIG_FEATURE_COMMAND_EDITING */
1532 const char *bb_applet_name = "debug stuff usage";
1534 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1538 int main(int argc, char **argv)
1542 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1543 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1544 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1545 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1550 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1551 setlocale(LC_ALL, "");
1555 l = cmdedit_read_input(prompt, buff);
1556 if(l > 0 && buff[l-1] == '\n') {
1558 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1563 printf("*** cmdedit_read_input() detect ^D\n");