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>
45 #ifdef BB_FEATURE_SH_TAB_COMPLETION
59 #define BB_FEATURE_SH_COMMAND_EDITING
60 #define BB_FEATURE_SH_TAB_COMPLETION
61 #define BB_FEATURE_SH_USERNAME_COMPLETION
62 #define BB_FEATURE_NONPRINTABLE_INVERSE_PUT
63 #define BB_FEATURE_BASH_STYLE_PROMT
64 #define BB_FEATURE_CLEAN_UP
70 #ifdef BB_FEATURE_SH_COMMAND_EDITING
72 #ifndef BB_FEATURE_SH_TAB_COMPLETION
73 #undef BB_FEATURE_SH_USERNAME_COMPLETION
76 #if defined(BB_FEATURE_SH_USERNAME_COMPLETION) || defined(BB_FEATURE_BASH_STYLE_PROMT)
77 #define BB_FEATURE_GETUSERNAME_AND_HOMEDIR
80 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
82 #include "pwd_grp/pwd.h"
86 #endif /* advanced FEATURES */
90 void *xrealloc(void *old, size_t size)
92 return realloc(old, size);
95 void *xmalloc(size_t size)
99 char *xstrdup(const char *s)
104 void *xcalloc(size_t size, size_t se)
106 return calloc(size, se);
109 #define error_msg(s, d) fprintf(stderr, s, d)
119 /* Maximum length of the linked list for the command line history */
120 static const int MAX_HISTORY = 15;
122 /* First element in command line list */
123 static struct history *his_front = NULL;
125 /* Last element in command line list */
126 static struct history *his_end = NULL;
129 /* ED: sparc termios is broken: revert back to old termio handling. */
133 # define termios termio
134 # define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
135 # define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
137 # include <termios.h>
138 # define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
139 # define getTermSettings(fd,argp) tcgetattr(fd, argp);
142 /* Current termio and the previous termio before starting sh */
143 static struct termios initial_settings, new_settings;
146 #ifndef _POSIX_VDISABLE
147 #define _POSIX_VDISABLE '\0'
152 volatile int cmdedit_termw = 80; /* actual terminal width */
153 static int history_counter = 0; /* Number of commands in history list */
155 volatile int handlers_sets = 0; /* Set next bites: */
158 SET_ATEXIT = 1, /* when atexit() has been called and
159 get euid,uid,gid to fast compare */
160 SET_TERM_HANDLERS = 2, /* set many terminates signal handlers */
161 SET_WCHG_HANDLERS = 4, /* winchg signal handler */
162 SET_RESET_TERM = 8, /* if the terminal needs to be reset upon exit */
166 static int cmdedit_x; /* real x terminal position */
167 static int cmdedit_y; /* pseudoreal y terminal position */
168 static int cmdedit_prmt_len; /* lenght prompt without colores string */
170 static int cursor; /* required global for signal handler */
171 static int len; /* --- "" - - "" - -"- --""-- --""--- */
172 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
174 #ifndef BB_FEATURE_BASH_STYLE_PROMT
177 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
179 /* Link into lash to reset context to 0 on ^C and such */
180 extern unsigned int shell_context;
183 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
184 static char *user_buf = "";
185 static char *home_pwd_buf = "";
189 #ifdef BB_FEATURE_BASH_STYLE_PROMT
190 static char *hostname_buf = "";
191 static int num_ok_lines = 1;
195 #ifdef BB_FEATURE_SH_TAB_COMPLETION
197 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
204 #endif /* BB_FEATURE_SH_TAB_COMPLETION */
207 static void cmdedit_setwidth(int w, int redraw_flg);
209 static void win_changed(int nsig)
211 struct winsize win = { 0, 0, 0, 0 };
212 static __sighandler_t previous_SIGWINCH_handler; /* for reset */
214 /* emulate || signal call */
215 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
216 ioctl(0, TIOCGWINSZ, &win);
217 if (win.ws_col > 0) {
218 cmdedit_setwidth(win.ws_col, nsig == SIGWINCH);
221 /* Unix not all standart in recall signal */
223 if (nsig == -SIGWINCH) /* save previous handler */
224 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
225 else if (nsig == SIGWINCH) /* signaled called handler */
226 signal(SIGWINCH, win_changed); /* set for next call */
228 /* set previous handler */
229 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
232 static void cmdedit_reset_term(void)
234 if ((handlers_sets & SET_RESET_TERM) != 0) {
235 /* sparc and other have broken termios support: use old termio handling. */
236 setTermSettings(fileno(stdin), (void *) &initial_settings);
237 handlers_sets &= ~SET_RESET_TERM;
239 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
240 /* reset SIGWINCH handler to previous (default) */
242 handlers_sets &= ~SET_WCHG_HANDLERS;
245 #ifdef BB_FEATURE_CLEAN_UP
249 //while(his_front!=his_end) {
250 while (his_front != his_end) {
261 /* special for recount position for scroll and remove terminal margin effect */
262 static void cmdedit_set_out_char(int next_char)
265 int c = command_ps[cursor];
268 c = ' '; /* destroy end char? */
269 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
270 if (!isprint(c)) { /* Inverse put non-printable characters */
271 if (((unsigned char) c) >= 128)
273 if (((unsigned char) c) < ' ')
277 printf("\033[7m%c\033[0m", c);
281 if (++cmdedit_x >= cmdedit_termw) {
282 /* terminal is scrolled down */
288 /* destroy "(auto)margin" */
295 /* Move to end line. Bonus: rewrite line from cursor */
296 static void input_end(void)
299 cmdedit_set_out_char(0);
302 /* Go to the next line */
303 static void goto_new_line(void)
311 static inline void out1str(const char *s)
315 static inline void beep(void)
320 /* Move back one charactor */
321 /* special for slow terminal */
322 static void input_backward(int num)
326 cursor -= num; /* new cursor (in command, not terminal) */
328 if (cmdedit_x >= num) { /* no to up line */
335 printf("\033[%dD", num);
340 putchar('\r'); /* back to first terminal pos. */
341 num -= cmdedit_x; /* set previous backward */
343 count_y = 1 + num / cmdedit_termw;
344 printf("\033[%dA", count_y);
345 cmdedit_y -= count_y;
346 /* require forward after uping */
347 cmdedit_x = cmdedit_termw * count_y - num;
348 printf("\033[%dC", cmdedit_x); /* set term cursor */
352 static void put_prompt(void)
354 out1str(cmdedit_prompt);
355 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
359 #ifdef BB_FEATURE_BASH_STYLE_PROMT
361 add_to_prompt(char **prmt_mem_ptr, int *alm, int *prmt_len,
364 int l = strlen(addb);
367 if (*alm < (*prmt_len) + 1) {
368 *alm = (*prmt_len) + 1;
369 *prmt_mem_ptr = xrealloc(*prmt_mem_ptr, *alm);
371 strcat(*prmt_mem_ptr, addb);
375 static void parse_prompt(const char *prmt_ptr)
377 #ifdef BB_FEATURE_BASH_STYLE_PROMT
378 int alm = strlen(prmt_ptr) + 1; /* supposedly require memory */
381 int flg_not_length = '[';
382 char *prmt_mem_ptr = xstrdup(prmt_ptr);
383 char pwd_buf[PATH_MAX + 1];
399 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, user_buf);
402 if (hostname_buf[0] == 0) {
403 hostname_buf = xcalloc(256, 1);
404 if (gethostname(hostname_buf, 255) < 0) {
405 strcpy(hostname_buf, "?");
407 char *s = strchr(hostname_buf, '.');
413 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len,
417 c = my_euid == 0 ? '#' : '$';
420 if (pwd_buf[0] == 0) {
423 getcwd(pwd_buf, PATH_MAX);
424 l = strlen(home_pwd_buf);
425 if (home_pwd_buf[0] != 0 &&
426 strncmp(home_pwd_buf, pwd_buf, l) == 0) {
427 strcpy(pwd_buf + 1, pwd_buf + l);
431 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, pwd_buf);
434 snprintf(buf, sizeof(buf), "%d", num_ok_lines);
435 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
438 case 'E': /* \e \E = \033 */
458 for (l = 0; l < 3;) {
460 buf[l++] = *prmt_ptr;
462 ho = strtol(buf, &eho, c == 'x' ? 16 : 8);
463 if (ho > UCHAR_MAX || (eho - buf) < l) {
470 ho = strtol(buf, 0, c == 'x' ? 16 : 8);
471 c = ho == 0 ? '?' : (char) ho;
476 if (c == flg_not_length) {
477 flg_not_length = flg_not_length == '[' ? ']' : '[';
485 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
486 if (flg_not_length == ']')
489 cmdedit_prmt_len = prmt_len - sub_len;
490 cmdedit_prompt = prmt_mem_ptr;
492 cmdedit_prompt = prmt_ptr;
493 cmdedit_prmt_len = strlen(prmt_ptr);
499 /* draw promt, editor line, and clear tail */
500 static void redraw(int y, int back_cursor)
502 if (y > 0) /* up to start y */
503 printf("\033[%dA", y);
504 cmdedit_y = 0; /* new quasireal y */
507 input_end(); /* rewrite */
508 printf("\033[J"); /* destroy tail after cursor */
509 input_backward(back_cursor);
512 /* Delete the char in front of the cursor */
513 static void input_delete(void)
520 strcpy(command_ps + j, command_ps + j + 1);
522 input_end(); /* rewtite new line */
523 cmdedit_set_out_char(0); /* destroy end char */
524 input_backward(cursor - j); /* back to old pos cursor */
527 /* Delete the char in back of the cursor */
528 static void input_backspace(void)
537 /* Move forward one charactor */
538 static void input_forward(void)
541 cmdedit_set_out_char(command_ps[cursor + 1]);
545 static void clean_up_and_die(int sig)
549 exit(EXIT_SUCCESS); /* cmdedit_reset_term() called in atexit */
550 cmdedit_reset_term();
553 static void cmdedit_setwidth(int w, int redraw_flg)
555 cmdedit_termw = cmdedit_prmt_len + 2;
556 if (w <= cmdedit_termw) {
557 cmdedit_termw = cmdedit_termw % w;
559 if (w > cmdedit_termw) {
564 /* new y for current cursor */
565 int new_y = (cursor + cmdedit_prmt_len) / w;
568 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
574 extern void cmdedit_init(void)
576 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
577 /* emulate usage handler to set handler and call yours work */
578 win_changed(-SIGWINCH);
579 handlers_sets |= SET_WCHG_HANDLERS;
582 if ((handlers_sets & SET_ATEXIT) == 0) {
583 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
584 struct passwd *entry;
587 entry = getpwuid(my_euid);
589 user_buf = xstrdup(entry->pw_name);
590 home_pwd_buf = xstrdup(entry->pw_dir);
594 #ifdef BB_FEATURE_SH_TAB_COMPLETION
596 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
601 #endif /* BB_FEATURE_SH_TAB_COMPLETION */
602 handlers_sets |= SET_ATEXIT;
603 atexit(cmdedit_reset_term); /* be sure to do this only once */
606 if ((handlers_sets & SET_TERM_HANDLERS) == 0) {
607 signal(SIGKILL, clean_up_and_die);
608 signal(SIGINT, clean_up_and_die);
609 signal(SIGQUIT, clean_up_and_die);
610 signal(SIGTERM, clean_up_and_die);
611 handlers_sets |= SET_TERM_HANDLERS;
616 #ifdef BB_FEATURE_SH_TAB_COMPLETION
618 static int is_execute(const struct stat *st)
620 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
621 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
622 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
623 (st->st_mode & S_IXOTH)) return TRUE;
627 #ifdef BB_FEATURE_SH_USERNAME_COMPLETION
629 static char **username_tab_completion(char *ud, int *num_matches)
631 struct passwd *entry;
636 ud++; /* ~user/... to user/... */
637 userlen = strlen(ud);
639 if (num_matches == 0) { /* "~/..." or "~user/..." */
640 char *sav_ud = ud - 1;
643 if (*ud == '/') { /* "~/..." */
647 temp = strchr(ud, '/');
648 *temp = 0; /* ~user\0 */
649 entry = getpwnam(ud);
650 *temp = '/'; /* restore ~user/... */
653 home = entry->pw_dir;
656 if ((userlen + strlen(home) + 1) < BUFSIZ) {
657 char temp2[BUFSIZ]; /* argument size */
660 sprintf(temp2, "%s%s", home, ud);
661 strcpy(sav_ud, temp2);
664 return 0; /* void, result save to argument :-) */
667 char **matches = (char **) NULL;
672 while ((entry = getpwent()) != NULL) {
673 /* Null usernames should result in all users as possible completions. */
674 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
676 temp = xmalloc(3 + strlen(entry->pw_name));
677 sprintf(temp, "~%s/", entry->pw_name);
678 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
680 matches[nm++] = temp;
689 #endif /* BB_FEATURE_SH_USERNAME_COMPLETION */
697 static int path_parse(char ***p, int flags)
703 /* if not setenv PATH variable, to search cur dir "." */
704 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
705 /* PATH=<empty> or PATH=:<empty> */
706 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
714 npth++; /* count words is + 1 count ':' */
715 tmp = strchr(tmp, ':');
718 break; /* :<empty> */
723 *p = xmalloc(npth * sizeof(char *));
726 (*p)[0] = xstrdup(tmp);
727 npth = 1; /* count words is + 1 count ':' */
730 tmp = strchr(tmp, ':');
732 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
734 break; /* :<empty> */
737 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
743 static char *add_quote_for_spec_chars(char *found)
746 char *s = xmalloc((strlen(found) + 1) * 2);
749 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
757 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
765 int nm = *num_matches;
768 char **paths = path1;
771 char found[BUFSIZ + 4 + PATH_MAX];
772 char *pfind = strrchr(command, '/');
777 /* no dir, if flags==EXE_ONLY - get paths, else "." */
778 npaths = path_parse(&paths, type);
782 /* save for change */
783 strcpy(dirbuf, command);
785 dirbuf[(pfind - command) + 1] = 0;
786 #ifdef BB_FEATURE_SH_USERNAME_COMPLETION
787 if (dirbuf[0] == '~') /* ~/... or ~user/... */
788 username_tab_completion(dirbuf, 0);
790 /* "strip" dirname in command */
794 npaths = 1; /* only 1 dir */
797 for (i = 0; i < npaths; i++) {
799 dir = opendir(paths[i]);
800 if (!dir) /* Don't print an error */
803 while ((next = readdir(dir)) != NULL) {
804 const char *str_merge = "%s/%s";
805 char *str_found = next->d_name;
808 if (strncmp(str_found, pfind, strlen(pfind)))
810 /* not see .name without .match */
811 if (*str_found == '.' && *pfind == 0) {
812 if (*paths[i] == '/' && paths[i][1] == 0
813 && str_found[1] == 0) str_found = ""; /* only "/" */
817 if (paths[i][strlen(paths[i]) - 1] == '/')
819 sprintf(found, str_merge, paths[i], str_found);
820 /* hmm, remover in progress? */
821 if (stat(found, &st) < 0)
823 /* find with dirs ? */
824 if (paths[i] != dirbuf)
825 strcpy(found, next->d_name); /* only name */
826 if (S_ISDIR(st.st_mode)) {
827 /* name is directory */
828 /* algorithmic only "/" ? */
831 str_found = add_quote_for_spec_chars(found);
833 /* not put found file if search only dirs for cd */
834 if (type == FIND_DIR_ONLY)
836 str_found = add_quote_for_spec_chars(found);
837 if (type == FIND_FILE_ONLY ||
838 (type == FIND_EXE_ONLY && is_execute(&st) == TRUE))
839 strcat(str_found, " ");
841 /* Add it to the list */
842 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
844 matches[nm++] = str_found;
848 if (paths != path1) {
849 free(paths[0]); /* allocated memory only in first member */
856 static int match_compare(const void *a, const void *b)
858 return strcmp(*(char **) a, *(char **) b);
863 #define QUOT (UCHAR_MAX+1)
865 #define collapse_pos(is, in) { \
866 memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \
867 memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); }
869 static int find_match(char *matchBuf, int *len_with_quotes)
874 int int_buf[BUFSIZ + 1];
875 int pos_buf[BUFSIZ + 1];
877 /* set to integer dimension characters and own positions */
879 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
880 if (int_buf[i] == 0) {
881 pos_buf[i] = -1; /* indicator end line */
887 /* mask \+symbol and convert '\t' to ' ' */
888 for (i = j = 0; matchBuf[i]; i++, j++)
889 if (matchBuf[i] == '\\') {
890 collapse_pos(j, j + 1);
893 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
894 if (matchBuf[i] == '\t') /* algorithm equivalent */
895 int_buf[j] = ' ' | QUOT;
898 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
899 else if (matchBuf[i] == '\t')
903 /* mask "symbols" or 'symbols' */
905 for (i = 0; int_buf[i]; i++) {
907 if (c == '\'' || c == '"') {
916 } else if (c2 != 0 && c != '$')
920 /* skip commands with arguments if line have commands delimiters */
921 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
922 for (i = 0; int_buf[i]; i++) {
925 j = i ? int_buf[i - 1] : -1;
927 if (c == ';' || c == '&' || c == '|') {
928 command_mode = 1 + (c == c2);
930 if (j == '>' || j == '<')
932 } else if (c == '|' && j == '>')
936 collapse_pos(0, i + command_mode);
937 i = -1; /* hack incremet */
940 /* collapse `command...` */
941 for (i = 0; int_buf[i]; i++)
942 if (int_buf[i] == '`') {
943 for (j = i + 1; int_buf[j]; j++)
944 if (int_buf[j] == '`') {
945 collapse_pos(i, j + 1);
950 /* not found close ` - command mode, collapse all previous */
951 collapse_pos(0, i + 1);
954 i--; /* hack incremet */
957 /* collapse (command...(command...)...) or {command...{command...}...} */
958 c = 0; /* "recursive" level */
960 for (i = 0; int_buf[i]; i++)
961 if (int_buf[i] == '(' || int_buf[i] == '{') {
962 if (int_buf[i] == '(')
966 collapse_pos(0, i + 1);
967 i = -1; /* hack incremet */
969 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
970 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
971 if (int_buf[i] == ')')
975 collapse_pos(0, i + 1);
976 i = -1; /* hack incremet */
979 /* skip first not quote space */
980 for (i = 0; int_buf[i]; i++)
981 if (int_buf[i] != ' ')
986 /* set find mode for completion */
987 command_mode = FIND_EXE_ONLY;
988 for (i = 0; int_buf[i]; i++)
989 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
990 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
991 && strncmp(&matchBuf[pos_buf[0]], "cd", 2) == 0)
992 command_mode = FIND_DIR_ONLY;
994 command_mode = FIND_FILE_ONLY;
999 for (i = 0; int_buf[i]; i++);
1000 /* find last word */
1001 for (--i; i >= 0; i--) {
1003 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
1004 collapse_pos(0, i + 1);
1008 /* skip first not quoted '\'' or '"' */
1009 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
1010 /* collapse quote or unquote // or /~ */
1011 while ((int_buf[i] & ~QUOT) == '/' && (
1012 (int_buf[i + 1] & ~QUOT) == '/'
1013 || (int_buf[i + 1] & ~QUOT) ==
1018 /* set only match and destroy quotes */
1020 for (i = 0; pos_buf[i] >= 0; i++) {
1021 matchBuf[i] = matchBuf[pos_buf[i]];
1025 /* old lenght matchBuf with quotes symbols */
1026 *len_with_quotes = j ? j - pos_buf[0] : 0;
1028 return command_mode;
1032 static void input_tab(int *lastWasTab)
1034 /* Do TAB completion */
1035 static int num_matches;
1036 static char **matches;
1038 if (lastWasTab == 0) { /* free all memory */
1040 while (num_matches > 0)
1041 free(matches[--num_matches]);
1043 matches = (char **) NULL;
1047 if (*lastWasTab == FALSE) {
1051 char matchBuf[BUFSIZ];
1055 *lastWasTab = TRUE; /* flop trigger */
1057 /* Make a local copy of the string -- up
1058 * to the position of the cursor */
1059 tmp = strncpy(matchBuf, command_ps, cursor);
1062 find_type = find_match(matchBuf, &recalc_pos);
1064 /* Free up any memory already allocated */
1067 #ifdef BB_FEATURE_SH_USERNAME_COMPLETION
1068 /* If the word starts with `~' and there is no slash in the word,
1069 * then try completing this word as a username. */
1071 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1072 matches = username_tab_completion(matchBuf, &num_matches);
1074 /* Try to match any executable in our path and everything
1075 * in the current working directory that matches. */
1078 exe_n_cwd_tab_completion(matchBuf, &num_matches,
1081 /* Did we find exactly one match? */
1082 if (!matches || num_matches > 1) {
1087 return; /* not found */
1089 qsort(matches, num_matches, sizeof(char *), match_compare);
1091 /* find minimal match */
1092 tmp = xstrdup(matches[0]);
1093 for (tmp1 = tmp; *tmp1; tmp1++)
1094 for (len_found = 1; len_found < num_matches; len_found++)
1095 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1099 if (*tmp == 0) { /* have unique */
1103 } else { /* one match */
1105 /* for next completion current found */
1106 *lastWasTab = FALSE;
1109 len_found = strlen(tmp);
1110 /* have space to placed match? */
1111 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1113 /* before word for match */
1114 command_ps[cursor - recalc_pos] = 0;
1115 /* save tail line */
1116 strcpy(matchBuf, command_ps + cursor);
1118 strcat(command_ps, tmp);
1120 strcat(command_ps, matchBuf);
1121 /* back to begin word for match */
1122 input_backward(recalc_pos);
1124 recalc_pos = cursor + len_found;
1126 len = strlen(command_ps);
1127 /* write out the matched command */
1129 input_backward(cursor - recalc_pos);
1131 if (tmp != matches[0])
1134 /* Ok -- the last char was a TAB. Since they
1135 * just hit TAB again, print a list of all the
1136 * available choices... */
1137 if (matches && num_matches > 0) {
1139 int sav_cursor = cursor; /* change goto_new_line() */
1141 /* Go to the next line */
1143 for (i = 0, col = 0; i < num_matches; i++) {
1144 l = strlen(matches[i]);
1147 printf("%-14s ", matches[i]);
1154 col -= (col / cmdedit_termw) * cmdedit_termw;
1155 if (col > 60 && matches[i + 1] != NULL) {
1160 /* Go to the next line and rewrite */
1162 redraw(0, len - sav_cursor);
1166 #endif /* BB_FEATURE_SH_TAB_COMPLETION */
1168 static void get_previous_history(struct history **hp, struct history *p)
1172 (*hp)->s = xstrdup(command_ps);
1176 static inline void get_next_history(struct history **hp)
1178 get_previous_history(hp, (*hp)->n);
1188 * This function is used to grab a character buffer
1189 * from the input file descriptor and allows you to
1190 * a string with full command editing (sortof like
1193 * The following standard commands are not implemented:
1194 * ESC-b -- Move back one word
1195 * ESC-f -- Move forward one word
1196 * ESC-d -- Delete back one word
1197 * ESC-h -- Delete forward one word
1198 * CTL-t -- Transpose two characters
1200 * Furthermore, the "vi" command editing keys are not implemented.
1203 extern void cmdedit_read_input(char *prompt, char command[BUFSIZ])
1206 int inputFd = fileno(stdin);
1209 int lastWasTab = FALSE;
1211 struct history *hp = his_end;
1213 /* prepare before init handlers */
1214 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1216 command_ps = command;
1218 if (new_settings.c_cc[VMIN] == 0) { /* first call */
1220 getTermSettings(inputFd, (void *) &initial_settings);
1221 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1223 new_settings.c_cc[VMIN] = 1;
1224 new_settings.c_cc[VTIME] = 0;
1225 new_settings.c_cc[VINTR] = _POSIX_VDISABLE; /* Turn off CTRL-C, so we can trap it */
1226 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1227 new_settings.c_lflag &= ~(ECHO | ECHOCTL | ECHONL); /* Turn off echoing */
1232 setTermSettings(inputFd, (void *) &new_settings);
1233 handlers_sets |= SET_RESET_TERM;
1235 /* Print out the command prompt */
1236 parse_prompt(prompt);
1237 /* Now initialize things */
1242 fflush(stdout); /* buffered out to fast */
1244 if (read(inputFd, &c, 1) < 1)
1255 /* Control-a -- Beginning of line */
1256 input_backward(cursor);
1259 /* Control-b -- Move back one character */
1263 /* Control-c -- stop gathering input */
1265 /* Link into lash to reset context to 0 on ^C and such */
1268 /* Go to the next line */
1274 /* Control-d -- Delete one character, or exit
1275 * if the len=0 and no chars to delete */
1278 clean_up_and_die(0);
1284 /* Control-e -- End of line */
1288 /* Control-f -- Move forward one character */
1293 /* Control-h and DEL */
1297 #ifdef BB_FEATURE_SH_TAB_COMPLETION
1298 input_tab(&lastWasTab);
1302 /* Control-n -- Get next command in history */
1303 if (hp && hp->n && hp->n->s) {
1304 get_next_history(&hp);
1311 /* Control-p -- Get previous command from history */
1313 get_previous_history(&hp, hp->p);
1320 /* Control-U -- Clear line before cursor */
1322 strcpy(command, command + cursor);
1323 redraw(cmdedit_y, len -= cursor);
1328 /* escape sequence follows */
1329 if (read(inputFd, &c, 1) < 1)
1331 /* different vt100 emulations */
1332 if (c == '[' || c == 'O') {
1333 if (read(inputFd, &c, 1) < 1)
1337 #ifdef BB_FEATURE_SH_TAB_COMPLETION
1338 case '\t': /* Alt-Tab */
1340 input_tab(&lastWasTab);
1344 /* Up Arrow -- Get previous command from history */
1346 get_previous_history(&hp, hp->p);
1353 /* Down Arrow -- Get next command in history */
1354 if (hp && hp->n && hp->n->s) {
1355 get_next_history(&hp);
1362 /* Rewrite the line with the selected history item */
1364 /* change command */
1365 len = strlen(strcpy(command, hp->s));
1366 /* redraw and go to end line */
1367 redraw(cmdedit_y, 0);
1370 /* Right Arrow -- Move forward one character */
1374 /* Left Arrow -- Move back one character */
1384 input_backward(cursor);
1392 if (!(c >= '1' && c <= '9'))
1396 if (c >= '1' && c <= '9')
1398 if (read(inputFd, &c, 1) < 1)
1404 default: /* If it's regular input, do the normal thing */
1405 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1406 /* Control-V -- Add non-printable symbol */
1408 if (read(inputFd, &c, 1) < 1)
1416 if (!isprint(c)) /* Skip non-printable characters */
1419 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1424 if (cursor == (len - 1)) { /* Append if at the end of the line */
1425 *(command + cursor) = c;
1426 *(command + cursor + 1) = 0;
1427 cmdedit_set_out_char(0);
1428 } else { /* Insert otherwise */
1431 memmove(command + sc + 1, command + sc, len - sc);
1432 *(command + sc) = c;
1434 /* rewrite from cursor */
1436 /* to prev x pos + 1 */
1437 input_backward(cursor - sc);
1442 if (break_out) /* Enter is the command terminator, no more input. */
1449 setTermSettings(inputFd, (void *) &initial_settings);
1450 handlers_sets &= ~SET_RESET_TERM;
1452 /* Handle command history log */
1453 if (len) { /* no put empty line */
1455 struct history *h = his_end;
1458 ss = xstrdup(command); /* duplicate */
1461 /* No previous history -- this memory is never freed */
1462 h = his_front = xmalloc(sizeof(struct history));
1463 h->n = xmalloc(sizeof(struct history));
1473 /* Add a new history command -- this memory is never freed */
1474 h->n = xmalloc(sizeof(struct history));
1482 /* After max history, remove the oldest command */
1483 if (history_counter >= MAX_HISTORY) {
1485 struct history *p = his_front->n;
1495 #if defined(BB_FEATURE_BASH_STYLE_PROMT)
1499 command[len++] = '\n'; /* set '\n' */
1501 #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_SH_TAB_COMPLETION)
1502 input_tab(0); /* strong free */
1504 #if defined(BB_FEATURE_BASH_STYLE_PROMT)
1505 free(cmdedit_prompt);
1511 /* Undo the effects of cmdedit_init(). */
1512 extern void cmdedit_terminate(void)
1514 cmdedit_reset_term();
1515 if ((handlers_sets & SET_TERM_HANDLERS) != 0) {
1516 signal(SIGKILL, SIG_DFL);
1517 signal(SIGINT, SIG_DFL);
1518 signal(SIGQUIT, SIG_DFL);
1519 signal(SIGTERM, SIG_DFL);
1520 signal(SIGWINCH, SIG_DFL);
1521 handlers_sets &= ~SET_TERM_HANDLERS;
1525 #endif /* BB_FEATURE_SH_COMMAND_EDITING */
1530 unsigned int shell_context;
1532 int main(int argc, char **argv)
1536 #if defined(BB_FEATURE_BASH_STYLE_PROMT)
1537 "\\[\\033[32;1m\\]\\u@\\[\\033[33;1m\\]\\h:\
1538 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1539 \\!\\[\\033[36;1m\\]\\$ \\[\\033[0m\\]";
1546 cmdedit_read_input(prompt, buff);
1547 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1548 } while (shell_context);
1549 printf("*** cmdedit_read_input() detect ^C\n");