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_SH_COMMAND_EDITING
45 #define BB_FEATURE_SH_TAB_COMPLETION
46 #define BB_FEATURE_USERNAME_COMPLETION
47 #define BB_FEATURE_NONPRINTABLE_INVERSE_PUT
48 #define BB_FEATURE_BASH_STYLE_PROMT
49 #define BB_FEATURE_CLEAN_UP
57 #ifdef BB_FEATURE_SH_COMMAND_EDITING
59 #ifndef BB_FEATURE_SH_TAB_COMPLETION
60 #undef BB_FEATURE_USERNAME_COMPLETION
63 #if defined(BB_FEATURE_USERNAME_COMPLETION) || defined(BB_FEATURE_BASH_STYLE_PROMT)
64 #define BB_FEATURE_GETUSERNAME_AND_HOMEDIR
72 #include <sys/ioctl.h>
77 #ifdef BB_FEATURE_SH_TAB_COMPLETION
82 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
84 #include "pwd_grp/pwd.h"
88 #endif /* advanced FEATURES */
92 void *xrealloc(void *old, size_t size)
94 return realloc(old, size);
97 void *xmalloc(size_t size)
101 char *xstrdup(const char *s)
106 void *xcalloc(size_t size, size_t se)
108 return calloc(size, se);
111 #define error_msg(s, d) fprintf(stderr, s, d)
121 /* Maximum length of the linked list for the command line history */
122 static const int MAX_HISTORY = 15;
124 /* First element in command line list */
125 static struct history *his_front = NULL;
127 /* Last element in command line list */
128 static struct history *his_end = NULL;
131 /* ED: sparc termios is broken: revert back to old termio handling. */
135 # define termios termio
136 # define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
137 # define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
139 # include <termios.h>
140 # define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
141 # define getTermSettings(fd,argp) tcgetattr(fd, argp);
144 /* Current termio and the previous termio before starting sh */
145 static struct termios initial_settings, new_settings;
148 #ifndef _POSIX_VDISABLE
149 #define _POSIX_VDISABLE '\0'
154 volatile int cmdedit_termw = 80; /* actual terminal width */
155 static int history_counter = 0; /* Number of commands in history list */
157 volatile int handlers_sets = 0; /* Set next bites: */
160 SET_ATEXIT = 1, /* when atexit() has been called and
161 get euid,uid,gid to fast compare */
162 SET_TERM_HANDLERS = 2, /* set many terminates signal handlers */
163 SET_WCHG_HANDLERS = 4, /* winchg signal handler */
164 SET_RESET_TERM = 8, /* if the terminal needs to be reset upon exit */
168 static int cmdedit_x; /* real x terminal position */
169 static int cmdedit_y; /* pseudoreal y terminal position */
170 static int cmdedit_prmt_len; /* lenght prompt without colores string */
172 static int cursor; /* required global for signal handler */
173 static int len; /* --- "" - - "" - -"- --""-- --""--- */
174 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
176 #ifndef BB_FEATURE_BASH_STYLE_PROMT
179 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
181 /* Link into lash to reset context to 0 on ^C and such */
182 extern unsigned int shell_context;
185 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
186 static char *user_buf = "";
187 static char *home_pwd_buf = "";
191 #ifdef BB_FEATURE_BASH_STYLE_PROMT
192 static char *hostname_buf = "";
193 static int num_ok_lines = 1;
197 #ifdef BB_FEATURE_SH_TAB_COMPLETION
199 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
206 #endif /* BB_FEATURE_SH_TAB_COMPLETION */
209 static void cmdedit_setwidth(int w, int redraw_flg);
211 static void win_changed(int nsig)
213 struct winsize win = { 0, 0, 0, 0 };
214 static __sighandler_t previous_SIGWINCH_handler; /* for reset */
216 /* emulate || signal call */
217 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
218 ioctl(0, TIOCGWINSZ, &win);
219 if (win.ws_col > 0) {
220 cmdedit_setwidth(win.ws_col, nsig == SIGWINCH);
223 /* Unix not all standart in recall signal */
225 if (nsig == -SIGWINCH) /* save previous handler */
226 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
227 else if (nsig == SIGWINCH) /* signaled called handler */
228 signal(SIGWINCH, win_changed); /* set for next call */
230 /* set previous handler */
231 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
234 static void cmdedit_reset_term(void)
236 if ((handlers_sets & SET_RESET_TERM) != 0) {
237 /* sparc and other have broken termios support: use old termio handling. */
238 setTermSettings(fileno(stdin), (void *) &initial_settings);
239 handlers_sets &= ~SET_RESET_TERM;
241 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
242 /* reset SIGWINCH handler to previous (default) */
244 handlers_sets &= ~SET_WCHG_HANDLERS;
247 #ifdef BB_FEATURE_CLEAN_UP
251 //while(his_front!=his_end) {
252 while (his_front != his_end) {
263 /* special for recount position for scroll and remove terminal margin effect */
264 static void cmdedit_set_out_char(int next_char)
267 int c = command_ps[cursor];
270 c = ' '; /* destroy end char? */
271 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
272 if (!isprint(c)) { /* Inverse put non-printable characters */
273 if (((unsigned char) c) >= 128)
275 if (((unsigned char) c) < ' ')
279 printf("\033[7m%c\033[0m", c);
283 if (++cmdedit_x >= cmdedit_termw) {
284 /* terminal is scrolled down */
290 /* destroy "(auto)margin" */
297 /* Move to end line. Bonus: rewrite line from cursor */
298 static void input_end(void)
301 cmdedit_set_out_char(0);
304 /* Go to the next line */
305 static void goto_new_line(void)
313 static inline void out1str(const char *s)
317 static inline void beep(void)
322 /* Move back one charactor */
323 /* special for slow terminal */
324 static void input_backward(int num)
328 cursor -= num; /* new cursor (in command, not terminal) */
330 if (cmdedit_x >= num) { /* no to up line */
337 printf("\033[%dD", num);
342 putchar('\r'); /* back to first terminal pos. */
343 num -= cmdedit_x; /* set previous backward */
345 count_y = 1 + num / cmdedit_termw;
346 printf("\033[%dA", count_y);
347 cmdedit_y -= count_y;
348 /* require forward after uping */
349 cmdedit_x = cmdedit_termw * count_y - num;
350 printf("\033[%dC", cmdedit_x); /* set term cursor */
354 static void put_prompt(void)
356 out1str(cmdedit_prompt);
357 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
361 #ifdef BB_FEATURE_BASH_STYLE_PROMT
363 add_to_prompt(char **prmt_mem_ptr, int *alm, int *prmt_len,
366 int l = strlen(addb);
369 if (*alm < (*prmt_len) + 1) {
370 *alm = (*prmt_len) + 1;
371 *prmt_mem_ptr = xrealloc(*prmt_mem_ptr, *alm);
373 strcat(*prmt_mem_ptr, addb);
377 static void parse_prompt(const char *prmt_ptr)
379 #ifdef BB_FEATURE_BASH_STYLE_PROMT
380 int alm = strlen(prmt_ptr) + 1; /* supposedly require memory */
383 int flg_not_length = '[';
384 char *prmt_mem_ptr = xstrdup(prmt_ptr);
385 char pwd_buf[PATH_MAX + 1];
401 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, user_buf);
404 if (hostname_buf[0] == 0) {
405 hostname_buf = xcalloc(256, 1);
406 if (gethostname(hostname_buf, 255) < 0) {
407 strcpy(hostname_buf, "?");
409 char *s = strchr(hostname_buf, '.');
415 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len,
419 c = my_euid == 0 ? '#' : '$';
422 if (pwd_buf[0] == 0) {
425 getcwd(pwd_buf, PATH_MAX);
426 l = strlen(home_pwd_buf);
427 if (home_pwd_buf[0] != 0 &&
428 strncmp(home_pwd_buf, pwd_buf, l) == 0) {
429 strcpy(pwd_buf + 1, pwd_buf + l);
433 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, pwd_buf);
436 snprintf(buf, sizeof(buf), "%d", num_ok_lines);
437 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
440 case 'E': /* \e \E = \033 */
460 for (l = 0; l < 3;) {
462 buf[l++] = *prmt_ptr;
464 ho = strtol(buf, &eho, c == 'x' ? 16 : 8);
465 if (ho > UCHAR_MAX || (eho - buf) < l) {
472 ho = strtol(buf, 0, c == 'x' ? 16 : 8);
473 c = ho == 0 ? '?' : (char) ho;
478 if (c == flg_not_length) {
479 flg_not_length = flg_not_length == '[' ? ']' : '[';
487 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
488 if (flg_not_length == ']')
491 cmdedit_prmt_len = prmt_len - sub_len;
492 cmdedit_prompt = prmt_mem_ptr;
494 cmdedit_prompt = prmt_ptr;
495 cmdedit_prmt_len = strlen(prmt_ptr);
501 /* draw promt, editor line, and clear tail */
502 static void redraw(int y, int back_cursor)
504 if (y > 0) /* up to start y */
505 printf("\033[%dA", y);
506 cmdedit_y = 0; /* new quasireal y */
509 input_end(); /* rewrite */
510 printf("\033[J"); /* destroy tail after cursor */
511 input_backward(back_cursor);
514 /* Delete the char in front of the cursor */
515 static void input_delete(void)
522 strcpy(command_ps + j, command_ps + j + 1);
524 input_end(); /* rewtite new line */
525 cmdedit_set_out_char(0); /* destroy end char */
526 input_backward(cursor - j); /* back to old pos cursor */
529 /* Delete the char in back of the cursor */
530 static void input_backspace(void)
539 /* Move forward one charactor */
540 static void input_forward(void)
543 cmdedit_set_out_char(command_ps[cursor + 1]);
547 static void clean_up_and_die(int sig)
551 exit(EXIT_SUCCESS); /* cmdedit_reset_term() called in atexit */
552 cmdedit_reset_term();
555 static void cmdedit_setwidth(int w, int redraw_flg)
557 cmdedit_termw = cmdedit_prmt_len + 2;
558 if (w <= cmdedit_termw) {
559 cmdedit_termw = cmdedit_termw % w;
561 if (w > cmdedit_termw) {
566 /* new y for current cursor */
567 int new_y = (cursor + cmdedit_prmt_len) / w;
570 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
576 extern void cmdedit_init(void)
578 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
579 /* emulate usage handler to set handler and call yours work */
580 win_changed(-SIGWINCH);
581 handlers_sets |= SET_WCHG_HANDLERS;
584 if ((handlers_sets & SET_ATEXIT) == 0) {
585 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
586 struct passwd *entry;
589 entry = getpwuid(my_euid);
591 user_buf = xstrdup(entry->pw_name);
592 home_pwd_buf = xstrdup(entry->pw_dir);
596 #ifdef BB_FEATURE_SH_TAB_COMPLETION
598 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
603 #endif /* BB_FEATURE_SH_TAB_COMPLETION */
604 handlers_sets |= SET_ATEXIT;
605 atexit(cmdedit_reset_term); /* be sure to do this only once */
608 if ((handlers_sets & SET_TERM_HANDLERS) == 0) {
609 signal(SIGKILL, clean_up_and_die);
610 signal(SIGINT, clean_up_and_die);
611 signal(SIGQUIT, clean_up_and_die);
612 signal(SIGTERM, clean_up_and_die);
613 handlers_sets |= SET_TERM_HANDLERS;
618 #ifdef BB_FEATURE_SH_TAB_COMPLETION
620 static int is_execute(const struct stat *st)
622 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
623 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
624 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
625 (st->st_mode & S_IXOTH)) return TRUE;
629 #ifdef BB_FEATURE_USERNAME_COMPLETION
631 static char **username_tab_completion(char *ud, int *num_matches)
633 struct passwd *entry;
638 ud++; /* ~user/... to user/... */
639 userlen = strlen(ud);
641 if (num_matches == 0) { /* "~/..." or "~user/..." */
642 char *sav_ud = ud - 1;
645 if (*ud == '/') { /* "~/..." */
649 temp = strchr(ud, '/');
650 *temp = 0; /* ~user\0 */
651 entry = getpwnam(ud);
652 *temp = '/'; /* restore ~user/... */
655 home = entry->pw_dir;
658 if ((userlen + strlen(home) + 1) < BUFSIZ) {
659 char temp2[BUFSIZ]; /* argument size */
662 sprintf(temp2, "%s%s", home, ud);
663 strcpy(sav_ud, temp2);
666 return 0; /* void, result save to argument :-) */
669 char **matches = (char **) NULL;
674 while ((entry = getpwent()) != NULL) {
675 /* Null usernames should result in all users as possible completions. */
676 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
678 temp = xmalloc(3 + strlen(entry->pw_name));
679 sprintf(temp, "~%s/", entry->pw_name);
680 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
682 matches[nm++] = temp;
691 #endif /* BB_FEATURE_USERNAME_COMPLETION */
699 static int path_parse(char ***p, int flags)
705 /* if not setenv PATH variable, to search cur dir "." */
706 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
707 /* PATH=<empty> or PATH=:<empty> */
708 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
716 npth++; /* count words is + 1 count ':' */
717 tmp = strchr(tmp, ':');
720 break; /* :<empty> */
725 *p = xmalloc(npth * sizeof(char *));
728 (*p)[0] = xstrdup(tmp);
729 npth = 1; /* count words is + 1 count ':' */
732 tmp = strchr(tmp, ':');
734 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
736 break; /* :<empty> */
739 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
745 static char *add_quote_for_spec_chars(char *found)
748 char *s = xmalloc((strlen(found) + 1) * 2);
751 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
759 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
767 int nm = *num_matches;
770 char **paths = path1;
773 char found[BUFSIZ + 4 + PATH_MAX];
774 char *pfind = strrchr(command, '/');
779 /* no dir, if flags==EXE_ONLY - get paths, else "." */
780 npaths = path_parse(&paths, type);
784 /* save for change */
785 strcpy(dirbuf, command);
787 dirbuf[(pfind - command) + 1] = 0;
788 #ifdef BB_FEATURE_USERNAME_COMPLETION
789 if (dirbuf[0] == '~') /* ~/... or ~user/... */
790 username_tab_completion(dirbuf, 0);
792 /* "strip" dirname in command */
796 npaths = 1; /* only 1 dir */
799 for (i = 0; i < npaths; i++) {
801 dir = opendir(paths[i]);
802 if (!dir) /* Don't print an error */
805 while ((next = readdir(dir)) != NULL) {
806 const char *str_merge = "%s/%s";
807 char *str_found = next->d_name;
810 if (strncmp(str_found, pfind, strlen(pfind)))
812 /* not see .name without .match */
813 if (*str_found == '.' && *pfind == 0) {
814 if (*paths[i] == '/' && paths[i][1] == 0
815 && str_found[1] == 0) str_found = ""; /* only "/" */
819 if (paths[i][strlen(paths[i]) - 1] == '/')
821 sprintf(found, str_merge, paths[i], str_found);
822 /* hmm, remover in progress? */
823 if (stat(found, &st) < 0)
825 /* find with dirs ? */
826 if (paths[i] != dirbuf)
827 strcpy(found, next->d_name); /* only name */
828 if (S_ISDIR(st.st_mode)) {
829 /* name is directory */
830 /* algorithmic only "/" ? */
833 str_found = add_quote_for_spec_chars(found);
835 /* not put found file if search only dirs for cd */
836 if (type == FIND_DIR_ONLY)
838 str_found = add_quote_for_spec_chars(found);
839 if (type == FIND_FILE_ONLY ||
840 (type == FIND_EXE_ONLY && is_execute(&st) == TRUE))
841 strcat(str_found, " ");
843 /* Add it to the list */
844 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
846 matches[nm++] = str_found;
850 if (paths != path1) {
851 free(paths[0]); /* allocated memory only in first member */
858 static int match_compare(const void *a, const void *b)
860 return strcmp(*(char **) a, *(char **) b);
865 #define QUOT (UCHAR_MAX+1)
867 #define collapse_pos(is, in) { \
868 memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \
869 memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); }
871 static int find_match(char *matchBuf, int *len_with_quotes)
876 int int_buf[BUFSIZ + 1];
877 int pos_buf[BUFSIZ + 1];
879 /* set to integer dimension characters and own positions */
881 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
882 if (int_buf[i] == 0) {
883 pos_buf[i] = -1; /* indicator end line */
889 /* mask \+symbol and convert '\t' to ' ' */
890 for (i = j = 0; matchBuf[i]; i++, j++)
891 if (matchBuf[i] == '\\') {
892 collapse_pos(j, j + 1);
895 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
896 if (matchBuf[i] == '\t') /* algorithm equivalent */
897 int_buf[j] = ' ' | QUOT;
900 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
901 else if (matchBuf[i] == '\t')
905 /* mask "symbols" or 'symbols' */
907 for (i = 0; int_buf[i]; i++) {
909 if (c == '\'' || c == '"') {
918 } else if (c2 != 0 && c != '$')
922 /* skip commands with arguments if line have commands delimiters */
923 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
924 for (i = 0; int_buf[i]; i++) {
927 j = i ? int_buf[i - 1] : -1;
929 if (c == ';' || c == '&' || c == '|') {
930 command_mode = 1 + (c == c2);
932 if (j == '>' || j == '<')
934 } else if (c == '|' && j == '>')
938 collapse_pos(0, i + command_mode);
939 i = -1; /* hack incremet */
942 /* collapse `command...` */
943 for (i = 0; int_buf[i]; i++)
944 if (int_buf[i] == '`') {
945 for (j = i + 1; int_buf[j]; j++)
946 if (int_buf[j] == '`') {
947 collapse_pos(i, j + 1);
952 /* not found close ` - command mode, collapse all previous */
953 collapse_pos(0, i + 1);
956 i--; /* hack incremet */
959 /* collapse (command...(command...)...) or {command...{command...}...} */
960 c = 0; /* "recursive" level */
962 for (i = 0; int_buf[i]; i++)
963 if (int_buf[i] == '(' || int_buf[i] == '{') {
964 if (int_buf[i] == '(')
968 collapse_pos(0, i + 1);
969 i = -1; /* hack incremet */
971 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
972 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
973 if (int_buf[i] == ')')
977 collapse_pos(0, i + 1);
978 i = -1; /* hack incremet */
981 /* skip first not quote space */
982 for (i = 0; int_buf[i]; i++)
983 if (int_buf[i] != ' ')
988 /* set find mode for completion */
989 command_mode = FIND_EXE_ONLY;
990 for (i = 0; int_buf[i]; i++)
991 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
992 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
993 && strncmp(&matchBuf[pos_buf[0]], "cd", 2) == 0)
994 command_mode = FIND_DIR_ONLY;
996 command_mode = FIND_FILE_ONLY;
1001 for (i = 0; int_buf[i]; i++);
1002 /* find last word */
1003 for (--i; i >= 0; i--) {
1005 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
1006 collapse_pos(0, i + 1);
1010 /* skip first not quoted '\'' or '"' */
1011 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
1012 /* collapse quote or unquote // or /~ */
1013 while ((int_buf[i] & ~QUOT) == '/' && (
1014 (int_buf[i + 1] & ~QUOT) == '/'
1015 || (int_buf[i + 1] & ~QUOT) ==
1020 /* set only match and destroy quotes */
1022 for (i = 0; pos_buf[i] >= 0; i++) {
1023 matchBuf[i] = matchBuf[pos_buf[i]];
1027 /* old lenght matchBuf with quotes symbols */
1028 *len_with_quotes = j ? j - pos_buf[0] : 0;
1030 return command_mode;
1034 static void input_tab(int *lastWasTab)
1036 /* Do TAB completion */
1037 static int num_matches;
1038 static char **matches;
1040 if (lastWasTab == 0) { /* free all memory */
1042 while (num_matches > 0)
1043 free(matches[--num_matches]);
1045 matches = (char **) NULL;
1049 if (*lastWasTab == FALSE) {
1053 char matchBuf[BUFSIZ];
1057 *lastWasTab = TRUE; /* flop trigger */
1059 /* Make a local copy of the string -- up
1060 * to the position of the cursor */
1061 tmp = strncpy(matchBuf, command_ps, cursor);
1064 find_type = find_match(matchBuf, &recalc_pos);
1066 /* Free up any memory already allocated */
1069 #ifdef BB_FEATURE_USERNAME_COMPLETION
1070 /* If the word starts with `~' and there is no slash in the word,
1071 * then try completing this word as a username. */
1073 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1074 matches = username_tab_completion(matchBuf, &num_matches);
1076 /* Try to match any executable in our path and everything
1077 * in the current working directory that matches. */
1080 exe_n_cwd_tab_completion(matchBuf, &num_matches,
1083 /* Did we find exactly one match? */
1084 if (!matches || num_matches > 1) {
1089 return; /* not found */
1091 qsort(matches, num_matches, sizeof(char *), match_compare);
1093 /* find minimal match */
1094 tmp = xstrdup(matches[0]);
1095 for (tmp1 = tmp; *tmp1; tmp1++)
1096 for (len_found = 1; len_found < num_matches; len_found++)
1097 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1101 if (*tmp == 0) { /* have unique */
1105 } else { /* one match */
1107 /* for next completion current found */
1108 *lastWasTab = FALSE;
1111 len_found = strlen(tmp);
1112 /* have space to placed match? */
1113 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1115 /* before word for match */
1116 command_ps[cursor - recalc_pos] = 0;
1117 /* save tail line */
1118 strcpy(matchBuf, command_ps + cursor);
1120 strcat(command_ps, tmp);
1122 strcat(command_ps, matchBuf);
1123 /* back to begin word for match */
1124 input_backward(recalc_pos);
1126 recalc_pos = cursor + len_found;
1128 len = strlen(command_ps);
1129 /* write out the matched command */
1131 input_backward(cursor - recalc_pos);
1133 if (tmp != matches[0])
1136 /* Ok -- the last char was a TAB. Since they
1137 * just hit TAB again, print a list of all the
1138 * available choices... */
1139 if (matches && num_matches > 0) {
1141 int sav_cursor = cursor; /* change goto_new_line() */
1143 /* Go to the next line */
1145 for (i = 0, col = 0; i < num_matches; i++) {
1146 l = strlen(matches[i]);
1149 printf("%-14s ", matches[i]);
1156 col -= (col / cmdedit_termw) * cmdedit_termw;
1157 if (col > 60 && matches[i + 1] != NULL) {
1162 /* Go to the next line and rewrite */
1164 redraw(0, len - sav_cursor);
1168 #endif /* BB_FEATURE_SH_TAB_COMPLETION */
1170 static void get_previous_history(struct history **hp, struct history *p)
1174 (*hp)->s = xstrdup(command_ps);
1178 static inline void get_next_history(struct history **hp)
1180 get_previous_history(hp, (*hp)->n);
1190 * This function is used to grab a character buffer
1191 * from the input file descriptor and allows you to
1192 * a string with full command editing (sortof like
1195 * The following standard commands are not implemented:
1196 * ESC-b -- Move back one word
1197 * ESC-f -- Move forward one word
1198 * ESC-d -- Delete back one word
1199 * ESC-h -- Delete forward one word
1200 * CTL-t -- Transpose two characters
1202 * Furthermore, the "vi" command editing keys are not implemented.
1205 extern void cmdedit_read_input(char *prompt, char command[BUFSIZ])
1208 int inputFd = fileno(stdin);
1211 int lastWasTab = FALSE;
1213 struct history *hp = his_end;
1215 /* prepare before init handlers */
1216 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1218 command_ps = command;
1220 if (new_settings.c_cc[VMIN] == 0) { /* first call */
1222 getTermSettings(inputFd, (void *) &initial_settings);
1223 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1225 new_settings.c_cc[VMIN] = 1;
1226 new_settings.c_cc[VTIME] = 0;
1227 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
1228 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1229 new_settings.c_lflag &= ~(ECHO | ECHOCTL | ECHONL); /* Turn off echoing */
1234 setTermSettings(inputFd, (void *) &new_settings);
1235 handlers_sets |= SET_RESET_TERM;
1237 /* Print out the command prompt */
1238 parse_prompt(prompt);
1239 /* Now initialize things */
1244 fflush(stdout); /* buffered out to fast */
1246 if (read(inputFd, &c, 1) < 1)
1257 /* Control-a -- Beginning of line */
1258 input_backward(cursor);
1261 /* Control-b -- Move back one character */
1265 /* Control-c -- stop gathering input */
1267 /* Link into lash to reset context to 0 on ^C and such */
1270 /* Go to the next line */
1276 /* Control-d -- Delete one character, or exit
1277 * if the len=0 and no chars to delete */
1280 clean_up_and_die(0);
1286 /* Control-e -- End of line */
1290 /* Control-f -- Move forward one character */
1295 /* Control-h and DEL */
1299 #ifdef BB_FEATURE_SH_TAB_COMPLETION
1300 input_tab(&lastWasTab);
1304 /* Control-n -- Get next command in history */
1305 if (hp && hp->n && hp->n->s) {
1306 get_next_history(&hp);
1313 /* Control-p -- Get previous command from history */
1315 get_previous_history(&hp, hp->p);
1322 /* Control-U -- Clear line before cursor */
1324 strcpy(command, command + cursor);
1325 redraw(cmdedit_y, len -= cursor);
1330 /* escape sequence follows */
1331 if (read(inputFd, &c, 1) < 1)
1333 /* different vt100 emulations */
1334 if (c == '[' || c == 'O') {
1335 if (read(inputFd, &c, 1) < 1)
1339 #ifdef BB_FEATURE_SH_TAB_COMPLETION
1340 case '\t': /* Alt-Tab */
1342 input_tab(&lastWasTab);
1346 /* Up Arrow -- Get previous command from history */
1348 get_previous_history(&hp, hp->p);
1355 /* Down Arrow -- Get next command in history */
1356 if (hp && hp->n && hp->n->s) {
1357 get_next_history(&hp);
1364 /* Rewrite the line with the selected history item */
1366 /* change command */
1367 len = strlen(strcpy(command, hp->s));
1368 /* redraw and go to end line */
1369 redraw(cmdedit_y, 0);
1372 /* Right Arrow -- Move forward one character */
1376 /* Left Arrow -- Move back one character */
1386 input_backward(cursor);
1394 if (!(c >= '1' && c <= '9'))
1398 if (c >= '1' && c <= '9')
1400 if (read(inputFd, &c, 1) < 1)
1406 default: /* If it's regular input, do the normal thing */
1407 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1408 /* Control-V -- Add non-printable symbol */
1410 if (read(inputFd, &c, 1) < 1)
1418 if (!isprint(c)) /* Skip non-printable characters */
1421 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1426 if (cursor == (len - 1)) { /* Append if at the end of the line */
1427 *(command + cursor) = c;
1428 *(command + cursor + 1) = 0;
1429 cmdedit_set_out_char(0);
1430 } else { /* Insert otherwise */
1433 memmove(command + sc + 1, command + sc, len - sc);
1434 *(command + sc) = c;
1436 /* rewrite from cursor */
1438 /* to prev x pos + 1 */
1439 input_backward(cursor - sc);
1444 if (break_out) /* Enter is the command terminator, no more input. */
1451 setTermSettings(inputFd, (void *) &initial_settings);
1452 handlers_sets &= ~SET_RESET_TERM;
1454 /* Handle command history log */
1455 if (len) { /* no put empty line */
1457 struct history *h = his_end;
1460 ss = xstrdup(command); /* duplicate */
1463 /* No previous history -- this memory is never freed */
1464 h = his_front = xmalloc(sizeof(struct history));
1465 h->n = xmalloc(sizeof(struct history));
1475 /* Add a new history command -- this memory is never freed */
1476 h->n = xmalloc(sizeof(struct history));
1484 /* After max history, remove the oldest command */
1485 if (history_counter >= MAX_HISTORY) {
1487 struct history *p = his_front->n;
1497 #if defined(BB_FEATURE_BASH_STYLE_PROMT)
1501 command[len++] = '\n'; /* set '\n' */
1503 #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_SH_TAB_COMPLETION)
1504 input_tab(0); /* strong free */
1506 #if defined(BB_FEATURE_BASH_STYLE_PROMT)
1507 free(cmdedit_prompt);
1513 /* Undo the effects of cmdedit_init(). */
1514 extern void cmdedit_terminate(void)
1516 cmdedit_reset_term();
1517 if ((handlers_sets & SET_TERM_HANDLERS) != 0) {
1518 signal(SIGKILL, SIG_DFL);
1519 signal(SIGINT, SIG_DFL);
1520 signal(SIGQUIT, SIG_DFL);
1521 signal(SIGTERM, SIG_DFL);
1522 signal(SIGWINCH, SIG_DFL);
1523 handlers_sets &= ~SET_TERM_HANDLERS;
1527 #endif /* BB_FEATURE_SH_COMMAND_EDITING */
1532 unsigned int shell_context;
1534 int main(int argc, char **argv)
1538 #if defined(BB_FEATURE_BASH_STYLE_PROMT)
1539 "\\[\\033[32;1m\\]\\u@\\[\\033[33;1m\\]\\h:\
1540 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1541 \\!\\[\\033[36;1m\\]\\$ \\[\\033[0m\\]";
1548 cmdedit_read_input(prompt, buff);
1549 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1550 } while (shell_context);
1551 printf("*** cmdedit_read_input() detect ^C\n");