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 BB_LOCALE_SUPPORT
47 #define Isprint(c) isprint((c))
49 #define Isprint(c) ( (c) >= ' ' && (c) != ((unsigned char)'\233') )
58 #define BB_FEATURE_COMMAND_EDITING
59 #define BB_FEATURE_COMMAND_TAB_COMPLETION
60 #define BB_FEATURE_COMMAND_USERNAME_COMPLETION
61 #define BB_FEATURE_NONPRINTABLE_INVERSE_PUT
62 #define BB_FEATURE_CLEAN_UP
68 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
73 #ifdef BB_FEATURE_COMMAND_EDITING
75 #ifndef BB_FEATURE_COMMAND_TAB_COMPLETION
76 #undef BB_FEATURE_COMMAND_USERNAME_COMPLETION
79 #if defined(BB_FEATURE_COMMAND_USERNAME_COMPLETION) || defined(BB_FEATURE_SH_FANCY_PROMPT)
80 #define BB_FEATURE_GETUSERNAME_AND_HOMEDIR
83 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
85 # include "pwd_grp/pwd.h"
89 #endif /* advanced FEATURES */
99 /* Maximum length of the linked list for the command line history */
100 static const int MAX_HISTORY = 15;
102 /* First element in command line list */
103 static struct history *his_front = NULL;
105 /* Last element in command line list */
106 static struct history *his_end = NULL;
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 */
119 static int history_counter = 0; /* Number of commands in history list */
121 volatile int handlers_sets = 0; /* Set next bites: */
124 SET_ATEXIT = 1, /* when atexit() has been called
125 and get euid,uid,gid to fast compare */
126 SET_WCHG_HANDLERS = 2, /* winchg signal handler */
127 SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */
131 static int cmdedit_x; /* real x terminal position */
132 static int cmdedit_y; /* pseudoreal y terminal position */
133 static int cmdedit_prmt_len; /* lenght prompt without colores string */
135 static int cursor; /* required global for signal handler */
136 static int len; /* --- "" - - "" - -"- --""-- --""--- */
137 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
139 #ifndef BB_FEATURE_SH_FANCY_PROMPT
142 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
144 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
145 static char *user_buf = "";
146 static char *home_pwd_buf = "";
150 #ifdef BB_FEATURE_SH_FANCY_PROMPT
151 static char *hostname_buf = "";
152 static int num_ok_lines = 1;
156 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
158 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
165 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
167 /* It seems that libc5 doesn't know what a sighandler_t is... */
168 #if (__GLIBC__ <= 2) && (__GLIBC_MINOR__ < 1)
169 typedef void (*sighandler_t) (int);
172 static void cmdedit_setwidth(int w, int redraw_flg);
174 static void win_changed(int nsig)
176 struct winsize win = { 0, 0, 0, 0 };
177 static sighandler_t previous_SIGWINCH_handler; /* for reset */
179 /* emulate || signal call */
180 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
181 ioctl(0, TIOCGWINSZ, &win);
182 if (win.ws_col > 0) {
183 cmdedit_setwidth(win.ws_col, nsig == SIGWINCH);
186 /* Unix not all standart in recall signal */
188 if (nsig == -SIGWINCH) /* save previous handler */
189 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
190 else if (nsig == SIGWINCH) /* signaled called handler */
191 signal(SIGWINCH, win_changed); /* set for next call */
193 /* set previous handler */
194 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
197 static void cmdedit_reset_term(void)
199 if ((handlers_sets & SET_RESET_TERM) != 0) {
200 /* sparc and other have broken termios support: use old termio handling. */
201 setTermSettings(fileno(stdin), (void *) &initial_settings);
202 handlers_sets &= ~SET_RESET_TERM;
204 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
205 /* reset SIGWINCH handler to previous (default) */
207 handlers_sets &= ~SET_WCHG_HANDLERS;
210 #ifdef BB_FEATURE_CLEAN_UP
214 while (his_front != his_end) {
225 /* special for recount position for scroll and remove terminal margin effect */
226 static void cmdedit_set_out_char(int next_char)
229 int c = (int)((unsigned char) command_ps[cursor]);
232 c = ' '; /* destroy end char? */
233 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
234 if (!Isprint(c)) { /* Inverse put non-printable characters */
241 printf("\033[7m%c\033[0m", c);
245 if (++cmdedit_x >= cmdedit_termw) {
246 /* terminal is scrolled down */
252 /* destroy "(auto)margin" */
259 /* Move to end line. Bonus: rewrite line from cursor */
260 static void input_end(void)
263 cmdedit_set_out_char(0);
266 /* Go to the next line */
267 static void goto_new_line(void)
275 static inline void out1str(const char *s)
279 static inline void beep(void)
284 /* Move back one charactor */
285 /* special for slow terminal */
286 static void input_backward(int num)
290 cursor -= num; /* new cursor (in command, not terminal) */
292 if (cmdedit_x >= num) { /* no to up line */
299 printf("\033[%dD", num);
304 putchar('\r'); /* back to first terminal pos. */
305 num -= cmdedit_x; /* set previous backward */
307 count_y = 1 + num / cmdedit_termw;
308 printf("\033[%dA", count_y);
309 cmdedit_y -= count_y;
310 /* require forward after uping */
311 cmdedit_x = cmdedit_termw * count_y - num;
312 printf("\033[%dC", cmdedit_x); /* set term cursor */
316 static void put_prompt(void)
318 out1str(cmdedit_prompt);
319 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
321 cmdedit_y = 0; /* new quasireal y */
324 #ifndef BB_FEATURE_SH_FANCY_PROMPT
325 static void parse_prompt(const char *prmt_ptr)
327 cmdedit_prompt = prmt_ptr;
328 cmdedit_prmt_len = strlen(prmt_ptr);
332 static void parse_prompt(const char *prmt_ptr)
336 char flg_not_length = '[';
337 char *prmt_mem_ptr = xcalloc(1, 1);
338 char *pwd_buf = xgetcwd(0);
339 char buf2[PATH_MAX + 1];
345 pwd_buf=(char *)unknown;
353 const char *cp = prmt_ptr;
356 c = process_escape_sequence(&prmt_ptr);
362 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
370 pbuf = xcalloc(256, 1);
371 if (gethostname(pbuf, 255) < 0) {
374 char *s = strchr(pbuf, '.');
383 c = my_euid == 0 ? '#' : '$';
385 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
388 l = strlen(home_pwd_buf);
389 if (home_pwd_buf[0] != 0 &&
390 strncmp(home_pwd_buf, pbuf, l) == 0 &&
391 (pbuf[l]=='/' || pbuf[l]=='\0') &&
392 strlen(pwd_buf+l)<PATH_MAX) {
395 strcpy(pbuf+1, pwd_buf+l);
401 cp = strrchr(pbuf,'/');
402 if ( (cp != NULL) && (cp != pbuf) )
406 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
408 case 'e': case 'E': /* \e \E = \033 */
412 for (l = 0; l < 3;) {
414 buf2[l++] = *prmt_ptr;
416 h = strtol(buf2, &pbuf, 16);
417 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
424 c = (char)strtol(buf2, 0, 16);
430 if (c == flg_not_length) {
431 flg_not_length = flg_not_length == '[' ? ']' : '[';
440 prmt_len += strlen(pbuf);
441 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
442 if (flg_not_length == ']')
445 if(pwd_buf!=(char *)unknown)
447 cmdedit_prompt = prmt_mem_ptr;
448 cmdedit_prmt_len = prmt_len - sub_len;
454 /* draw promt, editor line, and clear tail */
455 static void redraw(int y, int back_cursor)
457 if (y > 0) /* up to start y */
458 printf("\033[%dA", y);
461 input_end(); /* rewrite */
462 printf("\033[J"); /* destroy tail after cursor */
463 input_backward(back_cursor);
466 /* Delete the char in front of the cursor */
467 static void input_delete(void)
474 strcpy(command_ps + j, command_ps + j + 1);
476 input_end(); /* rewtite new line */
477 cmdedit_set_out_char(0); /* destroy end char */
478 input_backward(cursor - j); /* back to old pos cursor */
481 /* Delete the char in back of the cursor */
482 static void input_backspace(void)
491 /* Move forward one charactor */
492 static void input_forward(void)
495 cmdedit_set_out_char(command_ps[cursor + 1]);
499 static void cmdedit_setwidth(int w, int redraw_flg)
501 cmdedit_termw = cmdedit_prmt_len + 2;
502 if (w <= cmdedit_termw) {
503 cmdedit_termw = cmdedit_termw % w;
505 if (w > cmdedit_termw) {
509 /* new y for current cursor */
510 int new_y = (cursor + cmdedit_prmt_len) / w;
513 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
519 static void cmdedit_init(void)
521 cmdedit_prmt_len = 0;
522 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
523 /* emulate usage handler to set handler and call yours work */
524 win_changed(-SIGWINCH);
525 handlers_sets |= SET_WCHG_HANDLERS;
528 if ((handlers_sets & SET_ATEXIT) == 0) {
529 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
530 struct passwd *entry;
533 entry = getpwuid(my_euid);
535 user_buf = xstrdup(entry->pw_name);
536 home_pwd_buf = xstrdup(entry->pw_dir);
540 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
542 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
547 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
548 handlers_sets |= SET_ATEXIT;
549 atexit(cmdedit_reset_term); /* be sure to do this only once */
553 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
555 static int is_execute(const struct stat *st)
557 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
558 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
559 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
560 (st->st_mode & S_IXOTH)) return TRUE;
564 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
566 static char **username_tab_completion(char *ud, int *num_matches)
568 struct passwd *entry;
573 ud++; /* ~user/... to user/... */
574 userlen = strlen(ud);
576 if (num_matches == 0) { /* "~/..." or "~user/..." */
577 char *sav_ud = ud - 1;
580 if (*ud == '/') { /* "~/..." */
584 temp = strchr(ud, '/');
585 *temp = 0; /* ~user\0 */
586 entry = getpwnam(ud);
587 *temp = '/'; /* restore ~user/... */
590 home = entry->pw_dir;
593 if ((userlen + strlen(home) + 1) < BUFSIZ) {
594 char temp2[BUFSIZ]; /* argument size */
597 sprintf(temp2, "%s%s", home, ud);
598 strcpy(sav_ud, temp2);
601 return 0; /* void, result save to argument :-) */
604 char **matches = (char **) NULL;
609 while ((entry = getpwent()) != NULL) {
610 /* Null usernames should result in all users as possible completions. */
611 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
613 temp = xmalloc(3 + strlen(entry->pw_name));
614 sprintf(temp, "~%s/", entry->pw_name);
615 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
617 matches[nm++] = temp;
626 #endif /* BB_FEATURE_COMMAND_USERNAME_COMPLETION */
634 static int path_parse(char ***p, int flags)
640 /* if not setenv PATH variable, to search cur dir "." */
641 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
642 /* PATH=<empty> or PATH=:<empty> */
643 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
651 npth++; /* count words is + 1 count ':' */
652 tmp = strchr(tmp, ':');
655 break; /* :<empty> */
660 *p = xmalloc(npth * sizeof(char *));
663 (*p)[0] = xstrdup(tmp);
664 npth = 1; /* count words is + 1 count ':' */
667 tmp = strchr(tmp, ':');
669 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
671 break; /* :<empty> */
674 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
680 static char *add_quote_for_spec_chars(char *found)
683 char *s = xmalloc((strlen(found) + 1) * 2);
686 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
694 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
702 int nm = *num_matches;
705 char **paths = path1;
709 char *pfind = strrchr(command, '/');
714 /* no dir, if flags==EXE_ONLY - get paths, else "." */
715 npaths = path_parse(&paths, type);
719 /* save for change */
720 strcpy(dirbuf, command);
722 dirbuf[(pfind - command) + 1] = 0;
723 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
724 if (dirbuf[0] == '~') /* ~/... or ~user/... */
725 username_tab_completion(dirbuf, 0);
727 /* "strip" dirname in command */
731 npaths = 1; /* only 1 dir */
734 for (i = 0; i < npaths; i++) {
736 dir = opendir(paths[i]);
737 if (!dir) /* Don't print an error */
740 while ((next = readdir(dir)) != NULL) {
741 char *str_found = next->d_name;
744 if (strncmp(str_found, pfind, strlen(pfind)))
746 /* not see .name without .match */
747 if (*str_found == '.' && *pfind == 0) {
748 if (*paths[i] == '/' && paths[i][1] == 0
749 && str_found[1] == 0) str_found = ""; /* only "/" */
753 found = concat_path_file(paths[i], str_found);
754 /* hmm, remover in progress? */
755 if (stat(found, &st) < 0)
757 /* find with dirs ? */
758 if (paths[i] != dirbuf)
759 strcpy(found, next->d_name); /* only name */
760 if (S_ISDIR(st.st_mode)) {
761 /* name is directory */
763 found = concat_path_file(found, "");
765 str_found = add_quote_for_spec_chars(found);
767 /* not put found file if search only dirs for cd */
768 if (type == FIND_DIR_ONLY)
770 str_found = add_quote_for_spec_chars(found);
771 if (type == FIND_FILE_ONLY ||
772 (type == FIND_EXE_ONLY && is_execute(&st) == TRUE))
773 strcat(str_found, " ");
775 /* Add it to the list */
776 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
778 matches[nm++] = str_found;
784 if (paths != path1) {
785 free(paths[0]); /* allocated memory only in first member */
792 static int match_compare(const void *a, const void *b)
794 return strcmp(*(char **) a, *(char **) b);
799 #define QUOT (UCHAR_MAX+1)
801 #define collapse_pos(is, in) { \
802 memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \
803 memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); }
805 static int find_match(char *matchBuf, int *len_with_quotes)
810 int int_buf[BUFSIZ + 1];
811 int pos_buf[BUFSIZ + 1];
813 /* set to integer dimension characters and own positions */
815 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
816 if (int_buf[i] == 0) {
817 pos_buf[i] = -1; /* indicator end line */
823 /* mask \+symbol and convert '\t' to ' ' */
824 for (i = j = 0; matchBuf[i]; i++, j++)
825 if (matchBuf[i] == '\\') {
826 collapse_pos(j, j + 1);
829 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
830 if (matchBuf[i] == '\t') /* algorithm equivalent */
831 int_buf[j] = ' ' | QUOT;
834 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
835 else if (matchBuf[i] == '\t')
839 /* mask "symbols" or 'symbols' */
841 for (i = 0; int_buf[i]; i++) {
843 if (c == '\'' || c == '"') {
852 } else if (c2 != 0 && c != '$')
856 /* skip commands with arguments if line have commands delimiters */
857 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
858 for (i = 0; int_buf[i]; i++) {
861 j = i ? int_buf[i - 1] : -1;
863 if (c == ';' || c == '&' || c == '|') {
864 command_mode = 1 + (c == c2);
866 if (j == '>' || j == '<')
868 } else if (c == '|' && j == '>')
872 collapse_pos(0, i + command_mode);
873 i = -1; /* hack incremet */
876 /* collapse `command...` */
877 for (i = 0; int_buf[i]; i++)
878 if (int_buf[i] == '`') {
879 for (j = i + 1; int_buf[j]; j++)
880 if (int_buf[j] == '`') {
881 collapse_pos(i, j + 1);
886 /* not found close ` - command mode, collapse all previous */
887 collapse_pos(0, i + 1);
890 i--; /* hack incremet */
893 /* collapse (command...(command...)...) or {command...{command...}...} */
894 c = 0; /* "recursive" level */
896 for (i = 0; int_buf[i]; i++)
897 if (int_buf[i] == '(' || int_buf[i] == '{') {
898 if (int_buf[i] == '(')
902 collapse_pos(0, i + 1);
903 i = -1; /* hack incremet */
905 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
906 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
907 if (int_buf[i] == ')')
911 collapse_pos(0, i + 1);
912 i = -1; /* hack incremet */
915 /* skip first not quote space */
916 for (i = 0; int_buf[i]; i++)
917 if (int_buf[i] != ' ')
922 /* set find mode for completion */
923 command_mode = FIND_EXE_ONLY;
924 for (i = 0; int_buf[i]; i++)
925 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
926 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
927 && matchBuf[pos_buf[0]]=='c'
928 && matchBuf[pos_buf[1]]=='d' )
929 command_mode = FIND_DIR_ONLY;
931 command_mode = FIND_FILE_ONLY;
936 for (i = 0; int_buf[i]; i++);
938 for (--i; i >= 0; i--) {
940 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
941 collapse_pos(0, i + 1);
945 /* skip first not quoted '\'' or '"' */
946 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
947 /* collapse quote or unquote // or /~ */
948 while ((int_buf[i] & ~QUOT) == '/' &&
949 ((int_buf[i + 1] & ~QUOT) == '/'
950 || (int_buf[i + 1] & ~QUOT) == '~')) {
954 /* set only match and destroy quotes */
956 for (c = 0; pos_buf[i] >= 0; i++) {
957 matchBuf[c++] = matchBuf[pos_buf[i]];
961 /* old lenght matchBuf with quotes symbols */
962 *len_with_quotes = j ? j - pos_buf[0] : 0;
968 static void input_tab(int *lastWasTab)
970 /* Do TAB completion */
971 static int num_matches;
972 static char **matches;
974 if (lastWasTab == 0) { /* free all memory */
976 while (num_matches > 0)
977 free(matches[--num_matches]);
979 matches = (char **) NULL;
983 if (*lastWasTab == FALSE) {
987 char matchBuf[BUFSIZ];
991 *lastWasTab = TRUE; /* flop trigger */
993 /* Make a local copy of the string -- up
994 * to the position of the cursor */
995 tmp = strncpy(matchBuf, command_ps, cursor);
998 find_type = find_match(matchBuf, &recalc_pos);
1000 /* Free up any memory already allocated */
1003 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
1004 /* If the word starts with `~' and there is no slash in the word,
1005 * then try completing this word as a username. */
1007 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1008 matches = username_tab_completion(matchBuf, &num_matches);
1010 /* Try to match any executable in our path and everything
1011 * in the current working directory that matches. */
1014 exe_n_cwd_tab_completion(matchBuf,
1015 &num_matches, find_type);
1016 /* Remove duplicate found */
1020 for(i=0; i<(num_matches-1); i++)
1021 for(j=i+1; j<num_matches; j++)
1022 if(matches[i]!=0 && matches[j]!=0 &&
1023 strcmp(matches[i], matches[j])==0) {
1031 if(!strcmp(matches[i], "./"))
1033 else if(!strcmp(matches[i], "../"))
1035 matches[num_matches++]=matches[i];
1038 /* Did we find exactly one match? */
1039 if (!matches || num_matches > 1) {
1044 return; /* not found */
1046 qsort(matches, num_matches, sizeof(char *), match_compare);
1048 /* find minimal match */
1049 tmp = xstrdup(matches[0]);
1050 for (tmp1 = tmp; *tmp1; tmp1++)
1051 for (len_found = 1; len_found < num_matches; len_found++)
1052 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1056 if (*tmp == 0) { /* have unique */
1060 } else { /* one match */
1062 /* for next completion current found */
1063 *lastWasTab = FALSE;
1066 len_found = strlen(tmp);
1067 /* have space to placed match? */
1068 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1070 /* before word for match */
1071 command_ps[cursor - recalc_pos] = 0;
1072 /* save tail line */
1073 strcpy(matchBuf, command_ps + cursor);
1075 strcat(command_ps, tmp);
1077 strcat(command_ps, matchBuf);
1078 /* back to begin word for match */
1079 input_backward(recalc_pos);
1081 recalc_pos = cursor + len_found;
1083 len = strlen(command_ps);
1084 /* write out the matched command */
1085 redraw(cmdedit_y, len - recalc_pos);
1087 if (tmp != matches[0])
1090 /* Ok -- the last char was a TAB. Since they
1091 * just hit TAB again, print a list of all the
1092 * available choices... */
1093 if (matches && num_matches > 0) {
1095 int sav_cursor = cursor; /* change goto_new_line() */
1097 /* Go to the next line */
1099 for (i = 0, col = 0; i < num_matches; i++) {
1100 l = strlen(matches[i]);
1103 printf("%-14s ", matches[i]);
1110 col -= (col / cmdedit_termw) * cmdedit_termw;
1111 if (col > 60 && matches[i + 1] != NULL) {
1116 /* Go to the next line and rewrite */
1118 redraw(0, len - sav_cursor);
1122 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
1124 static void get_previous_history(struct history **hp, struct history *p)
1128 (*hp)->s = xstrdup(command_ps);
1132 static inline void get_next_history(struct history **hp)
1134 get_previous_history(hp, (*hp)->n);
1144 * This function is used to grab a character buffer
1145 * from the input file descriptor and allows you to
1146 * a string with full command editing (sortof like
1149 * The following standard commands are not implemented:
1150 * ESC-b -- Move back one word
1151 * ESC-f -- Move forward one word
1152 * ESC-d -- Delete back one word
1153 * ESC-h -- Delete forward one word
1154 * CTL-t -- Transpose two characters
1156 * Furthermore, the "vi" command editing keys are not implemented.
1161 int cmdedit_read_input(char *prompt, char command[BUFSIZ])
1165 int lastWasTab = FALSE;
1166 unsigned char c = 0;
1167 struct history *hp = his_end;
1169 /* prepare before init handlers */
1170 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1172 command_ps = command;
1174 getTermSettings(0, (void *) &initial_settings);
1175 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1176 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1177 /* Turn off echoing and CTRL-C, so we can trap it */
1178 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1180 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1181 new_settings.c_cc[VMIN] = 1;
1182 new_settings.c_cc[VTIME] = 0;
1183 /* Turn off CTRL-C, so we can trap it */
1184 # ifndef _POSIX_VDISABLE
1185 # define _POSIX_VDISABLE '\0'
1187 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1191 setTermSettings(0, (void *) &new_settings);
1192 handlers_sets |= SET_RESET_TERM;
1194 /* Now initialize things */
1196 /* Print out the command prompt */
1197 parse_prompt(prompt);
1201 fflush(stdout); /* buffered out to fast */
1203 if (safe_read(0, &c, 1) < 1)
1204 /* if we can't read input then exit */
1205 goto prepare_to_die;
1215 /* Control-a -- Beginning of line */
1216 input_backward(cursor);
1219 /* Control-b -- Move back one character */
1223 /* Control-c -- stop gathering input */
1231 /* Control-d -- Delete one character, or exit
1232 * if the len=0 and no chars to delete */
1235 #if !defined(BB_ASH)
1238 /* cmdedit_reset_term() called in atexit */
1241 break_out = -1; /* for control stoped jobs */
1249 /* Control-e -- End of line */
1253 /* Control-f -- Move forward one character */
1258 /* Control-h and DEL */
1262 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1263 input_tab(&lastWasTab);
1267 /* Control-n -- Get next command in history */
1268 if (hp && hp->n && hp->n->s) {
1269 get_next_history(&hp);
1276 /* Control-p -- Get previous command from history */
1278 get_previous_history(&hp, hp->p);
1285 /* Control-U -- Clear line before cursor */
1287 strcpy(command, command + cursor);
1288 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 BB_FEATURE_COMMAND_TAB_COMPLETION
1303 case '\t': /* Alt-Tab */
1305 input_tab(&lastWasTab);
1309 /* Up Arrow -- Get previous command from history */
1311 get_previous_history(&hp, hp->p);
1318 /* Down Arrow -- Get next command in history */
1319 if (hp && hp->n && hp->n->s) {
1320 get_next_history(&hp);
1327 /* Rewrite the line with the selected history item */
1329 /* change command */
1330 len = strlen(strcpy(command, hp->s));
1331 /* redraw and go to end line */
1332 redraw(cmdedit_y, 0);
1335 /* Right Arrow -- Move forward one character */
1339 /* Left Arrow -- Move back one character */
1349 input_backward(cursor);
1357 if (!(c >= '1' && c <= '9'))
1361 if (c >= '1' && c <= '9')
1363 if (safe_read(0, &c, 1) < 1)
1364 goto prepare_to_die;
1369 default: /* If it's regular input, do the normal thing */
1370 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1371 /* Control-V -- Add non-printable symbol */
1373 if (safe_read(0, &c, 1) < 1)
1374 goto prepare_to_die;
1381 if (!Isprint(c)) /* Skip non-printable characters */
1384 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1389 if (cursor == (len - 1)) { /* Append if at the end of the line */
1390 *(command + cursor) = c;
1391 *(command + cursor + 1) = 0;
1392 cmdedit_set_out_char(0);
1393 } else { /* Insert otherwise */
1396 memmove(command + sc + 1, command + sc, len - sc);
1397 *(command + sc) = c;
1399 /* rewrite from cursor */
1401 /* to prev x pos + 1 */
1402 input_backward(cursor - sc);
1407 if (break_out) /* Enter is the command terminator, no more input. */
1414 setTermSettings(0, (void *) &initial_settings);
1415 handlers_sets &= ~SET_RESET_TERM;
1417 /* Handle command history log */
1418 if (len) { /* no put empty line */
1420 struct history *h = his_end;
1423 ss = xstrdup(command); /* duplicate */
1426 /* No previous history -- this memory is never freed */
1427 h = his_front = xmalloc(sizeof(struct history));
1428 h->n = xmalloc(sizeof(struct history));
1438 /* Add a new history command -- this memory is never freed */
1439 h->n = xmalloc(sizeof(struct history));
1447 /* After max history, remove the oldest command */
1448 if (history_counter >= MAX_HISTORY) {
1450 struct history *p = his_front->n;
1460 #if defined(BB_FEATURE_SH_FANCY_PROMPT)
1465 command[len++] = '\n'; /* set '\n' */
1468 #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_COMMAND_TAB_COMPLETION)
1469 input_tab(0); /* strong free */
1471 #if defined(BB_FEATURE_SH_FANCY_PROMPT)
1472 free(cmdedit_prompt);
1474 cmdedit_reset_term();
1480 #endif /* BB_FEATURE_COMMAND_EDITING */
1485 const char *applet_name = "debug stuff usage";
1486 const char *memory_exhausted = "Memory exhausted";
1488 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1492 int main(int argc, char **argv)
1496 #if defined(BB_FEATURE_SH_FANCY_PROMPT)
1497 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1498 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1499 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1504 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1505 setlocale(LC_ALL, "");
1509 cmdedit_read_input(prompt, buff);
1513 if(l > 0 && buff[l-1] == '\n')
1515 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1517 printf("*** cmdedit_read_input() detect ^C\n");