1 /* vi: set sw=4 ts=4: */
3 * Termios command line History and Editting.
5 * Copyright (c) 1986-2003 may safely be consumed by a BSD or GPL license.
6 * Written by: Vladimir Oleynik <dzo@simtreas.ru>
9 * Adam Rogoyski <rogoyski@cs.utexas.edu>
10 * Dave Cinege <dcinege@psychosis.com>
11 * Jakub Jelinek (c) 1995
12 * Erik Andersen <andersen@codepoet.org> (Majorly adjusted for busybox)
14 * This code is 'as is' with no warranty.
21 Terminal key codes are not extensive, and more will probably
22 need to be added. This version was created on Debian GNU/Linux 2.x.
23 Delete, Backspace, Home, End, and the arrow keys were tested
24 to work in an Xterm and console. Ctrl-A also works as Home.
25 Ctrl-E also works as End.
27 Small bugs (simple effect):
28 - not true viewing if terminal size (x*y symbols) less
29 size (prompt + editor`s line + 2 symbols)
30 - not true viewing if length prompt less terminal width
39 #include <sys/ioctl.h>
46 #ifdef CONFIG_LOCALE_SUPPORT
47 #define Isprint(c) isprint((c))
49 #define Isprint(c) ( (c) >= ' ' && (c) != ((unsigned char)'\233') )
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)
263 static inline void beep(void)
268 /* Move back one charactor */
269 /* special for slow terminal */
270 static void input_backward(int num)
274 cursor -= num; /* new cursor (in command, not terminal) */
276 if (cmdedit_x >= num) { /* no to up line */
283 printf("\033[%dD", num);
288 putchar('\r'); /* back to first terminal pos. */
289 num -= cmdedit_x; /* set previous backward */
291 count_y = 1 + num / cmdedit_termw;
292 printf("\033[%dA", count_y);
293 cmdedit_y -= count_y;
294 /* require forward after uping */
295 cmdedit_x = cmdedit_termw * count_y - num;
296 printf("\033[%dC", cmdedit_x); /* set term cursor */
300 static void put_prompt(void)
302 out1str(cmdedit_prompt);
303 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
305 cmdedit_y = 0; /* new quasireal y */
308 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
309 static void parse_prompt(const char *prmt_ptr)
311 cmdedit_prompt = prmt_ptr;
312 cmdedit_prmt_len = strlen(prmt_ptr);
316 static void parse_prompt(const char *prmt_ptr)
320 char flg_not_length = '[';
321 char *prmt_mem_ptr = xcalloc(1, 1);
322 char *pwd_buf = xgetcwd(0);
323 char buf2[PATH_MAX + 1];
329 pwd_buf=(char *)bb_msg_unknown;
337 const char *cp = prmt_ptr;
340 c = bb_process_escape_sequence(&prmt_ptr);
346 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
354 pbuf = xcalloc(256, 1);
355 if (gethostname(pbuf, 255) < 0) {
358 char *s = strchr(pbuf, '.');
367 c = my_euid == 0 ? '#' : '$';
369 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
372 l = strlen(home_pwd_buf);
373 if (home_pwd_buf[0] != 0 &&
374 strncmp(home_pwd_buf, pbuf, l) == 0 &&
375 (pbuf[l]=='/' || pbuf[l]=='\0') &&
376 strlen(pwd_buf+l)<PATH_MAX) {
379 strcpy(pbuf+1, pwd_buf+l);
385 cp = strrchr(pbuf,'/');
386 if ( (cp != NULL) && (cp != pbuf) )
390 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
392 case 'e': case 'E': /* \e \E = \033 */
396 for (l = 0; l < 3;) {
398 buf2[l++] = *prmt_ptr;
400 h = strtol(buf2, &pbuf, 16);
401 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
408 c = (char)strtol(buf2, 0, 16);
414 if (c == flg_not_length) {
415 flg_not_length = flg_not_length == '[' ? ']' : '[';
424 prmt_len += strlen(pbuf);
425 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
426 if (flg_not_length == ']')
429 if(pwd_buf!=(char *)bb_msg_unknown)
431 cmdedit_prompt = prmt_mem_ptr;
432 cmdedit_prmt_len = prmt_len - sub_len;
438 /* draw promt, editor line, and clear tail */
439 static void redraw(int y, int back_cursor)
441 if (y > 0) /* up to start y */
442 printf("\033[%dA", y);
445 input_end(); /* rewrite */
446 printf("\033[J"); /* destroy tail after cursor */
447 input_backward(back_cursor);
450 /* Delete the char in front of the cursor */
451 static void input_delete(void)
458 strcpy(command_ps + j, command_ps + j + 1);
460 input_end(); /* rewtite new line */
461 cmdedit_set_out_char(0); /* destroy end char */
462 input_backward(cursor - j); /* back to old pos cursor */
465 /* Delete the char in back of the cursor */
466 static void input_backspace(void)
475 /* Move forward one charactor */
476 static void input_forward(void)
479 cmdedit_set_out_char(command_ps[cursor + 1]);
483 static void cmdedit_setwidth(int w, int redraw_flg)
485 cmdedit_termw = cmdedit_prmt_len + 2;
486 if (w <= cmdedit_termw) {
487 cmdedit_termw = cmdedit_termw % w;
489 if (w > cmdedit_termw) {
493 /* new y for current cursor */
494 int new_y = (cursor + cmdedit_prmt_len) / w;
497 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
503 static void cmdedit_init(void)
505 cmdedit_prmt_len = 0;
506 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
507 /* emulate usage handler to set handler and call yours work */
508 win_changed(-SIGWINCH);
509 handlers_sets |= SET_WCHG_HANDLERS;
512 if ((handlers_sets & SET_ATEXIT) == 0) {
513 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
514 struct passwd *entry;
517 entry = getpwuid(my_euid);
519 user_buf = bb_xstrdup(entry->pw_name);
520 home_pwd_buf = bb_xstrdup(entry->pw_dir);
524 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
526 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
531 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
532 handlers_sets |= SET_ATEXIT;
533 atexit(cmdedit_reset_term); /* be sure to do this only once */
537 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
539 static int is_execute(const struct stat *st)
541 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
542 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
543 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
544 (st->st_mode & S_IXOTH)) return TRUE;
548 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
550 static char **username_tab_completion(char *ud, int *num_matches)
552 struct passwd *entry;
557 ud++; /* ~user/... to user/... */
558 userlen = strlen(ud);
560 if (num_matches == 0) { /* "~/..." or "~user/..." */
561 char *sav_ud = ud - 1;
564 if (*ud == '/') { /* "~/..." */
568 temp = strchr(ud, '/');
569 *temp = 0; /* ~user\0 */
570 entry = getpwnam(ud);
571 *temp = '/'; /* restore ~user/... */
574 home = entry->pw_dir;
577 if ((userlen + strlen(home) + 1) < BUFSIZ) {
578 char temp2[BUFSIZ]; /* argument size */
581 sprintf(temp2, "%s%s", home, ud);
582 strcpy(sav_ud, temp2);
585 return 0; /* void, result save to argument :-) */
588 char **matches = (char **) NULL;
593 while ((entry = getpwent()) != NULL) {
594 /* Null usernames should result in all users as possible completions. */
595 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
597 bb_xasprintf(&temp, "~%s/", entry->pw_name);
598 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
600 matches[nm++] = temp;
609 #endif /* CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION */
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 = getenv("PATH")) == 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 fputs ( history [i], fp );
1209 * This function is used to grab a character buffer
1210 * from the input file descriptor and allows you to
1211 * a string with full command editing (sortof like
1214 * The following standard commands are not implemented:
1215 * ESC-b -- Move back one word
1216 * ESC-f -- Move forward one word
1217 * ESC-d -- Delete back one word
1218 * ESC-h -- Delete forward one word
1219 * CTL-t -- Transpose two characters
1221 * Furthermore, the "vi" command editing keys are not implemented.
1226 int cmdedit_read_input(char *prompt, char command[BUFSIZ])
1230 int lastWasTab = FALSE;
1231 unsigned char c = 0;
1233 /* prepare before init handlers */
1234 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1236 command_ps = command;
1238 getTermSettings(0, (void *) &initial_settings);
1239 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1240 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1241 /* Turn off echoing and CTRL-C, so we can trap it */
1242 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1244 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1245 new_settings.c_cc[VMIN] = 1;
1246 new_settings.c_cc[VTIME] = 0;
1247 /* Turn off CTRL-C, so we can trap it */
1248 # ifndef _POSIX_VDISABLE
1249 # define _POSIX_VDISABLE '\0'
1251 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 */
1291 #if !defined(CONFIG_ASH)
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 */
1516 if(break_out == 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();
1527 #if !defined(CONFIG_ASH)
1530 return break_out < 0 ? break_out : len;
1536 #endif /* CONFIG_FEATURE_COMMAND_EDITING */
1541 const char *bb_applet_name = "debug stuff usage";
1542 const char *memory_exhausted = "Memory exhausted";
1544 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1548 int main(int argc, char **argv)
1552 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1553 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1554 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1555 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1560 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1561 setlocale(LC_ALL, "");
1565 cmdedit_read_input(prompt, buff);
1569 if(l > 0 && buff[l-1] == '\n')
1571 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1573 printf("*** cmdedit_read_input() detect ^C\n");