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 <vodz@usa.net>
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_SIMPLE_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;
109 /* ED: sparc termios is broken: revert back to old termio handling. */
113 # define termios termio
114 # define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
115 # define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
117 # include <termios.h>
118 # define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
119 # define getTermSettings(fd,argp) tcgetattr(fd, argp);
122 /* Current termio and the previous termio before starting sh */
123 static struct termios initial_settings, new_settings;
126 #ifndef _POSIX_VDISABLE
127 #define _POSIX_VDISABLE '\0'
132 volatile int cmdedit_termw = 80; /* actual terminal width */
133 static int history_counter = 0; /* Number of commands in history list */
135 volatile int handlers_sets = 0; /* Set next bites: */
138 SET_ATEXIT = 1, /* when atexit() has been called
139 and get euid,uid,gid to fast compare */
140 SET_TERM_HANDLERS = 2, /* set many terminates signal handlers */
141 SET_WCHG_HANDLERS = 4, /* winchg signal handler */
142 SET_RESET_TERM = 8, /* if the terminal needs to be reset upon exit */
146 static int cmdedit_x; /* real x terminal position */
147 static int cmdedit_y; /* pseudoreal y terminal position */
148 static int cmdedit_prmt_len; /* lenght prompt without colores string */
150 static int cursor; /* required global for signal handler */
151 static int len; /* --- "" - - "" - -"- --""-- --""--- */
152 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
154 #ifdef BB_FEATURE_SH_SIMPLE_PROMPT
157 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
159 /* Link into lash to reset context to 0 on ^C and such */
160 extern unsigned int shell_context;
163 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
164 static char *user_buf = "";
165 static char *home_pwd_buf = "";
169 #ifndef BB_FEATURE_SH_SIMPLE_PROMPT
170 static char *hostname_buf = "";
171 static int num_ok_lines = 1;
175 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
177 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
184 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
187 static void cmdedit_setwidth(int w, int redraw_flg);
189 static void win_changed(int nsig)
191 struct winsize win = { 0, 0, 0, 0 };
192 static __sighandler_t previous_SIGWINCH_handler; /* for reset */
194 /* emulate || signal call */
195 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
196 ioctl(0, TIOCGWINSZ, &win);
197 if (win.ws_col > 0) {
198 cmdedit_setwidth(win.ws_col, nsig == SIGWINCH);
201 /* Unix not all standart in recall signal */
203 if (nsig == -SIGWINCH) /* save previous handler */
204 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
205 else if (nsig == SIGWINCH) /* signaled called handler */
206 signal(SIGWINCH, win_changed); /* set for next call */
208 /* set previous handler */
209 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
212 static void cmdedit_reset_term(void)
214 if ((handlers_sets & SET_RESET_TERM) != 0) {
215 /* sparc and other have broken termios support: use old termio handling. */
216 setTermSettings(fileno(stdin), (void *) &initial_settings);
217 handlers_sets &= ~SET_RESET_TERM;
219 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
220 /* reset SIGWINCH handler to previous (default) */
222 handlers_sets &= ~SET_WCHG_HANDLERS;
225 #ifdef BB_FEATURE_CLEAN_UP
229 while (his_front != his_end) {
240 /* special for recount position for scroll and remove terminal margin effect */
241 static void cmdedit_set_out_char(int next_char)
244 int c = (int)((unsigned char) command_ps[cursor]);
247 c = ' '; /* destroy end char? */
248 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
249 if (!Isprint(c)) { /* Inverse put non-printable characters */
256 printf("\033[7m%c\033[0m", c);
260 if (++cmdedit_x >= cmdedit_termw) {
261 /* terminal is scrolled down */
267 /* destroy "(auto)margin" */
274 /* Move to end line. Bonus: rewrite line from cursor */
275 static void input_end(void)
278 cmdedit_set_out_char(0);
281 /* Go to the next line */
282 static void goto_new_line(void)
290 static inline void out1str(const char *s)
294 static inline void beep(void)
299 /* Move back one charactor */
300 /* special for slow terminal */
301 static void input_backward(int num)
305 cursor -= num; /* new cursor (in command, not terminal) */
307 if (cmdedit_x >= num) { /* no to up line */
314 printf("\033[%dD", num);
319 putchar('\r'); /* back to first terminal pos. */
320 num -= cmdedit_x; /* set previous backward */
322 count_y = 1 + num / cmdedit_termw;
323 printf("\033[%dA", count_y);
324 cmdedit_y -= count_y;
325 /* require forward after uping */
326 cmdedit_x = cmdedit_termw * count_y - num;
327 printf("\033[%dC", cmdedit_x); /* set term cursor */
331 static void put_prompt(void)
333 out1str(cmdedit_prompt);
334 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
338 #ifdef BB_FEATURE_SH_SIMPLE_PROMPT
339 static void parse_prompt(const char *prmt_ptr)
341 cmdedit_prompt = prmt_ptr;
342 cmdedit_prmt_len = strlen(prmt_ptr);
346 static void parse_prompt(const char *prmt_ptr)
350 char flg_not_length = '[';
351 char *prmt_mem_ptr = xcalloc(1, 1);
352 char *pwd_buf = xgetcwd(0);
353 char buf2[PATH_MAX + 1];
367 const char *cp = prmt_ptr;
370 c = process_escape_sequence(&prmt_ptr);
376 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
384 pbuf = xcalloc(256, 1);
385 if (gethostname(pbuf, 255) < 0) {
388 char *s = strchr(pbuf, '.');
397 c = my_euid == 0 ? '#' : '$';
399 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
402 l = strlen(home_pwd_buf);
403 if (home_pwd_buf[0] != 0 &&
404 strncmp(home_pwd_buf, pbuf, l) == 0 &&
405 (pbuf[l]=='/' || pbuf[l]=='\0') &&
406 strlen(pwd_buf+l)<PATH_MAX) {
409 strcpy(pbuf+1, pwd_buf+l);
415 cp = strrchr(pbuf,'/');
416 if ( (cp != NULL) && (cp != pbuf) )
420 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
422 case 'e': case 'E': /* \e \E = \033 */
426 for (l = 0; l < 3;) {
428 buf2[l++] = *prmt_ptr;
430 h = strtol(buf2, &pbuf, 16);
431 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
438 c = (char)strtol(buf2, 0, 16);
444 if (c == flg_not_length) {
445 flg_not_length = flg_not_length == '[' ? ']' : '[';
454 prmt_len += strlen(pbuf);
455 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
456 if (flg_not_length == ']')
460 cmdedit_prompt = prmt_mem_ptr;
461 cmdedit_prmt_len = prmt_len - sub_len;
467 /* draw promt, editor line, and clear tail */
468 static void redraw(int y, int back_cursor)
470 if (y > 0) /* up to start y */
471 printf("\033[%dA", y);
472 cmdedit_y = 0; /* new quasireal y */
475 input_end(); /* rewrite */
476 printf("\033[J"); /* destroy tail after cursor */
477 input_backward(back_cursor);
480 /* Delete the char in front of the cursor */
481 static void input_delete(void)
488 strcpy(command_ps + j, command_ps + j + 1);
490 input_end(); /* rewtite new line */
491 cmdedit_set_out_char(0); /* destroy end char */
492 input_backward(cursor - j); /* back to old pos cursor */
495 /* Delete the char in back of the cursor */
496 static void input_backspace(void)
505 /* Move forward one charactor */
506 static void input_forward(void)
509 cmdedit_set_out_char(command_ps[cursor + 1]);
513 static void clean_up_and_die(int sig)
517 exit(EXIT_SUCCESS); /* cmdedit_reset_term() called in atexit */
518 cmdedit_reset_term();
521 static void cmdedit_setwidth(int w, int redraw_flg)
523 cmdedit_termw = cmdedit_prmt_len + 2;
524 if (w <= cmdedit_termw) {
525 cmdedit_termw = cmdedit_termw % w;
527 if (w > cmdedit_termw) {
531 /* new y for current cursor */
532 int new_y = (cursor + cmdedit_prmt_len) / w;
535 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
541 extern void cmdedit_init(void)
543 cmdedit_prmt_len = 0;
544 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
545 /* emulate usage handler to set handler and call yours work */
546 win_changed(-SIGWINCH);
547 handlers_sets |= SET_WCHG_HANDLERS;
550 if ((handlers_sets & SET_ATEXIT) == 0) {
551 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
552 struct passwd *entry;
555 entry = getpwuid(my_euid);
557 user_buf = xstrdup(entry->pw_name);
558 home_pwd_buf = xstrdup(entry->pw_dir);
562 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
564 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
569 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
570 handlers_sets |= SET_ATEXIT;
571 atexit(cmdedit_reset_term); /* be sure to do this only once */
574 if ((handlers_sets & SET_TERM_HANDLERS) == 0) {
575 signal(SIGKILL, clean_up_and_die);
576 signal(SIGINT, clean_up_and_die);
577 signal(SIGQUIT, clean_up_and_die);
578 signal(SIGTERM, clean_up_and_die);
579 handlers_sets |= SET_TERM_HANDLERS;
584 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
586 static int is_execute(const struct stat *st)
588 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
589 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
590 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
591 (st->st_mode & S_IXOTH)) return TRUE;
595 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
597 static char **username_tab_completion(char *ud, int *num_matches)
599 struct passwd *entry;
604 ud++; /* ~user/... to user/... */
605 userlen = strlen(ud);
607 if (num_matches == 0) { /* "~/..." or "~user/..." */
608 char *sav_ud = ud - 1;
611 if (*ud == '/') { /* "~/..." */
615 temp = strchr(ud, '/');
616 *temp = 0; /* ~user\0 */
617 entry = getpwnam(ud);
618 *temp = '/'; /* restore ~user/... */
621 home = entry->pw_dir;
624 if ((userlen + strlen(home) + 1) < BUFSIZ) {
625 char temp2[BUFSIZ]; /* argument size */
628 sprintf(temp2, "%s%s", home, ud);
629 strcpy(sav_ud, temp2);
632 return 0; /* void, result save to argument :-) */
635 char **matches = (char **) NULL;
640 while ((entry = getpwent()) != NULL) {
641 /* Null usernames should result in all users as possible completions. */
642 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
644 temp = xmalloc(3 + strlen(entry->pw_name));
645 sprintf(temp, "~%s/", entry->pw_name);
646 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
648 matches[nm++] = temp;
657 #endif /* BB_FEATURE_COMMAND_USERNAME_COMPLETION */
665 static int path_parse(char ***p, int flags)
671 /* if not setenv PATH variable, to search cur dir "." */
672 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
673 /* PATH=<empty> or PATH=:<empty> */
674 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
682 npth++; /* count words is + 1 count ':' */
683 tmp = strchr(tmp, ':');
686 break; /* :<empty> */
691 *p = xmalloc(npth * sizeof(char *));
694 (*p)[0] = xstrdup(tmp);
695 npth = 1; /* count words is + 1 count ':' */
698 tmp = strchr(tmp, ':');
700 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
702 break; /* :<empty> */
705 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
711 static char *add_quote_for_spec_chars(char *found)
714 char *s = xmalloc((strlen(found) + 1) * 2);
717 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
725 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
733 int nm = *num_matches;
736 char **paths = path1;
740 char *pfind = strrchr(command, '/');
745 /* no dir, if flags==EXE_ONLY - get paths, else "." */
746 npaths = path_parse(&paths, type);
750 /* save for change */
751 strcpy(dirbuf, command);
753 dirbuf[(pfind - command) + 1] = 0;
754 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
755 if (dirbuf[0] == '~') /* ~/... or ~user/... */
756 username_tab_completion(dirbuf, 0);
758 /* "strip" dirname in command */
762 npaths = 1; /* only 1 dir */
765 for (i = 0; i < npaths; i++) {
767 dir = opendir(paths[i]);
768 if (!dir) /* Don't print an error */
771 while ((next = readdir(dir)) != NULL) {
772 char *str_found = next->d_name;
775 if (strncmp(str_found, pfind, strlen(pfind)))
777 /* not see .name without .match */
778 if (*str_found == '.' && *pfind == 0) {
779 if (*paths[i] == '/' && paths[i][1] == 0
780 && str_found[1] == 0) str_found = ""; /* only "/" */
784 found = concat_path_file(paths[i], str_found);
785 /* hmm, remover in progress? */
786 if (stat(found, &st) < 0)
788 /* find with dirs ? */
789 if (paths[i] != dirbuf)
790 strcpy(found, next->d_name); /* only name */
791 if (S_ISDIR(st.st_mode)) {
792 /* name is directory */
794 found = concat_path_file(found, "");
796 str_found = add_quote_for_spec_chars(found);
798 /* not put found file if search only dirs for cd */
799 if (type == FIND_DIR_ONLY)
801 str_found = add_quote_for_spec_chars(found);
802 if (type == FIND_FILE_ONLY ||
803 (type == FIND_EXE_ONLY && is_execute(&st) == TRUE))
804 strcat(str_found, " ");
806 /* Add it to the list */
807 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
809 matches[nm++] = str_found;
815 if (paths != path1) {
816 free(paths[0]); /* allocated memory only in first member */
823 static int match_compare(const void *a, const void *b)
825 return strcmp(*(char **) a, *(char **) b);
830 #define QUOT (UCHAR_MAX+1)
832 #define collapse_pos(is, in) { \
833 memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \
834 memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); }
836 static int find_match(char *matchBuf, int *len_with_quotes)
841 int int_buf[BUFSIZ + 1];
842 int pos_buf[BUFSIZ + 1];
844 /* set to integer dimension characters and own positions */
846 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
847 if (int_buf[i] == 0) {
848 pos_buf[i] = -1; /* indicator end line */
854 /* mask \+symbol and convert '\t' to ' ' */
855 for (i = j = 0; matchBuf[i]; i++, j++)
856 if (matchBuf[i] == '\\') {
857 collapse_pos(j, j + 1);
860 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
861 if (matchBuf[i] == '\t') /* algorithm equivalent */
862 int_buf[j] = ' ' | QUOT;
865 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
866 else if (matchBuf[i] == '\t')
870 /* mask "symbols" or 'symbols' */
872 for (i = 0; int_buf[i]; i++) {
874 if (c == '\'' || c == '"') {
883 } else if (c2 != 0 && c != '$')
887 /* skip commands with arguments if line have commands delimiters */
888 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
889 for (i = 0; int_buf[i]; i++) {
892 j = i ? int_buf[i - 1] : -1;
894 if (c == ';' || c == '&' || c == '|') {
895 command_mode = 1 + (c == c2);
897 if (j == '>' || j == '<')
899 } else if (c == '|' && j == '>')
903 collapse_pos(0, i + command_mode);
904 i = -1; /* hack incremet */
907 /* collapse `command...` */
908 for (i = 0; int_buf[i]; i++)
909 if (int_buf[i] == '`') {
910 for (j = i + 1; int_buf[j]; j++)
911 if (int_buf[j] == '`') {
912 collapse_pos(i, j + 1);
917 /* not found close ` - command mode, collapse all previous */
918 collapse_pos(0, i + 1);
921 i--; /* hack incremet */
924 /* collapse (command...(command...)...) or {command...{command...}...} */
925 c = 0; /* "recursive" level */
927 for (i = 0; int_buf[i]; i++)
928 if (int_buf[i] == '(' || int_buf[i] == '{') {
929 if (int_buf[i] == '(')
933 collapse_pos(0, i + 1);
934 i = -1; /* hack incremet */
936 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
937 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
938 if (int_buf[i] == ')')
942 collapse_pos(0, i + 1);
943 i = -1; /* hack incremet */
946 /* skip first not quote space */
947 for (i = 0; int_buf[i]; i++)
948 if (int_buf[i] != ' ')
953 /* set find mode for completion */
954 command_mode = FIND_EXE_ONLY;
955 for (i = 0; int_buf[i]; i++)
956 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
957 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
958 && matchBuf[pos_buf[0]]=='c'
959 && matchBuf[pos_buf[1]]=='d' )
960 command_mode = FIND_DIR_ONLY;
962 command_mode = FIND_FILE_ONLY;
967 for (i = 0; int_buf[i]; i++);
969 for (--i; i >= 0; i--) {
971 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
972 collapse_pos(0, i + 1);
976 /* skip first not quoted '\'' or '"' */
977 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
978 /* collapse quote or unquote // or /~ */
979 while ((int_buf[i] & ~QUOT) == '/' &&
980 ((int_buf[i + 1] & ~QUOT) == '/'
981 || (int_buf[i + 1] & ~QUOT) == '~')) {
988 /* set only match and destroy quotes */
990 for (i = 0; pos_buf[i] >= 0; i++) {
991 matchBuf[i] = matchBuf[pos_buf[i]];
995 /* old lenght matchBuf with quotes symbols */
996 *len_with_quotes = j ? j - pos_buf[0] : 0;
1002 static void input_tab(int *lastWasTab)
1004 /* Do TAB completion */
1005 static int num_matches;
1006 static char **matches;
1008 if (lastWasTab == 0) { /* free all memory */
1010 while (num_matches > 0)
1011 free(matches[--num_matches]);
1013 matches = (char **) NULL;
1017 if (*lastWasTab == FALSE) {
1021 char matchBuf[BUFSIZ];
1025 *lastWasTab = TRUE; /* flop trigger */
1027 /* Make a local copy of the string -- up
1028 * to the position of the cursor */
1029 tmp = strncpy(matchBuf, command_ps, cursor);
1032 find_type = find_match(matchBuf, &recalc_pos);
1034 /* Free up any memory already allocated */
1037 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
1038 /* If the word starts with `~' and there is no slash in the word,
1039 * then try completing this word as a username. */
1041 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1042 matches = username_tab_completion(matchBuf, &num_matches);
1044 /* Try to match any executable in our path and everything
1045 * in the current working directory that matches. */
1048 exe_n_cwd_tab_completion(matchBuf, &num_matches,
1051 /* Did we find exactly one match? */
1052 if (!matches || num_matches > 1) {
1057 return; /* not found */
1059 qsort(matches, num_matches, sizeof(char *), match_compare);
1061 /* find minimal match */
1062 tmp = xstrdup(matches[0]);
1063 for (tmp1 = tmp; *tmp1; tmp1++)
1064 for (len_found = 1; len_found < num_matches; len_found++)
1065 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1069 if (*tmp == 0) { /* have unique */
1073 } else { /* one match */
1075 /* for next completion current found */
1076 *lastWasTab = FALSE;
1079 len_found = strlen(tmp);
1080 /* have space to placed match? */
1081 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1083 /* before word for match */
1084 command_ps[cursor - recalc_pos] = 0;
1085 /* save tail line */
1086 strcpy(matchBuf, command_ps + cursor);
1088 strcat(command_ps, tmp);
1090 strcat(command_ps, matchBuf);
1091 /* back to begin word for match */
1092 input_backward(recalc_pos);
1094 recalc_pos = cursor + len_found;
1096 len = strlen(command_ps);
1097 /* write out the matched command */
1099 input_backward(cursor - recalc_pos);
1101 if (tmp != matches[0])
1104 /* Ok -- the last char was a TAB. Since they
1105 * just hit TAB again, print a list of all the
1106 * available choices... */
1107 if (matches && num_matches > 0) {
1109 int sav_cursor = cursor; /* change goto_new_line() */
1111 /* Go to the next line */
1113 for (i = 0, col = 0; i < num_matches; i++) {
1114 l = strlen(matches[i]);
1117 printf("%-14s ", matches[i]);
1124 col -= (col / cmdedit_termw) * cmdedit_termw;
1125 if (col > 60 && matches[i + 1] != NULL) {
1130 /* Go to the next line and rewrite */
1132 redraw(0, len - sav_cursor);
1136 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
1138 static void get_previous_history(struct history **hp, struct history *p)
1142 (*hp)->s = xstrdup(command_ps);
1146 static inline void get_next_history(struct history **hp)
1148 get_previous_history(hp, (*hp)->n);
1158 * This function is used to grab a character buffer
1159 * from the input file descriptor and allows you to
1160 * a string with full command editing (sortof like
1163 * The following standard commands are not implemented:
1164 * ESC-b -- Move back one word
1165 * ESC-f -- Move forward one word
1166 * ESC-d -- Delete back one word
1167 * ESC-h -- Delete forward one word
1168 * CTL-t -- Transpose two characters
1170 * Furthermore, the "vi" command editing keys are not implemented.
1174 extern void cmdedit_read_input(char *prompt, char command[BUFSIZ])
1177 int inputFd = fileno(stdin);
1180 int lastWasTab = FALSE;
1181 unsigned char c = 0;
1182 struct history *hp = his_end;
1184 /* prepare before init handlers */
1185 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1187 command_ps = command;
1189 if (new_settings.c_cc[VMIN] == 0) { /* first call */
1191 getTermSettings(inputFd, (void *) &initial_settings);
1192 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1194 new_settings.c_cc[VMIN] = 1;
1195 new_settings.c_cc[VTIME] = 0;
1196 /* Turn off CTRL-C, so we can trap it */
1197 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1198 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1199 /* Turn off echoing */
1200 new_settings.c_lflag &= ~(ECHO | ECHOCTL | ECHONL);
1205 setTermSettings(inputFd, (void *) &new_settings);
1206 handlers_sets |= SET_RESET_TERM;
1208 /* Now initialize things */
1210 /* Print out the command prompt */
1211 parse_prompt(prompt);
1215 fflush(stdout); /* buffered out to fast */
1217 if (read(inputFd, &c, 1) < 1)
1218 /* if we can't read input then exit */
1219 goto prepare_to_die;
1229 /* Control-a -- Beginning of line */
1230 input_backward(cursor);
1233 /* Control-b -- Move back one character */
1237 /* Control-c -- stop gathering input */
1239 /* Link into lash to reset context to 0 on ^C and such */
1242 /* Go to the next line */
1248 /* Control-d -- Delete one character, or exit
1249 * if the len=0 and no chars to delete */
1253 clean_up_and_die(0);
1259 /* Control-e -- End of line */
1263 /* Control-f -- Move forward one character */
1268 /* Control-h and DEL */
1272 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1273 input_tab(&lastWasTab);
1277 /* Control-n -- Get next command in history */
1278 if (hp && hp->n && hp->n->s) {
1279 get_next_history(&hp);
1286 /* Control-p -- Get previous command from history */
1288 get_previous_history(&hp, hp->p);
1295 /* Control-U -- Clear line before cursor */
1297 strcpy(command, command + cursor);
1298 redraw(cmdedit_y, len -= cursor);
1303 /* escape sequence follows */
1304 if (read(inputFd, &c, 1) < 1)
1306 /* different vt100 emulations */
1307 if (c == '[' || c == 'O') {
1308 if (read(inputFd, &c, 1) < 1)
1312 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1313 case '\t': /* Alt-Tab */
1315 input_tab(&lastWasTab);
1319 /* Up Arrow -- Get previous command from history */
1321 get_previous_history(&hp, hp->p);
1328 /* Down Arrow -- Get next command in history */
1329 if (hp && hp->n && hp->n->s) {
1330 get_next_history(&hp);
1337 /* Rewrite the line with the selected history item */
1339 /* change command */
1340 len = strlen(strcpy(command, hp->s));
1341 /* redraw and go to end line */
1342 redraw(cmdedit_y, 0);
1345 /* Right Arrow -- Move forward one character */
1349 /* Left Arrow -- Move back one character */
1359 input_backward(cursor);
1367 if (!(c >= '1' && c <= '9'))
1371 if (c >= '1' && c <= '9')
1373 if (read(inputFd, &c, 1) < 1)
1379 default: /* If it's regular input, do the normal thing */
1380 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1381 /* Control-V -- Add non-printable symbol */
1383 if (read(inputFd, &c, 1) < 1)
1391 if (!Isprint(c)) /* Skip non-printable characters */
1394 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1399 if (cursor == (len - 1)) { /* Append if at the end of the line */
1400 *(command + cursor) = c;
1401 *(command + cursor + 1) = 0;
1402 cmdedit_set_out_char(0);
1403 } else { /* Insert otherwise */
1406 memmove(command + sc + 1, command + sc, len - sc);
1407 *(command + sc) = c;
1409 /* rewrite from cursor */
1411 /* to prev x pos + 1 */
1412 input_backward(cursor - sc);
1417 if (break_out) /* Enter is the command terminator, no more input. */
1424 setTermSettings(inputFd, (void *) &initial_settings);
1425 handlers_sets &= ~SET_RESET_TERM;
1427 /* Handle command history log */
1428 if (len) { /* no put empty line */
1430 struct history *h = his_end;
1433 ss = xstrdup(command); /* duplicate */
1436 /* No previous history -- this memory is never freed */
1437 h = his_front = xmalloc(sizeof(struct history));
1438 h->n = xmalloc(sizeof(struct history));
1448 /* Add a new history command -- this memory is never freed */
1449 h->n = xmalloc(sizeof(struct history));
1457 /* After max history, remove the oldest command */
1458 if (history_counter >= MAX_HISTORY) {
1460 struct history *p = his_front->n;
1470 #if !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
1474 command[len++] = '\n'; /* set '\n' */
1476 #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_COMMAND_TAB_COMPLETION)
1477 input_tab(0); /* strong free */
1479 #if !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
1480 free(cmdedit_prompt);
1486 /* Undo the effects of cmdedit_init(). */
1487 extern void cmdedit_terminate(void)
1489 cmdedit_reset_term();
1490 if ((handlers_sets & SET_TERM_HANDLERS) != 0) {
1491 signal(SIGKILL, SIG_DFL);
1492 signal(SIGINT, SIG_DFL);
1493 signal(SIGQUIT, SIG_DFL);
1494 signal(SIGTERM, SIG_DFL);
1495 signal(SIGWINCH, SIG_DFL);
1496 handlers_sets &= ~SET_TERM_HANDLERS;
1500 #endif /* BB_FEATURE_COMMAND_EDITING */
1505 const char *applet_name = "debug stuff usage";
1506 const char *memory_exhausted = "Memory exhausted";
1508 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1512 unsigned int shell_context;
1514 int main(int argc, char **argv)
1518 #if !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
1519 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1520 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1521 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1526 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1527 setlocale(LC_ALL, "");
1532 cmdedit_read_input(prompt, buff);
1534 if(l > 0 && buff[l-1] == '\n')
1536 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1537 } while (shell_context);
1538 printf("*** cmdedit_read_input() detect ^C\n");