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 */
575 if ((handlers_sets & SET_TERM_HANDLERS) == 0) {
576 signal(SIGKILL, clean_up_and_die);
577 signal(SIGINT, clean_up_and_die);
578 signal(SIGQUIT, clean_up_and_die);
579 signal(SIGTERM, clean_up_and_die);
580 handlers_sets |= SET_TERM_HANDLERS;
586 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
588 static int is_execute(const struct stat *st)
590 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
591 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
592 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
593 (st->st_mode & S_IXOTH)) return TRUE;
597 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
599 static char **username_tab_completion(char *ud, int *num_matches)
601 struct passwd *entry;
606 ud++; /* ~user/... to user/... */
607 userlen = strlen(ud);
609 if (num_matches == 0) { /* "~/..." or "~user/..." */
610 char *sav_ud = ud - 1;
613 if (*ud == '/') { /* "~/..." */
617 temp = strchr(ud, '/');
618 *temp = 0; /* ~user\0 */
619 entry = getpwnam(ud);
620 *temp = '/'; /* restore ~user/... */
623 home = entry->pw_dir;
626 if ((userlen + strlen(home) + 1) < BUFSIZ) {
627 char temp2[BUFSIZ]; /* argument size */
630 sprintf(temp2, "%s%s", home, ud);
631 strcpy(sav_ud, temp2);
634 return 0; /* void, result save to argument :-) */
637 char **matches = (char **) NULL;
642 while ((entry = getpwent()) != NULL) {
643 /* Null usernames should result in all users as possible completions. */
644 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
646 temp = xmalloc(3 + strlen(entry->pw_name));
647 sprintf(temp, "~%s/", entry->pw_name);
648 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
650 matches[nm++] = temp;
659 #endif /* BB_FEATURE_COMMAND_USERNAME_COMPLETION */
667 static int path_parse(char ***p, int flags)
673 /* if not setenv PATH variable, to search cur dir "." */
674 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
675 /* PATH=<empty> or PATH=:<empty> */
676 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
684 npth++; /* count words is + 1 count ':' */
685 tmp = strchr(tmp, ':');
688 break; /* :<empty> */
693 *p = xmalloc(npth * sizeof(char *));
696 (*p)[0] = xstrdup(tmp);
697 npth = 1; /* count words is + 1 count ':' */
700 tmp = strchr(tmp, ':');
702 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
704 break; /* :<empty> */
707 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
713 static char *add_quote_for_spec_chars(char *found)
716 char *s = xmalloc((strlen(found) + 1) * 2);
719 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
727 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
735 int nm = *num_matches;
738 char **paths = path1;
742 char *pfind = strrchr(command, '/');
747 /* no dir, if flags==EXE_ONLY - get paths, else "." */
748 npaths = path_parse(&paths, type);
752 /* save for change */
753 strcpy(dirbuf, command);
755 dirbuf[(pfind - command) + 1] = 0;
756 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
757 if (dirbuf[0] == '~') /* ~/... or ~user/... */
758 username_tab_completion(dirbuf, 0);
760 /* "strip" dirname in command */
764 npaths = 1; /* only 1 dir */
767 for (i = 0; i < npaths; i++) {
769 dir = opendir(paths[i]);
770 if (!dir) /* Don't print an error */
773 while ((next = readdir(dir)) != NULL) {
774 char *str_found = next->d_name;
777 if (strncmp(str_found, pfind, strlen(pfind)))
779 /* not see .name without .match */
780 if (*str_found == '.' && *pfind == 0) {
781 if (*paths[i] == '/' && paths[i][1] == 0
782 && str_found[1] == 0) str_found = ""; /* only "/" */
786 found = concat_path_file(paths[i], str_found);
787 /* hmm, remover in progress? */
788 if (stat(found, &st) < 0)
790 /* find with dirs ? */
791 if (paths[i] != dirbuf)
792 strcpy(found, next->d_name); /* only name */
793 if (S_ISDIR(st.st_mode)) {
794 /* name is directory */
796 found = concat_path_file(found, "");
798 str_found = add_quote_for_spec_chars(found);
800 /* not put found file if search only dirs for cd */
801 if (type == FIND_DIR_ONLY)
803 str_found = add_quote_for_spec_chars(found);
804 if (type == FIND_FILE_ONLY ||
805 (type == FIND_EXE_ONLY && is_execute(&st) == TRUE))
806 strcat(str_found, " ");
808 /* Add it to the list */
809 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
811 matches[nm++] = str_found;
817 if (paths != path1) {
818 free(paths[0]); /* allocated memory only in first member */
825 static int match_compare(const void *a, const void *b)
827 return strcmp(*(char **) a, *(char **) b);
832 #define QUOT (UCHAR_MAX+1)
834 #define collapse_pos(is, in) { \
835 memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \
836 memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); }
838 static int find_match(char *matchBuf, int *len_with_quotes)
843 int int_buf[BUFSIZ + 1];
844 int pos_buf[BUFSIZ + 1];
846 /* set to integer dimension characters and own positions */
848 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
849 if (int_buf[i] == 0) {
850 pos_buf[i] = -1; /* indicator end line */
856 /* mask \+symbol and convert '\t' to ' ' */
857 for (i = j = 0; matchBuf[i]; i++, j++)
858 if (matchBuf[i] == '\\') {
859 collapse_pos(j, j + 1);
862 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
863 if (matchBuf[i] == '\t') /* algorithm equivalent */
864 int_buf[j] = ' ' | QUOT;
867 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
868 else if (matchBuf[i] == '\t')
872 /* mask "symbols" or 'symbols' */
874 for (i = 0; int_buf[i]; i++) {
876 if (c == '\'' || c == '"') {
885 } else if (c2 != 0 && c != '$')
889 /* skip commands with arguments if line have commands delimiters */
890 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
891 for (i = 0; int_buf[i]; i++) {
894 j = i ? int_buf[i - 1] : -1;
896 if (c == ';' || c == '&' || c == '|') {
897 command_mode = 1 + (c == c2);
899 if (j == '>' || j == '<')
901 } else if (c == '|' && j == '>')
905 collapse_pos(0, i + command_mode);
906 i = -1; /* hack incremet */
909 /* collapse `command...` */
910 for (i = 0; int_buf[i]; i++)
911 if (int_buf[i] == '`') {
912 for (j = i + 1; int_buf[j]; j++)
913 if (int_buf[j] == '`') {
914 collapse_pos(i, j + 1);
919 /* not found close ` - command mode, collapse all previous */
920 collapse_pos(0, i + 1);
923 i--; /* hack incremet */
926 /* collapse (command...(command...)...) or {command...{command...}...} */
927 c = 0; /* "recursive" level */
929 for (i = 0; int_buf[i]; i++)
930 if (int_buf[i] == '(' || int_buf[i] == '{') {
931 if (int_buf[i] == '(')
935 collapse_pos(0, i + 1);
936 i = -1; /* hack incremet */
938 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
939 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
940 if (int_buf[i] == ')')
944 collapse_pos(0, i + 1);
945 i = -1; /* hack incremet */
948 /* skip first not quote space */
949 for (i = 0; int_buf[i]; i++)
950 if (int_buf[i] != ' ')
955 /* set find mode for completion */
956 command_mode = FIND_EXE_ONLY;
957 for (i = 0; int_buf[i]; i++)
958 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
959 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
960 && matchBuf[pos_buf[0]]=='c'
961 && matchBuf[pos_buf[1]]=='d' )
962 command_mode = FIND_DIR_ONLY;
964 command_mode = FIND_FILE_ONLY;
969 for (i = 0; int_buf[i]; i++);
971 for (--i; i >= 0; i--) {
973 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
974 collapse_pos(0, i + 1);
978 /* skip first not quoted '\'' or '"' */
979 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
980 /* collapse quote or unquote // or /~ */
981 while ((int_buf[i] & ~QUOT) == '/' &&
982 ((int_buf[i + 1] & ~QUOT) == '/'
983 || (int_buf[i + 1] & ~QUOT) == '~')) {
990 /* set only match and destroy quotes */
992 for (i = 0; pos_buf[i] >= 0; i++) {
993 matchBuf[i] = matchBuf[pos_buf[i]];
997 /* old lenght matchBuf with quotes symbols */
998 *len_with_quotes = j ? j - pos_buf[0] : 0;
1000 return command_mode;
1004 static void input_tab(int *lastWasTab)
1006 /* Do TAB completion */
1007 static int num_matches;
1008 static char **matches;
1010 if (lastWasTab == 0) { /* free all memory */
1012 while (num_matches > 0)
1013 free(matches[--num_matches]);
1015 matches = (char **) NULL;
1019 if (*lastWasTab == FALSE) {
1023 char matchBuf[BUFSIZ];
1027 *lastWasTab = TRUE; /* flop trigger */
1029 /* Make a local copy of the string -- up
1030 * to the position of the cursor */
1031 tmp = strncpy(matchBuf, command_ps, cursor);
1034 find_type = find_match(matchBuf, &recalc_pos);
1036 /* Free up any memory already allocated */
1039 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
1040 /* If the word starts with `~' and there is no slash in the word,
1041 * then try completing this word as a username. */
1043 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1044 matches = username_tab_completion(matchBuf, &num_matches);
1046 /* Try to match any executable in our path and everything
1047 * in the current working directory that matches. */
1050 exe_n_cwd_tab_completion(matchBuf, &num_matches,
1053 /* Did we find exactly one match? */
1054 if (!matches || num_matches > 1) {
1059 return; /* not found */
1061 qsort(matches, num_matches, sizeof(char *), match_compare);
1063 /* find minimal match */
1064 tmp = xstrdup(matches[0]);
1065 for (tmp1 = tmp; *tmp1; tmp1++)
1066 for (len_found = 1; len_found < num_matches; len_found++)
1067 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1071 if (*tmp == 0) { /* have unique */
1075 } else { /* one match */
1077 /* for next completion current found */
1078 *lastWasTab = FALSE;
1081 len_found = strlen(tmp);
1082 /* have space to placed match? */
1083 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1085 /* before word for match */
1086 command_ps[cursor - recalc_pos] = 0;
1087 /* save tail line */
1088 strcpy(matchBuf, command_ps + cursor);
1090 strcat(command_ps, tmp);
1092 strcat(command_ps, matchBuf);
1093 /* back to begin word for match */
1094 input_backward(recalc_pos);
1096 recalc_pos = cursor + len_found;
1098 len = strlen(command_ps);
1099 /* write out the matched command */
1101 input_backward(cursor - recalc_pos);
1103 if (tmp != matches[0])
1106 /* Ok -- the last char was a TAB. Since they
1107 * just hit TAB again, print a list of all the
1108 * available choices... */
1109 if (matches && num_matches > 0) {
1111 int sav_cursor = cursor; /* change goto_new_line() */
1113 /* Go to the next line */
1115 for (i = 0, col = 0; i < num_matches; i++) {
1116 l = strlen(matches[i]);
1119 printf("%-14s ", matches[i]);
1126 col -= (col / cmdedit_termw) * cmdedit_termw;
1127 if (col > 60 && matches[i + 1] != NULL) {
1132 /* Go to the next line and rewrite */
1134 redraw(0, len - sav_cursor);
1138 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
1140 static void get_previous_history(struct history **hp, struct history *p)
1144 (*hp)->s = xstrdup(command_ps);
1148 static inline void get_next_history(struct history **hp)
1150 get_previous_history(hp, (*hp)->n);
1160 * This function is used to grab a character buffer
1161 * from the input file descriptor and allows you to
1162 * a string with full command editing (sortof like
1165 * The following standard commands are not implemented:
1166 * ESC-b -- Move back one word
1167 * ESC-f -- Move forward one word
1168 * ESC-d -- Delete back one word
1169 * ESC-h -- Delete forward one word
1170 * CTL-t -- Transpose two characters
1172 * Furthermore, the "vi" command editing keys are not implemented.
1176 extern void cmdedit_read_input(char *prompt, char command[BUFSIZ])
1179 int inputFd = fileno(stdin);
1182 int lastWasTab = FALSE;
1183 unsigned char c = 0;
1184 struct history *hp = his_end;
1186 /* prepare before init handlers */
1187 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1189 command_ps = command;
1191 if (new_settings.c_cc[VMIN] == 0) { /* first call */
1193 getTermSettings(inputFd, (void *) &initial_settings);
1194 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1196 new_settings.c_cc[VMIN] = 1;
1197 new_settings.c_cc[VTIME] = 0;
1198 /* Turn off CTRL-C, so we can trap it */
1199 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1200 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1201 /* Turn off echoing */
1202 new_settings.c_lflag &= ~(ECHO | ECHOCTL | ECHONL);
1207 setTermSettings(inputFd, (void *) &new_settings);
1208 handlers_sets |= SET_RESET_TERM;
1210 /* Now initialize things */
1212 /* Print out the command prompt */
1213 parse_prompt(prompt);
1217 fflush(stdout); /* buffered out to fast */
1219 if (read(inputFd, &c, 1) < 1)
1220 /* if we can't read input then exit */
1221 goto prepare_to_die;
1231 /* Control-a -- Beginning of line */
1232 input_backward(cursor);
1235 /* Control-b -- Move back one character */
1239 /* Control-c -- stop gathering input */
1241 /* Link into lash to reset context to 0 on ^C and such */
1244 /* Go to the next line */
1250 /* Control-d -- Delete one character, or exit
1251 * if the len=0 and no chars to delete */
1255 clean_up_and_die(0);
1261 /* Control-e -- End of line */
1265 /* Control-f -- Move forward one character */
1270 /* Control-h and DEL */
1274 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1275 input_tab(&lastWasTab);
1279 /* Control-n -- Get next command in history */
1280 if (hp && hp->n && hp->n->s) {
1281 get_next_history(&hp);
1288 /* Control-p -- Get previous command from history */
1290 get_previous_history(&hp, hp->p);
1297 /* Control-U -- Clear line before cursor */
1299 strcpy(command, command + cursor);
1300 redraw(cmdedit_y, len -= cursor);
1305 /* escape sequence follows */
1306 if (read(inputFd, &c, 1) < 1)
1308 /* different vt100 emulations */
1309 if (c == '[' || c == 'O') {
1310 if (read(inputFd, &c, 1) < 1)
1314 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1315 case '\t': /* Alt-Tab */
1317 input_tab(&lastWasTab);
1321 /* Up Arrow -- Get previous command from history */
1323 get_previous_history(&hp, hp->p);
1330 /* Down Arrow -- Get next command in history */
1331 if (hp && hp->n && hp->n->s) {
1332 get_next_history(&hp);
1339 /* Rewrite the line with the selected history item */
1341 /* change command */
1342 len = strlen(strcpy(command, hp->s));
1343 /* redraw and go to end line */
1344 redraw(cmdedit_y, 0);
1347 /* Right Arrow -- Move forward one character */
1351 /* Left Arrow -- Move back one character */
1361 input_backward(cursor);
1369 if (!(c >= '1' && c <= '9'))
1373 if (c >= '1' && c <= '9')
1375 if (read(inputFd, &c, 1) < 1)
1381 default: /* If it's regular input, do the normal thing */
1382 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1383 /* Control-V -- Add non-printable symbol */
1385 if (read(inputFd, &c, 1) < 1)
1393 if (!Isprint(c)) /* Skip non-printable characters */
1396 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1401 if (cursor == (len - 1)) { /* Append if at the end of the line */
1402 *(command + cursor) = c;
1403 *(command + cursor + 1) = 0;
1404 cmdedit_set_out_char(0);
1405 } else { /* Insert otherwise */
1408 memmove(command + sc + 1, command + sc, len - sc);
1409 *(command + sc) = c;
1411 /* rewrite from cursor */
1413 /* to prev x pos + 1 */
1414 input_backward(cursor - sc);
1419 if (break_out) /* Enter is the command terminator, no more input. */
1426 setTermSettings(inputFd, (void *) &initial_settings);
1427 handlers_sets &= ~SET_RESET_TERM;
1429 /* Handle command history log */
1430 if (len) { /* no put empty line */
1432 struct history *h = his_end;
1435 ss = xstrdup(command); /* duplicate */
1438 /* No previous history -- this memory is never freed */
1439 h = his_front = xmalloc(sizeof(struct history));
1440 h->n = xmalloc(sizeof(struct history));
1450 /* Add a new history command -- this memory is never freed */
1451 h->n = xmalloc(sizeof(struct history));
1459 /* After max history, remove the oldest command */
1460 if (history_counter >= MAX_HISTORY) {
1462 struct history *p = his_front->n;
1472 #if !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
1476 command[len++] = '\n'; /* set '\n' */
1478 #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_COMMAND_TAB_COMPLETION)
1479 input_tab(0); /* strong free */
1481 #if !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
1482 free(cmdedit_prompt);
1488 /* Undo the effects of cmdedit_init(). */
1489 extern void cmdedit_terminate(void)
1491 cmdedit_reset_term();
1493 if ((handlers_sets & SET_TERM_HANDLERS) != 0) {
1494 signal(SIGKILL, SIG_DFL);
1495 signal(SIGINT, SIG_DFL);
1496 signal(SIGQUIT, SIG_DFL);
1497 signal(SIGTERM, SIG_DFL);
1498 signal(SIGWINCH, SIG_DFL);
1499 handlers_sets &= ~SET_TERM_HANDLERS;
1504 #endif /* BB_FEATURE_COMMAND_EDITING */
1509 const char *applet_name = "debug stuff usage";
1510 const char *memory_exhausted = "Memory exhausted";
1512 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1516 unsigned int shell_context;
1518 int main(int argc, char **argv)
1522 #if !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
1523 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1524 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1525 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1530 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1531 setlocale(LC_ALL, "");
1536 cmdedit_read_input(prompt, buff);
1538 if(l > 0 && buff[l-1] == '\n')
1540 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1541 } while (shell_context);
1542 printf("*** cmdedit_read_input() detect ^C\n");