1 /* vi: set sw=4 ts=4: */
3 * Termios command line History and Editing.
5 * Copyright (c) 1986-2003 may safely be consumed by a BSD or GPL license.
6 * Written by: Vladimir Oleynik <dzo@simtreas.ru>
9 * Adam Rogoyski <rogoyski@cs.utexas.edu>
10 * Dave Cinege <dcinege@psychosis.com>
11 * Jakub Jelinek (c) 1995
12 * Erik Andersen <andersen@codepoet.org> (Majorly adjusted for busybox)
14 * This code is 'as is' with no warranty.
21 Terminal key codes are not extensive, and more will probably
22 need to be added. This version was created on Debian GNU/Linux 2.x.
23 Delete, Backspace, Home, End, and the arrow keys were tested
24 to work in an Xterm and console. Ctrl-A also works as Home.
25 Ctrl-E also works as End.
27 Small bugs (simple effect):
28 - not true viewing if terminal size (x*y symbols) less
29 size (prompt + editor`s line + 2 symbols)
30 - not true viewing if length prompt less terminal width
39 #include <sys/ioctl.h>
46 #include "../shell/cmdedit.h"
49 #ifdef CONFIG_LOCALE_SUPPORT
50 #define Isprint(c) isprint((c))
52 #define Isprint(c) ( (c) >= ' ' && (c) != ((unsigned char)'\233') )
57 /* pretect redefined for test */
58 #undef CONFIG_FEATURE_COMMAND_EDITING
59 #undef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
60 #undef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
61 #undef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
62 #undef CONFIG_FEATURE_CLEAN_UP
64 #define CONFIG_FEATURE_COMMAND_EDITING
65 #define CONFIG_FEATURE_COMMAND_TAB_COMPLETION
66 #define CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
67 #define CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
68 #define CONFIG_FEATURE_CLEAN_UP
72 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
77 #ifdef CONFIG_FEATURE_COMMAND_EDITING
79 #if defined(CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION) || defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
80 #define CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
83 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
85 #endif /* advanced FEATURES */
88 /* Maximum length of the linked list for the command line history */
89 #ifndef CONFIG_FEATURE_COMMAND_HISTORY
90 #define MAX_HISTORY 15
92 #define MAX_HISTORY CONFIG_FEATURE_COMMAND_HISTORY
96 #warning cmdedit: You set MAX_HISTORY < 1. The history algorithm switched off.
98 static char *history[MAX_HISTORY+1]; /* history + current */
99 /* saved history lines */
100 static int n_history;
101 /* current pointer to history line */
102 static int cur_history;
106 #define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
107 #define getTermSettings(fd,argp) tcgetattr(fd, argp);
109 /* Current termio and the previous termio before starting sh */
110 static struct termios initial_settings, new_settings;
114 volatile int cmdedit_termw = 80; /* actual terminal width */
116 volatile int handlers_sets = 0; /* Set next bites: */
119 SET_ATEXIT = 1, /* when atexit() has been called
120 and get euid,uid,gid to fast compare */
121 SET_WCHG_HANDLERS = 2, /* winchg signal handler */
122 SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */
126 static int cmdedit_x; /* real x terminal position */
127 static int cmdedit_y; /* pseudoreal y terminal position */
128 static int cmdedit_prmt_len; /* lenght prompt without colores string */
130 static int cursor; /* required global for signal handler */
131 static int len; /* --- "" - - "" - -"- --""-- --""--- */
132 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
134 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
137 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
139 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
140 static char *user_buf = "";
141 static char *home_pwd_buf = "";
145 #ifdef CONFIG_FEATURE_SH_FANCY_PROMPT
146 static char *hostname_buf;
147 static int num_ok_lines = 1;
151 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
153 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
160 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
162 static void cmdedit_setwidth(int w, int redraw_flg);
164 static void win_changed(int nsig)
166 static sighandler_t previous_SIGWINCH_handler; /* for reset */
168 /* emulate || signal call */
169 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
171 get_terminal_width_height(0, &width, NULL);
172 cmdedit_setwidth(width, nsig == SIGWINCH);
174 /* Unix not all standart in recall signal */
176 if (nsig == -SIGWINCH) /* save previous handler */
177 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
178 else if (nsig == SIGWINCH) /* signaled called handler */
179 signal(SIGWINCH, win_changed); /* set for next call */
181 /* set previous handler */
182 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
185 static void cmdedit_reset_term(void)
187 if ((handlers_sets & SET_RESET_TERM) != 0) {
188 /* sparc and other have broken termios support: use old termio handling. */
189 setTermSettings(STDIN_FILENO, (void *) &initial_settings);
190 handlers_sets &= ~SET_RESET_TERM;
192 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
193 /* reset SIGWINCH handler to previous (default) */
195 handlers_sets &= ~SET_WCHG_HANDLERS;
201 /* special for recount position for scroll and remove terminal margin effect */
202 static void cmdedit_set_out_char(int next_char)
205 int c = (int)((unsigned char) command_ps[cursor]);
208 c = ' '; /* destroy end char? */
209 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
210 if (!Isprint(c)) { /* Inverse put non-printable characters */
217 printf("\033[7m%c\033[0m", c);
221 if (++cmdedit_x >= cmdedit_termw) {
222 /* terminal is scrolled down */
228 /* destroy "(auto)margin" */
235 /* Move to end line. Bonus: rewrite line from cursor */
236 static void input_end(void)
239 cmdedit_set_out_char(0);
242 /* Go to the next line */
243 static void goto_new_line(void)
251 static inline void out1str(const char *s)
257 static inline void beep(void)
262 /* Move back one character */
263 /* special for slow terminal */
264 static void input_backward(int num)
268 cursor -= num; /* new cursor (in command, not terminal) */
270 if (cmdedit_x >= num) { /* no to up line */
277 printf("\033[%dD", num);
282 putchar('\r'); /* back to first terminal pos. */
283 num -= cmdedit_x; /* set previous backward */
285 count_y = 1 + num / cmdedit_termw;
286 printf("\033[%dA", count_y);
287 cmdedit_y -= count_y;
288 /* require forward after uping */
289 cmdedit_x = cmdedit_termw * count_y - num;
290 printf("\033[%dC", cmdedit_x); /* set term cursor */
294 static void put_prompt(void)
296 out1str(cmdedit_prompt);
297 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
299 cmdedit_y = 0; /* new quasireal y */
302 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
303 static void parse_prompt(const char *prmt_ptr)
305 cmdedit_prompt = prmt_ptr;
306 cmdedit_prmt_len = strlen(prmt_ptr);
310 static void parse_prompt(const char *prmt_ptr)
314 char flg_not_length = '[';
315 char *prmt_mem_ptr = xcalloc(1, 1);
316 char *pwd_buf = xgetcwd(0);
317 char buf2[PATH_MAX + 1];
323 pwd_buf=(char *)bb_msg_unknown;
331 const char *cp = prmt_ptr;
334 c = bb_process_escape_sequence(&prmt_ptr);
340 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
348 pbuf = xcalloc(256, 1);
349 if (gethostname(pbuf, 255) < 0) {
352 char *s = strchr(pbuf, '.');
361 c = my_euid == 0 ? '#' : '$';
363 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
366 l = strlen(home_pwd_buf);
367 if (home_pwd_buf[0] != 0 &&
368 strncmp(home_pwd_buf, pbuf, l) == 0 &&
369 (pbuf[l]=='/' || pbuf[l]=='\0') &&
370 strlen(pwd_buf+l)<PATH_MAX) {
373 strcpy(pbuf+1, pwd_buf+l);
379 cp = strrchr(pbuf,'/');
380 if ( (cp != NULL) && (cp != pbuf) )
384 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
386 case 'e': case 'E': /* \e \E = \033 */
390 for (l = 0; l < 3;) {
392 buf2[l++] = *prmt_ptr;
394 h = strtol(buf2, &pbuf, 16);
395 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
402 c = (char)strtol(buf2, 0, 16);
408 if (c == flg_not_length) {
409 flg_not_length = flg_not_length == '[' ? ']' : '[';
418 prmt_len += strlen(pbuf);
419 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
420 if (flg_not_length == ']')
423 if(pwd_buf!=(char *)bb_msg_unknown)
425 cmdedit_prompt = prmt_mem_ptr;
426 cmdedit_prmt_len = prmt_len - sub_len;
432 /* draw prompt, editor line, and clear tail */
433 static void redraw(int y, int back_cursor)
435 if (y > 0) /* up to start y */
436 printf("\033[%dA", y);
439 input_end(); /* rewrite */
440 printf("\033[J"); /* destroy tail after cursor */
441 input_backward(back_cursor);
444 /* Delete the char in front of the cursor */
445 static void input_delete(void)
452 strcpy(command_ps + j, command_ps + j + 1);
454 input_end(); /* rewtite new line */
455 cmdedit_set_out_char(0); /* destroy end char */
456 input_backward(cursor - j); /* back to old pos cursor */
459 /* Delete the char in back of the cursor */
460 static void input_backspace(void)
469 /* Move forward one character */
470 static void input_forward(void)
473 cmdedit_set_out_char(command_ps[cursor + 1]);
477 static void cmdedit_setwidth(int w, int redraw_flg)
479 cmdedit_termw = cmdedit_prmt_len + 2;
480 if (w <= cmdedit_termw) {
481 cmdedit_termw = cmdedit_termw % w;
483 if (w > cmdedit_termw) {
487 /* new y for current cursor */
488 int new_y = (cursor + cmdedit_prmt_len) / w;
491 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
497 static void cmdedit_init(void)
499 cmdedit_prmt_len = 0;
500 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
501 /* emulate usage handler to set handler and call yours work */
502 win_changed(-SIGWINCH);
503 handlers_sets |= SET_WCHG_HANDLERS;
506 if ((handlers_sets & SET_ATEXIT) == 0) {
507 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
508 struct passwd *entry;
511 entry = getpwuid(my_euid);
513 user_buf = bb_xstrdup(entry->pw_name);
514 home_pwd_buf = bb_xstrdup(entry->pw_dir);
518 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
520 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
525 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
526 handlers_sets |= SET_ATEXIT;
527 atexit(cmdedit_reset_term); /* be sure to do this only once */
531 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
533 static int is_execute(const struct stat *st)
535 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
536 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
537 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
538 (st->st_mode & S_IXOTH)) return TRUE;
542 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
544 static char **username_tab_completion(char *ud, int *num_matches)
546 struct passwd *entry;
551 ud++; /* ~user/... to user/... */
552 userlen = strlen(ud);
554 if (num_matches == 0) { /* "~/..." or "~user/..." */
555 char *sav_ud = ud - 1;
558 if (*ud == '/') { /* "~/..." */
562 temp = strchr(ud, '/');
563 *temp = 0; /* ~user\0 */
564 entry = getpwnam(ud);
565 *temp = '/'; /* restore ~user/... */
568 home = entry->pw_dir;
571 if ((userlen + strlen(home) + 1) < BUFSIZ) {
572 char temp2[BUFSIZ]; /* argument size */
575 sprintf(temp2, "%s%s", home, ud);
576 strcpy(sav_ud, temp2);
579 return 0; /* void, result save to argument :-) */
582 char **matches = (char **) NULL;
587 while ((entry = getpwent()) != NULL) {
588 /* Null usernames should result in all users as possible completions. */
589 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
591 bb_xasprintf(&temp, "~%s/", entry->pw_name);
592 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
594 matches[nm++] = temp;
603 #endif /* CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION */
612 const char *cmdedit_path_lookup;
614 #define cmdedit_path_lookup getenv("PATH")
617 static int path_parse(char ***p, int flags)
623 /* if not setenv PATH variable, to search cur dir "." */
624 if (flags != FIND_EXE_ONLY || (pth = cmdedit_path_lookup) == 0 ||
625 /* PATH=<empty> or PATH=:<empty> */
626 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
634 npth++; /* count words is + 1 count ':' */
635 tmp = strchr(tmp, ':');
638 break; /* :<empty> */
643 *p = xmalloc(npth * sizeof(char *));
646 (*p)[0] = bb_xstrdup(tmp);
647 npth = 1; /* count words is + 1 count ':' */
650 tmp = strchr(tmp, ':');
652 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
654 break; /* :<empty> */
657 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
663 static char *add_quote_for_spec_chars(char *found)
666 char *s = xmalloc((strlen(found) + 1) * 2);
669 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
677 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
685 int nm = *num_matches;
688 char **paths = path1;
692 char *pfind = strrchr(command, '/');
697 /* no dir, if flags==EXE_ONLY - get paths, else "." */
698 npaths = path_parse(&paths, type);
702 /* save for change */
703 strcpy(dirbuf, command);
705 dirbuf[(pfind - command) + 1] = 0;
706 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
707 if (dirbuf[0] == '~') /* ~/... or ~user/... */
708 username_tab_completion(dirbuf, 0);
710 /* "strip" dirname in command */
714 npaths = 1; /* only 1 dir */
717 for (i = 0; i < npaths; i++) {
719 dir = opendir(paths[i]);
720 if (!dir) /* Don't print an error */
723 while ((next = readdir(dir)) != NULL) {
724 char *str_found = next->d_name;
727 if (strncmp(str_found, pfind, strlen(pfind)))
729 /* not see .name without .match */
730 if (*str_found == '.' && *pfind == 0) {
731 if (*paths[i] == '/' && paths[i][1] == 0
732 && str_found[1] == 0) str_found = ""; /* only "/" */
736 found = concat_path_file(paths[i], str_found);
737 /* hmm, remover in progress? */
738 if (stat(found, &st) < 0)
740 /* find with dirs ? */
741 if (paths[i] != dirbuf)
742 strcpy(found, next->d_name); /* only name */
743 if (S_ISDIR(st.st_mode)) {
744 /* name is directory */
746 found = concat_path_file(found, "");
748 str_found = add_quote_for_spec_chars(found);
750 /* not put found file if search only dirs for cd */
751 if (type == FIND_DIR_ONLY)
753 str_found = add_quote_for_spec_chars(found);
754 if (type == FIND_FILE_ONLY ||
755 (type == FIND_EXE_ONLY && is_execute(&st)))
756 strcat(str_found, " ");
758 /* Add it to the list */
759 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
761 matches[nm++] = str_found;
767 if (paths != path1) {
768 free(paths[0]); /* allocated memory only in first member */
775 static int match_compare(const void *a, const void *b)
777 return strcmp(*(char **) a, *(char **) b);
782 #define QUOT (UCHAR_MAX+1)
784 #define collapse_pos(is, in) { \
785 memcpy(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
786 memcpy(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
788 static int find_match(char *matchBuf, int *len_with_quotes)
793 int int_buf[BUFSIZ + 1];
794 int pos_buf[BUFSIZ + 1];
796 /* set to integer dimension characters and own positions */
798 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
799 if (int_buf[i] == 0) {
800 pos_buf[i] = -1; /* indicator end line */
806 /* mask \+symbol and convert '\t' to ' ' */
807 for (i = j = 0; matchBuf[i]; i++, j++)
808 if (matchBuf[i] == '\\') {
809 collapse_pos(j, j + 1);
812 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
813 if (matchBuf[i] == '\t') /* algorithm equivalent */
814 int_buf[j] = ' ' | QUOT;
817 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
818 else if (matchBuf[i] == '\t')
822 /* mask "symbols" or 'symbols' */
824 for (i = 0; int_buf[i]; i++) {
826 if (c == '\'' || c == '"') {
835 } else if (c2 != 0 && c != '$')
839 /* skip commands with arguments if line have commands delimiters */
840 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
841 for (i = 0; int_buf[i]; i++) {
844 j = i ? int_buf[i - 1] : -1;
846 if (c == ';' || c == '&' || c == '|') {
847 command_mode = 1 + (c == c2);
849 if (j == '>' || j == '<')
851 } else if (c == '|' && j == '>')
855 collapse_pos(0, i + command_mode);
856 i = -1; /* hack incremet */
859 /* collapse `command...` */
860 for (i = 0; int_buf[i]; i++)
861 if (int_buf[i] == '`') {
862 for (j = i + 1; int_buf[j]; j++)
863 if (int_buf[j] == '`') {
864 collapse_pos(i, j + 1);
869 /* not found close ` - command mode, collapse all previous */
870 collapse_pos(0, i + 1);
873 i--; /* hack incremet */
876 /* collapse (command...(command...)...) or {command...{command...}...} */
877 c = 0; /* "recursive" level */
879 for (i = 0; int_buf[i]; i++)
880 if (int_buf[i] == '(' || int_buf[i] == '{') {
881 if (int_buf[i] == '(')
885 collapse_pos(0, i + 1);
886 i = -1; /* hack incremet */
888 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
889 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
890 if (int_buf[i] == ')')
894 collapse_pos(0, i + 1);
895 i = -1; /* hack incremet */
898 /* skip first not quote space */
899 for (i = 0; int_buf[i]; i++)
900 if (int_buf[i] != ' ')
905 /* set find mode for completion */
906 command_mode = FIND_EXE_ONLY;
907 for (i = 0; int_buf[i]; i++)
908 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
909 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
910 && matchBuf[pos_buf[0]]=='c'
911 && matchBuf[pos_buf[1]]=='d' )
912 command_mode = FIND_DIR_ONLY;
914 command_mode = FIND_FILE_ONLY;
919 for (i = 0; int_buf[i]; i++);
921 for (--i; i >= 0; i--) {
923 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
924 collapse_pos(0, i + 1);
928 /* skip first not quoted '\'' or '"' */
929 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
930 /* collapse quote or unquote // or /~ */
931 while ((int_buf[i] & ~QUOT) == '/' &&
932 ((int_buf[i + 1] & ~QUOT) == '/'
933 || (int_buf[i + 1] & ~QUOT) == '~')) {
937 /* set only match and destroy quotes */
939 for (c = 0; pos_buf[i] >= 0; i++) {
940 matchBuf[c++] = matchBuf[pos_buf[i]];
944 /* old lenght matchBuf with quotes symbols */
945 *len_with_quotes = j ? j - pos_buf[0] : 0;
951 display by column original ideas from ls applet,
952 very optimize by my :)
954 static void showfiles(char **matches, int nfiles)
957 int column_width = 0;
960 /* find the longest file name- use that as the column width */
961 for (row = 0; row < nrows; row++) {
962 int l = strlen(matches[row]);
964 if (column_width < l)
967 column_width += 2; /* min space for columns */
968 ncols = cmdedit_termw / column_width;
973 nrows++; /* round up fractionals */
974 column_width = -column_width; /* for printf("%-Ns", ...); */
978 for (row = 0; row < nrows; row++) {
982 for(nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++)
983 printf("%*s", column_width, matches[n]);
984 printf("%s\n", matches[n]);
989 static void input_tab(int *lastWasTab)
991 /* Do TAB completion */
992 static int num_matches;
993 static char **matches;
995 if (lastWasTab == 0) { /* free all memory */
997 while (num_matches > 0)
998 free(matches[--num_matches]);
1000 matches = (char **) NULL;
1004 if (! *lastWasTab) {
1008 char matchBuf[BUFSIZ];
1012 *lastWasTab = TRUE; /* flop trigger */
1014 /* Make a local copy of the string -- up
1015 * to the position of the cursor */
1016 tmp = strncpy(matchBuf, command_ps, cursor);
1019 find_type = find_match(matchBuf, &recalc_pos);
1021 /* Free up any memory already allocated */
1024 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
1025 /* If the word starts with `~' and there is no slash in the word,
1026 * then try completing this word as a username. */
1028 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1029 matches = username_tab_completion(matchBuf, &num_matches);
1031 /* Try to match any executable in our path and everything
1032 * in the current working directory that matches. */
1035 exe_n_cwd_tab_completion(matchBuf,
1036 &num_matches, find_type);
1037 /* Remove duplicate found */
1041 for(i=0; i<(num_matches-1); i++)
1042 for(j=i+1; j<num_matches; j++)
1043 if(matches[i]!=0 && matches[j]!=0 &&
1044 strcmp(matches[i], matches[j])==0) {
1052 if(!strcmp(matches[i], "./"))
1054 else if(!strcmp(matches[i], "../"))
1056 matches[num_matches++]=matches[i];
1059 /* Did we find exactly one match? */
1060 if (!matches || num_matches > 1) {
1065 return; /* not found */
1067 qsort(matches, num_matches, sizeof(char *), match_compare);
1069 /* find minimal match */
1070 tmp = bb_xstrdup(matches[0]);
1071 for (tmp1 = tmp; *tmp1; tmp1++)
1072 for (len_found = 1; len_found < num_matches; len_found++)
1073 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1077 if (*tmp == 0) { /* have unique */
1081 } else { /* one match */
1083 /* for next completion current found */
1084 *lastWasTab = FALSE;
1087 len_found = strlen(tmp);
1088 /* have space to placed match? */
1089 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1091 /* before word for match */
1092 command_ps[cursor - recalc_pos] = 0;
1093 /* save tail line */
1094 strcpy(matchBuf, command_ps + cursor);
1096 strcat(command_ps, tmp);
1098 strcat(command_ps, matchBuf);
1099 /* back to begin word for match */
1100 input_backward(recalc_pos);
1102 recalc_pos = cursor + len_found;
1104 len = strlen(command_ps);
1105 /* write out the matched command */
1106 redraw(cmdedit_y, len - recalc_pos);
1108 if (tmp != matches[0])
1111 /* Ok -- the last char was a TAB. Since they
1112 * just hit TAB again, print a list of all the
1113 * available choices... */
1114 if (matches && num_matches > 0) {
1115 int sav_cursor = cursor; /* change goto_new_line() */
1117 /* Go to the next line */
1119 showfiles(matches, num_matches);
1120 redraw(0, len - sav_cursor);
1124 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
1126 #if MAX_HISTORY >= 1
1127 static void get_previous_history(void)
1129 if(command_ps[0] != 0 || history[cur_history] == 0) {
1130 free(history[cur_history]);
1131 history[cur_history] = bb_xstrdup(command_ps);
1136 static int get_next_history(void)
1138 int ch = cur_history;
1140 if (ch < n_history) {
1141 get_previous_history(); /* save the current history line */
1142 return (cur_history = ch+1);
1149 #ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY
1150 extern void load_history ( const char *fromfile )
1157 for(hi = n_history; hi > 0; ) {
1159 free ( history [hi] );
1162 if (( fp = fopen ( fromfile, "r" ))) {
1164 for ( hi = 0; hi < MAX_HISTORY; ) {
1165 char * hl = bb_get_chomped_line_from_file(fp);
1173 if(l == 0 || hl[0] == ' ') {
1177 history [hi++] = hl;
1181 cur_history = n_history = hi;
1184 extern void save_history ( const char *tofile )
1186 FILE *fp = fopen ( tofile, "w" );
1191 for ( i = 0; i < n_history; i++ ) {
1192 fprintf(fp, "%s\n", history [i]);
1208 * This function is used to grab a character buffer
1209 * from the input file descriptor and allows you to
1210 * a string with full command editing (sort of like
1213 * The following standard commands are not implemented:
1214 * ESC-b -- Move back one word
1215 * ESC-f -- Move forward one word
1216 * ESC-d -- Delete back one word
1217 * ESC-h -- Delete forward one word
1218 * CTL-t -- Transpose two characters
1220 * Furthermore, the "vi" command editing keys are not implemented.
1225 int cmdedit_read_input(char *prompt, char command[BUFSIZ])
1229 int lastWasTab = FALSE;
1230 unsigned char c = 0;
1232 /* prepare before init handlers */
1233 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1235 command_ps = command;
1237 getTermSettings(0, (void *) &initial_settings);
1238 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1239 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1240 /* Turn off echoing and CTRL-C, so we can trap it */
1241 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1242 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1243 new_settings.c_cc[VMIN] = 1;
1244 new_settings.c_cc[VTIME] = 0;
1245 /* Turn off CTRL-C, so we can trap it */
1246 # ifndef _POSIX_VDISABLE
1247 # define _POSIX_VDISABLE '\0'
1249 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1252 setTermSettings(0, (void *) &new_settings);
1253 handlers_sets |= SET_RESET_TERM;
1255 /* Now initialize things */
1257 /* Print out the command prompt */
1258 parse_prompt(prompt);
1262 fflush(stdout); /* buffered out to fast */
1264 if (safe_read(0, &c, 1) < 1)
1265 /* if we can't read input then exit */
1266 goto prepare_to_die;
1276 /* Control-a -- Beginning of line */
1277 input_backward(cursor);
1280 /* Control-b -- Move back one character */
1284 /* Control-c -- stop gathering input */
1293 break_out = -1; /* to control traps */
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 /* to control stopped jobs */
1308 len = break_out = -1;
1316 /* Control-e -- End of line */
1320 /* Control-f -- Move forward one character */
1325 /* Control-h and DEL */
1329 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1330 input_tab(&lastWasTab);
1334 /* Control-k -- clear to end of line */
1335 *(command + cursor) = 0;
1340 /* Control-l -- clear screen */
1342 redraw(0, len-cursor);
1344 #if MAX_HISTORY >= 1
1346 /* Control-n -- Get next command in history */
1347 if (get_next_history())
1351 /* Control-p -- Get previous command from history */
1352 if (cur_history > 0) {
1353 get_previous_history();
1361 /* Control-U -- Clear line before cursor */
1363 strcpy(command, command + cursor);
1364 redraw(cmdedit_y, len -= cursor);
1368 /* Control-W -- Remove the last word */
1369 while (cursor > 0 && isspace(command[cursor-1]))
1371 while (cursor > 0 &&!isspace(command[cursor-1]))
1375 /* escape sequence follows */
1376 if (safe_read(0, &c, 1) < 1)
1377 goto prepare_to_die;
1378 /* different vt100 emulations */
1379 if (c == '[' || c == 'O') {
1380 if (safe_read(0, &c, 1) < 1)
1381 goto prepare_to_die;
1383 if (c >= '1' && c <= '9') {
1384 unsigned char dummy;
1386 if (safe_read(0, &dummy, 1) < 1)
1387 goto prepare_to_die;
1392 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1393 case '\t': /* Alt-Tab */
1395 input_tab(&lastWasTab);
1398 #if MAX_HISTORY >= 1
1400 /* Up Arrow -- Get previous command from history */
1401 if (cur_history > 0) {
1402 get_previous_history();
1409 /* Down Arrow -- Get next command in history */
1410 if (!get_next_history())
1412 /* Rewrite the line with the selected history item */
1414 /* change command */
1415 len = strlen(strcpy(command, history[cur_history]));
1416 /* redraw and go to end line */
1417 redraw(cmdedit_y, 0);
1421 /* Right Arrow -- Move forward one character */
1425 /* Left Arrow -- Move back one character */
1435 input_backward(cursor);
1449 default: /* If it's regular input, do the normal thing */
1450 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1451 /* Control-V -- Add non-printable symbol */
1453 if (safe_read(0, &c, 1) < 1)
1454 goto prepare_to_die;
1461 if (!Isprint(c)) /* Skip non-printable characters */
1464 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1469 if (cursor == (len - 1)) { /* Append if at the end of the line */
1470 *(command + cursor) = c;
1471 *(command + cursor + 1) = 0;
1472 cmdedit_set_out_char(0);
1473 } else { /* Insert otherwise */
1476 memmove(command + sc + 1, command + sc, len - sc);
1477 *(command + sc) = c;
1479 /* rewrite from cursor */
1481 /* to prev x pos + 1 */
1482 input_backward(cursor - sc);
1487 if (break_out) /* Enter is the command terminator, no more input. */
1494 setTermSettings(0, (void *) &initial_settings);
1495 handlers_sets &= ~SET_RESET_TERM;
1497 #if MAX_HISTORY >= 1
1498 /* Handle command history log */
1499 /* cleanup may be saved current command line */
1500 if (len> 0) { /* no put empty line */
1503 free(history[MAX_HISTORY]);
1504 history[MAX_HISTORY] = 0;
1505 /* After max history, remove the oldest command */
1506 if (i >= MAX_HISTORY) {
1508 for(i = 0; i < (MAX_HISTORY-1); i++)
1509 history[i] = history[i+1];
1511 history[i++] = bb_xstrdup(command);
1514 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1518 #else /* MAX_HISTORY < 1 */
1519 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1520 if (len > 0) { /* no put empty line */
1524 #endif /* MAX_HISTORY >= 1 */
1525 if (break_out > 0) {
1526 command[len++] = '\n'; /* set '\n' */
1529 #if defined(CONFIG_FEATURE_CLEAN_UP) && defined(CONFIG_FEATURE_COMMAND_TAB_COMPLETION)
1530 input_tab(0); /* strong free */
1532 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1533 free(cmdedit_prompt);
1535 cmdedit_reset_term();
1541 #endif /* CONFIG_FEATURE_COMMAND_EDITING */
1546 const char *bb_applet_name = "debug stuff usage";
1548 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1552 int main(int argc, char **argv)
1556 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1557 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1558 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1559 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1564 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1565 setlocale(LC_ALL, "");
1569 l = cmdedit_read_input(prompt, buff);
1570 if(l > 0 && buff[l-1] == '\n') {
1572 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1577 printf("*** cmdedit_read_input() detect ^D\n");