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 <andersee@debian.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 #define MAX_HISTORY 15
95 #warning cmdedit: You set MAX_HISTORY < 1. The history algorithm switched off.
97 static char *history[MAX_HISTORY+1]; /* history + current */
98 /* saved history lines */
100 /* current pointer to history line */
101 static int cur_history;
105 #define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
106 #define getTermSettings(fd,argp) tcgetattr(fd, argp);
108 /* Current termio and the previous termio before starting sh */
109 static struct termios initial_settings, new_settings;
113 volatile int cmdedit_termw = 80; /* actual terminal width */
115 volatile int handlers_sets = 0; /* Set next bites: */
118 SET_ATEXIT = 1, /* when atexit() has been called
119 and get euid,uid,gid to fast compare */
120 SET_WCHG_HANDLERS = 2, /* winchg signal handler */
121 SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */
125 static int cmdedit_x; /* real x terminal position */
126 static int cmdedit_y; /* pseudoreal y terminal position */
127 static int cmdedit_prmt_len; /* lenght prompt without colores string */
129 static int cursor; /* required global for signal handler */
130 static int len; /* --- "" - - "" - -"- --""-- --""--- */
131 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
133 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
136 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
138 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
139 static char *user_buf = "";
140 static char *home_pwd_buf = "";
144 #ifdef CONFIG_FEATURE_SH_FANCY_PROMPT
145 static char *hostname_buf;
146 static int num_ok_lines = 1;
150 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
152 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
159 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
161 /* It seems that libc5 doesn't know what a sighandler_t is... */
162 #if (__GLIBC__ <= 2) && (__GLIBC_MINOR__ < 1)
163 typedef void (*sighandler_t) (int);
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 *)unknown;
336 const char *cp = prmt_ptr;
339 c = 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 *)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 = xstrdup(entry->pw_name);
519 home_pwd_buf = 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_asprintf(&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] = 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 static void input_tab(int *lastWasTab)
952 /* Do TAB completion */
953 static int num_matches;
954 static char **matches;
956 if (lastWasTab == 0) { /* free all memory */
958 while (num_matches > 0)
959 free(matches[--num_matches]);
961 matches = (char **) NULL;
969 char matchBuf[BUFSIZ];
973 *lastWasTab = TRUE; /* flop trigger */
975 /* Make a local copy of the string -- up
976 * to the position of the cursor */
977 tmp = strncpy(matchBuf, command_ps, cursor);
980 find_type = find_match(matchBuf, &recalc_pos);
982 /* Free up any memory already allocated */
985 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
986 /* If the word starts with `~' and there is no slash in the word,
987 * then try completing this word as a username. */
989 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
990 matches = username_tab_completion(matchBuf, &num_matches);
992 /* Try to match any executable in our path and everything
993 * in the current working directory that matches. */
996 exe_n_cwd_tab_completion(matchBuf,
997 &num_matches, find_type);
998 /* Remove duplicate found */
1002 for(i=0; i<(num_matches-1); i++)
1003 for(j=i+1; j<num_matches; j++)
1004 if(matches[i]!=0 && matches[j]!=0 &&
1005 strcmp(matches[i], matches[j])==0) {
1013 if(!strcmp(matches[i], "./"))
1015 else if(!strcmp(matches[i], "../"))
1017 matches[num_matches++]=matches[i];
1020 /* Did we find exactly one match? */
1021 if (!matches || num_matches > 1) {
1026 return; /* not found */
1028 qsort(matches, num_matches, sizeof(char *), match_compare);
1030 /* find minimal match */
1031 tmp = xstrdup(matches[0]);
1032 for (tmp1 = tmp; *tmp1; tmp1++)
1033 for (len_found = 1; len_found < num_matches; len_found++)
1034 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1038 if (*tmp == 0) { /* have unique */
1042 } else { /* one match */
1044 /* for next completion current found */
1045 *lastWasTab = FALSE;
1048 len_found = strlen(tmp);
1049 /* have space to placed match? */
1050 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1052 /* before word for match */
1053 command_ps[cursor - recalc_pos] = 0;
1054 /* save tail line */
1055 strcpy(matchBuf, command_ps + cursor);
1057 strcat(command_ps, tmp);
1059 strcat(command_ps, matchBuf);
1060 /* back to begin word for match */
1061 input_backward(recalc_pos);
1063 recalc_pos = cursor + len_found;
1065 len = strlen(command_ps);
1066 /* write out the matched command */
1067 redraw(cmdedit_y, len - recalc_pos);
1069 if (tmp != matches[0])
1072 /* Ok -- the last char was a TAB. Since they
1073 * just hit TAB again, print a list of all the
1074 * available choices... */
1075 if (matches && num_matches > 0) {
1077 int sav_cursor = cursor; /* change goto_new_line() */
1079 /* Go to the next line */
1081 for (i = 0, col = 0; i < num_matches; i++) {
1082 l = strlen(matches[i]);
1085 printf("%-14s ", matches[i]);
1092 col -= (col / cmdedit_termw) * cmdedit_termw;
1093 if (col > 60 && matches[i + 1] != NULL) {
1098 /* Go to the next line and rewrite */
1100 redraw(0, len - sav_cursor);
1104 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
1106 #if MAX_HISTORY >= 1
1107 static void get_previous_history(void)
1109 if(command_ps[0] != 0 || history[cur_history] == 0) {
1110 free(history[cur_history]);
1111 history[cur_history] = xstrdup(command_ps);
1116 static int get_next_history(void)
1118 int ch = cur_history;
1120 if (ch < n_history) {
1121 get_previous_history(); /* save the current history line */
1122 return (cur_history = ch+1);
1137 * This function is used to grab a character buffer
1138 * from the input file descriptor and allows you to
1139 * a string with full command editing (sortof like
1142 * The following standard commands are not implemented:
1143 * ESC-b -- Move back one word
1144 * ESC-f -- Move forward one word
1145 * ESC-d -- Delete back one word
1146 * ESC-h -- Delete forward one word
1147 * CTL-t -- Transpose two characters
1149 * Furthermore, the "vi" command editing keys are not implemented.
1154 int cmdedit_read_input(char *prompt, char command[BUFSIZ])
1158 int lastWasTab = FALSE;
1159 unsigned char c = 0;
1161 /* prepare before init handlers */
1162 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1164 command_ps = command;
1166 getTermSettings(0, (void *) &initial_settings);
1167 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1168 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1169 /* Turn off echoing and CTRL-C, so we can trap it */
1170 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1172 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1173 new_settings.c_cc[VMIN] = 1;
1174 new_settings.c_cc[VTIME] = 0;
1175 /* Turn off CTRL-C, so we can trap it */
1176 # ifndef _POSIX_VDISABLE
1177 # define _POSIX_VDISABLE '\0'
1179 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1183 setTermSettings(0, (void *) &new_settings);
1184 handlers_sets |= SET_RESET_TERM;
1186 /* Now initialize things */
1188 /* Print out the command prompt */
1189 parse_prompt(prompt);
1193 fflush(stdout); /* buffered out to fast */
1195 if (safe_read(0, &c, 1) < 1)
1196 /* if we can't read input then exit */
1197 goto prepare_to_die;
1207 /* Control-a -- Beginning of line */
1208 input_backward(cursor);
1211 /* Control-b -- Move back one character */
1215 /* Control-c -- stop gathering input */
1223 /* Control-d -- Delete one character, or exit
1224 * if the len=0 and no chars to delete */
1227 #if !defined(CONFIG_ASH)
1230 /* cmdedit_reset_term() called in atexit */
1233 break_out = -1; /* for control stoped jobs */
1241 /* Control-e -- End of line */
1245 /* Control-f -- Move forward one character */
1250 /* Control-h and DEL */
1254 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1255 input_tab(&lastWasTab);
1259 /* Control-k -- clear to end of line */
1260 *(command + cursor) = 0;
1265 /* Control-l -- clear screen */
1267 redraw(0, len-cursor);
1269 #if MAX_HISTORY >= 1
1271 /* Control-n -- Get next command in history */
1272 if (get_next_history())
1276 /* Control-p -- Get previous command from history */
1277 if (cur_history > 0) {
1278 get_previous_history();
1286 /* Control-U -- Clear line before cursor */
1288 strcpy(command, command + cursor);
1289 redraw(cmdedit_y, len -= cursor);
1293 /* escape sequence follows */
1294 if (safe_read(0, &c, 1) < 1)
1295 goto prepare_to_die;
1296 /* different vt100 emulations */
1297 if (c == '[' || c == 'O') {
1298 if (safe_read(0, &c, 1) < 1)
1299 goto prepare_to_die;
1302 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1303 case '\t': /* Alt-Tab */
1305 input_tab(&lastWasTab);
1308 #if MAX_HISTORY >= 1
1310 /* Up Arrow -- Get previous command from history */
1311 if (cur_history > 0) {
1312 get_previous_history();
1319 /* Down Arrow -- Get next command in history */
1320 if (!get_next_history())
1322 /* Rewrite the line with the selected history item */
1324 /* change command */
1325 len = strlen(strcpy(command, history[cur_history]));
1326 /* redraw and go to end line */
1327 redraw(cmdedit_y, 0);
1331 /* Right Arrow -- Move forward one character */
1335 /* Left Arrow -- Move back one character */
1345 input_backward(cursor);
1353 if (!(c >= '1' && c <= '9'))
1357 if (c >= '1' && c <= '9')
1359 if (safe_read(0, &c, 1) < 1)
1360 goto prepare_to_die;
1365 default: /* If it's regular input, do the normal thing */
1366 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1367 /* Control-V -- Add non-printable symbol */
1369 if (safe_read(0, &c, 1) < 1)
1370 goto prepare_to_die;
1377 if (!Isprint(c)) /* Skip non-printable characters */
1380 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1385 if (cursor == (len - 1)) { /* Append if at the end of the line */
1386 *(command + cursor) = c;
1387 *(command + cursor + 1) = 0;
1388 cmdedit_set_out_char(0);
1389 } else { /* Insert otherwise */
1392 memmove(command + sc + 1, command + sc, len - sc);
1393 *(command + sc) = c;
1395 /* rewrite from cursor */
1397 /* to prev x pos + 1 */
1398 input_backward(cursor - sc);
1403 if (break_out) /* Enter is the command terminator, no more input. */
1410 setTermSettings(0, (void *) &initial_settings);
1411 handlers_sets &= ~SET_RESET_TERM;
1413 #if MAX_HISTORY >= 1
1414 /* Handle command history log */
1415 /* cleanup may be saved current command line */
1416 free(history[MAX_HISTORY]);
1417 history[MAX_HISTORY] = 0;
1418 if (len) { /* no put empty line */
1420 /* After max history, remove the oldest command */
1421 if (i >= MAX_HISTORY) {
1423 for(i = 0; i < (MAX_HISTORY-1); i++)
1424 history[i] = history[i+1];
1426 history[i++] = xstrdup(command);
1429 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1433 #else /* MAX_HISTORY < 1 */
1434 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1435 if (len) { /* no put empty line */
1439 #endif /* MAX_HISTORY >= 1 */
1441 command[len++] = '\n'; /* set '\n' */
1444 #if defined(CONFIG_FEATURE_CLEAN_UP) && defined(CONFIG_FEATURE_COMMAND_TAB_COMPLETION)
1445 input_tab(0); /* strong free */
1447 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1448 free(cmdedit_prompt);
1450 cmdedit_reset_term();
1456 #endif /* CONFIG_FEATURE_COMMAND_EDITING */
1461 const char *applet_name = "debug stuff usage";
1462 const char *memory_exhausted = "Memory exhausted";
1464 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1468 int main(int argc, char **argv)
1472 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1473 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1474 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1475 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1480 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1481 setlocale(LC_ALL, "");
1485 cmdedit_read_input(prompt, buff);
1489 if(l > 0 && buff[l-1] == '\n')
1491 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1493 printf("*** cmdedit_read_input() detect ^C\n");