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 static void cmdedit_setwidth(int w, int redraw_flg);
168 static void win_changed(int nsig)
170 struct winsize win = { 0, 0, 0, 0 };
171 static sighandler_t previous_SIGWINCH_handler; /* for reset */
173 /* emulate || signal call */
174 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
175 ioctl(0, TIOCGWINSZ, &win);
176 if (win.ws_col > 0) {
177 cmdedit_setwidth(win.ws_col, nsig == SIGWINCH);
180 /* Unix not all standart in recall signal */
182 if (nsig == -SIGWINCH) /* save previous handler */
183 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
184 else if (nsig == SIGWINCH) /* signaled called handler */
185 signal(SIGWINCH, win_changed); /* set for next call */
187 /* set previous handler */
188 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
191 static void cmdedit_reset_term(void)
193 if ((handlers_sets & SET_RESET_TERM) != 0) {
194 /* sparc and other have broken termios support: use old termio handling. */
195 setTermSettings(fileno(stdin), (void *) &initial_settings);
196 handlers_sets &= ~SET_RESET_TERM;
198 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
199 /* reset SIGWINCH handler to previous (default) */
201 handlers_sets &= ~SET_WCHG_HANDLERS;
207 /* special for recount position for scroll and remove terminal margin effect */
208 static void cmdedit_set_out_char(int next_char)
211 int c = (int)((unsigned char) command_ps[cursor]);
214 c = ' '; /* destroy end char? */
215 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
216 if (!Isprint(c)) { /* Inverse put non-printable characters */
223 printf("\033[7m%c\033[0m", c);
227 if (++cmdedit_x >= cmdedit_termw) {
228 /* terminal is scrolled down */
234 /* destroy "(auto)margin" */
241 /* Move to end line. Bonus: rewrite line from cursor */
242 static void input_end(void)
245 cmdedit_set_out_char(0);
248 /* Go to the next line */
249 static void goto_new_line(void)
257 static inline void out1str(const char *s)
262 static inline void beep(void)
267 /* Move back one charactor */
268 /* special for slow terminal */
269 static void input_backward(int num)
273 cursor -= num; /* new cursor (in command, not terminal) */
275 if (cmdedit_x >= num) { /* no to up line */
282 printf("\033[%dD", num);
287 putchar('\r'); /* back to first terminal pos. */
288 num -= cmdedit_x; /* set previous backward */
290 count_y = 1 + num / cmdedit_termw;
291 printf("\033[%dA", count_y);
292 cmdedit_y -= count_y;
293 /* require forward after uping */
294 cmdedit_x = cmdedit_termw * count_y - num;
295 printf("\033[%dC", cmdedit_x); /* set term cursor */
299 static void put_prompt(void)
301 out1str(cmdedit_prompt);
302 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
304 cmdedit_y = 0; /* new quasireal y */
307 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
308 static void parse_prompt(const char *prmt_ptr)
310 cmdedit_prompt = prmt_ptr;
311 cmdedit_prmt_len = strlen(prmt_ptr);
315 static void parse_prompt(const char *prmt_ptr)
319 char flg_not_length = '[';
320 char *prmt_mem_ptr = xcalloc(1, 1);
321 char *pwd_buf = xgetcwd(0);
322 char buf2[PATH_MAX + 1];
328 pwd_buf=(char *)bb_msg_unknown;
336 const char *cp = prmt_ptr;
339 c = bb_process_escape_sequence(&prmt_ptr);
345 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
353 pbuf = xcalloc(256, 1);
354 if (gethostname(pbuf, 255) < 0) {
357 char *s = strchr(pbuf, '.');
366 c = my_euid == 0 ? '#' : '$';
368 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
371 l = strlen(home_pwd_buf);
372 if (home_pwd_buf[0] != 0 &&
373 strncmp(home_pwd_buf, pbuf, l) == 0 &&
374 (pbuf[l]=='/' || pbuf[l]=='\0') &&
375 strlen(pwd_buf+l)<PATH_MAX) {
378 strcpy(pbuf+1, pwd_buf+l);
384 cp = strrchr(pbuf,'/');
385 if ( (cp != NULL) && (cp != pbuf) )
389 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
391 case 'e': case 'E': /* \e \E = \033 */
395 for (l = 0; l < 3;) {
397 buf2[l++] = *prmt_ptr;
399 h = strtol(buf2, &pbuf, 16);
400 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
407 c = (char)strtol(buf2, 0, 16);
413 if (c == flg_not_length) {
414 flg_not_length = flg_not_length == '[' ? ']' : '[';
423 prmt_len += strlen(pbuf);
424 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
425 if (flg_not_length == ']')
428 if(pwd_buf!=(char *)bb_msg_unknown)
430 cmdedit_prompt = prmt_mem_ptr;
431 cmdedit_prmt_len = prmt_len - sub_len;
437 /* draw promt, editor line, and clear tail */
438 static void redraw(int y, int back_cursor)
440 if (y > 0) /* up to start y */
441 printf("\033[%dA", y);
444 input_end(); /* rewrite */
445 printf("\033[J"); /* destroy tail after cursor */
446 input_backward(back_cursor);
449 /* Delete the char in front of the cursor */
450 static void input_delete(void)
457 strcpy(command_ps + j, command_ps + j + 1);
459 input_end(); /* rewtite new line */
460 cmdedit_set_out_char(0); /* destroy end char */
461 input_backward(cursor - j); /* back to old pos cursor */
464 /* Delete the char in back of the cursor */
465 static void input_backspace(void)
474 /* Move forward one charactor */
475 static void input_forward(void)
478 cmdedit_set_out_char(command_ps[cursor + 1]);
482 static void cmdedit_setwidth(int w, int redraw_flg)
484 cmdedit_termw = cmdedit_prmt_len + 2;
485 if (w <= cmdedit_termw) {
486 cmdedit_termw = cmdedit_termw % w;
488 if (w > cmdedit_termw) {
492 /* new y for current cursor */
493 int new_y = (cursor + cmdedit_prmt_len) / w;
496 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
502 static void cmdedit_init(void)
504 cmdedit_prmt_len = 0;
505 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
506 /* emulate usage handler to set handler and call yours work */
507 win_changed(-SIGWINCH);
508 handlers_sets |= SET_WCHG_HANDLERS;
511 if ((handlers_sets & SET_ATEXIT) == 0) {
512 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
513 struct passwd *entry;
516 entry = getpwuid(my_euid);
518 user_buf = bb_xstrdup(entry->pw_name);
519 home_pwd_buf = bb_xstrdup(entry->pw_dir);
523 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
525 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
530 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
531 handlers_sets |= SET_ATEXIT;
532 atexit(cmdedit_reset_term); /* be sure to do this only once */
536 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
538 static int is_execute(const struct stat *st)
540 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
541 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
542 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
543 (st->st_mode & S_IXOTH)) return TRUE;
547 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
549 static char **username_tab_completion(char *ud, int *num_matches)
551 struct passwd *entry;
556 ud++; /* ~user/... to user/... */
557 userlen = strlen(ud);
559 if (num_matches == 0) { /* "~/..." or "~user/..." */
560 char *sav_ud = ud - 1;
563 if (*ud == '/') { /* "~/..." */
567 temp = strchr(ud, '/');
568 *temp = 0; /* ~user\0 */
569 entry = getpwnam(ud);
570 *temp = '/'; /* restore ~user/... */
573 home = entry->pw_dir;
576 if ((userlen + strlen(home) + 1) < BUFSIZ) {
577 char temp2[BUFSIZ]; /* argument size */
580 sprintf(temp2, "%s%s", home, ud);
581 strcpy(sav_ud, temp2);
584 return 0; /* void, result save to argument :-) */
587 char **matches = (char **) NULL;
592 while ((entry = getpwent()) != NULL) {
593 /* Null usernames should result in all users as possible completions. */
594 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
596 bb_xasprintf(&temp, "~%s/", entry->pw_name);
597 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
599 matches[nm++] = temp;
608 #endif /* CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION */
616 static int path_parse(char ***p, int flags)
622 /* if not setenv PATH variable, to search cur dir "." */
623 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
624 /* PATH=<empty> or PATH=:<empty> */
625 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
633 npth++; /* count words is + 1 count ':' */
634 tmp = strchr(tmp, ':');
637 break; /* :<empty> */
642 *p = xmalloc(npth * sizeof(char *));
645 (*p)[0] = bb_xstrdup(tmp);
646 npth = 1; /* count words is + 1 count ':' */
649 tmp = strchr(tmp, ':');
651 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
653 break; /* :<empty> */
656 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
662 static char *add_quote_for_spec_chars(char *found)
665 char *s = xmalloc((strlen(found) + 1) * 2);
668 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
676 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
684 int nm = *num_matches;
687 char **paths = path1;
691 char *pfind = strrchr(command, '/');
696 /* no dir, if flags==EXE_ONLY - get paths, else "." */
697 npaths = path_parse(&paths, type);
701 /* save for change */
702 strcpy(dirbuf, command);
704 dirbuf[(pfind - command) + 1] = 0;
705 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
706 if (dirbuf[0] == '~') /* ~/... or ~user/... */
707 username_tab_completion(dirbuf, 0);
709 /* "strip" dirname in command */
713 npaths = 1; /* only 1 dir */
716 for (i = 0; i < npaths; i++) {
718 dir = opendir(paths[i]);
719 if (!dir) /* Don't print an error */
722 while ((next = readdir(dir)) != NULL) {
723 char *str_found = next->d_name;
726 if (strncmp(str_found, pfind, strlen(pfind)))
728 /* not see .name without .match */
729 if (*str_found == '.' && *pfind == 0) {
730 if (*paths[i] == '/' && paths[i][1] == 0
731 && str_found[1] == 0) str_found = ""; /* only "/" */
735 found = concat_path_file(paths[i], str_found);
736 /* hmm, remover in progress? */
737 if (stat(found, &st) < 0)
739 /* find with dirs ? */
740 if (paths[i] != dirbuf)
741 strcpy(found, next->d_name); /* only name */
742 if (S_ISDIR(st.st_mode)) {
743 /* name is directory */
745 found = concat_path_file(found, "");
747 str_found = add_quote_for_spec_chars(found);
749 /* not put found file if search only dirs for cd */
750 if (type == FIND_DIR_ONLY)
752 str_found = add_quote_for_spec_chars(found);
753 if (type == FIND_FILE_ONLY ||
754 (type == FIND_EXE_ONLY && is_execute(&st)))
755 strcat(str_found, " ");
757 /* Add it to the list */
758 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
760 matches[nm++] = str_found;
766 if (paths != path1) {
767 free(paths[0]); /* allocated memory only in first member */
774 static int match_compare(const void *a, const void *b)
776 return strcmp(*(char **) a, *(char **) b);
781 #define QUOT (UCHAR_MAX+1)
783 #define collapse_pos(is, in) { \
784 memcpy(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
785 memcpy(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
787 static int find_match(char *matchBuf, int *len_with_quotes)
792 int int_buf[BUFSIZ + 1];
793 int pos_buf[BUFSIZ + 1];
795 /* set to integer dimension characters and own positions */
797 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
798 if (int_buf[i] == 0) {
799 pos_buf[i] = -1; /* indicator end line */
805 /* mask \+symbol and convert '\t' to ' ' */
806 for (i = j = 0; matchBuf[i]; i++, j++)
807 if (matchBuf[i] == '\\') {
808 collapse_pos(j, j + 1);
811 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
812 if (matchBuf[i] == '\t') /* algorithm equivalent */
813 int_buf[j] = ' ' | QUOT;
816 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
817 else if (matchBuf[i] == '\t')
821 /* mask "symbols" or 'symbols' */
823 for (i = 0; int_buf[i]; i++) {
825 if (c == '\'' || c == '"') {
834 } else if (c2 != 0 && c != '$')
838 /* skip commands with arguments if line have commands delimiters */
839 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
840 for (i = 0; int_buf[i]; i++) {
843 j = i ? int_buf[i - 1] : -1;
845 if (c == ';' || c == '&' || c == '|') {
846 command_mode = 1 + (c == c2);
848 if (j == '>' || j == '<')
850 } else if (c == '|' && j == '>')
854 collapse_pos(0, i + command_mode);
855 i = -1; /* hack incremet */
858 /* collapse `command...` */
859 for (i = 0; int_buf[i]; i++)
860 if (int_buf[i] == '`') {
861 for (j = i + 1; int_buf[j]; j++)
862 if (int_buf[j] == '`') {
863 collapse_pos(i, j + 1);
868 /* not found close ` - command mode, collapse all previous */
869 collapse_pos(0, i + 1);
872 i--; /* hack incremet */
875 /* collapse (command...(command...)...) or {command...{command...}...} */
876 c = 0; /* "recursive" level */
878 for (i = 0; int_buf[i]; i++)
879 if (int_buf[i] == '(' || int_buf[i] == '{') {
880 if (int_buf[i] == '(')
884 collapse_pos(0, i + 1);
885 i = -1; /* hack incremet */
887 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
888 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
889 if (int_buf[i] == ')')
893 collapse_pos(0, i + 1);
894 i = -1; /* hack incremet */
897 /* skip first not quote space */
898 for (i = 0; int_buf[i]; i++)
899 if (int_buf[i] != ' ')
904 /* set find mode for completion */
905 command_mode = FIND_EXE_ONLY;
906 for (i = 0; int_buf[i]; i++)
907 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
908 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
909 && matchBuf[pos_buf[0]]=='c'
910 && matchBuf[pos_buf[1]]=='d' )
911 command_mode = FIND_DIR_ONLY;
913 command_mode = FIND_FILE_ONLY;
918 for (i = 0; int_buf[i]; i++);
920 for (--i; i >= 0; i--) {
922 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
923 collapse_pos(0, i + 1);
927 /* skip first not quoted '\'' or '"' */
928 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
929 /* collapse quote or unquote // or /~ */
930 while ((int_buf[i] & ~QUOT) == '/' &&
931 ((int_buf[i + 1] & ~QUOT) == '/'
932 || (int_buf[i + 1] & ~QUOT) == '~')) {
936 /* set only match and destroy quotes */
938 for (c = 0; pos_buf[i] >= 0; i++) {
939 matchBuf[c++] = matchBuf[pos_buf[i]];
943 /* old lenght matchBuf with quotes symbols */
944 *len_with_quotes = j ? j - pos_buf[0] : 0;
950 display by column original ideas from ls applet,
951 very optimize by my :)
953 static void showfiles(char **matches, int nfiles)
956 int column_width = 0;
959 /* find the longest file name- use that as the column width */
960 for (row = 0; row < nrows; row++) {
961 int l = strlen(matches[row]);
963 if (column_width < l)
966 column_width += 2; /* min space for columns */
967 ncols = cmdedit_termw / column_width;
972 nrows++; /* round up fractionals */
973 column_width = -column_width; /* for printf("%-Ns", ...); */
977 for (row = 0; row < nrows; row++) {
981 for(nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++)
982 printf("%*s", column_width, matches[n]);
983 printf("%s\n", matches[n]);
988 static void input_tab(int *lastWasTab)
990 /* Do TAB completion */
991 static int num_matches;
992 static char **matches;
994 if (lastWasTab == 0) { /* free all memory */
996 while (num_matches > 0)
997 free(matches[--num_matches]);
999 matches = (char **) NULL;
1003 if (! *lastWasTab) {
1007 char matchBuf[BUFSIZ];
1011 *lastWasTab = TRUE; /* flop trigger */
1013 /* Make a local copy of the string -- up
1014 * to the position of the cursor */
1015 tmp = strncpy(matchBuf, command_ps, cursor);
1018 find_type = find_match(matchBuf, &recalc_pos);
1020 /* Free up any memory already allocated */
1023 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
1024 /* If the word starts with `~' and there is no slash in the word,
1025 * then try completing this word as a username. */
1027 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1028 matches = username_tab_completion(matchBuf, &num_matches);
1030 /* Try to match any executable in our path and everything
1031 * in the current working directory that matches. */
1034 exe_n_cwd_tab_completion(matchBuf,
1035 &num_matches, find_type);
1036 /* Remove duplicate found */
1040 for(i=0; i<(num_matches-1); i++)
1041 for(j=i+1; j<num_matches; j++)
1042 if(matches[i]!=0 && matches[j]!=0 &&
1043 strcmp(matches[i], matches[j])==0) {
1051 if(!strcmp(matches[i], "./"))
1053 else if(!strcmp(matches[i], "../"))
1055 matches[num_matches++]=matches[i];
1058 /* Did we find exactly one match? */
1059 if (!matches || num_matches > 1) {
1064 return; /* not found */
1066 qsort(matches, num_matches, sizeof(char *), match_compare);
1068 /* find minimal match */
1069 tmp = bb_xstrdup(matches[0]);
1070 for (tmp1 = tmp; *tmp1; tmp1++)
1071 for (len_found = 1; len_found < num_matches; len_found++)
1072 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1076 if (*tmp == 0) { /* have unique */
1080 } else { /* one match */
1082 /* for next completion current found */
1083 *lastWasTab = FALSE;
1086 len_found = strlen(tmp);
1087 /* have space to placed match? */
1088 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1090 /* before word for match */
1091 command_ps[cursor - recalc_pos] = 0;
1092 /* save tail line */
1093 strcpy(matchBuf, command_ps + cursor);
1095 strcat(command_ps, tmp);
1097 strcat(command_ps, matchBuf);
1098 /* back to begin word for match */
1099 input_backward(recalc_pos);
1101 recalc_pos = cursor + len_found;
1103 len = strlen(command_ps);
1104 /* write out the matched command */
1105 redraw(cmdedit_y, len - recalc_pos);
1107 if (tmp != matches[0])
1110 /* Ok -- the last char was a TAB. Since they
1111 * just hit TAB again, print a list of all the
1112 * available choices... */
1113 if (matches && num_matches > 0) {
1114 int sav_cursor = cursor; /* change goto_new_line() */
1116 /* Go to the next line */
1118 showfiles(matches, num_matches);
1119 redraw(0, len - sav_cursor);
1123 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
1125 #if MAX_HISTORY >= 1
1126 static void get_previous_history(void)
1128 if(command_ps[0] != 0 || history[cur_history] == 0) {
1129 free(history[cur_history]);
1130 history[cur_history] = bb_xstrdup(command_ps);
1135 static int get_next_history(void)
1137 int ch = cur_history;
1139 if (ch < n_history) {
1140 get_previous_history(); /* save the current history line */
1141 return (cur_history = ch+1);
1148 #ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY
1149 extern void load_history ( const char *fromfile )
1156 for(hi = n_history; hi > 0; ) {
1158 free ( history [hi] );
1161 if (( fp = fopen ( fromfile, "r" ))) {
1163 for ( hi = 0; hi < MAX_HISTORY; ) {
1164 char * hl = bb_get_chomped_line_from_file(fp);
1172 if(l == 0 || hl[0] == ' ') {
1176 history [hi++] = hl;
1180 cur_history = n_history = hi;
1183 extern void save_history ( const char *tofile )
1185 FILE *fp = fopen ( tofile, "w" );
1190 for ( i = 0; i < n_history; i++ ) {
1191 fputs ( history [i], fp );
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 (sortof 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);
1243 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1244 new_settings.c_cc[VMIN] = 1;
1245 new_settings.c_cc[VTIME] = 0;
1246 /* Turn off CTRL-C, so we can trap it */
1247 # ifndef _POSIX_VDISABLE
1248 # define _POSIX_VDISABLE '\0'
1250 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1254 setTermSettings(0, (void *) &new_settings);
1255 handlers_sets |= SET_RESET_TERM;
1257 /* Now initialize things */
1259 /* Print out the command prompt */
1260 parse_prompt(prompt);
1264 fflush(stdout); /* buffered out to fast */
1266 if (safe_read(0, &c, 1) < 1)
1267 /* if we can't read input then exit */
1268 goto prepare_to_die;
1278 /* Control-a -- Beginning of line */
1279 input_backward(cursor);
1282 /* Control-b -- Move back one character */
1286 /* Control-c -- stop gathering input */
1294 /* Control-d -- Delete one character, or exit
1295 * if the len=0 and no chars to delete */
1298 #if !defined(CONFIG_ASH)
1301 /* cmdedit_reset_term() called in atexit */
1304 break_out = -1; /* for control stoped jobs */
1312 /* Control-e -- End of line */
1316 /* Control-f -- Move forward one character */
1321 /* Control-h and DEL */
1325 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1326 input_tab(&lastWasTab);
1330 /* Control-k -- clear to end of line */
1331 *(command + cursor) = 0;
1336 /* Control-l -- clear screen */
1338 redraw(0, len-cursor);
1340 #if MAX_HISTORY >= 1
1342 /* Control-n -- Get next command in history */
1343 if (get_next_history())
1347 /* Control-p -- Get previous command from history */
1348 if (cur_history > 0) {
1349 get_previous_history();
1357 /* Control-U -- Clear line before cursor */
1359 strcpy(command, command + cursor);
1360 redraw(cmdedit_y, len -= cursor);
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 */
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";
1533 const char *memory_exhausted = "Memory exhausted";
1535 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1539 int main(int argc, char **argv)
1543 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1544 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1545 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1546 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1551 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1552 setlocale(LC_ALL, "");
1556 cmdedit_read_input(prompt, buff);
1560 if(l > 0 && buff[l-1] == '\n')
1562 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1564 printf("*** cmdedit_read_input() detect ^C\n");