1 /* vi: set sw=4 ts=4: */
3 * Termios command line History and Editting.
5 * Copyright (c) 1986-2001 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 #define CONFIG_FEATURE_COMMAND_EDITING
59 #define CONFIG_FEATURE_COMMAND_TAB_COMPLETION
60 #define CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
61 #define CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
62 #define CONFIG_FEATURE_CLEAN_UP
68 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
73 #ifdef CONFIG_FEATURE_COMMAND_EDITING
75 #ifndef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
76 #undef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
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
89 #endif /* advanced FEATURES */
92 /* Maximum length of the linked list for the command line history */
93 #ifndef CONFIG_FEATURE_COMMAND_HISTORY
94 #define MAX_HISTORY 15
96 #define MAX_HISTORY CONFIG_FEATURE_COMMAND_HISTORY
100 #warning cmdedit: You set MAX_HISTORY < 1. The history algorithm switched off.
102 static char *history[MAX_HISTORY+1]; /* history + current */
103 /* saved history lines */
104 static int n_history;
105 /* current pointer to history line */
106 static int cur_history;
110 #define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
111 #define getTermSettings(fd,argp) tcgetattr(fd, argp);
113 /* Current termio and the previous termio before starting sh */
114 static struct termios initial_settings, new_settings;
118 volatile int cmdedit_termw = 80; /* actual terminal width */
120 volatile int handlers_sets = 0; /* Set next bites: */
123 SET_ATEXIT = 1, /* when atexit() has been called
124 and get euid,uid,gid to fast compare */
125 SET_WCHG_HANDLERS = 2, /* winchg signal handler */
126 SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */
130 static int cmdedit_x; /* real x terminal position */
131 static int cmdedit_y; /* pseudoreal y terminal position */
132 static int cmdedit_prmt_len; /* lenght prompt without colores string */
134 static int cursor; /* required global for signal handler */
135 static int len; /* --- "" - - "" - -"- --""-- --""--- */
136 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
138 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
141 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
143 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
144 static char *user_buf = "";
145 static char *home_pwd_buf = "";
149 #ifdef CONFIG_FEATURE_SH_FANCY_PROMPT
150 static char *hostname_buf;
151 static int num_ok_lines = 1;
155 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
157 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
164 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
166 /* It seems that libc5 doesn't know what a sighandler_t is... */
167 #if (__GLIBC__ <= 2) && (__GLIBC_MINOR__ < 1)
168 typedef void (*sighandler_t) (int);
171 static void cmdedit_setwidth(int w, int redraw_flg);
173 static void win_changed(int nsig)
175 struct winsize win = { 0, 0, 0, 0 };
176 static sighandler_t previous_SIGWINCH_handler; /* for reset */
178 /* emulate || signal call */
179 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
180 ioctl(0, TIOCGWINSZ, &win);
181 if (win.ws_col > 0) {
182 cmdedit_setwidth(win.ws_col, 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)
267 static inline void beep(void)
272 /* Move back one charactor */
273 /* special for slow terminal */
274 static void input_backward(int num)
278 cursor -= num; /* new cursor (in command, not terminal) */
280 if (cmdedit_x >= num) { /* no to up line */
287 printf("\033[%dD", num);
292 putchar('\r'); /* back to first terminal pos. */
293 num -= cmdedit_x; /* set previous backward */
295 count_y = 1 + num / cmdedit_termw;
296 printf("\033[%dA", count_y);
297 cmdedit_y -= count_y;
298 /* require forward after uping */
299 cmdedit_x = cmdedit_termw * count_y - num;
300 printf("\033[%dC", cmdedit_x); /* set term cursor */
304 static void put_prompt(void)
306 out1str(cmdedit_prompt);
307 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
309 cmdedit_y = 0; /* new quasireal y */
312 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
313 static void parse_prompt(const char *prmt_ptr)
315 cmdedit_prompt = prmt_ptr;
316 cmdedit_prmt_len = strlen(prmt_ptr);
320 static void parse_prompt(const char *prmt_ptr)
324 char flg_not_length = '[';
325 char *prmt_mem_ptr = xcalloc(1, 1);
326 char *pwd_buf = xgetcwd(0);
327 char buf2[PATH_MAX + 1];
333 pwd_buf=(char *)bb_msg_unknown;
341 const char *cp = prmt_ptr;
344 c = bb_process_escape_sequence(&prmt_ptr);
350 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
358 pbuf = xcalloc(256, 1);
359 if (gethostname(pbuf, 255) < 0) {
362 char *s = strchr(pbuf, '.');
371 c = my_euid == 0 ? '#' : '$';
373 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
376 l = strlen(home_pwd_buf);
377 if (home_pwd_buf[0] != 0 &&
378 strncmp(home_pwd_buf, pbuf, l) == 0 &&
379 (pbuf[l]=='/' || pbuf[l]=='\0') &&
380 strlen(pwd_buf+l)<PATH_MAX) {
383 strcpy(pbuf+1, pwd_buf+l);
389 cp = strrchr(pbuf,'/');
390 if ( (cp != NULL) && (cp != pbuf) )
394 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
396 case 'e': case 'E': /* \e \E = \033 */
400 for (l = 0; l < 3;) {
402 buf2[l++] = *prmt_ptr;
404 h = strtol(buf2, &pbuf, 16);
405 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
412 c = (char)strtol(buf2, 0, 16);
418 if (c == flg_not_length) {
419 flg_not_length = flg_not_length == '[' ? ']' : '[';
428 prmt_len += strlen(pbuf);
429 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
430 if (flg_not_length == ']')
433 if(pwd_buf!=(char *)bb_msg_unknown)
435 cmdedit_prompt = prmt_mem_ptr;
436 cmdedit_prmt_len = prmt_len - sub_len;
442 /* draw promt, editor line, and clear tail */
443 static void redraw(int y, int back_cursor)
445 if (y > 0) /* up to start y */
446 printf("\033[%dA", y);
449 input_end(); /* rewrite */
450 printf("\033[J"); /* destroy tail after cursor */
451 input_backward(back_cursor);
454 /* Delete the char in front of the cursor */
455 static void input_delete(void)
462 strcpy(command_ps + j, command_ps + j + 1);
464 input_end(); /* rewtite new line */
465 cmdedit_set_out_char(0); /* destroy end char */
466 input_backward(cursor - j); /* back to old pos cursor */
469 /* Delete the char in back of the cursor */
470 static void input_backspace(void)
479 /* Move forward one charactor */
480 static void input_forward(void)
483 cmdedit_set_out_char(command_ps[cursor + 1]);
487 static void cmdedit_setwidth(int w, int redraw_flg)
489 cmdedit_termw = cmdedit_prmt_len + 2;
490 if (w <= cmdedit_termw) {
491 cmdedit_termw = cmdedit_termw % w;
493 if (w > cmdedit_termw) {
497 /* new y for current cursor */
498 int new_y = (cursor + cmdedit_prmt_len) / w;
501 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
507 static void cmdedit_init(void)
509 cmdedit_prmt_len = 0;
510 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
511 /* emulate usage handler to set handler and call yours work */
512 win_changed(-SIGWINCH);
513 handlers_sets |= SET_WCHG_HANDLERS;
516 if ((handlers_sets & SET_ATEXIT) == 0) {
517 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
518 struct passwd *entry;
521 entry = getpwuid(my_euid);
523 user_buf = bb_xstrdup(entry->pw_name);
524 home_pwd_buf = bb_xstrdup(entry->pw_dir);
528 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
530 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
535 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
536 handlers_sets |= SET_ATEXIT;
537 atexit(cmdedit_reset_term); /* be sure to do this only once */
541 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
543 static int is_execute(const struct stat *st)
545 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
546 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
547 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
548 (st->st_mode & S_IXOTH)) return TRUE;
552 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
554 static char **username_tab_completion(char *ud, int *num_matches)
556 struct passwd *entry;
561 ud++; /* ~user/... to user/... */
562 userlen = strlen(ud);
564 if (num_matches == 0) { /* "~/..." or "~user/..." */
565 char *sav_ud = ud - 1;
568 if (*ud == '/') { /* "~/..." */
572 temp = strchr(ud, '/');
573 *temp = 0; /* ~user\0 */
574 entry = getpwnam(ud);
575 *temp = '/'; /* restore ~user/... */
578 home = entry->pw_dir;
581 if ((userlen + strlen(home) + 1) < BUFSIZ) {
582 char temp2[BUFSIZ]; /* argument size */
585 sprintf(temp2, "%s%s", home, ud);
586 strcpy(sav_ud, temp2);
589 return 0; /* void, result save to argument :-) */
592 char **matches = (char **) NULL;
597 while ((entry = getpwent()) != NULL) {
598 /* Null usernames should result in all users as possible completions. */
599 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
601 bb_xasprintf(&temp, "~%s/", entry->pw_name);
602 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
604 matches[nm++] = temp;
613 #endif /* CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION */
621 static int path_parse(char ***p, int flags)
627 /* if not setenv PATH variable, to search cur dir "." */
628 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
629 /* PATH=<empty> or PATH=:<empty> */
630 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
638 npth++; /* count words is + 1 count ':' */
639 tmp = strchr(tmp, ':');
642 break; /* :<empty> */
647 *p = xmalloc(npth * sizeof(char *));
650 (*p)[0] = bb_xstrdup(tmp);
651 npth = 1; /* count words is + 1 count ':' */
654 tmp = strchr(tmp, ':');
656 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
658 break; /* :<empty> */
661 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
667 static char *add_quote_for_spec_chars(char *found)
670 char *s = xmalloc((strlen(found) + 1) * 2);
673 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
681 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
689 int nm = *num_matches;
692 char **paths = path1;
696 char *pfind = strrchr(command, '/');
701 /* no dir, if flags==EXE_ONLY - get paths, else "." */
702 npaths = path_parse(&paths, type);
706 /* save for change */
707 strcpy(dirbuf, command);
709 dirbuf[(pfind - command) + 1] = 0;
710 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
711 if (dirbuf[0] == '~') /* ~/... or ~user/... */
712 username_tab_completion(dirbuf, 0);
714 /* "strip" dirname in command */
718 npaths = 1; /* only 1 dir */
721 for (i = 0; i < npaths; i++) {
723 dir = opendir(paths[i]);
724 if (!dir) /* Don't print an error */
727 while ((next = readdir(dir)) != NULL) {
728 char *str_found = next->d_name;
731 if (strncmp(str_found, pfind, strlen(pfind)))
733 /* not see .name without .match */
734 if (*str_found == '.' && *pfind == 0) {
735 if (*paths[i] == '/' && paths[i][1] == 0
736 && str_found[1] == 0) str_found = ""; /* only "/" */
740 found = concat_path_file(paths[i], str_found);
741 /* hmm, remover in progress? */
742 if (stat(found, &st) < 0)
744 /* find with dirs ? */
745 if (paths[i] != dirbuf)
746 strcpy(found, next->d_name); /* only name */
747 if (S_ISDIR(st.st_mode)) {
748 /* name is directory */
750 found = concat_path_file(found, "");
752 str_found = add_quote_for_spec_chars(found);
754 /* not put found file if search only dirs for cd */
755 if (type == FIND_DIR_ONLY)
757 str_found = add_quote_for_spec_chars(found);
758 if (type == FIND_FILE_ONLY ||
759 (type == FIND_EXE_ONLY && is_execute(&st)))
760 strcat(str_found, " ");
762 /* Add it to the list */
763 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
765 matches[nm++] = str_found;
771 if (paths != path1) {
772 free(paths[0]); /* allocated memory only in first member */
779 static int match_compare(const void *a, const void *b)
781 return strcmp(*(char **) a, *(char **) b);
786 #define QUOT (UCHAR_MAX+1)
788 #define collapse_pos(is, in) { \
789 memcpy(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
790 memcpy(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
792 static int find_match(char *matchBuf, int *len_with_quotes)
797 int int_buf[BUFSIZ + 1];
798 int pos_buf[BUFSIZ + 1];
800 /* set to integer dimension characters and own positions */
802 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
803 if (int_buf[i] == 0) {
804 pos_buf[i] = -1; /* indicator end line */
810 /* mask \+symbol and convert '\t' to ' ' */
811 for (i = j = 0; matchBuf[i]; i++, j++)
812 if (matchBuf[i] == '\\') {
813 collapse_pos(j, j + 1);
816 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
817 if (matchBuf[i] == '\t') /* algorithm equivalent */
818 int_buf[j] = ' ' | QUOT;
821 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
822 else if (matchBuf[i] == '\t')
826 /* mask "symbols" or 'symbols' */
828 for (i = 0; int_buf[i]; i++) {
830 if (c == '\'' || c == '"') {
839 } else if (c2 != 0 && c != '$')
843 /* skip commands with arguments if line have commands delimiters */
844 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
845 for (i = 0; int_buf[i]; i++) {
848 j = i ? int_buf[i - 1] : -1;
850 if (c == ';' || c == '&' || c == '|') {
851 command_mode = 1 + (c == c2);
853 if (j == '>' || j == '<')
855 } else if (c == '|' && j == '>')
859 collapse_pos(0, i + command_mode);
860 i = -1; /* hack incremet */
863 /* collapse `command...` */
864 for (i = 0; int_buf[i]; i++)
865 if (int_buf[i] == '`') {
866 for (j = i + 1; int_buf[j]; j++)
867 if (int_buf[j] == '`') {
868 collapse_pos(i, j + 1);
873 /* not found close ` - command mode, collapse all previous */
874 collapse_pos(0, i + 1);
877 i--; /* hack incremet */
880 /* collapse (command...(command...)...) or {command...{command...}...} */
881 c = 0; /* "recursive" level */
883 for (i = 0; int_buf[i]; i++)
884 if (int_buf[i] == '(' || int_buf[i] == '{') {
885 if (int_buf[i] == '(')
889 collapse_pos(0, i + 1);
890 i = -1; /* hack incremet */
892 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
893 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
894 if (int_buf[i] == ')')
898 collapse_pos(0, i + 1);
899 i = -1; /* hack incremet */
902 /* skip first not quote space */
903 for (i = 0; int_buf[i]; i++)
904 if (int_buf[i] != ' ')
909 /* set find mode for completion */
910 command_mode = FIND_EXE_ONLY;
911 for (i = 0; int_buf[i]; i++)
912 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
913 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
914 && matchBuf[pos_buf[0]]=='c'
915 && matchBuf[pos_buf[1]]=='d' )
916 command_mode = FIND_DIR_ONLY;
918 command_mode = FIND_FILE_ONLY;
923 for (i = 0; int_buf[i]; i++);
925 for (--i; i >= 0; i--) {
927 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
928 collapse_pos(0, i + 1);
932 /* skip first not quoted '\'' or '"' */
933 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
934 /* collapse quote or unquote // or /~ */
935 while ((int_buf[i] & ~QUOT) == '/' &&
936 ((int_buf[i + 1] & ~QUOT) == '/'
937 || (int_buf[i + 1] & ~QUOT) == '~')) {
941 /* set only match and destroy quotes */
943 for (c = 0; pos_buf[i] >= 0; i++) {
944 matchBuf[c++] = matchBuf[pos_buf[i]];
948 /* old lenght matchBuf with quotes symbols */
949 *len_with_quotes = j ? j - pos_buf[0] : 0;
955 display by column original ideas from ls applet,
956 very optimize by my :)
958 static void showfiles(char **matches, int nfiles)
961 int column_width = 0;
964 /* find the longest file name- use that as the column width */
965 for (row = 0; row < nrows; row++) {
966 int l = strlen(matches[row]);
968 if (column_width < l)
971 column_width += 2; /* min space for columns */
972 ncols = cmdedit_termw / column_width;
977 nrows++; /* round up fractionals */
978 column_width = -column_width; /* for printf("%-Ns", ...); */
982 for (row = 0; row < nrows; row++) {
986 for(nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++)
987 printf("%*s", column_width, matches[n]);
988 printf("%s\n", matches[n]);
993 static void input_tab(int *lastWasTab)
995 /* Do TAB completion */
996 static int num_matches;
997 static char **matches;
999 if (lastWasTab == 0) { /* free all memory */
1001 while (num_matches > 0)
1002 free(matches[--num_matches]);
1004 matches = (char **) NULL;
1008 if (! *lastWasTab) {
1012 char matchBuf[BUFSIZ];
1016 *lastWasTab = TRUE; /* flop trigger */
1018 /* Make a local copy of the string -- up
1019 * to the position of the cursor */
1020 tmp = strncpy(matchBuf, command_ps, cursor);
1023 find_type = find_match(matchBuf, &recalc_pos);
1025 /* Free up any memory already allocated */
1028 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
1029 /* If the word starts with `~' and there is no slash in the word,
1030 * then try completing this word as a username. */
1032 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1033 matches = username_tab_completion(matchBuf, &num_matches);
1035 /* Try to match any executable in our path and everything
1036 * in the current working directory that matches. */
1039 exe_n_cwd_tab_completion(matchBuf,
1040 &num_matches, find_type);
1041 /* Remove duplicate found */
1045 for(i=0; i<(num_matches-1); i++)
1046 for(j=i+1; j<num_matches; j++)
1047 if(matches[i]!=0 && matches[j]!=0 &&
1048 strcmp(matches[i], matches[j])==0) {
1056 if(!strcmp(matches[i], "./"))
1058 else if(!strcmp(matches[i], "../"))
1060 matches[num_matches++]=matches[i];
1063 /* Did we find exactly one match? */
1064 if (!matches || num_matches > 1) {
1069 return; /* not found */
1071 qsort(matches, num_matches, sizeof(char *), match_compare);
1073 /* find minimal match */
1074 tmp = bb_xstrdup(matches[0]);
1075 for (tmp1 = tmp; *tmp1; tmp1++)
1076 for (len_found = 1; len_found < num_matches; len_found++)
1077 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1081 if (*tmp == 0) { /* have unique */
1085 } else { /* one match */
1087 /* for next completion current found */
1088 *lastWasTab = FALSE;
1091 len_found = strlen(tmp);
1092 /* have space to placed match? */
1093 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1095 /* before word for match */
1096 command_ps[cursor - recalc_pos] = 0;
1097 /* save tail line */
1098 strcpy(matchBuf, command_ps + cursor);
1100 strcat(command_ps, tmp);
1102 strcat(command_ps, matchBuf);
1103 /* back to begin word for match */
1104 input_backward(recalc_pos);
1106 recalc_pos = cursor + len_found;
1108 len = strlen(command_ps);
1109 /* write out the matched command */
1110 redraw(cmdedit_y, len - recalc_pos);
1112 if (tmp != matches[0])
1115 /* Ok -- the last char was a TAB. Since they
1116 * just hit TAB again, print a list of all the
1117 * available choices... */
1118 if (matches && num_matches > 0) {
1119 int sav_cursor = cursor; /* change goto_new_line() */
1121 /* Go to the next line */
1123 showfiles(matches, num_matches);
1124 redraw(0, len - sav_cursor);
1128 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
1130 #if MAX_HISTORY >= 1
1131 static void get_previous_history(void)
1133 if(command_ps[0] != 0 || history[cur_history] == 0) {
1134 free(history[cur_history]);
1135 history[cur_history] = bb_xstrdup(command_ps);
1140 static int get_next_history(void)
1142 int ch = cur_history;
1144 if (ch < n_history) {
1145 get_previous_history(); /* save the current history line */
1146 return (cur_history = ch+1);
1153 #ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY
1154 extern void load_history ( const char *fromfile )
1161 for(hi = n_history; hi > 0; ) {
1163 free ( history [hi] );
1166 if (( fp = fopen ( fromfile, "r" ))) {
1168 for ( hi = 0; hi < MAX_HISTORY; ) {
1169 char * hl = bb_get_chomped_line_from_file(fp);
1177 if(l == 0 || hl[0] == ' ') {
1181 history [hi++] = hl;
1185 cur_history = n_history = hi;
1188 extern void save_history ( const char *tofile )
1190 FILE *fp = fopen ( tofile, "w" );
1195 for ( i = 0; i < n_history; i++ ) {
1196 fputs ( history [i], fp );
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);
1248 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1249 new_settings.c_cc[VMIN] = 1;
1250 new_settings.c_cc[VTIME] = 0;
1251 /* Turn off CTRL-C, so we can trap it */
1252 # ifndef _POSIX_VDISABLE
1253 # define _POSIX_VDISABLE '\0'
1255 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1259 setTermSettings(0, (void *) &new_settings);
1260 handlers_sets |= SET_RESET_TERM;
1262 /* Now initialize things */
1264 /* Print out the command prompt */
1265 parse_prompt(prompt);
1269 fflush(stdout); /* buffered out to fast */
1271 if (safe_read(0, &c, 1) < 1)
1272 /* if we can't read input then exit */
1273 goto prepare_to_die;
1283 /* Control-a -- Beginning of line */
1284 input_backward(cursor);
1287 /* Control-b -- Move back one character */
1291 /* Control-c -- stop gathering input */
1299 /* Control-d -- Delete one character, or exit
1300 * if the len=0 and no chars to delete */
1303 #if !defined(CONFIG_ASH)
1306 /* cmdedit_reset_term() called in atexit */
1309 break_out = -1; /* for control stoped jobs */
1317 /* Control-e -- End of line */
1321 /* Control-f -- Move forward one character */
1326 /* Control-h and DEL */
1330 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1331 input_tab(&lastWasTab);
1335 /* Control-k -- clear to end of line */
1336 *(command + cursor) = 0;
1341 /* Control-l -- clear screen */
1343 redraw(0, len-cursor);
1345 #if MAX_HISTORY >= 1
1347 /* Control-n -- Get next command in history */
1348 if (get_next_history())
1352 /* Control-p -- Get previous command from history */
1353 if (cur_history > 0) {
1354 get_previous_history();
1362 /* Control-U -- Clear line before cursor */
1364 strcpy(command, command + cursor);
1365 redraw(cmdedit_y, len -= cursor);
1369 /* escape sequence follows */
1370 if (safe_read(0, &c, 1) < 1)
1371 goto prepare_to_die;
1372 /* different vt100 emulations */
1373 if (c == '[' || c == 'O') {
1374 if (safe_read(0, &c, 1) < 1)
1375 goto prepare_to_die;
1378 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1379 case '\t': /* Alt-Tab */
1381 input_tab(&lastWasTab);
1384 #if MAX_HISTORY >= 1
1386 /* Up Arrow -- Get previous command from history */
1387 if (cur_history > 0) {
1388 get_previous_history();
1395 /* Down Arrow -- Get next command in history */
1396 if (!get_next_history())
1398 /* Rewrite the line with the selected history item */
1400 /* change command */
1401 len = strlen(strcpy(command, history[cur_history]));
1402 /* redraw and go to end line */
1403 redraw(cmdedit_y, 0);
1407 /* Right Arrow -- Move forward one character */
1411 /* Left Arrow -- Move back one character */
1421 input_backward(cursor);
1429 if (!(c >= '1' && c <= '9'))
1433 if (c >= '1' && c <= '9')
1435 if (safe_read(0, &c, 1) < 1)
1436 goto prepare_to_die;
1441 default: /* If it's regular input, do the normal thing */
1442 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1443 /* Control-V -- Add non-printable symbol */
1445 if (safe_read(0, &c, 1) < 1)
1446 goto prepare_to_die;
1453 if (!Isprint(c)) /* Skip non-printable characters */
1456 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1461 if (cursor == (len - 1)) { /* Append if at the end of the line */
1462 *(command + cursor) = c;
1463 *(command + cursor + 1) = 0;
1464 cmdedit_set_out_char(0);
1465 } else { /* Insert otherwise */
1468 memmove(command + sc + 1, command + sc, len - sc);
1469 *(command + sc) = c;
1471 /* rewrite from cursor */
1473 /* to prev x pos + 1 */
1474 input_backward(cursor - sc);
1479 if (break_out) /* Enter is the command terminator, no more input. */
1486 setTermSettings(0, (void *) &initial_settings);
1487 handlers_sets &= ~SET_RESET_TERM;
1489 #if MAX_HISTORY >= 1
1490 /* Handle command history log */
1491 /* cleanup may be saved current command line */
1492 free(history[MAX_HISTORY]);
1493 history[MAX_HISTORY] = 0;
1494 if (len) { /* no put empty line */
1496 /* After max history, remove the oldest command */
1497 if (i >= MAX_HISTORY) {
1499 for(i = 0; i < (MAX_HISTORY-1); i++)
1500 history[i] = history[i+1];
1502 history[i++] = bb_xstrdup(command);
1505 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1509 #else /* MAX_HISTORY < 1 */
1510 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1511 if (len) { /* no put empty line */
1515 #endif /* MAX_HISTORY >= 1 */
1517 command[len++] = '\n'; /* set '\n' */
1520 #if defined(CONFIG_FEATURE_CLEAN_UP) && defined(CONFIG_FEATURE_COMMAND_TAB_COMPLETION)
1521 input_tab(0); /* strong free */
1523 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1524 free(cmdedit_prompt);
1526 cmdedit_reset_term();
1532 #endif /* CONFIG_FEATURE_COMMAND_EDITING */
1537 const char *bb_applet_name = "debug stuff usage";
1538 const char *memory_exhausted = "Memory exhausted";
1540 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1544 int main(int argc, char **argv)
1548 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1549 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1550 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1551 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1556 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1557 setlocale(LC_ALL, "");
1561 cmdedit_read_input(prompt, buff);
1565 if(l > 0 && buff[l-1] == '\n')
1567 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1569 printf("*** cmdedit_read_input() detect ^C\n");