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)
44 //#define BB_FEATURE_NONPRINTABLE_INVERSE_PUT
45 //#define BB_FEATURE_BASH_STYLE_PROMT
53 #ifdef BB_FEATURE_SH_COMMAND_EDITING
55 #ifndef BB_FEATURE_SH_TAB_COMPLETION
56 #undef BB_FEATURE_SH_USERNAME_COMPLETION
59 #if defined(BB_FEATURE_SH_USERNAME_COMPLETION) || defined(BB_FEATURE_BASH_STYLE_PROMT)
60 #define BB_FEATURE_GETUSERNAME_AND_HOMEDIR
68 #include <sys/ioctl.h>
73 #ifdef BB_FEATURE_SH_TAB_COMPLETION
78 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
80 #include "pwd_grp/pwd.h"
84 #endif /* advanced FEATURES */
88 void *xrealloc(void *old, size_t size)
90 return realloc(old, size);
93 void *xmalloc(size_t size)
97 char *xstrdup(const char *s)
102 void *xcalloc(size_t size, size_t se)
104 return calloc(size, se);
107 #define error_msg(s, d) fprintf(stderr, s, d)
117 /* Maximum length of the linked list for the command line history */
118 static const int MAX_HISTORY = 15;
120 /* First element in command line list */
121 static struct history *his_front = NULL;
123 /* Last element in command line list */
124 static struct history *his_end = NULL;
127 /* ED: sparc termios is broken: revert back to old termio handling. */
131 # define termios termio
132 # define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
133 # define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
135 # include <termios.h>
136 # define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
137 # define getTermSettings(fd,argp) tcgetattr(fd, argp);
140 /* Current termio and the previous termio before starting sh */
141 static struct termios initial_settings, new_settings;
144 #ifndef _POSIX_VDISABLE
145 #define _POSIX_VDISABLE '\0'
150 volatile int cmdedit_termw = 80; /* actual terminal width */
151 static int history_counter = 0; /* Number of commands in history list */
153 volatile int handlers_sets = 0; /* Set next bites: */
156 SET_ATEXIT = 1, /* when atexit() has been called and
157 get euid,uid,gid to fast compare */
158 SET_TERM_HANDLERS = 2, /* set many terminates signal handlers */
159 SET_WCHG_HANDLERS = 4, /* winchg signal handler */
160 SET_RESET_TERM = 8, /* if the terminal needs to be reset upon exit */
164 static int cmdedit_x; /* real x terminal position */
165 static int cmdedit_y; /* pseudoreal y terminal position */
166 static int cmdedit_prmt_len; /* lenght prompt without colores string */
168 static int cursor; /* required global for signal handler */
169 static int len; /* --- "" - - "" - -"- --""-- --""--- */
170 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
172 #ifndef BB_FEATURE_BASH_STYLE_PROMT
175 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
177 /* Link into lash to reset context to 0 on ^C and such */
178 extern unsigned int shell_context;
181 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
182 static char *user_buf = "";
183 static char *home_pwd_buf = "";
187 #ifdef BB_FEATURE_BASH_STYLE_PROMT
188 static char *hostname_buf = "";
189 static int num_ok_lines = 1;
193 #ifdef BB_FEATURE_SH_TAB_COMPLETION
195 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
202 #endif /* BB_FEATURE_SH_TAB_COMPLETION */
205 static void cmdedit_setwidth(int w, int redraw_flg);
207 static void win_changed(int nsig)
209 struct winsize win = { 0, 0, 0, 0 };
210 static __sighandler_t previous_SIGWINCH_handler; /* for reset */
212 /* emulate || signal call */
213 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
214 ioctl(0, TIOCGWINSZ, &win);
215 if (win.ws_col > 0) {
216 cmdedit_setwidth(win.ws_col, nsig == SIGWINCH);
219 /* Unix not all standart in recall signal */
221 if (nsig == -SIGWINCH) /* save previous handler */
222 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
223 else if (nsig == SIGWINCH) /* signaled called handler */
224 signal(SIGWINCH, win_changed); /* set for next call */
226 /* set previous handler */
227 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
230 static void cmdedit_reset_term(void)
232 if ((handlers_sets & SET_RESET_TERM) != 0) {
233 /* sparc and other have broken termios support: use old termio handling. */
234 setTermSettings(fileno(stdin), (void *) &initial_settings);
235 handlers_sets &= ~SET_RESET_TERM;
237 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
238 /* reset SIGWINCH handler to previous (default) */
240 handlers_sets &= ~SET_WCHG_HANDLERS;
243 #ifdef BB_FEATURE_CLEAN_UP
247 //while(his_front!=his_end) {
248 while (his_front != his_end) {
259 /* special for recount position for scroll and remove terminal margin effect */
260 static void cmdedit_set_out_char(int next_char)
263 int c = command_ps[cursor];
266 c = ' '; /* destroy end char? */
267 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
268 if (!isprint(c)) { /* Inverse put non-printable characters */
269 if (((unsigned char) c) >= 128)
271 if (((unsigned char) c) < ' ')
275 printf("\033[7m%c\033[0m", c);
279 if (++cmdedit_x >= cmdedit_termw) {
280 /* terminal is scrolled down */
286 /* destroy "(auto)margin" */
293 /* Move to end line. Bonus: rewrite line from cursor */
294 static void input_end(void)
297 cmdedit_set_out_char(0);
300 /* Go to the next line */
301 static void goto_new_line(void)
309 static inline void out1str(const char *s)
313 static inline void beep(void)
318 /* Move back one charactor */
319 /* special for slow terminal */
320 static void input_backward(int num)
324 cursor -= num; /* new cursor (in command, not terminal) */
326 if (cmdedit_x >= num) { /* no to up line */
333 printf("\033[%dD", num);
338 putchar('\r'); /* back to first terminal pos. */
339 num -= cmdedit_x; /* set previous backward */
341 count_y = 1 + num / cmdedit_termw;
342 printf("\033[%dA", count_y);
343 cmdedit_y -= count_y;
344 /* require forward after uping */
345 cmdedit_x = cmdedit_termw * count_y - num;
346 printf("\033[%dC", cmdedit_x); /* set term cursor */
350 static void put_prompt(void)
352 out1str(cmdedit_prompt);
353 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
357 #ifdef BB_FEATURE_BASH_STYLE_PROMT
359 add_to_prompt(char **prmt_mem_ptr, int *alm, int *prmt_len,
362 int l = strlen(addb);
365 if (*alm < (*prmt_len) + 1) {
366 *alm = (*prmt_len) + 1;
367 *prmt_mem_ptr = xrealloc(*prmt_mem_ptr, *alm);
369 strcat(*prmt_mem_ptr, addb);
373 static void parse_prompt(const char *prmt_ptr)
375 #ifdef BB_FEATURE_BASH_STYLE_PROMT
376 int alm = strlen(prmt_ptr) + 1; /* supposedly require memory */
379 int flg_not_length = '[';
380 char *prmt_mem_ptr = xstrdup(prmt_ptr);
381 char pwd_buf[PATH_MAX + 1];
397 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, user_buf);
400 if (hostname_buf[0] == 0) {
401 hostname_buf = xcalloc(256, 1);
402 if (gethostname(hostname_buf, 255) < 0) {
403 strcpy(hostname_buf, "?");
405 char *s = strchr(hostname_buf, '.');
411 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len,
415 c = my_euid == 0 ? '#' : '$';
418 if (pwd_buf[0] == 0) {
421 getcwd(pwd_buf, PATH_MAX);
422 l = strlen(home_pwd_buf);
423 if (home_pwd_buf[0] != 0 &&
424 strncmp(home_pwd_buf, pwd_buf, l) == 0) {
425 strcpy(pwd_buf + 1, pwd_buf + l);
429 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, pwd_buf);
432 snprintf(buf, sizeof(buf), "%d", num_ok_lines);
433 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
436 case 'E': /* \e \E = \033 */
456 for (l = 0; l < 3;) {
458 buf[l++] = *prmt_ptr;
460 ho = strtol(buf, &eho, c == 'x' ? 16 : 8);
461 if (ho > UCHAR_MAX || (eho - buf) < l) {
468 ho = strtol(buf, 0, c == 'x' ? 16 : 8);
469 c = ho == 0 ? '?' : (char) ho;
474 if (c == flg_not_length) {
475 flg_not_length = flg_not_length == '[' ? ']' : '[';
483 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
484 if (flg_not_length == ']')
487 cmdedit_prmt_len = prmt_len - sub_len;
488 cmdedit_prompt = prmt_mem_ptr;
490 cmdedit_prompt = prmt_ptr;
491 cmdedit_prmt_len = strlen(prmt_ptr);
497 /* draw promt, editor line, and clear tail */
498 static void redraw(int y, int back_cursor)
500 if (y > 0) /* up to start y */
501 printf("\033[%dA", y);
502 cmdedit_y = 0; /* new quasireal y */
505 input_end(); /* rewrite */
506 printf("\033[J"); /* destroy tail after cursor */
507 input_backward(back_cursor);
510 /* Delete the char in front of the cursor */
511 static void input_delete(void)
518 strcpy(command_ps + j, command_ps + j + 1);
520 input_end(); /* rewtite new line */
521 cmdedit_set_out_char(0); /* destroy end char */
522 input_backward(cursor - j); /* back to old pos cursor */
525 /* Delete the char in back of the cursor */
526 static void input_backspace(void)
535 /* Move forward one charactor */
536 static void input_forward(void)
539 cmdedit_set_out_char(command_ps[cursor + 1]);
543 static void clean_up_and_die(int sig)
547 exit(EXIT_SUCCESS); /* cmdedit_reset_term() called in atexit */
548 cmdedit_reset_term();
551 static void cmdedit_setwidth(int w, int redraw_flg)
553 cmdedit_termw = cmdedit_prmt_len + 2;
554 if (w <= cmdedit_termw) {
555 cmdedit_termw = cmdedit_termw % w;
557 if (w > cmdedit_termw) {
562 /* new y for current cursor */
563 int new_y = (cursor + cmdedit_prmt_len) / w;
566 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
572 extern void cmdedit_init(void)
574 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
575 /* emulate usage handler to set handler and call yours work */
576 win_changed(-SIGWINCH);
577 handlers_sets |= SET_WCHG_HANDLERS;
580 if ((handlers_sets & SET_ATEXIT) == 0) {
581 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
582 struct passwd *entry;
585 entry = getpwuid(my_euid);
587 user_buf = xstrdup(entry->pw_name);
588 home_pwd_buf = xstrdup(entry->pw_dir);
592 #ifdef BB_FEATURE_SH_TAB_COMPLETION
594 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
599 #endif /* BB_FEATURE_SH_TAB_COMPLETION */
600 handlers_sets |= SET_ATEXIT;
601 atexit(cmdedit_reset_term); /* be sure to do this only once */
604 if ((handlers_sets & SET_TERM_HANDLERS) == 0) {
605 signal(SIGKILL, clean_up_and_die);
606 signal(SIGINT, clean_up_and_die);
607 signal(SIGQUIT, clean_up_and_die);
608 signal(SIGTERM, clean_up_and_die);
609 handlers_sets |= SET_TERM_HANDLERS;
614 #ifdef BB_FEATURE_SH_TAB_COMPLETION
616 static int is_execute(const struct stat *st)
618 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
619 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
620 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
621 (st->st_mode & S_IXOTH)) return TRUE;
625 #ifdef BB_FEATURE_SH_USERNAME_COMPLETION
627 static char **username_tab_completion(char *ud, int *num_matches)
629 struct passwd *entry;
634 ud++; /* ~user/... to user/... */
635 userlen = strlen(ud);
637 if (num_matches == 0) { /* "~/..." or "~user/..." */
638 char *sav_ud = ud - 1;
641 if (*ud == '/') { /* "~/..." */
645 temp = strchr(ud, '/');
646 *temp = 0; /* ~user\0 */
647 entry = getpwnam(ud);
648 *temp = '/'; /* restore ~user/... */
651 home = entry->pw_dir;
654 if ((userlen + strlen(home) + 1) < BUFSIZ) {
655 char temp2[BUFSIZ]; /* argument size */
658 sprintf(temp2, "%s%s", home, ud);
659 strcpy(sav_ud, temp2);
662 return 0; /* void, result save to argument :-) */
665 char **matches = (char **) NULL;
670 while ((entry = getpwent()) != NULL) {
671 /* Null usernames should result in all users as possible completions. */
672 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
674 temp = xmalloc(3 + strlen(entry->pw_name));
675 sprintf(temp, "~%s/", entry->pw_name);
676 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
678 matches[nm++] = temp;
687 #endif /* BB_FEATURE_SH_USERNAME_COMPLETION */
695 static int path_parse(char ***p, int flags)
701 /* if not setenv PATH variable, to search cur dir "." */
702 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
703 /* PATH=<empty> or PATH=:<empty> */
704 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
712 npth++; /* count words is + 1 count ':' */
713 tmp = strchr(tmp, ':');
716 break; /* :<empty> */
721 *p = xmalloc(npth * sizeof(char *));
724 (*p)[0] = xstrdup(tmp);
725 npth = 1; /* count words is + 1 count ':' */
728 tmp = strchr(tmp, ':');
730 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
732 break; /* :<empty> */
735 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
741 static char *add_quote_for_spec_chars(char *found)
744 char *s = xmalloc((strlen(found) + 1) * 2);
747 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
755 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
763 int nm = *num_matches;
766 char **paths = path1;
769 char found[BUFSIZ + 4 + PATH_MAX];
770 char *pfind = strrchr(command, '/');
775 /* no dir, if flags==EXE_ONLY - get paths, else "." */
776 npaths = path_parse(&paths, type);
780 /* save for change */
781 strcpy(dirbuf, command);
783 dirbuf[(pfind - command) + 1] = 0;
784 #ifdef BB_FEATURE_SH_USERNAME_COMPLETION
785 if (dirbuf[0] == '~') /* ~/... or ~user/... */
786 username_tab_completion(dirbuf, 0);
788 /* "strip" dirname in command */
792 npaths = 1; /* only 1 dir */
795 for (i = 0; i < npaths; i++) {
797 dir = opendir(paths[i]);
798 if (!dir) /* Don't print an error */
801 while ((next = readdir(dir)) != NULL) {
802 const char *str_merge = "%s/%s";
803 char *str_found = next->d_name;
806 if (strncmp(str_found, pfind, strlen(pfind)))
808 /* not see .name without .match */
809 if (*str_found == '.' && *pfind == 0) {
810 if (*paths[i] == '/' && paths[i][1] == 0
811 && str_found[1] == 0) str_found = ""; /* only "/" */
815 if (paths[i][strlen(paths[i]) - 1] == '/')
817 sprintf(found, str_merge, paths[i], str_found);
818 /* hmm, remover in progress? */
819 if (stat(found, &st) < 0)
821 /* find with dirs ? */
822 if (paths[i] != dirbuf)
823 strcpy(found, next->d_name); /* only name */
824 if (S_ISDIR(st.st_mode)) {
825 /* name is directory */
826 /* algorithmic only "/" ? */
829 str_found = add_quote_for_spec_chars(found);
831 /* not put found file if search only dirs for cd */
832 if (type == FIND_DIR_ONLY)
834 str_found = add_quote_for_spec_chars(found);
835 if (type == FIND_FILE_ONLY ||
836 (type == FIND_EXE_ONLY && is_execute(&st) == TRUE))
837 strcat(str_found, " ");
839 /* Add it to the list */
840 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
842 matches[nm++] = str_found;
846 if (paths != path1) {
847 free(paths[0]); /* allocated memory only in first member */
854 static int match_compare(const void *a, const void *b)
856 return strcmp(*(char **) a, *(char **) b);
861 #define QUOT (UCHAR_MAX+1)
863 #define collapse_pos(is, in) { \
864 memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \
865 memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); }
867 static int find_match(char *matchBuf, int *len_with_quotes)
872 int int_buf[BUFSIZ + 1];
873 int pos_buf[BUFSIZ + 1];
875 /* set to integer dimension characters and own positions */
877 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
878 if (int_buf[i] == 0) {
879 pos_buf[i] = -1; /* indicator end line */
885 /* mask \+symbol and convert '\t' to ' ' */
886 for (i = j = 0; matchBuf[i]; i++, j++)
887 if (matchBuf[i] == '\\') {
888 collapse_pos(j, j + 1);
891 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
892 if (matchBuf[i] == '\t') /* algorithm equivalent */
893 int_buf[j] = ' ' | QUOT;
896 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
897 else if (matchBuf[i] == '\t')
901 /* mask "symbols" or 'symbols' */
903 for (i = 0; int_buf[i]; i++) {
905 if (c == '\'' || c == '"') {
914 } else if (c2 != 0 && c != '$')
918 /* skip commands with arguments if line have commands delimiters */
919 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
920 for (i = 0; int_buf[i]; i++) {
923 j = i ? int_buf[i - 1] : -1;
925 if (c == ';' || c == '&' || c == '|') {
926 command_mode = 1 + (c == c2);
928 if (j == '>' || j == '<')
930 } else if (c == '|' && j == '>')
934 collapse_pos(0, i + command_mode);
935 i = -1; /* hack incremet */
938 /* collapse `command...` */
939 for (i = 0; int_buf[i]; i++)
940 if (int_buf[i] == '`') {
941 for (j = i + 1; int_buf[j]; j++)
942 if (int_buf[j] == '`') {
943 collapse_pos(i, j + 1);
948 /* not found close ` - command mode, collapse all previous */
949 collapse_pos(0, i + 1);
952 i--; /* hack incremet */
955 /* collapse (command...(command...)...) or {command...{command...}...} */
956 c = 0; /* "recursive" level */
958 for (i = 0; int_buf[i]; i++)
959 if (int_buf[i] == '(' || int_buf[i] == '{') {
960 if (int_buf[i] == '(')
964 collapse_pos(0, i + 1);
965 i = -1; /* hack incremet */
967 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
968 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
969 if (int_buf[i] == ')')
973 collapse_pos(0, i + 1);
974 i = -1; /* hack incremet */
977 /* skip first not quote space */
978 for (i = 0; int_buf[i]; i++)
979 if (int_buf[i] != ' ')
984 /* set find mode for completion */
985 command_mode = FIND_EXE_ONLY;
986 for (i = 0; int_buf[i]; i++)
987 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
988 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
989 && strncmp(&matchBuf[pos_buf[0]], "cd", 2) == 0)
990 command_mode = FIND_DIR_ONLY;
992 command_mode = FIND_FILE_ONLY;
997 for (i = 0; int_buf[i]; i++);
999 for (--i; i >= 0; i--) {
1001 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
1002 collapse_pos(0, i + 1);
1006 /* skip first not quoted '\'' or '"' */
1007 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
1008 /* collapse quote or unquote // or /~ */
1009 while ((int_buf[i] & ~QUOT) == '/' && (
1010 (int_buf[i + 1] & ~QUOT) == '/'
1011 || (int_buf[i + 1] & ~QUOT) ==
1016 /* set only match and destroy quotes */
1018 for (i = 0; pos_buf[i] >= 0; i++) {
1019 matchBuf[i] = matchBuf[pos_buf[i]];
1023 /* old lenght matchBuf with quotes symbols */
1024 *len_with_quotes = j ? j - pos_buf[0] : 0;
1026 return command_mode;
1030 static void input_tab(int *lastWasTab)
1032 /* Do TAB completion */
1033 static int num_matches;
1034 static char **matches;
1036 if (lastWasTab == 0) { /* free all memory */
1038 while (num_matches > 0)
1039 free(matches[--num_matches]);
1041 matches = (char **) NULL;
1045 if (*lastWasTab == FALSE) {
1049 char matchBuf[BUFSIZ];
1053 *lastWasTab = TRUE; /* flop trigger */
1055 /* Make a local copy of the string -- up
1056 * to the position of the cursor */
1057 tmp = strncpy(matchBuf, command_ps, cursor);
1060 find_type = find_match(matchBuf, &recalc_pos);
1062 /* Free up any memory already allocated */
1065 #ifdef BB_FEATURE_SH_USERNAME_COMPLETION
1066 /* If the word starts with `~' and there is no slash in the word,
1067 * then try completing this word as a username. */
1069 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1070 matches = username_tab_completion(matchBuf, &num_matches);
1072 /* Try to match any executable in our path and everything
1073 * in the current working directory that matches. */
1076 exe_n_cwd_tab_completion(matchBuf, &num_matches,
1079 /* Did we find exactly one match? */
1080 if (!matches || num_matches > 1) {
1085 return; /* not found */
1087 qsort(matches, num_matches, sizeof(char *), match_compare);
1089 /* find minimal match */
1090 tmp = xstrdup(matches[0]);
1091 for (tmp1 = tmp; *tmp1; tmp1++)
1092 for (len_found = 1; len_found < num_matches; len_found++)
1093 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1097 if (*tmp == 0) { /* have unique */
1101 } else { /* one match */
1103 /* for next completion current found */
1104 *lastWasTab = FALSE;
1107 len_found = strlen(tmp);
1108 /* have space to placed match? */
1109 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1111 /* before word for match */
1112 command_ps[cursor - recalc_pos] = 0;
1113 /* save tail line */
1114 strcpy(matchBuf, command_ps + cursor);
1116 strcat(command_ps, tmp);
1118 strcat(command_ps, matchBuf);
1119 /* back to begin word for match */
1120 input_backward(recalc_pos);
1122 recalc_pos = cursor + len_found;
1124 len = strlen(command_ps);
1125 /* write out the matched command */
1127 input_backward(cursor - recalc_pos);
1129 if (tmp != matches[0])
1132 /* Ok -- the last char was a TAB. Since they
1133 * just hit TAB again, print a list of all the
1134 * available choices... */
1135 if (matches && num_matches > 0) {
1137 int sav_cursor = cursor; /* change goto_new_line() */
1139 /* Go to the next line */
1141 for (i = 0, col = 0; i < num_matches; i++) {
1142 l = strlen(matches[i]);
1145 printf("%-14s ", matches[i]);
1152 col -= (col / cmdedit_termw) * cmdedit_termw;
1153 if (col > 60 && matches[i + 1] != NULL) {
1158 /* Go to the next line and rewrite */
1160 redraw(0, len - sav_cursor);
1164 #endif /* BB_FEATURE_SH_TAB_COMPLETION */
1166 static void get_previous_history(struct history **hp, struct history *p)
1170 (*hp)->s = xstrdup(command_ps);
1174 static inline void get_next_history(struct history **hp)
1176 get_previous_history(hp, (*hp)->n);
1186 * This function is used to grab a character buffer
1187 * from the input file descriptor and allows you to
1188 * a string with full command editing (sortof like
1191 * The following standard commands are not implemented:
1192 * ESC-b -- Move back one word
1193 * ESC-f -- Move forward one word
1194 * ESC-d -- Delete back one word
1195 * ESC-h -- Delete forward one word
1196 * CTL-t -- Transpose two characters
1198 * Furthermore, the "vi" command editing keys are not implemented.
1201 extern void cmdedit_read_input(char *prompt, char command[BUFSIZ])
1204 int inputFd = fileno(stdin);
1207 int lastWasTab = FALSE;
1209 struct history *hp = his_end;
1211 /* prepare before init handlers */
1212 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1214 command_ps = command;
1216 if (new_settings.c_cc[VMIN] == 0) { /* first call */
1218 getTermSettings(inputFd, (void *) &initial_settings);
1219 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1221 new_settings.c_cc[VMIN] = 1;
1222 new_settings.c_cc[VTIME] = 0;
1223 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
1224 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1225 new_settings.c_lflag &= ~(ECHO | ECHOCTL | ECHONL); /* Turn off echoing */
1230 setTermSettings(inputFd, (void *) &new_settings);
1231 handlers_sets |= SET_RESET_TERM;
1233 /* Print out the command prompt */
1234 parse_prompt(prompt);
1235 /* Now initialize things */
1240 fflush(stdout); /* buffered out to fast */
1242 if (read(inputFd, &c, 1) < 1)
1253 /* Control-a -- Beginning of line */
1254 input_backward(cursor);
1257 /* Control-b -- Move back one character */
1261 /* Control-c -- stop gathering input */
1263 /* Link into lash to reset context to 0 on ^C and such */
1266 /* Go to the next line */
1272 /* Control-d -- Delete one character, or exit
1273 * if the len=0 and no chars to delete */
1276 clean_up_and_die(0);
1282 /* Control-e -- End of line */
1286 /* Control-f -- Move forward one character */
1291 /* Control-h and DEL */
1295 #ifdef BB_FEATURE_SH_TAB_COMPLETION
1296 input_tab(&lastWasTab);
1300 /* Control-n -- Get next command in history */
1301 if (hp && hp->n && hp->n->s) {
1302 get_next_history(&hp);
1309 /* Control-p -- Get previous command from history */
1311 get_previous_history(&hp, hp->p);
1318 /* Control-U -- Clear line before cursor */
1320 strcpy(command, command + cursor);
1321 redraw(cmdedit_y, len -= cursor);
1326 /* escape sequence follows */
1327 if (read(inputFd, &c, 1) < 1)
1329 /* different vt100 emulations */
1330 if (c == '[' || c == 'O') {
1331 if (read(inputFd, &c, 1) < 1)
1335 #ifdef BB_FEATURE_SH_TAB_COMPLETION
1336 case '\t': /* Alt-Tab */
1338 input_tab(&lastWasTab);
1342 /* Up Arrow -- Get previous command from history */
1344 get_previous_history(&hp, hp->p);
1351 /* Down Arrow -- Get next command in history */
1352 if (hp && hp->n && hp->n->s) {
1353 get_next_history(&hp);
1360 /* Rewrite the line with the selected history item */
1362 /* change command */
1363 len = strlen(strcpy(command, hp->s));
1364 /* redraw and go to end line */
1365 redraw(cmdedit_y, 0);
1368 /* Right Arrow -- Move forward one character */
1372 /* Left Arrow -- Move back one character */
1382 input_backward(cursor);
1390 if (!(c >= '1' && c <= '9'))
1394 if (c >= '1' && c <= '9')
1396 if (read(inputFd, &c, 1) < 1)
1402 default: /* If it's regular input, do the normal thing */
1403 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1404 /* Control-V -- Add non-printable symbol */
1406 if (read(inputFd, &c, 1) < 1)
1414 if (!isprint(c)) /* Skip non-printable characters */
1417 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1422 if (cursor == (len - 1)) { /* Append if at the end of the line */
1423 *(command + cursor) = c;
1424 *(command + cursor + 1) = 0;
1425 cmdedit_set_out_char(0);
1426 } else { /* Insert otherwise */
1429 memmove(command + sc + 1, command + sc, len - sc);
1430 *(command + sc) = c;
1432 /* rewrite from cursor */
1434 /* to prev x pos + 1 */
1435 input_backward(cursor - sc);
1440 if (break_out) /* Enter is the command terminator, no more input. */
1447 setTermSettings(inputFd, (void *) &initial_settings);
1448 handlers_sets &= ~SET_RESET_TERM;
1450 /* Handle command history log */
1451 if (len) { /* no put empty line */
1453 struct history *h = his_end;
1456 ss = xstrdup(command); /* duplicate */
1459 /* No previous history -- this memory is never freed */
1460 h = his_front = xmalloc(sizeof(struct history));
1461 h->n = xmalloc(sizeof(struct history));
1471 /* Add a new history command -- this memory is never freed */
1472 h->n = xmalloc(sizeof(struct history));
1480 /* After max history, remove the oldest command */
1481 if (history_counter >= MAX_HISTORY) {
1483 struct history *p = his_front->n;
1493 #if defined(BB_FEATURE_BASH_STYLE_PROMT)
1497 command[len++] = '\n'; /* set '\n' */
1499 #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_SH_TAB_COMPLETION)
1500 input_tab(0); /* strong free */
1502 #if defined(BB_FEATURE_BASH_STYLE_PROMT)
1503 free(cmdedit_prompt);
1509 /* Undo the effects of cmdedit_init(). */
1510 extern void cmdedit_terminate(void)
1512 cmdedit_reset_term();
1513 if ((handlers_sets & SET_TERM_HANDLERS) != 0) {
1514 signal(SIGKILL, SIG_DFL);
1515 signal(SIGINT, SIG_DFL);
1516 signal(SIGQUIT, SIG_DFL);
1517 signal(SIGTERM, SIG_DFL);
1518 signal(SIGWINCH, SIG_DFL);
1519 handlers_sets &= ~SET_TERM_HANDLERS;
1523 #endif /* BB_FEATURE_SH_COMMAND_EDITING */
1528 unsigned int shell_context;
1530 int main(int argc, char **argv)
1534 #if defined(BB_FEATURE_BASH_STYLE_PROMT)
1535 "\\[\\033[32;1m\\]\\u@\\[\\033[33;1m\\]\\h:\
1536 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1537 \\!\\[\\033[36;1m\\]\\$ \\[\\033[0m\\]";
1544 cmdedit_read_input(prompt, buff);
1545 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1546 } while (shell_context);
1547 printf("*** cmdedit_read_input() detect ^C\n");