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)
40 #include <sys/ioctl.h>
53 #define BB_FEATURE_SH_COMMAND_EDITING
54 #define BB_FEATURE_SH_TAB_COMPLETION
55 #define BB_FEATURE_SH_USERNAME_COMPLETION
56 #define BB_FEATURE_NONPRINTABLE_INVERSE_PUT
57 #define BB_FEATURE_BASH_STYLE_PROMT
58 #define BB_FEATURE_CLEAN_UP
64 #ifdef BB_FEATURE_SH_TAB_COMPLETION
69 #ifdef BB_FEATURE_SH_COMMAND_EDITING
71 #ifndef BB_FEATURE_SH_TAB_COMPLETION
72 #undef BB_FEATURE_SH_USERNAME_COMPLETION
75 #if defined(BB_FEATURE_SH_USERNAME_COMPLETION) || defined(BB_FEATURE_BASH_STYLE_PROMT)
76 #define BB_FEATURE_GETUSERNAME_AND_HOMEDIR
79 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
81 #include "pwd_grp/pwd.h"
85 #endif /* advanced FEATURES */
89 void *xrealloc(void *old, size_t size)
91 return realloc(old, size);
94 void *xmalloc(size_t size)
98 char *xstrdup(const char *s)
103 void *xcalloc(size_t size, size_t se)
105 return calloc(size, se);
108 #define error_msg(s, d) fprintf(stderr, s, d)
118 /* Maximum length of the linked list for the command line history */
119 static const int MAX_HISTORY = 15;
121 /* First element in command line list */
122 static struct history *his_front = NULL;
124 /* Last element in command line list */
125 static struct history *his_end = NULL;
128 /* ED: sparc termios is broken: revert back to old termio handling. */
132 # define termios termio
133 # define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
134 # define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
136 # include <termios.h>
137 # define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
138 # define getTermSettings(fd,argp) tcgetattr(fd, argp);
141 /* Current termio and the previous termio before starting sh */
142 static struct termios initial_settings, new_settings;
145 #ifndef _POSIX_VDISABLE
146 #define _POSIX_VDISABLE '\0'
151 volatile int cmdedit_termw = 80; /* actual terminal width */
152 static int history_counter = 0; /* Number of commands in history list */
154 volatile int handlers_sets = 0; /* Set next bites: */
157 SET_ATEXIT = 1, /* when atexit() has been called and
158 get euid,uid,gid to fast compare */
159 SET_TERM_HANDLERS = 2, /* set many terminates signal handlers */
160 SET_WCHG_HANDLERS = 4, /* winchg signal handler */
161 SET_RESET_TERM = 8, /* if the terminal needs to be reset upon exit */
165 static int cmdedit_x; /* real x terminal position */
166 static int cmdedit_y; /* pseudoreal y terminal position */
167 static int cmdedit_prmt_len; /* lenght prompt without colores string */
169 static int cursor; /* required global for signal handler */
170 static int len; /* --- "" - - "" - -"- --""-- --""--- */
171 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
173 #ifndef BB_FEATURE_BASH_STYLE_PROMT
176 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
178 /* Link into lash to reset context to 0 on ^C and such */
179 extern unsigned int shell_context;
182 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
183 static char *user_buf = "";
184 static char *home_pwd_buf = "";
188 #ifdef BB_FEATURE_BASH_STYLE_PROMT
189 static char *hostname_buf = "";
190 static int num_ok_lines = 1;
194 #ifdef BB_FEATURE_SH_TAB_COMPLETION
196 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
203 #endif /* BB_FEATURE_SH_TAB_COMPLETION */
206 static void cmdedit_setwidth(int w, int redraw_flg);
208 static void win_changed(int nsig)
210 struct winsize win = { 0, 0, 0, 0 };
211 static __sighandler_t previous_SIGWINCH_handler; /* for reset */
213 /* emulate || signal call */
214 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
215 ioctl(0, TIOCGWINSZ, &win);
216 if (win.ws_col > 0) {
217 cmdedit_setwidth(win.ws_col, nsig == SIGWINCH);
220 /* Unix not all standart in recall signal */
222 if (nsig == -SIGWINCH) /* save previous handler */
223 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
224 else if (nsig == SIGWINCH) /* signaled called handler */
225 signal(SIGWINCH, win_changed); /* set for next call */
227 /* set previous handler */
228 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
231 static void cmdedit_reset_term(void)
233 if ((handlers_sets & SET_RESET_TERM) != 0) {
234 /* sparc and other have broken termios support: use old termio handling. */
235 setTermSettings(fileno(stdin), (void *) &initial_settings);
236 handlers_sets &= ~SET_RESET_TERM;
238 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
239 /* reset SIGWINCH handler to previous (default) */
241 handlers_sets &= ~SET_WCHG_HANDLERS;
244 #ifdef BB_FEATURE_CLEAN_UP
248 //while(his_front!=his_end) {
249 while (his_front != his_end) {
260 /* special for recount position for scroll and remove terminal margin effect */
261 static void cmdedit_set_out_char(int next_char)
264 int c = command_ps[cursor];
267 c = ' '; /* destroy end char? */
268 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
269 if (!isprint(c)) { /* Inverse put non-printable characters */
270 if (((unsigned char) c) >= 128)
272 if (((unsigned char) c) < ' ')
276 printf("\033[7m%c\033[0m", c);
280 if (++cmdedit_x >= cmdedit_termw) {
281 /* terminal is scrolled down */
287 /* destroy "(auto)margin" */
294 /* Move to end line. Bonus: rewrite line from cursor */
295 static void input_end(void)
298 cmdedit_set_out_char(0);
301 /* Go to the next line */
302 static void goto_new_line(void)
310 static inline void out1str(const char *s)
314 static inline void beep(void)
319 /* Move back one charactor */
320 /* special for slow terminal */
321 static void input_backward(int num)
325 cursor -= num; /* new cursor (in command, not terminal) */
327 if (cmdedit_x >= num) { /* no to up line */
334 printf("\033[%dD", num);
339 putchar('\r'); /* back to first terminal pos. */
340 num -= cmdedit_x; /* set previous backward */
342 count_y = 1 + num / cmdedit_termw;
343 printf("\033[%dA", count_y);
344 cmdedit_y -= count_y;
345 /* require forward after uping */
346 cmdedit_x = cmdedit_termw * count_y - num;
347 printf("\033[%dC", cmdedit_x); /* set term cursor */
351 static void put_prompt(void)
353 out1str(cmdedit_prompt);
354 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
358 #ifdef BB_FEATURE_BASH_STYLE_PROMT
360 add_to_prompt(char **prmt_mem_ptr, int *alm, int *prmt_len,
363 int l = strlen(addb);
366 if (*alm < (*prmt_len) + 1) {
367 *alm = (*prmt_len) + 1;
368 *prmt_mem_ptr = xrealloc(*prmt_mem_ptr, *alm);
370 strcat(*prmt_mem_ptr, addb);
374 static void parse_prompt(const char *prmt_ptr)
376 #ifdef BB_FEATURE_BASH_STYLE_PROMT
377 int alm = strlen(prmt_ptr) + 1; /* supposedly require memory */
380 int flg_not_length = '[';
381 char *prmt_mem_ptr = xstrdup(prmt_ptr);
382 char pwd_buf[PATH_MAX + 1];
398 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, user_buf);
401 if (hostname_buf[0] == 0) {
402 hostname_buf = xcalloc(256, 1);
403 if (gethostname(hostname_buf, 255) < 0) {
404 strcpy(hostname_buf, "?");
406 char *s = strchr(hostname_buf, '.');
412 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len,
416 c = my_euid == 0 ? '#' : '$';
419 if (pwd_buf[0] == 0) {
422 getcwd(pwd_buf, PATH_MAX);
423 l = strlen(home_pwd_buf);
424 if (home_pwd_buf[0] != 0 &&
425 strncmp(home_pwd_buf, pwd_buf, l) == 0) {
426 strcpy(pwd_buf + 1, pwd_buf + l);
430 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, pwd_buf);
433 snprintf(buf, sizeof(buf), "%d", num_ok_lines);
434 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
437 case 'E': /* \e \E = \033 */
457 for (l = 0; l < 3;) {
459 buf[l++] = *prmt_ptr;
461 ho = strtol(buf, &eho, c == 'x' ? 16 : 8);
462 if (ho > UCHAR_MAX || (eho - buf) < l) {
469 ho = strtol(buf, 0, c == 'x' ? 16 : 8);
470 c = ho == 0 ? '?' : (char) ho;
475 if (c == flg_not_length) {
476 flg_not_length = flg_not_length == '[' ? ']' : '[';
484 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
485 if (flg_not_length == ']')
488 cmdedit_prmt_len = prmt_len - sub_len;
489 cmdedit_prompt = prmt_mem_ptr;
491 cmdedit_prompt = prmt_ptr;
492 cmdedit_prmt_len = strlen(prmt_ptr);
498 /* draw promt, editor line, and clear tail */
499 static void redraw(int y, int back_cursor)
501 if (y > 0) /* up to start y */
502 printf("\033[%dA", y);
503 cmdedit_y = 0; /* new quasireal y */
506 input_end(); /* rewrite */
507 printf("\033[J"); /* destroy tail after cursor */
508 input_backward(back_cursor);
511 /* Delete the char in front of the cursor */
512 static void input_delete(void)
519 strcpy(command_ps + j, command_ps + j + 1);
521 input_end(); /* rewtite new line */
522 cmdedit_set_out_char(0); /* destroy end char */
523 input_backward(cursor - j); /* back to old pos cursor */
526 /* Delete the char in back of the cursor */
527 static void input_backspace(void)
536 /* Move forward one charactor */
537 static void input_forward(void)
540 cmdedit_set_out_char(command_ps[cursor + 1]);
544 static void clean_up_and_die(int sig)
548 exit(EXIT_SUCCESS); /* cmdedit_reset_term() called in atexit */
549 cmdedit_reset_term();
552 static void cmdedit_setwidth(int w, int redraw_flg)
554 cmdedit_termw = cmdedit_prmt_len + 2;
555 if (w <= cmdedit_termw) {
556 cmdedit_termw = cmdedit_termw % w;
558 if (w > cmdedit_termw) {
563 /* new y for current cursor */
564 int new_y = (cursor + cmdedit_prmt_len) / w;
567 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
573 extern void cmdedit_init(void)
575 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
576 /* emulate usage handler to set handler and call yours work */
577 win_changed(-SIGWINCH);
578 handlers_sets |= SET_WCHG_HANDLERS;
581 if ((handlers_sets & SET_ATEXIT) == 0) {
582 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
583 struct passwd *entry;
586 entry = getpwuid(my_euid);
588 user_buf = xstrdup(entry->pw_name);
589 home_pwd_buf = xstrdup(entry->pw_dir);
593 #ifdef BB_FEATURE_SH_TAB_COMPLETION
595 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
600 #endif /* BB_FEATURE_SH_TAB_COMPLETION */
601 handlers_sets |= SET_ATEXIT;
602 atexit(cmdedit_reset_term); /* be sure to do this only once */
605 if ((handlers_sets & SET_TERM_HANDLERS) == 0) {
606 signal(SIGKILL, clean_up_and_die);
607 signal(SIGINT, clean_up_and_die);
608 signal(SIGQUIT, clean_up_and_die);
609 signal(SIGTERM, clean_up_and_die);
610 handlers_sets |= SET_TERM_HANDLERS;
615 #ifdef BB_FEATURE_SH_TAB_COMPLETION
617 static int is_execute(const struct stat *st)
619 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
620 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
621 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
622 (st->st_mode & S_IXOTH)) return TRUE;
626 #ifdef BB_FEATURE_SH_USERNAME_COMPLETION
628 static char **username_tab_completion(char *ud, int *num_matches)
630 struct passwd *entry;
635 ud++; /* ~user/... to user/... */
636 userlen = strlen(ud);
638 if (num_matches == 0) { /* "~/..." or "~user/..." */
639 char *sav_ud = ud - 1;
642 if (*ud == '/') { /* "~/..." */
646 temp = strchr(ud, '/');
647 *temp = 0; /* ~user\0 */
648 entry = getpwnam(ud);
649 *temp = '/'; /* restore ~user/... */
652 home = entry->pw_dir;
655 if ((userlen + strlen(home) + 1) < BUFSIZ) {
656 char temp2[BUFSIZ]; /* argument size */
659 sprintf(temp2, "%s%s", home, ud);
660 strcpy(sav_ud, temp2);
663 return 0; /* void, result save to argument :-) */
666 char **matches = (char **) NULL;
671 while ((entry = getpwent()) != NULL) {
672 /* Null usernames should result in all users as possible completions. */
673 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
675 temp = xmalloc(3 + strlen(entry->pw_name));
676 sprintf(temp, "~%s/", entry->pw_name);
677 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
679 matches[nm++] = temp;
688 #endif /* BB_FEATURE_SH_USERNAME_COMPLETION */
696 static int path_parse(char ***p, int flags)
702 /* if not setenv PATH variable, to search cur dir "." */
703 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
704 /* PATH=<empty> or PATH=:<empty> */
705 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
713 npth++; /* count words is + 1 count ':' */
714 tmp = strchr(tmp, ':');
717 break; /* :<empty> */
722 *p = xmalloc(npth * sizeof(char *));
725 (*p)[0] = xstrdup(tmp);
726 npth = 1; /* count words is + 1 count ':' */
729 tmp = strchr(tmp, ':');
731 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
733 break; /* :<empty> */
736 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
742 static char *add_quote_for_spec_chars(char *found)
745 char *s = xmalloc((strlen(found) + 1) * 2);
748 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
756 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
764 int nm = *num_matches;
767 char **paths = path1;
770 char found[BUFSIZ + 4 + PATH_MAX];
771 char *pfind = strrchr(command, '/');
776 /* no dir, if flags==EXE_ONLY - get paths, else "." */
777 npaths = path_parse(&paths, type);
781 /* save for change */
782 strcpy(dirbuf, command);
784 dirbuf[(pfind - command) + 1] = 0;
785 #ifdef BB_FEATURE_SH_USERNAME_COMPLETION
786 if (dirbuf[0] == '~') /* ~/... or ~user/... */
787 username_tab_completion(dirbuf, 0);
789 /* "strip" dirname in command */
793 npaths = 1; /* only 1 dir */
796 for (i = 0; i < npaths; i++) {
798 dir = opendir(paths[i]);
799 if (!dir) /* Don't print an error */
802 while ((next = readdir(dir)) != NULL) {
803 const char *str_merge = "%s/%s";
804 char *str_found = next->d_name;
807 if (strncmp(str_found, pfind, strlen(pfind)))
809 /* not see .name without .match */
810 if (*str_found == '.' && *pfind == 0) {
811 if (*paths[i] == '/' && paths[i][1] == 0
812 && str_found[1] == 0) str_found = ""; /* only "/" */
816 if (paths[i][strlen(paths[i]) - 1] == '/')
818 sprintf(found, str_merge, paths[i], str_found);
819 /* hmm, remover in progress? */
820 if (stat(found, &st) < 0)
822 /* find with dirs ? */
823 if (paths[i] != dirbuf)
824 strcpy(found, next->d_name); /* only name */
825 if (S_ISDIR(st.st_mode)) {
826 /* name is directory */
827 /* algorithmic only "/" ? */
830 str_found = add_quote_for_spec_chars(found);
832 /* not put found file if search only dirs for cd */
833 if (type == FIND_DIR_ONLY)
835 str_found = add_quote_for_spec_chars(found);
836 if (type == FIND_FILE_ONLY ||
837 (type == FIND_EXE_ONLY && is_execute(&st) == TRUE))
838 strcat(str_found, " ");
840 /* Add it to the list */
841 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
843 matches[nm++] = str_found;
847 if (paths != path1) {
848 free(paths[0]); /* allocated memory only in first member */
855 static int match_compare(const void *a, const void *b)
857 return strcmp(*(char **) a, *(char **) b);
862 #define QUOT (UCHAR_MAX+1)
864 #define collapse_pos(is, in) { \
865 memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \
866 memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); }
868 static int find_match(char *matchBuf, int *len_with_quotes)
873 int int_buf[BUFSIZ + 1];
874 int pos_buf[BUFSIZ + 1];
876 /* set to integer dimension characters and own positions */
878 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
879 if (int_buf[i] == 0) {
880 pos_buf[i] = -1; /* indicator end line */
886 /* mask \+symbol and convert '\t' to ' ' */
887 for (i = j = 0; matchBuf[i]; i++, j++)
888 if (matchBuf[i] == '\\') {
889 collapse_pos(j, j + 1);
892 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
893 if (matchBuf[i] == '\t') /* algorithm equivalent */
894 int_buf[j] = ' ' | QUOT;
897 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
898 else if (matchBuf[i] == '\t')
902 /* mask "symbols" or 'symbols' */
904 for (i = 0; int_buf[i]; i++) {
906 if (c == '\'' || c == '"') {
915 } else if (c2 != 0 && c != '$')
919 /* skip commands with arguments if line have commands delimiters */
920 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
921 for (i = 0; int_buf[i]; i++) {
924 j = i ? int_buf[i - 1] : -1;
926 if (c == ';' || c == '&' || c == '|') {
927 command_mode = 1 + (c == c2);
929 if (j == '>' || j == '<')
931 } else if (c == '|' && j == '>')
935 collapse_pos(0, i + command_mode);
936 i = -1; /* hack incremet */
939 /* collapse `command...` */
940 for (i = 0; int_buf[i]; i++)
941 if (int_buf[i] == '`') {
942 for (j = i + 1; int_buf[j]; j++)
943 if (int_buf[j] == '`') {
944 collapse_pos(i, j + 1);
949 /* not found close ` - command mode, collapse all previous */
950 collapse_pos(0, i + 1);
953 i--; /* hack incremet */
956 /* collapse (command...(command...)...) or {command...{command...}...} */
957 c = 0; /* "recursive" level */
959 for (i = 0; int_buf[i]; i++)
960 if (int_buf[i] == '(' || int_buf[i] == '{') {
961 if (int_buf[i] == '(')
965 collapse_pos(0, i + 1);
966 i = -1; /* hack incremet */
968 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
969 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
970 if (int_buf[i] == ')')
974 collapse_pos(0, i + 1);
975 i = -1; /* hack incremet */
978 /* skip first not quote space */
979 for (i = 0; int_buf[i]; i++)
980 if (int_buf[i] != ' ')
985 /* set find mode for completion */
986 command_mode = FIND_EXE_ONLY;
987 for (i = 0; int_buf[i]; i++)
988 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
989 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
990 && strncmp(&matchBuf[pos_buf[0]], "cd", 2) == 0)
991 command_mode = FIND_DIR_ONLY;
993 command_mode = FIND_FILE_ONLY;
998 for (i = 0; int_buf[i]; i++);
1000 for (--i; i >= 0; i--) {
1002 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
1003 collapse_pos(0, i + 1);
1007 /* skip first not quoted '\'' or '"' */
1008 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
1009 /* collapse quote or unquote // or /~ */
1010 while ((int_buf[i] & ~QUOT) == '/' &&
1011 ((int_buf[i + 1] & ~QUOT) == '/'
1012 || (int_buf[i + 1] & ~QUOT) == '~')) {
1019 /* set only match and destroy quotes */
1021 for (i = 0; pos_buf[i] >= 0; i++) {
1022 matchBuf[i] = matchBuf[pos_buf[i]];
1026 /* old lenght matchBuf with quotes symbols */
1027 *len_with_quotes = j ? j - pos_buf[0] : 0;
1029 return command_mode;
1033 static void input_tab(int *lastWasTab)
1035 /* Do TAB completion */
1036 static int num_matches;
1037 static char **matches;
1039 if (lastWasTab == 0) { /* free all memory */
1041 while (num_matches > 0)
1042 free(matches[--num_matches]);
1044 matches = (char **) NULL;
1048 if (*lastWasTab == FALSE) {
1052 char matchBuf[BUFSIZ];
1056 *lastWasTab = TRUE; /* flop trigger */
1058 /* Make a local copy of the string -- up
1059 * to the position of the cursor */
1060 tmp = strncpy(matchBuf, command_ps, cursor);
1063 find_type = find_match(matchBuf, &recalc_pos);
1065 /* Free up any memory already allocated */
1068 #ifdef BB_FEATURE_SH_USERNAME_COMPLETION
1069 /* If the word starts with `~' and there is no slash in the word,
1070 * then try completing this word as a username. */
1072 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1073 matches = username_tab_completion(matchBuf, &num_matches);
1075 /* Try to match any executable in our path and everything
1076 * in the current working directory that matches. */
1079 exe_n_cwd_tab_completion(matchBuf, &num_matches,
1082 /* Did we find exactly one match? */
1083 if (!matches || num_matches > 1) {
1088 return; /* not found */
1090 qsort(matches, num_matches, sizeof(char *), match_compare);
1092 /* find minimal match */
1093 tmp = xstrdup(matches[0]);
1094 for (tmp1 = tmp; *tmp1; tmp1++)
1095 for (len_found = 1; len_found < num_matches; len_found++)
1096 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1100 if (*tmp == 0) { /* have unique */
1104 } else { /* one match */
1106 /* for next completion current found */
1107 *lastWasTab = FALSE;
1110 len_found = strlen(tmp);
1111 /* have space to placed match? */
1112 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1114 /* before word for match */
1115 command_ps[cursor - recalc_pos] = 0;
1116 /* save tail line */
1117 strcpy(matchBuf, command_ps + cursor);
1119 strcat(command_ps, tmp);
1121 strcat(command_ps, matchBuf);
1122 /* back to begin word for match */
1123 input_backward(recalc_pos);
1125 recalc_pos = cursor + len_found;
1127 len = strlen(command_ps);
1128 /* write out the matched command */
1130 input_backward(cursor - recalc_pos);
1132 if (tmp != matches[0])
1135 /* Ok -- the last char was a TAB. Since they
1136 * just hit TAB again, print a list of all the
1137 * available choices... */
1138 if (matches && num_matches > 0) {
1140 int sav_cursor = cursor; /* change goto_new_line() */
1142 /* Go to the next line */
1144 for (i = 0, col = 0; i < num_matches; i++) {
1145 l = strlen(matches[i]);
1148 printf("%-14s ", matches[i]);
1155 col -= (col / cmdedit_termw) * cmdedit_termw;
1156 if (col > 60 && matches[i + 1] != NULL) {
1161 /* Go to the next line and rewrite */
1163 redraw(0, len - sav_cursor);
1167 #endif /* BB_FEATURE_SH_TAB_COMPLETION */
1169 static void get_previous_history(struct history **hp, struct history *p)
1173 (*hp)->s = xstrdup(command_ps);
1177 static inline void get_next_history(struct history **hp)
1179 get_previous_history(hp, (*hp)->n);
1189 * This function is used to grab a character buffer
1190 * from the input file descriptor and allows you to
1191 * a string with full command editing (sortof like
1194 * The following standard commands are not implemented:
1195 * ESC-b -- Move back one word
1196 * ESC-f -- Move forward one word
1197 * ESC-d -- Delete back one word
1198 * ESC-h -- Delete forward one word
1199 * CTL-t -- Transpose two characters
1201 * Furthermore, the "vi" command editing keys are not implemented.
1204 extern void cmdedit_read_input(char *prompt, char command[BUFSIZ])
1207 int inputFd = fileno(stdin);
1210 int lastWasTab = FALSE;
1212 struct history *hp = his_end;
1214 /* prepare before init handlers */
1215 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1217 command_ps = command;
1219 if (new_settings.c_cc[VMIN] == 0) { /* first call */
1221 getTermSettings(inputFd, (void *) &initial_settings);
1222 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1224 new_settings.c_cc[VMIN] = 1;
1225 new_settings.c_cc[VTIME] = 0;
1226 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
1227 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1228 new_settings.c_lflag &= ~(ECHO | ECHOCTL | ECHONL); /* Turn off echoing */
1233 setTermSettings(inputFd, (void *) &new_settings);
1234 handlers_sets |= SET_RESET_TERM;
1236 /* Print out the command prompt */
1237 parse_prompt(prompt);
1238 /* Now initialize things */
1243 fflush(stdout); /* buffered out to fast */
1245 if (read(inputFd, &c, 1) < 1)
1256 /* Control-a -- Beginning of line */
1257 input_backward(cursor);
1260 /* Control-b -- Move back one character */
1264 /* Control-c -- stop gathering input */
1266 /* Link into lash to reset context to 0 on ^C and such */
1269 /* Go to the next line */
1275 /* Control-d -- Delete one character, or exit
1276 * if the len=0 and no chars to delete */
1279 clean_up_and_die(0);
1285 /* Control-e -- End of line */
1289 /* Control-f -- Move forward one character */
1294 /* Control-h and DEL */
1298 #ifdef BB_FEATURE_SH_TAB_COMPLETION
1299 input_tab(&lastWasTab);
1303 /* Control-n -- Get next command in history */
1304 if (hp && hp->n && hp->n->s) {
1305 get_next_history(&hp);
1312 /* Control-p -- Get previous command from history */
1314 get_previous_history(&hp, hp->p);
1321 /* Control-U -- Clear line before cursor */
1323 strcpy(command, command + cursor);
1324 redraw(cmdedit_y, len -= cursor);
1329 /* escape sequence follows */
1330 if (read(inputFd, &c, 1) < 1)
1332 /* different vt100 emulations */
1333 if (c == '[' || c == 'O') {
1334 if (read(inputFd, &c, 1) < 1)
1338 #ifdef BB_FEATURE_SH_TAB_COMPLETION
1339 case '\t': /* Alt-Tab */
1341 input_tab(&lastWasTab);
1345 /* Up Arrow -- Get previous command from history */
1347 get_previous_history(&hp, hp->p);
1354 /* Down Arrow -- Get next command in history */
1355 if (hp && hp->n && hp->n->s) {
1356 get_next_history(&hp);
1363 /* Rewrite the line with the selected history item */
1365 /* change command */
1366 len = strlen(strcpy(command, hp->s));
1367 /* redraw and go to end line */
1368 redraw(cmdedit_y, 0);
1371 /* Right Arrow -- Move forward one character */
1375 /* Left Arrow -- Move back one character */
1385 input_backward(cursor);
1393 if (!(c >= '1' && c <= '9'))
1397 if (c >= '1' && c <= '9')
1399 if (read(inputFd, &c, 1) < 1)
1405 default: /* If it's regular input, do the normal thing */
1406 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1407 /* Control-V -- Add non-printable symbol */
1409 if (read(inputFd, &c, 1) < 1)
1417 if (!isprint(c)) /* Skip non-printable characters */
1420 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1425 if (cursor == (len - 1)) { /* Append if at the end of the line */
1426 *(command + cursor) = c;
1427 *(command + cursor + 1) = 0;
1428 cmdedit_set_out_char(0);
1429 } else { /* Insert otherwise */
1432 memmove(command + sc + 1, command + sc, len - sc);
1433 *(command + sc) = c;
1435 /* rewrite from cursor */
1437 /* to prev x pos + 1 */
1438 input_backward(cursor - sc);
1443 if (break_out) /* Enter is the command terminator, no more input. */
1450 setTermSettings(inputFd, (void *) &initial_settings);
1451 handlers_sets &= ~SET_RESET_TERM;
1453 /* Handle command history log */
1454 if (len) { /* no put empty line */
1456 struct history *h = his_end;
1459 ss = xstrdup(command); /* duplicate */
1462 /* No previous history -- this memory is never freed */
1463 h = his_front = xmalloc(sizeof(struct history));
1464 h->n = xmalloc(sizeof(struct history));
1474 /* Add a new history command -- this memory is never freed */
1475 h->n = xmalloc(sizeof(struct history));
1483 /* After max history, remove the oldest command */
1484 if (history_counter >= MAX_HISTORY) {
1486 struct history *p = his_front->n;
1496 #if defined(BB_FEATURE_BASH_STYLE_PROMT)
1500 command[len++] = '\n'; /* set '\n' */
1502 #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_SH_TAB_COMPLETION)
1503 input_tab(0); /* strong free */
1505 #if defined(BB_FEATURE_BASH_STYLE_PROMT)
1506 free(cmdedit_prompt);
1512 /* Undo the effects of cmdedit_init(). */
1513 extern void cmdedit_terminate(void)
1515 cmdedit_reset_term();
1516 if ((handlers_sets & SET_TERM_HANDLERS) != 0) {
1517 signal(SIGKILL, SIG_DFL);
1518 signal(SIGINT, SIG_DFL);
1519 signal(SIGQUIT, SIG_DFL);
1520 signal(SIGTERM, SIG_DFL);
1521 signal(SIGWINCH, SIG_DFL);
1522 handlers_sets &= ~SET_TERM_HANDLERS;
1526 #endif /* BB_FEATURE_SH_COMMAND_EDITING */
1531 unsigned int shell_context;
1533 int main(int argc, char **argv)
1537 #if defined(BB_FEATURE_BASH_STYLE_PROMT)
1538 "\\[\\033[32;1m\\]\\u@\\[\\033[33;1m\\]\\h:\
1539 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1540 \\!\\[\\033[36;1m\\]\\$ \\[\\033[0m\\]";
1547 cmdedit_read_input(prompt, buff);
1548 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1549 } while (shell_context);
1550 printf("*** cmdedit_read_input() detect ^C\n");