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_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;
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 #ifndef BB_FEATURE_SH_FANCY_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 #ifdef BB_FEATURE_SH_FANCY_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 #ifndef BB_FEATURE_SH_FANCY_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;
583 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
585 static int is_execute(const struct stat *st)
587 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
588 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
589 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
590 (st->st_mode & S_IXOTH)) return TRUE;
594 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
596 static char **username_tab_completion(char *ud, int *num_matches)
598 struct passwd *entry;
603 ud++; /* ~user/... to user/... */
604 userlen = strlen(ud);
606 if (num_matches == 0) { /* "~/..." or "~user/..." */
607 char *sav_ud = ud - 1;
610 if (*ud == '/') { /* "~/..." */
614 temp = strchr(ud, '/');
615 *temp = 0; /* ~user\0 */
616 entry = getpwnam(ud);
617 *temp = '/'; /* restore ~user/... */
620 home = entry->pw_dir;
623 if ((userlen + strlen(home) + 1) < BUFSIZ) {
624 char temp2[BUFSIZ]; /* argument size */
627 sprintf(temp2, "%s%s", home, ud);
628 strcpy(sav_ud, temp2);
631 return 0; /* void, result save to argument :-) */
634 char **matches = (char **) NULL;
639 while ((entry = getpwent()) != NULL) {
640 /* Null usernames should result in all users as possible completions. */
641 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
643 temp = xmalloc(3 + strlen(entry->pw_name));
644 sprintf(temp, "~%s/", entry->pw_name);
645 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
647 matches[nm++] = temp;
656 #endif /* BB_FEATURE_COMMAND_USERNAME_COMPLETION */
664 static int path_parse(char ***p, int flags)
670 /* if not setenv PATH variable, to search cur dir "." */
671 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
672 /* PATH=<empty> or PATH=:<empty> */
673 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
681 npth++; /* count words is + 1 count ':' */
682 tmp = strchr(tmp, ':');
685 break; /* :<empty> */
690 *p = xmalloc(npth * sizeof(char *));
693 (*p)[0] = xstrdup(tmp);
694 npth = 1; /* count words is + 1 count ':' */
697 tmp = strchr(tmp, ':');
699 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
701 break; /* :<empty> */
704 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
710 static char *add_quote_for_spec_chars(char *found)
713 char *s = xmalloc((strlen(found) + 1) * 2);
716 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
724 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
732 int nm = *num_matches;
735 char **paths = path1;
739 char *pfind = strrchr(command, '/');
744 /* no dir, if flags==EXE_ONLY - get paths, else "." */
745 npaths = path_parse(&paths, type);
749 /* save for change */
750 strcpy(dirbuf, command);
752 dirbuf[(pfind - command) + 1] = 0;
753 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
754 if (dirbuf[0] == '~') /* ~/... or ~user/... */
755 username_tab_completion(dirbuf, 0);
757 /* "strip" dirname in command */
761 npaths = 1; /* only 1 dir */
764 for (i = 0; i < npaths; i++) {
766 dir = opendir(paths[i]);
767 if (!dir) /* Don't print an error */
770 while ((next = readdir(dir)) != NULL) {
771 char *str_found = next->d_name;
774 if (strncmp(str_found, pfind, strlen(pfind)))
776 /* not see .name without .match */
777 if (*str_found == '.' && *pfind == 0) {
778 if (*paths[i] == '/' && paths[i][1] == 0
779 && str_found[1] == 0) str_found = ""; /* only "/" */
783 found = concat_path_file(paths[i], str_found);
784 /* hmm, remover in progress? */
785 if (stat(found, &st) < 0)
787 /* find with dirs ? */
788 if (paths[i] != dirbuf)
789 strcpy(found, next->d_name); /* only name */
790 if (S_ISDIR(st.st_mode)) {
791 /* name is directory */
793 found = concat_path_file(found, "");
795 str_found = add_quote_for_spec_chars(found);
797 /* not put found file if search only dirs for cd */
798 if (type == FIND_DIR_ONLY)
800 str_found = add_quote_for_spec_chars(found);
801 if (type == FIND_FILE_ONLY ||
802 (type == FIND_EXE_ONLY && is_execute(&st) == TRUE))
803 strcat(str_found, " ");
805 /* Add it to the list */
806 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
808 matches[nm++] = str_found;
814 if (paths != path1) {
815 free(paths[0]); /* allocated memory only in first member */
822 static int match_compare(const void *a, const void *b)
824 return strcmp(*(char **) a, *(char **) b);
829 #define QUOT (UCHAR_MAX+1)
831 #define collapse_pos(is, in) { \
832 memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \
833 memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); }
835 static int find_match(char *matchBuf, int *len_with_quotes)
840 int int_buf[BUFSIZ + 1];
841 int pos_buf[BUFSIZ + 1];
843 /* set to integer dimension characters and own positions */
845 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
846 if (int_buf[i] == 0) {
847 pos_buf[i] = -1; /* indicator end line */
853 /* mask \+symbol and convert '\t' to ' ' */
854 for (i = j = 0; matchBuf[i]; i++, j++)
855 if (matchBuf[i] == '\\') {
856 collapse_pos(j, j + 1);
859 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
860 if (matchBuf[i] == '\t') /* algorithm equivalent */
861 int_buf[j] = ' ' | QUOT;
864 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
865 else if (matchBuf[i] == '\t')
869 /* mask "symbols" or 'symbols' */
871 for (i = 0; int_buf[i]; i++) {
873 if (c == '\'' || c == '"') {
882 } else if (c2 != 0 && c != '$')
886 /* skip commands with arguments if line have commands delimiters */
887 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
888 for (i = 0; int_buf[i]; i++) {
891 j = i ? int_buf[i - 1] : -1;
893 if (c == ';' || c == '&' || c == '|') {
894 command_mode = 1 + (c == c2);
896 if (j == '>' || j == '<')
898 } else if (c == '|' && j == '>')
902 collapse_pos(0, i + command_mode);
903 i = -1; /* hack incremet */
906 /* collapse `command...` */
907 for (i = 0; int_buf[i]; i++)
908 if (int_buf[i] == '`') {
909 for (j = i + 1; int_buf[j]; j++)
910 if (int_buf[j] == '`') {
911 collapse_pos(i, j + 1);
916 /* not found close ` - command mode, collapse all previous */
917 collapse_pos(0, i + 1);
920 i--; /* hack incremet */
923 /* collapse (command...(command...)...) or {command...{command...}...} */
924 c = 0; /* "recursive" level */
926 for (i = 0; int_buf[i]; i++)
927 if (int_buf[i] == '(' || int_buf[i] == '{') {
928 if (int_buf[i] == '(')
932 collapse_pos(0, i + 1);
933 i = -1; /* hack incremet */
935 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
936 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
937 if (int_buf[i] == ')')
941 collapse_pos(0, i + 1);
942 i = -1; /* hack incremet */
945 /* skip first not quote space */
946 for (i = 0; int_buf[i]; i++)
947 if (int_buf[i] != ' ')
952 /* set find mode for completion */
953 command_mode = FIND_EXE_ONLY;
954 for (i = 0; int_buf[i]; i++)
955 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
956 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
957 && matchBuf[pos_buf[0]]=='c'
958 && matchBuf[pos_buf[1]]=='d' )
959 command_mode = FIND_DIR_ONLY;
961 command_mode = FIND_FILE_ONLY;
966 for (i = 0; int_buf[i]; i++);
968 for (--i; i >= 0; i--) {
970 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
971 collapse_pos(0, i + 1);
975 /* skip first not quoted '\'' or '"' */
976 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
977 /* collapse quote or unquote // or /~ */
978 while ((int_buf[i] & ~QUOT) == '/' &&
979 ((int_buf[i + 1] & ~QUOT) == '/'
980 || (int_buf[i + 1] & ~QUOT) == '~')) {
984 /* set only match and destroy quotes */
986 for (c = 0; pos_buf[i] >= 0; i++) {
987 matchBuf[c++] = matchBuf[pos_buf[i]];
991 /* old lenght matchBuf with quotes symbols */
992 *len_with_quotes = j ? j - pos_buf[0] : 0;
998 static void input_tab(int *lastWasTab)
1000 /* Do TAB completion */
1001 static int num_matches;
1002 static char **matches;
1004 if (lastWasTab == 0) { /* free all memory */
1006 while (num_matches > 0)
1007 free(matches[--num_matches]);
1009 matches = (char **) NULL;
1013 if (*lastWasTab == FALSE) {
1017 char matchBuf[BUFSIZ];
1021 *lastWasTab = TRUE; /* flop trigger */
1023 /* Make a local copy of the string -- up
1024 * to the position of the cursor */
1025 tmp = strncpy(matchBuf, command_ps, cursor);
1028 find_type = find_match(matchBuf, &recalc_pos);
1030 /* Free up any memory already allocated */
1033 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
1034 /* If the word starts with `~' and there is no slash in the word,
1035 * then try completing this word as a username. */
1037 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1038 matches = username_tab_completion(matchBuf, &num_matches);
1040 /* Try to match any executable in our path and everything
1041 * in the current working directory that matches. */
1044 exe_n_cwd_tab_completion(matchBuf, &num_matches,
1047 /* Did we find exactly one match? */
1048 if (!matches || num_matches > 1) {
1053 return; /* not found */
1055 qsort(matches, num_matches, sizeof(char *), match_compare);
1057 /* find minimal match */
1058 tmp = xstrdup(matches[0]);
1059 for (tmp1 = tmp; *tmp1; tmp1++)
1060 for (len_found = 1; len_found < num_matches; len_found++)
1061 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1065 if (*tmp == 0) { /* have unique */
1069 } else { /* one match */
1071 /* for next completion current found */
1072 *lastWasTab = FALSE;
1075 len_found = strlen(tmp);
1076 /* have space to placed match? */
1077 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1079 /* before word for match */
1080 command_ps[cursor - recalc_pos] = 0;
1081 /* save tail line */
1082 strcpy(matchBuf, command_ps + cursor);
1084 strcat(command_ps, tmp);
1086 strcat(command_ps, matchBuf);
1087 /* back to begin word for match */
1088 input_backward(recalc_pos);
1090 recalc_pos = cursor + len_found;
1092 len = strlen(command_ps);
1093 /* write out the matched command */
1094 redraw(cmdedit_y, len - recalc_pos);
1096 if (tmp != matches[0])
1099 /* Ok -- the last char was a TAB. Since they
1100 * just hit TAB again, print a list of all the
1101 * available choices... */
1102 if (matches && num_matches > 0) {
1104 int sav_cursor = cursor; /* change goto_new_line() */
1106 /* Go to the next line */
1108 for (i = 0, col = 0; i < num_matches; i++) {
1109 l = strlen(matches[i]);
1112 printf("%-14s ", matches[i]);
1119 col -= (col / cmdedit_termw) * cmdedit_termw;
1120 if (col > 60 && matches[i + 1] != NULL) {
1125 /* Go to the next line and rewrite */
1127 redraw(0, len - sav_cursor);
1131 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
1133 static void get_previous_history(struct history **hp, struct history *p)
1137 (*hp)->s = xstrdup(command_ps);
1141 static inline void get_next_history(struct history **hp)
1143 get_previous_history(hp, (*hp)->n);
1153 * This function is used to grab a character buffer
1154 * from the input file descriptor and allows you to
1155 * a string with full command editing (sortof like
1158 * The following standard commands are not implemented:
1159 * ESC-b -- Move back one word
1160 * ESC-f -- Move forward one word
1161 * ESC-d -- Delete back one word
1162 * ESC-h -- Delete forward one word
1163 * CTL-t -- Transpose two characters
1165 * Furthermore, the "vi" command editing keys are not implemented.
1169 extern void cmdedit_read_input(char *prompt, char command[BUFSIZ])
1172 int inputFd = fileno(stdin);
1175 int lastWasTab = FALSE;
1176 unsigned char c = 0;
1177 struct history *hp = his_end;
1179 /* prepare before init handlers */
1180 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1182 command_ps = command;
1184 if (new_settings.c_cc[VMIN] == 0) { /* first call */
1186 getTermSettings(inputFd, (void *) &initial_settings);
1187 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1189 new_settings.c_cc[VMIN] = 1;
1190 new_settings.c_cc[VTIME] = 0;
1191 /* Turn off CTRL-C, so we can trap it */
1192 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1193 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1194 /* Turn off echoing */
1195 new_settings.c_lflag &= ~(ECHO | ECHOCTL | ECHONL);
1200 setTermSettings(inputFd, (void *) &new_settings);
1201 handlers_sets |= SET_RESET_TERM;
1203 /* Now initialize things */
1205 /* Print out the command prompt */
1206 parse_prompt(prompt);
1210 fflush(stdout); /* buffered out to fast */
1212 if (read(inputFd, &c, 1) < 1)
1213 /* if we can't read input then exit */
1214 goto prepare_to_die;
1224 /* Control-a -- Beginning of line */
1225 input_backward(cursor);
1228 /* Control-b -- Move back one character */
1232 /* Control-c -- stop gathering input */
1234 /* Link into lash to reset context to 0 on ^C and such */
1237 /* Go to the next line */
1243 /* Control-d -- Delete one character, or exit
1244 * if the len=0 and no chars to delete */
1248 clean_up_and_die(0);
1254 /* Control-e -- End of line */
1258 /* Control-f -- Move forward one character */
1263 /* Control-h and DEL */
1267 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1268 input_tab(&lastWasTab);
1272 /* Control-n -- Get next command in history */
1273 if (hp && hp->n && hp->n->s) {
1274 get_next_history(&hp);
1281 /* Control-p -- Get previous command from history */
1283 get_previous_history(&hp, hp->p);
1290 /* Control-U -- Clear line before cursor */
1292 strcpy(command, command + cursor);
1293 redraw(cmdedit_y, len -= cursor);
1298 /* escape sequence follows */
1299 if (read(inputFd, &c, 1) < 1)
1301 /* different vt100 emulations */
1302 if (c == '[' || c == 'O') {
1303 if (read(inputFd, &c, 1) < 1)
1307 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1308 case '\t': /* Alt-Tab */
1310 input_tab(&lastWasTab);
1314 /* Up Arrow -- Get previous command from history */
1316 get_previous_history(&hp, hp->p);
1323 /* Down Arrow -- Get next command in history */
1324 if (hp && hp->n && hp->n->s) {
1325 get_next_history(&hp);
1332 /* Rewrite the line with the selected history item */
1334 /* change command */
1335 len = strlen(strcpy(command, hp->s));
1336 /* redraw and go to end line */
1337 redraw(cmdedit_y, 0);
1340 /* Right Arrow -- Move forward one character */
1344 /* Left Arrow -- Move back one character */
1354 input_backward(cursor);
1362 if (!(c >= '1' && c <= '9'))
1366 if (c >= '1' && c <= '9')
1368 if (read(inputFd, &c, 1) < 1)
1374 default: /* If it's regular input, do the normal thing */
1375 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1376 /* Control-V -- Add non-printable symbol */
1378 if (read(inputFd, &c, 1) < 1)
1386 if (!Isprint(c)) /* Skip non-printable characters */
1389 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1394 if (cursor == (len - 1)) { /* Append if at the end of the line */
1395 *(command + cursor) = c;
1396 *(command + cursor + 1) = 0;
1397 cmdedit_set_out_char(0);
1398 } else { /* Insert otherwise */
1401 memmove(command + sc + 1, command + sc, len - sc);
1402 *(command + sc) = c;
1404 /* rewrite from cursor */
1406 /* to prev x pos + 1 */
1407 input_backward(cursor - sc);
1412 if (break_out) /* Enter is the command terminator, no more input. */
1419 setTermSettings(inputFd, (void *) &initial_settings);
1420 handlers_sets &= ~SET_RESET_TERM;
1422 /* Handle command history log */
1423 if (len) { /* no put empty line */
1425 struct history *h = his_end;
1428 ss = xstrdup(command); /* duplicate */
1431 /* No previous history -- this memory is never freed */
1432 h = his_front = xmalloc(sizeof(struct history));
1433 h->n = xmalloc(sizeof(struct history));
1443 /* Add a new history command -- this memory is never freed */
1444 h->n = xmalloc(sizeof(struct history));
1452 /* After max history, remove the oldest command */
1453 if (history_counter >= MAX_HISTORY) {
1455 struct history *p = his_front->n;
1465 #if defined(BB_FEATURE_SH_FANCY_PROMPT)
1469 command[len++] = '\n'; /* set '\n' */
1471 #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_COMMAND_TAB_COMPLETION)
1472 input_tab(0); /* strong free */
1474 #if defined(BB_FEATURE_SH_FANCY_PROMPT)
1475 free(cmdedit_prompt);
1481 /* Undo the effects of cmdedit_init(). */
1482 extern void cmdedit_terminate(void)
1484 cmdedit_reset_term();
1485 if ((handlers_sets & SET_TERM_HANDLERS) != 0) {
1486 signal(SIGKILL, SIG_DFL);
1487 signal(SIGINT, SIG_DFL);
1488 signal(SIGQUIT, SIG_DFL);
1489 signal(SIGTERM, SIG_DFL);
1490 signal(SIGWINCH, SIG_DFL);
1491 handlers_sets &= ~SET_TERM_HANDLERS;
1495 #endif /* BB_FEATURE_COMMAND_EDITING */
1500 const char *applet_name = "debug stuff usage";
1501 const char *memory_exhausted = "Memory exhausted";
1503 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1507 unsigned int shell_context;
1509 int main(int argc, char **argv)
1513 #if defined(BB_FEATURE_SH_FANCY_PROMPT)
1514 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1515 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1516 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1521 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1522 setlocale(LC_ALL, "");
1527 cmdedit_read_input(prompt, buff);
1529 if(l > 0 && buff[l-1] == '\n')
1531 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1532 } while (shell_context);
1533 printf("*** cmdedit_read_input() detect ^C\n");