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 #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
88 #endif /* advanced FEATURES */
91 /* Maximum length of the linked list for the command line history */
92 #ifndef CONFIG_FEATURE_COMMAND_HISTORY
93 #define MAX_HISTORY 15
95 #define MAX_HISTORY CONFIG_FEATURE_COMMAND_HISTORY
99 #warning cmdedit: You set MAX_HISTORY < 1. The history algorithm switched off.
101 static char *history[MAX_HISTORY+1]; /* history + current */
102 /* saved history lines */
103 static int n_history;
104 /* current pointer to history line */
105 static int cur_history;
109 #define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
110 #define getTermSettings(fd,argp) tcgetattr(fd, argp);
112 /* Current termio and the previous termio before starting sh */
113 static struct termios initial_settings, new_settings;
117 volatile int cmdedit_termw = 80; /* actual terminal width */
119 volatile int handlers_sets = 0; /* Set next bites: */
122 SET_ATEXIT = 1, /* when atexit() has been called
123 and get euid,uid,gid to fast compare */
124 SET_WCHG_HANDLERS = 2, /* winchg signal handler */
125 SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */
129 static int cmdedit_x; /* real x terminal position */
130 static int cmdedit_y; /* pseudoreal y terminal position */
131 static int cmdedit_prmt_len; /* lenght prompt without colores string */
133 static int cursor; /* required global for signal handler */
134 static int len; /* --- "" - - "" - -"- --""-- --""--- */
135 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
137 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
140 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
142 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
143 static char *user_buf = "";
144 static char *home_pwd_buf = "";
148 #ifdef CONFIG_FEATURE_SH_FANCY_PROMPT
149 static char *hostname_buf;
150 static int num_ok_lines = 1;
154 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
156 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
163 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
165 static void cmdedit_setwidth(int w, int redraw_flg);
167 static void win_changed(int nsig)
169 static sighandler_t previous_SIGWINCH_handler; /* for reset */
171 /* emulate || signal call */
172 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
174 get_terminal_width_height(0, &width, NULL);
175 cmdedit_setwidth(width, nsig == SIGWINCH);
177 /* Unix not all standart in recall signal */
179 if (nsig == -SIGWINCH) /* save previous handler */
180 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
181 else if (nsig == SIGWINCH) /* signaled called handler */
182 signal(SIGWINCH, win_changed); /* set for next call */
184 /* set previous handler */
185 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
188 static void cmdedit_reset_term(void)
190 if ((handlers_sets & SET_RESET_TERM) != 0) {
191 /* sparc and other have broken termios support: use old termio handling. */
192 setTermSettings(fileno(stdin), (void *) &initial_settings);
193 handlers_sets &= ~SET_RESET_TERM;
195 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
196 /* reset SIGWINCH handler to previous (default) */
198 handlers_sets &= ~SET_WCHG_HANDLERS;
204 /* special for recount position for scroll and remove terminal margin effect */
205 static void cmdedit_set_out_char(int next_char)
208 int c = (int)((unsigned char) command_ps[cursor]);
211 c = ' '; /* destroy end char? */
212 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
213 if (!Isprint(c)) { /* Inverse put non-printable characters */
220 printf("\033[7m%c\033[0m", c);
224 if (++cmdedit_x >= cmdedit_termw) {
225 /* terminal is scrolled down */
231 /* destroy "(auto)margin" */
238 /* Move to end line. Bonus: rewrite line from cursor */
239 static void input_end(void)
242 cmdedit_set_out_char(0);
245 /* Go to the next line */
246 static void goto_new_line(void)
254 static inline void out1str(const char *s)
260 static inline void beep(void)
265 /* Move back one charactor */
266 /* special for slow terminal */
267 static void input_backward(int num)
271 cursor -= num; /* new cursor (in command, not terminal) */
273 if (cmdedit_x >= num) { /* no to up line */
280 printf("\033[%dD", num);
285 putchar('\r'); /* back to first terminal pos. */
286 num -= cmdedit_x; /* set previous backward */
288 count_y = 1 + num / cmdedit_termw;
289 printf("\033[%dA", count_y);
290 cmdedit_y -= count_y;
291 /* require forward after uping */
292 cmdedit_x = cmdedit_termw * count_y - num;
293 printf("\033[%dC", cmdedit_x); /* set term cursor */
297 static void put_prompt(void)
299 out1str(cmdedit_prompt);
300 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
302 cmdedit_y = 0; /* new quasireal y */
305 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
306 static void parse_prompt(const char *prmt_ptr)
308 cmdedit_prompt = prmt_ptr;
309 cmdedit_prmt_len = strlen(prmt_ptr);
313 static void parse_prompt(const char *prmt_ptr)
317 char flg_not_length = '[';
318 char *prmt_mem_ptr = xcalloc(1, 1);
319 char *pwd_buf = xgetcwd(0);
320 char buf2[PATH_MAX + 1];
326 pwd_buf=(char *)bb_msg_unknown;
334 const char *cp = prmt_ptr;
337 c = bb_process_escape_sequence(&prmt_ptr);
343 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
351 pbuf = xcalloc(256, 1);
352 if (gethostname(pbuf, 255) < 0) {
355 char *s = strchr(pbuf, '.');
364 c = my_euid == 0 ? '#' : '$';
366 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
369 l = strlen(home_pwd_buf);
370 if (home_pwd_buf[0] != 0 &&
371 strncmp(home_pwd_buf, pbuf, l) == 0 &&
372 (pbuf[l]=='/' || pbuf[l]=='\0') &&
373 strlen(pwd_buf+l)<PATH_MAX) {
376 strcpy(pbuf+1, pwd_buf+l);
382 cp = strrchr(pbuf,'/');
383 if ( (cp != NULL) && (cp != pbuf) )
387 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
389 case 'e': case 'E': /* \e \E = \033 */
393 for (l = 0; l < 3;) {
395 buf2[l++] = *prmt_ptr;
397 h = strtol(buf2, &pbuf, 16);
398 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
405 c = (char)strtol(buf2, 0, 16);
411 if (c == flg_not_length) {
412 flg_not_length = flg_not_length == '[' ? ']' : '[';
421 prmt_len += strlen(pbuf);
422 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
423 if (flg_not_length == ']')
426 if(pwd_buf!=(char *)bb_msg_unknown)
428 cmdedit_prompt = prmt_mem_ptr;
429 cmdedit_prmt_len = prmt_len - sub_len;
435 /* draw promt, editor line, and clear tail */
436 static void redraw(int y, int back_cursor)
438 if (y > 0) /* up to start y */
439 printf("\033[%dA", y);
442 input_end(); /* rewrite */
443 printf("\033[J"); /* destroy tail after cursor */
444 input_backward(back_cursor);
447 /* Delete the char in front of the cursor */
448 static void input_delete(void)
455 strcpy(command_ps + j, command_ps + j + 1);
457 input_end(); /* rewtite new line */
458 cmdedit_set_out_char(0); /* destroy end char */
459 input_backward(cursor - j); /* back to old pos cursor */
462 /* Delete the char in back of the cursor */
463 static void input_backspace(void)
472 /* Move forward one charactor */
473 static void input_forward(void)
476 cmdedit_set_out_char(command_ps[cursor + 1]);
480 static void cmdedit_setwidth(int w, int redraw_flg)
482 cmdedit_termw = cmdedit_prmt_len + 2;
483 if (w <= cmdedit_termw) {
484 cmdedit_termw = cmdedit_termw % w;
486 if (w > cmdedit_termw) {
490 /* new y for current cursor */
491 int new_y = (cursor + cmdedit_prmt_len) / w;
494 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
500 static void cmdedit_init(void)
502 cmdedit_prmt_len = 0;
503 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
504 /* emulate usage handler to set handler and call yours work */
505 win_changed(-SIGWINCH);
506 handlers_sets |= SET_WCHG_HANDLERS;
509 if ((handlers_sets & SET_ATEXIT) == 0) {
510 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
511 struct passwd *entry;
514 entry = getpwuid(my_euid);
516 user_buf = bb_xstrdup(entry->pw_name);
517 home_pwd_buf = bb_xstrdup(entry->pw_dir);
521 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
523 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
528 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
529 handlers_sets |= SET_ATEXIT;
530 atexit(cmdedit_reset_term); /* be sure to do this only once */
534 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
536 static int is_execute(const struct stat *st)
538 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
539 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
540 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
541 (st->st_mode & S_IXOTH)) return TRUE;
545 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
547 static char **username_tab_completion(char *ud, int *num_matches)
549 struct passwd *entry;
554 ud++; /* ~user/... to user/... */
555 userlen = strlen(ud);
557 if (num_matches == 0) { /* "~/..." or "~user/..." */
558 char *sav_ud = ud - 1;
561 if (*ud == '/') { /* "~/..." */
565 temp = strchr(ud, '/');
566 *temp = 0; /* ~user\0 */
567 entry = getpwnam(ud);
568 *temp = '/'; /* restore ~user/... */
571 home = entry->pw_dir;
574 if ((userlen + strlen(home) + 1) < BUFSIZ) {
575 char temp2[BUFSIZ]; /* argument size */
578 sprintf(temp2, "%s%s", home, ud);
579 strcpy(sav_ud, temp2);
582 return 0; /* void, result save to argument :-) */
585 char **matches = (char **) NULL;
590 while ((entry = getpwent()) != NULL) {
591 /* Null usernames should result in all users as possible completions. */
592 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
594 bb_xasprintf(&temp, "~%s/", entry->pw_name);
595 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
597 matches[nm++] = temp;
606 #endif /* CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION */
615 const char *cmdedit_path_lookup;
617 #define cmdedit_path_lookup getenv("PATH")
620 static int path_parse(char ***p, int flags)
626 /* if not setenv PATH variable, to search cur dir "." */
627 if (flags != FIND_EXE_ONLY || (pth = cmdedit_path_lookup) == 0 ||
628 /* PATH=<empty> or PATH=:<empty> */
629 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
637 npth++; /* count words is + 1 count ':' */
638 tmp = strchr(tmp, ':');
641 break; /* :<empty> */
646 *p = xmalloc(npth * sizeof(char *));
649 (*p)[0] = bb_xstrdup(tmp);
650 npth = 1; /* count words is + 1 count ':' */
653 tmp = strchr(tmp, ':');
655 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
657 break; /* :<empty> */
660 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
666 static char *add_quote_for_spec_chars(char *found)
669 char *s = xmalloc((strlen(found) + 1) * 2);
672 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
680 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
688 int nm = *num_matches;
691 char **paths = path1;
695 char *pfind = strrchr(command, '/');
700 /* no dir, if flags==EXE_ONLY - get paths, else "." */
701 npaths = path_parse(&paths, type);
705 /* save for change */
706 strcpy(dirbuf, command);
708 dirbuf[(pfind - command) + 1] = 0;
709 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
710 if (dirbuf[0] == '~') /* ~/... or ~user/... */
711 username_tab_completion(dirbuf, 0);
713 /* "strip" dirname in command */
717 npaths = 1; /* only 1 dir */
720 for (i = 0; i < npaths; i++) {
722 dir = opendir(paths[i]);
723 if (!dir) /* Don't print an error */
726 while ((next = readdir(dir)) != NULL) {
727 char *str_found = next->d_name;
730 if (strncmp(str_found, pfind, strlen(pfind)))
732 /* not see .name without .match */
733 if (*str_found == '.' && *pfind == 0) {
734 if (*paths[i] == '/' && paths[i][1] == 0
735 && str_found[1] == 0) str_found = ""; /* only "/" */
739 found = concat_path_file(paths[i], str_found);
740 /* hmm, remover in progress? */
741 if (stat(found, &st) < 0)
743 /* find with dirs ? */
744 if (paths[i] != dirbuf)
745 strcpy(found, next->d_name); /* only name */
746 if (S_ISDIR(st.st_mode)) {
747 /* name is directory */
749 found = concat_path_file(found, "");
751 str_found = add_quote_for_spec_chars(found);
753 /* not put found file if search only dirs for cd */
754 if (type == FIND_DIR_ONLY)
756 str_found = add_quote_for_spec_chars(found);
757 if (type == FIND_FILE_ONLY ||
758 (type == FIND_EXE_ONLY && is_execute(&st)))
759 strcat(str_found, " ");
761 /* Add it to the list */
762 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
764 matches[nm++] = str_found;
770 if (paths != path1) {
771 free(paths[0]); /* allocated memory only in first member */
778 static int match_compare(const void *a, const void *b)
780 return strcmp(*(char **) a, *(char **) b);
785 #define QUOT (UCHAR_MAX+1)
787 #define collapse_pos(is, in) { \
788 memcpy(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
789 memcpy(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
791 static int find_match(char *matchBuf, int *len_with_quotes)
796 int int_buf[BUFSIZ + 1];
797 int pos_buf[BUFSIZ + 1];
799 /* set to integer dimension characters and own positions */
801 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
802 if (int_buf[i] == 0) {
803 pos_buf[i] = -1; /* indicator end line */
809 /* mask \+symbol and convert '\t' to ' ' */
810 for (i = j = 0; matchBuf[i]; i++, j++)
811 if (matchBuf[i] == '\\') {
812 collapse_pos(j, j + 1);
815 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
816 if (matchBuf[i] == '\t') /* algorithm equivalent */
817 int_buf[j] = ' ' | QUOT;
820 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
821 else if (matchBuf[i] == '\t')
825 /* mask "symbols" or 'symbols' */
827 for (i = 0; int_buf[i]; i++) {
829 if (c == '\'' || c == '"') {
838 } else if (c2 != 0 && c != '$')
842 /* skip commands with arguments if line have commands delimiters */
843 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
844 for (i = 0; int_buf[i]; i++) {
847 j = i ? int_buf[i - 1] : -1;
849 if (c == ';' || c == '&' || c == '|') {
850 command_mode = 1 + (c == c2);
852 if (j == '>' || j == '<')
854 } else if (c == '|' && j == '>')
858 collapse_pos(0, i + command_mode);
859 i = -1; /* hack incremet */
862 /* collapse `command...` */
863 for (i = 0; int_buf[i]; i++)
864 if (int_buf[i] == '`') {
865 for (j = i + 1; int_buf[j]; j++)
866 if (int_buf[j] == '`') {
867 collapse_pos(i, j + 1);
872 /* not found close ` - command mode, collapse all previous */
873 collapse_pos(0, i + 1);
876 i--; /* hack incremet */
879 /* collapse (command...(command...)...) or {command...{command...}...} */
880 c = 0; /* "recursive" level */
882 for (i = 0; int_buf[i]; i++)
883 if (int_buf[i] == '(' || int_buf[i] == '{') {
884 if (int_buf[i] == '(')
888 collapse_pos(0, i + 1);
889 i = -1; /* hack incremet */
891 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
892 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
893 if (int_buf[i] == ')')
897 collapse_pos(0, i + 1);
898 i = -1; /* hack incremet */
901 /* skip first not quote space */
902 for (i = 0; int_buf[i]; i++)
903 if (int_buf[i] != ' ')
908 /* set find mode for completion */
909 command_mode = FIND_EXE_ONLY;
910 for (i = 0; int_buf[i]; i++)
911 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
912 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
913 && matchBuf[pos_buf[0]]=='c'
914 && matchBuf[pos_buf[1]]=='d' )
915 command_mode = FIND_DIR_ONLY;
917 command_mode = FIND_FILE_ONLY;
922 for (i = 0; int_buf[i]; i++);
924 for (--i; i >= 0; i--) {
926 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
927 collapse_pos(0, i + 1);
931 /* skip first not quoted '\'' or '"' */
932 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
933 /* collapse quote or unquote // or /~ */
934 while ((int_buf[i] & ~QUOT) == '/' &&
935 ((int_buf[i + 1] & ~QUOT) == '/'
936 || (int_buf[i + 1] & ~QUOT) == '~')) {
940 /* set only match and destroy quotes */
942 for (c = 0; pos_buf[i] >= 0; i++) {
943 matchBuf[c++] = matchBuf[pos_buf[i]];
947 /* old lenght matchBuf with quotes symbols */
948 *len_with_quotes = j ? j - pos_buf[0] : 0;
954 display by column original ideas from ls applet,
955 very optimize by my :)
957 static void showfiles(char **matches, int nfiles)
960 int column_width = 0;
963 /* find the longest file name- use that as the column width */
964 for (row = 0; row < nrows; row++) {
965 int l = strlen(matches[row]);
967 if (column_width < l)
970 column_width += 2; /* min space for columns */
971 ncols = cmdedit_termw / column_width;
976 nrows++; /* round up fractionals */
977 column_width = -column_width; /* for printf("%-Ns", ...); */
981 for (row = 0; row < nrows; row++) {
985 for(nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++)
986 printf("%*s", column_width, matches[n]);
987 printf("%s\n", matches[n]);
992 static void input_tab(int *lastWasTab)
994 /* Do TAB completion */
995 static int num_matches;
996 static char **matches;
998 if (lastWasTab == 0) { /* free all memory */
1000 while (num_matches > 0)
1001 free(matches[--num_matches]);
1003 matches = (char **) NULL;
1007 if (! *lastWasTab) {
1011 char matchBuf[BUFSIZ];
1015 *lastWasTab = TRUE; /* flop trigger */
1017 /* Make a local copy of the string -- up
1018 * to the position of the cursor */
1019 tmp = strncpy(matchBuf, command_ps, cursor);
1022 find_type = find_match(matchBuf, &recalc_pos);
1024 /* Free up any memory already allocated */
1027 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
1028 /* If the word starts with `~' and there is no slash in the word,
1029 * then try completing this word as a username. */
1031 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1032 matches = username_tab_completion(matchBuf, &num_matches);
1034 /* Try to match any executable in our path and everything
1035 * in the current working directory that matches. */
1038 exe_n_cwd_tab_completion(matchBuf,
1039 &num_matches, find_type);
1040 /* Remove duplicate found */
1044 for(i=0; i<(num_matches-1); i++)
1045 for(j=i+1; j<num_matches; j++)
1046 if(matches[i]!=0 && matches[j]!=0 &&
1047 strcmp(matches[i], matches[j])==0) {
1055 if(!strcmp(matches[i], "./"))
1057 else if(!strcmp(matches[i], "../"))
1059 matches[num_matches++]=matches[i];
1062 /* Did we find exactly one match? */
1063 if (!matches || num_matches > 1) {
1068 return; /* not found */
1070 qsort(matches, num_matches, sizeof(char *), match_compare);
1072 /* find minimal match */
1073 tmp = bb_xstrdup(matches[0]);
1074 for (tmp1 = tmp; *tmp1; tmp1++)
1075 for (len_found = 1; len_found < num_matches; len_found++)
1076 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1080 if (*tmp == 0) { /* have unique */
1084 } else { /* one match */
1086 /* for next completion current found */
1087 *lastWasTab = FALSE;
1090 len_found = strlen(tmp);
1091 /* have space to placed match? */
1092 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1094 /* before word for match */
1095 command_ps[cursor - recalc_pos] = 0;
1096 /* save tail line */
1097 strcpy(matchBuf, command_ps + cursor);
1099 strcat(command_ps, tmp);
1101 strcat(command_ps, matchBuf);
1102 /* back to begin word for match */
1103 input_backward(recalc_pos);
1105 recalc_pos = cursor + len_found;
1107 len = strlen(command_ps);
1108 /* write out the matched command */
1109 redraw(cmdedit_y, len - recalc_pos);
1111 if (tmp != matches[0])
1114 /* Ok -- the last char was a TAB. Since they
1115 * just hit TAB again, print a list of all the
1116 * available choices... */
1117 if (matches && num_matches > 0) {
1118 int sav_cursor = cursor; /* change goto_new_line() */
1120 /* Go to the next line */
1122 showfiles(matches, num_matches);
1123 redraw(0, len - sav_cursor);
1127 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
1129 #if MAX_HISTORY >= 1
1130 static void get_previous_history(void)
1132 if(command_ps[0] != 0 || history[cur_history] == 0) {
1133 free(history[cur_history]);
1134 history[cur_history] = bb_xstrdup(command_ps);
1139 static int get_next_history(void)
1141 int ch = cur_history;
1143 if (ch < n_history) {
1144 get_previous_history(); /* save the current history line */
1145 return (cur_history = ch+1);
1152 #ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY
1153 extern void load_history ( const char *fromfile )
1160 for(hi = n_history; hi > 0; ) {
1162 free ( history [hi] );
1165 if (( fp = fopen ( fromfile, "r" ))) {
1167 for ( hi = 0; hi < MAX_HISTORY; ) {
1168 char * hl = bb_get_chomped_line_from_file(fp);
1176 if(l == 0 || hl[0] == ' ') {
1180 history [hi++] = hl;
1184 cur_history = n_history = hi;
1187 extern void save_history ( const char *tofile )
1189 FILE *fp = fopen ( tofile, "w" );
1194 for ( i = 0; i < n_history; i++ ) {
1195 fprintf(fp, "%s\n", history [i]);
1211 * This function is used to grab a character buffer
1212 * from the input file descriptor and allows you to
1213 * a string with full command editing (sortof like
1216 * The following standard commands are not implemented:
1217 * ESC-b -- Move back one word
1218 * ESC-f -- Move forward one word
1219 * ESC-d -- Delete back one word
1220 * ESC-h -- Delete forward one word
1221 * CTL-t -- Transpose two characters
1223 * Furthermore, the "vi" command editing keys are not implemented.
1228 int cmdedit_read_input(char *prompt, char command[BUFSIZ])
1232 int lastWasTab = FALSE;
1233 unsigned char c = 0;
1235 /* prepare before init handlers */
1236 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1238 command_ps = command;
1240 getTermSettings(0, (void *) &initial_settings);
1241 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1242 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1243 /* Turn off echoing and CTRL-C, so we can trap it */
1244 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1245 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1246 new_settings.c_cc[VMIN] = 1;
1247 new_settings.c_cc[VTIME] = 0;
1248 /* Turn off CTRL-C, so we can trap it */
1249 # ifndef _POSIX_VDISABLE
1250 # define _POSIX_VDISABLE '\0'
1252 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1255 setTermSettings(0, (void *) &new_settings);
1256 handlers_sets |= SET_RESET_TERM;
1258 /* Now initialize things */
1260 /* Print out the command prompt */
1261 parse_prompt(prompt);
1265 fflush(stdout); /* buffered out to fast */
1267 if (safe_read(0, &c, 1) < 1)
1268 /* if we can't read input then exit */
1269 goto prepare_to_die;
1279 /* Control-a -- Beginning of line */
1280 input_backward(cursor);
1283 /* Control-b -- Move back one character */
1287 /* Control-c -- stop gathering input */
1295 /* Control-d -- Delete one character, or exit
1296 * if the len=0 and no chars to delete */
1299 #if !defined(CONFIG_ASH)
1302 /* cmdedit_reset_term() called in atexit */
1305 break_out = -1; /* for control stoped jobs */
1313 /* Control-e -- End of line */
1317 /* Control-f -- Move forward one character */
1322 /* Control-h and DEL */
1326 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1327 input_tab(&lastWasTab);
1331 /* Control-k -- clear to end of line */
1332 *(command + cursor) = 0;
1337 /* Control-l -- clear screen */
1339 redraw(0, len-cursor);
1341 #if MAX_HISTORY >= 1
1343 /* Control-n -- Get next command in history */
1344 if (get_next_history())
1348 /* Control-p -- Get previous command from history */
1349 if (cur_history > 0) {
1350 get_previous_history();
1358 /* Control-U -- Clear line before cursor */
1360 strcpy(command, command + cursor);
1361 redraw(cmdedit_y, len -= cursor);
1365 /* Control-W -- Remove the last word */
1366 while (cursor > 0 && isspace(command[cursor-1]))
1368 while (cursor > 0 &&!isspace(command[cursor-1]))
1372 /* escape sequence follows */
1373 if (safe_read(0, &c, 1) < 1)
1374 goto prepare_to_die;
1375 /* different vt100 emulations */
1376 if (c == '[' || c == 'O') {
1377 if (safe_read(0, &c, 1) < 1)
1378 goto prepare_to_die;
1381 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1382 case '\t': /* Alt-Tab */
1384 input_tab(&lastWasTab);
1387 #if MAX_HISTORY >= 1
1389 /* Up Arrow -- Get previous command from history */
1390 if (cur_history > 0) {
1391 get_previous_history();
1398 /* Down Arrow -- Get next command in history */
1399 if (!get_next_history())
1401 /* Rewrite the line with the selected history item */
1403 /* change command */
1404 len = strlen(strcpy(command, history[cur_history]));
1405 /* redraw and go to end line */
1406 redraw(cmdedit_y, 0);
1410 /* Right Arrow -- Move forward one character */
1414 /* Left Arrow -- Move back one character */
1424 input_backward(cursor);
1432 if (!(c >= '1' && c <= '9'))
1436 if (c >= '1' && c <= '9')
1438 if (safe_read(0, &c, 1) < 1)
1439 goto prepare_to_die;
1444 default: /* If it's regular input, do the normal thing */
1445 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1446 /* Control-V -- Add non-printable symbol */
1448 if (safe_read(0, &c, 1) < 1)
1449 goto prepare_to_die;
1456 if (!Isprint(c)) /* Skip non-printable characters */
1459 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1464 if (cursor == (len - 1)) { /* Append if at the end of the line */
1465 *(command + cursor) = c;
1466 *(command + cursor + 1) = 0;
1467 cmdedit_set_out_char(0);
1468 } else { /* Insert otherwise */
1471 memmove(command + sc + 1, command + sc, len - sc);
1472 *(command + sc) = c;
1474 /* rewrite from cursor */
1476 /* to prev x pos + 1 */
1477 input_backward(cursor - sc);
1482 if (break_out) /* Enter is the command terminator, no more input. */
1489 setTermSettings(0, (void *) &initial_settings);
1490 handlers_sets &= ~SET_RESET_TERM;
1492 #if MAX_HISTORY >= 1
1493 /* Handle command history log */
1494 /* cleanup may be saved current command line */
1495 free(history[MAX_HISTORY]);
1496 history[MAX_HISTORY] = 0;
1497 if (len) { /* no put empty line */
1499 /* After max history, remove the oldest command */
1500 if (i >= MAX_HISTORY) {
1502 for(i = 0; i < (MAX_HISTORY-1); i++)
1503 history[i] = history[i+1];
1505 history[i++] = bb_xstrdup(command);
1508 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1512 #else /* MAX_HISTORY < 1 */
1513 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1514 if (len) { /* no put empty line */
1518 #endif /* MAX_HISTORY >= 1 */
1519 if (break_out > 0) {
1520 command[len++] = '\n'; /* set '\n' */
1523 #if defined(CONFIG_FEATURE_CLEAN_UP) && defined(CONFIG_FEATURE_COMMAND_TAB_COMPLETION)
1524 input_tab(0); /* strong free */
1526 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1527 free(cmdedit_prompt);
1529 cmdedit_reset_term();
1535 #endif /* CONFIG_FEATURE_COMMAND_EDITING */
1540 const char *bb_applet_name = "debug stuff usage";
1542 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1546 int main(int argc, char **argv)
1550 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1551 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1552 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1553 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1558 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1559 setlocale(LC_ALL, "");
1563 l = cmdedit_read_input(prompt, buff);
1564 if(l > 0 && buff[l-1] == '\n') {
1566 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1571 printf("*** cmdedit_read_input() detect ^D\n");