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 static sighandler_t previous_SIGWINCH_handler; /* for reset */
172 /* emulate || signal call */
173 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
175 get_terminal_width_height(0, &width, NULL);
176 cmdedit_setwidth(width, nsig == SIGWINCH);
178 /* Unix not all standart in recall signal */
180 if (nsig == -SIGWINCH) /* save previous handler */
181 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
182 else if (nsig == SIGWINCH) /* signaled called handler */
183 signal(SIGWINCH, win_changed); /* set for next call */
185 /* set previous handler */
186 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
189 static void cmdedit_reset_term(void)
191 if ((handlers_sets & SET_RESET_TERM) != 0) {
192 /* sparc and other have broken termios support: use old termio handling. */
193 setTermSettings(fileno(stdin), (void *) &initial_settings);
194 handlers_sets &= ~SET_RESET_TERM;
196 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
197 /* reset SIGWINCH handler to previous (default) */
199 handlers_sets &= ~SET_WCHG_HANDLERS;
205 /* special for recount position for scroll and remove terminal margin effect */
206 static void cmdedit_set_out_char(int next_char)
209 int c = (int)((unsigned char) command_ps[cursor]);
212 c = ' '; /* destroy end char? */
213 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
214 if (!Isprint(c)) { /* Inverse put non-printable characters */
221 printf("\033[7m%c\033[0m", c);
225 if (++cmdedit_x >= cmdedit_termw) {
226 /* terminal is scrolled down */
232 /* destroy "(auto)margin" */
239 /* Move to end line. Bonus: rewrite line from cursor */
240 static void input_end(void)
243 cmdedit_set_out_char(0);
246 /* Go to the next line */
247 static void goto_new_line(void)
255 static inline void out1str(const char *s)
261 static inline void beep(void)
266 /* Move back one charactor */
267 /* special for slow terminal */
268 static void input_backward(int num)
272 cursor -= num; /* new cursor (in command, not terminal) */
274 if (cmdedit_x >= num) { /* no to up line */
281 printf("\033[%dD", num);
286 putchar('\r'); /* back to first terminal pos. */
287 num -= cmdedit_x; /* set previous backward */
289 count_y = 1 + num / cmdedit_termw;
290 printf("\033[%dA", count_y);
291 cmdedit_y -= count_y;
292 /* require forward after uping */
293 cmdedit_x = cmdedit_termw * count_y - num;
294 printf("\033[%dC", cmdedit_x); /* set term cursor */
298 static void put_prompt(void)
300 out1str(cmdedit_prompt);
301 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
303 cmdedit_y = 0; /* new quasireal y */
306 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
307 static void parse_prompt(const char *prmt_ptr)
309 cmdedit_prompt = prmt_ptr;
310 cmdedit_prmt_len = strlen(prmt_ptr);
314 static void parse_prompt(const char *prmt_ptr)
318 char flg_not_length = '[';
319 char *prmt_mem_ptr = xcalloc(1, 1);
320 char *pwd_buf = xgetcwd(0);
321 char buf2[PATH_MAX + 1];
327 pwd_buf=(char *)bb_msg_unknown;
335 const char *cp = prmt_ptr;
338 c = bb_process_escape_sequence(&prmt_ptr);
344 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
352 pbuf = xcalloc(256, 1);
353 if (gethostname(pbuf, 255) < 0) {
356 char *s = strchr(pbuf, '.');
365 c = my_euid == 0 ? '#' : '$';
367 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
370 l = strlen(home_pwd_buf);
371 if (home_pwd_buf[0] != 0 &&
372 strncmp(home_pwd_buf, pbuf, l) == 0 &&
373 (pbuf[l]=='/' || pbuf[l]=='\0') &&
374 strlen(pwd_buf+l)<PATH_MAX) {
377 strcpy(pbuf+1, pwd_buf+l);
383 cp = strrchr(pbuf,'/');
384 if ( (cp != NULL) && (cp != pbuf) )
388 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
390 case 'e': case 'E': /* \e \E = \033 */
394 for (l = 0; l < 3;) {
396 buf2[l++] = *prmt_ptr;
398 h = strtol(buf2, &pbuf, 16);
399 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
406 c = (char)strtol(buf2, 0, 16);
412 if (c == flg_not_length) {
413 flg_not_length = flg_not_length == '[' ? ']' : '[';
422 prmt_len += strlen(pbuf);
423 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
424 if (flg_not_length == ']')
427 if(pwd_buf!=(char *)bb_msg_unknown)
429 cmdedit_prompt = prmt_mem_ptr;
430 cmdedit_prmt_len = prmt_len - sub_len;
436 /* draw promt, editor line, and clear tail */
437 static void redraw(int y, int back_cursor)
439 if (y > 0) /* up to start y */
440 printf("\033[%dA", y);
443 input_end(); /* rewrite */
444 printf("\033[J"); /* destroy tail after cursor */
445 input_backward(back_cursor);
448 /* Delete the char in front of the cursor */
449 static void input_delete(void)
456 strcpy(command_ps + j, command_ps + j + 1);
458 input_end(); /* rewtite new line */
459 cmdedit_set_out_char(0); /* destroy end char */
460 input_backward(cursor - j); /* back to old pos cursor */
463 /* Delete the char in back of the cursor */
464 static void input_backspace(void)
473 /* Move forward one charactor */
474 static void input_forward(void)
477 cmdedit_set_out_char(command_ps[cursor + 1]);
481 static void cmdedit_setwidth(int w, int redraw_flg)
483 cmdedit_termw = cmdedit_prmt_len + 2;
484 if (w <= cmdedit_termw) {
485 cmdedit_termw = cmdedit_termw % w;
487 if (w > cmdedit_termw) {
491 /* new y for current cursor */
492 int new_y = (cursor + cmdedit_prmt_len) / w;
495 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
501 static void cmdedit_init(void)
503 cmdedit_prmt_len = 0;
504 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
505 /* emulate usage handler to set handler and call yours work */
506 win_changed(-SIGWINCH);
507 handlers_sets |= SET_WCHG_HANDLERS;
510 if ((handlers_sets & SET_ATEXIT) == 0) {
511 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
512 struct passwd *entry;
515 entry = getpwuid(my_euid);
517 user_buf = bb_xstrdup(entry->pw_name);
518 home_pwd_buf = bb_xstrdup(entry->pw_dir);
522 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
524 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
529 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
530 handlers_sets |= SET_ATEXIT;
531 atexit(cmdedit_reset_term); /* be sure to do this only once */
535 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
537 static int is_execute(const struct stat *st)
539 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
540 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
541 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
542 (st->st_mode & S_IXOTH)) return TRUE;
546 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
548 static char **username_tab_completion(char *ud, int *num_matches)
550 struct passwd *entry;
555 ud++; /* ~user/... to user/... */
556 userlen = strlen(ud);
558 if (num_matches == 0) { /* "~/..." or "~user/..." */
559 char *sav_ud = ud - 1;
562 if (*ud == '/') { /* "~/..." */
566 temp = strchr(ud, '/');
567 *temp = 0; /* ~user\0 */
568 entry = getpwnam(ud);
569 *temp = '/'; /* restore ~user/... */
572 home = entry->pw_dir;
575 if ((userlen + strlen(home) + 1) < BUFSIZ) {
576 char temp2[BUFSIZ]; /* argument size */
579 sprintf(temp2, "%s%s", home, ud);
580 strcpy(sav_ud, temp2);
583 return 0; /* void, result save to argument :-) */
586 char **matches = (char **) NULL;
591 while ((entry = getpwent()) != NULL) {
592 /* Null usernames should result in all users as possible completions. */
593 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
595 bb_xasprintf(&temp, "~%s/", entry->pw_name);
596 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
598 matches[nm++] = temp;
607 #endif /* CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION */
615 static int path_parse(char ***p, int flags)
621 /* if not setenv PATH variable, to search cur dir "." */
622 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
623 /* PATH=<empty> or PATH=:<empty> */
624 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
632 npth++; /* count words is + 1 count ':' */
633 tmp = strchr(tmp, ':');
636 break; /* :<empty> */
641 *p = xmalloc(npth * sizeof(char *));
644 (*p)[0] = bb_xstrdup(tmp);
645 npth = 1; /* count words is + 1 count ':' */
648 tmp = strchr(tmp, ':');
650 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
652 break; /* :<empty> */
655 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
661 static char *add_quote_for_spec_chars(char *found)
664 char *s = xmalloc((strlen(found) + 1) * 2);
667 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
675 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
683 int nm = *num_matches;
686 char **paths = path1;
690 char *pfind = strrchr(command, '/');
695 /* no dir, if flags==EXE_ONLY - get paths, else "." */
696 npaths = path_parse(&paths, type);
700 /* save for change */
701 strcpy(dirbuf, command);
703 dirbuf[(pfind - command) + 1] = 0;
704 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
705 if (dirbuf[0] == '~') /* ~/... or ~user/... */
706 username_tab_completion(dirbuf, 0);
708 /* "strip" dirname in command */
712 npaths = 1; /* only 1 dir */
715 for (i = 0; i < npaths; i++) {
717 dir = opendir(paths[i]);
718 if (!dir) /* Don't print an error */
721 while ((next = readdir(dir)) != NULL) {
722 char *str_found = next->d_name;
725 if (strncmp(str_found, pfind, strlen(pfind)))
727 /* not see .name without .match */
728 if (*str_found == '.' && *pfind == 0) {
729 if (*paths[i] == '/' && paths[i][1] == 0
730 && str_found[1] == 0) str_found = ""; /* only "/" */
734 found = concat_path_file(paths[i], str_found);
735 /* hmm, remover in progress? */
736 if (stat(found, &st) < 0)
738 /* find with dirs ? */
739 if (paths[i] != dirbuf)
740 strcpy(found, next->d_name); /* only name */
741 if (S_ISDIR(st.st_mode)) {
742 /* name is directory */
744 found = concat_path_file(found, "");
746 str_found = add_quote_for_spec_chars(found);
748 /* not put found file if search only dirs for cd */
749 if (type == FIND_DIR_ONLY)
751 str_found = add_quote_for_spec_chars(found);
752 if (type == FIND_FILE_ONLY ||
753 (type == FIND_EXE_ONLY && is_execute(&st)))
754 strcat(str_found, " ");
756 /* Add it to the list */
757 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
759 matches[nm++] = str_found;
765 if (paths != path1) {
766 free(paths[0]); /* allocated memory only in first member */
773 static int match_compare(const void *a, const void *b)
775 return strcmp(*(char **) a, *(char **) b);
780 #define QUOT (UCHAR_MAX+1)
782 #define collapse_pos(is, in) { \
783 memcpy(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
784 memcpy(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
786 static int find_match(char *matchBuf, int *len_with_quotes)
791 int int_buf[BUFSIZ + 1];
792 int pos_buf[BUFSIZ + 1];
794 /* set to integer dimension characters and own positions */
796 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
797 if (int_buf[i] == 0) {
798 pos_buf[i] = -1; /* indicator end line */
804 /* mask \+symbol and convert '\t' to ' ' */
805 for (i = j = 0; matchBuf[i]; i++, j++)
806 if (matchBuf[i] == '\\') {
807 collapse_pos(j, j + 1);
810 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
811 if (matchBuf[i] == '\t') /* algorithm equivalent */
812 int_buf[j] = ' ' | QUOT;
815 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
816 else if (matchBuf[i] == '\t')
820 /* mask "symbols" or 'symbols' */
822 for (i = 0; int_buf[i]; i++) {
824 if (c == '\'' || c == '"') {
833 } else if (c2 != 0 && c != '$')
837 /* skip commands with arguments if line have commands delimiters */
838 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
839 for (i = 0; int_buf[i]; i++) {
842 j = i ? int_buf[i - 1] : -1;
844 if (c == ';' || c == '&' || c == '|') {
845 command_mode = 1 + (c == c2);
847 if (j == '>' || j == '<')
849 } else if (c == '|' && j == '>')
853 collapse_pos(0, i + command_mode);
854 i = -1; /* hack incremet */
857 /* collapse `command...` */
858 for (i = 0; int_buf[i]; i++)
859 if (int_buf[i] == '`') {
860 for (j = i + 1; int_buf[j]; j++)
861 if (int_buf[j] == '`') {
862 collapse_pos(i, j + 1);
867 /* not found close ` - command mode, collapse all previous */
868 collapse_pos(0, i + 1);
871 i--; /* hack incremet */
874 /* collapse (command...(command...)...) or {command...{command...}...} */
875 c = 0; /* "recursive" level */
877 for (i = 0; int_buf[i]; i++)
878 if (int_buf[i] == '(' || int_buf[i] == '{') {
879 if (int_buf[i] == '(')
883 collapse_pos(0, i + 1);
884 i = -1; /* hack incremet */
886 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
887 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
888 if (int_buf[i] == ')')
892 collapse_pos(0, i + 1);
893 i = -1; /* hack incremet */
896 /* skip first not quote space */
897 for (i = 0; int_buf[i]; i++)
898 if (int_buf[i] != ' ')
903 /* set find mode for completion */
904 command_mode = FIND_EXE_ONLY;
905 for (i = 0; int_buf[i]; i++)
906 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
907 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
908 && matchBuf[pos_buf[0]]=='c'
909 && matchBuf[pos_buf[1]]=='d' )
910 command_mode = FIND_DIR_ONLY;
912 command_mode = FIND_FILE_ONLY;
917 for (i = 0; int_buf[i]; i++);
919 for (--i; i >= 0; i--) {
921 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
922 collapse_pos(0, i + 1);
926 /* skip first not quoted '\'' or '"' */
927 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
928 /* collapse quote or unquote // or /~ */
929 while ((int_buf[i] & ~QUOT) == '/' &&
930 ((int_buf[i + 1] & ~QUOT) == '/'
931 || (int_buf[i + 1] & ~QUOT) == '~')) {
935 /* set only match and destroy quotes */
937 for (c = 0; pos_buf[i] >= 0; i++) {
938 matchBuf[c++] = matchBuf[pos_buf[i]];
942 /* old lenght matchBuf with quotes symbols */
943 *len_with_quotes = j ? j - pos_buf[0] : 0;
949 display by column original ideas from ls applet,
950 very optimize by my :)
952 static void showfiles(char **matches, int nfiles)
955 int column_width = 0;
958 /* find the longest file name- use that as the column width */
959 for (row = 0; row < nrows; row++) {
960 int l = strlen(matches[row]);
962 if (column_width < l)
965 column_width += 2; /* min space for columns */
966 ncols = cmdedit_termw / column_width;
971 nrows++; /* round up fractionals */
972 column_width = -column_width; /* for printf("%-Ns", ...); */
976 for (row = 0; row < nrows; row++) {
980 for(nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++)
981 printf("%*s", column_width, matches[n]);
982 printf("%s\n", matches[n]);
987 static void input_tab(int *lastWasTab)
989 /* Do TAB completion */
990 static int num_matches;
991 static char **matches;
993 if (lastWasTab == 0) { /* free all memory */
995 while (num_matches > 0)
996 free(matches[--num_matches]);
998 matches = (char **) NULL;
1002 if (! *lastWasTab) {
1006 char matchBuf[BUFSIZ];
1010 *lastWasTab = TRUE; /* flop trigger */
1012 /* Make a local copy of the string -- up
1013 * to the position of the cursor */
1014 tmp = strncpy(matchBuf, command_ps, cursor);
1017 find_type = find_match(matchBuf, &recalc_pos);
1019 /* Free up any memory already allocated */
1022 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
1023 /* If the word starts with `~' and there is no slash in the word,
1024 * then try completing this word as a username. */
1026 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1027 matches = username_tab_completion(matchBuf, &num_matches);
1029 /* Try to match any executable in our path and everything
1030 * in the current working directory that matches. */
1033 exe_n_cwd_tab_completion(matchBuf,
1034 &num_matches, find_type);
1035 /* Remove duplicate found */
1039 for(i=0; i<(num_matches-1); i++)
1040 for(j=i+1; j<num_matches; j++)
1041 if(matches[i]!=0 && matches[j]!=0 &&
1042 strcmp(matches[i], matches[j])==0) {
1050 if(!strcmp(matches[i], "./"))
1052 else if(!strcmp(matches[i], "../"))
1054 matches[num_matches++]=matches[i];
1057 /* Did we find exactly one match? */
1058 if (!matches || num_matches > 1) {
1063 return; /* not found */
1065 qsort(matches, num_matches, sizeof(char *), match_compare);
1067 /* find minimal match */
1068 tmp = bb_xstrdup(matches[0]);
1069 for (tmp1 = tmp; *tmp1; tmp1++)
1070 for (len_found = 1; len_found < num_matches; len_found++)
1071 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1075 if (*tmp == 0) { /* have unique */
1079 } else { /* one match */
1081 /* for next completion current found */
1082 *lastWasTab = FALSE;
1085 len_found = strlen(tmp);
1086 /* have space to placed match? */
1087 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1089 /* before word for match */
1090 command_ps[cursor - recalc_pos] = 0;
1091 /* save tail line */
1092 strcpy(matchBuf, command_ps + cursor);
1094 strcat(command_ps, tmp);
1096 strcat(command_ps, matchBuf);
1097 /* back to begin word for match */
1098 input_backward(recalc_pos);
1100 recalc_pos = cursor + len_found;
1102 len = strlen(command_ps);
1103 /* write out the matched command */
1104 redraw(cmdedit_y, len - recalc_pos);
1106 if (tmp != matches[0])
1109 /* Ok -- the last char was a TAB. Since they
1110 * just hit TAB again, print a list of all the
1111 * available choices... */
1112 if (matches && num_matches > 0) {
1113 int sav_cursor = cursor; /* change goto_new_line() */
1115 /* Go to the next line */
1117 showfiles(matches, num_matches);
1118 redraw(0, len - sav_cursor);
1122 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
1124 #if MAX_HISTORY >= 1
1125 static void get_previous_history(void)
1127 if(command_ps[0] != 0 || history[cur_history] == 0) {
1128 free(history[cur_history]);
1129 history[cur_history] = bb_xstrdup(command_ps);
1134 static int get_next_history(void)
1136 int ch = cur_history;
1138 if (ch < n_history) {
1139 get_previous_history(); /* save the current history line */
1140 return (cur_history = ch+1);
1147 #ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY
1148 extern void load_history ( const char *fromfile )
1155 for(hi = n_history; hi > 0; ) {
1157 free ( history [hi] );
1160 if (( fp = fopen ( fromfile, "r" ))) {
1162 for ( hi = 0; hi < MAX_HISTORY; ) {
1163 char * hl = bb_get_chomped_line_from_file(fp);
1171 if(l == 0 || hl[0] == ' ') {
1175 history [hi++] = hl;
1179 cur_history = n_history = hi;
1182 extern void save_history ( const char *tofile )
1184 FILE *fp = fopen ( tofile, "w" );
1189 for ( i = 0; i < n_history; i++ ) {
1190 fputs ( history [i], fp );
1207 * This function is used to grab a character buffer
1208 * from the input file descriptor and allows you to
1209 * a string with full command editing (sortof like
1212 * The following standard commands are not implemented:
1213 * ESC-b -- Move back one word
1214 * ESC-f -- Move forward one word
1215 * ESC-d -- Delete back one word
1216 * ESC-h -- Delete forward one word
1217 * CTL-t -- Transpose two characters
1219 * Furthermore, the "vi" command editing keys are not implemented.
1224 int cmdedit_read_input(char *prompt, char command[BUFSIZ])
1228 int lastWasTab = FALSE;
1229 unsigned char c = 0;
1231 /* prepare before init handlers */
1232 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1234 command_ps = command;
1236 getTermSettings(0, (void *) &initial_settings);
1237 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1238 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1239 /* Turn off echoing and CTRL-C, so we can trap it */
1240 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;
1253 setTermSettings(0, (void *) &new_settings);
1254 handlers_sets |= SET_RESET_TERM;
1256 /* Now initialize things */
1258 /* Print out the command prompt */
1259 parse_prompt(prompt);
1263 fflush(stdout); /* buffered out to fast */
1265 if (safe_read(0, &c, 1) < 1)
1266 /* if we can't read input then exit */
1267 goto prepare_to_die;
1277 /* Control-a -- Beginning of line */
1278 input_backward(cursor);
1281 /* Control-b -- Move back one character */
1285 /* Control-c -- stop gathering input */
1289 #if !defined(CONFIG_ASH)
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 break_out = -1; /* for control stoped jobs */
1315 /* Control-e -- End of line */
1319 /* Control-f -- Move forward one character */
1324 /* Control-h and DEL */
1328 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1329 input_tab(&lastWasTab);
1333 /* Control-k -- clear to end of line */
1334 *(command + cursor) = 0;
1339 /* Control-l -- clear screen */
1341 redraw(0, len-cursor);
1343 #if MAX_HISTORY >= 1
1345 /* Control-n -- Get next command in history */
1346 if (get_next_history())
1350 /* Control-p -- Get previous command from history */
1351 if (cur_history > 0) {
1352 get_previous_history();
1360 /* Control-U -- Clear line before cursor */
1362 strcpy(command, command + cursor);
1363 redraw(cmdedit_y, len -= cursor);
1367 /* escape sequence follows */
1368 if (safe_read(0, &c, 1) < 1)
1369 goto prepare_to_die;
1370 /* different vt100 emulations */
1371 if (c == '[' || c == 'O') {
1372 if (safe_read(0, &c, 1) < 1)
1373 goto prepare_to_die;
1376 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1377 case '\t': /* Alt-Tab */
1379 input_tab(&lastWasTab);
1382 #if MAX_HISTORY >= 1
1384 /* Up Arrow -- Get previous command from history */
1385 if (cur_history > 0) {
1386 get_previous_history();
1393 /* Down Arrow -- Get next command in history */
1394 if (!get_next_history())
1396 /* Rewrite the line with the selected history item */
1398 /* change command */
1399 len = strlen(strcpy(command, history[cur_history]));
1400 /* redraw and go to end line */
1401 redraw(cmdedit_y, 0);
1405 /* Right Arrow -- Move forward one character */
1409 /* Left Arrow -- Move back one character */
1419 input_backward(cursor);
1427 if (!(c >= '1' && c <= '9'))
1431 if (c >= '1' && c <= '9')
1433 if (safe_read(0, &c, 1) < 1)
1434 goto prepare_to_die;
1439 default: /* If it's regular input, do the normal thing */
1440 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1441 /* Control-V -- Add non-printable symbol */
1443 if (safe_read(0, &c, 1) < 1)
1444 goto prepare_to_die;
1451 if (!Isprint(c)) /* Skip non-printable characters */
1454 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1459 if (cursor == (len - 1)) { /* Append if at the end of the line */
1460 *(command + cursor) = c;
1461 *(command + cursor + 1) = 0;
1462 cmdedit_set_out_char(0);
1463 } else { /* Insert otherwise */
1466 memmove(command + sc + 1, command + sc, len - sc);
1467 *(command + sc) = c;
1469 /* rewrite from cursor */
1471 /* to prev x pos + 1 */
1472 input_backward(cursor - sc);
1477 if (break_out) /* Enter is the command terminator, no more input. */
1484 setTermSettings(0, (void *) &initial_settings);
1485 handlers_sets &= ~SET_RESET_TERM;
1487 #if MAX_HISTORY >= 1
1488 /* Handle command history log */
1489 /* cleanup may be saved current command line */
1490 free(history[MAX_HISTORY]);
1491 history[MAX_HISTORY] = 0;
1492 if (len) { /* no put empty line */
1494 /* After max history, remove the oldest command */
1495 if (i >= MAX_HISTORY) {
1497 for(i = 0; i < (MAX_HISTORY-1); i++)
1498 history[i] = history[i+1];
1500 history[i++] = bb_xstrdup(command);
1503 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1507 #else /* MAX_HISTORY < 1 */
1508 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1509 if (len) { /* no put empty line */
1513 #endif /* MAX_HISTORY >= 1 */
1514 if(break_out == 1) {
1515 command[len++] = '\n'; /* set '\n' */
1518 #if defined(CONFIG_FEATURE_CLEAN_UP) && defined(CONFIG_FEATURE_COMMAND_TAB_COMPLETION)
1519 input_tab(0); /* strong free */
1521 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1522 free(cmdedit_prompt);
1524 cmdedit_reset_term();
1525 #if !defined(CONFIG_ASH)
1528 return break_out < 0 ? break_out : len;
1534 #endif /* CONFIG_FEATURE_COMMAND_EDITING */
1539 const char *bb_applet_name = "debug stuff usage";
1540 const char *memory_exhausted = "Memory exhausted";
1542 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1546 int main(int argc, char **argv)
1550 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1551 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1552 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1553 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1558 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1559 setlocale(LC_ALL, "");
1563 cmdedit_read_input(prompt, buff);
1567 if(l > 0 && buff[l-1] == '\n')
1569 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1571 printf("*** cmdedit_read_input() detect ^C\n");