1 /* vi: set sw=4 ts=4: */
3 * Termios command line History and Editting.
5 * Copyright (c) 1986-2001 may safely be consumed by a BSD or GPL license.
6 * Written by: Vladimir Oleynik <vodz@usa.net>
9 * Adam Rogoyski <rogoyski@cs.utexas.edu>
10 * Dave Cinege <dcinege@psychosis.com>
11 * Jakub Jelinek (c) 1995
12 * Erik Andersen <andersee@debian.org> (Majorly adjusted for busybox)
14 * This code is 'as is' with no warranty.
21 Terminal key codes are not extensive, and more will probably
22 need to be added. This version was created on Debian GNU/Linux 2.x.
23 Delete, Backspace, Home, End, and the arrow keys were tested
24 to work in an Xterm and console. Ctrl-A also works as Home.
25 Ctrl-E also works as End.
27 Small bugs (simple effect):
28 - not true viewing if terminal size (x*y symbols) less
29 size (prompt + editor`s line + 2 symbols)
30 - not true viewing if length prompt less terminal width
41 #include <sys/ioctl.h>
54 #define BB_FEATURE_COMMAND_EDITING
55 #define BB_FEATURE_COMMAND_TAB_COMPLETION
56 #define BB_FEATURE_COMMAND_USERNAME_COMPLETION
57 #define BB_FEATURE_NONPRINTABLE_INVERSE_PUT
58 #undef BB_FEATURE_SIMPLE_PROMPT
59 #define BB_FEATURE_CLEAN_UP
72 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
77 #ifdef BB_FEATURE_COMMAND_EDITING
79 #ifndef BB_FEATURE_COMMAND_TAB_COMPLETION
80 #undef BB_FEATURE_COMMAND_USERNAME_COMPLETION
83 #if defined(BB_FEATURE_COMMAND_USERNAME_COMPLETION) || !defined(BB_FEATURE_SIMPLE_PROMPT)
84 #define BB_FEATURE_GETUSERNAME_AND_HOMEDIR
87 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
89 #include "pwd_grp/pwd.h"
93 #endif /* advanced FEATURES */
97 void *xrealloc(void *old, size_t size)
99 return realloc(old, size);
102 void *xmalloc(size_t size)
106 char *xstrdup(const char *s)
111 void *xcalloc(size_t size, size_t se)
113 return calloc(size, se);
116 #define error_msg(s, d) fprintf(stderr, s, d)
126 /* Maximum length of the linked list for the command line history */
127 static const int MAX_HISTORY = 15;
129 /* First element in command line list */
130 static struct history *his_front = NULL;
132 /* Last element in command line list */
133 static struct history *his_end = NULL;
136 /* ED: sparc termios is broken: revert back to old termio handling. */
140 # define termios termio
141 # define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
142 # define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
144 # include <termios.h>
145 # define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
146 # define getTermSettings(fd,argp) tcgetattr(fd, argp);
149 /* Current termio and the previous termio before starting sh */
150 static struct termios initial_settings, new_settings;
153 #ifndef _POSIX_VDISABLE
154 #define _POSIX_VDISABLE '\0'
159 volatile int cmdedit_termw = 80; /* actual terminal width */
160 static int history_counter = 0; /* Number of commands in history list */
162 volatile int handlers_sets = 0; /* Set next bites: */
165 SET_ATEXIT = 1, /* when atexit() has been called
166 and get euid,uid,gid to fast compare */
167 SET_TERM_HANDLERS = 2, /* set many terminates signal handlers */
168 SET_WCHG_HANDLERS = 4, /* winchg signal handler */
169 SET_RESET_TERM = 8, /* if the terminal needs to be reset upon exit */
173 static int cmdedit_x; /* real x terminal position */
174 static int cmdedit_y; /* pseudoreal y terminal position */
175 static int cmdedit_prmt_len; /* lenght prompt without colores string */
177 static int cursor; /* required global for signal handler */
178 static int len; /* --- "" - - "" - -"- --""-- --""--- */
179 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
181 #ifdef BB_FEATURE_SIMPLE_PROMPT
184 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
186 /* Link into lash to reset context to 0 on ^C and such */
187 extern unsigned int shell_context;
190 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
191 static char *user_buf = "";
192 static char *home_pwd_buf = "";
196 #ifndef BB_FEATURE_SIMPLE_PROMPT
197 static char *hostname_buf = "";
198 static int num_ok_lines = 1;
202 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
204 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
211 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
214 static void cmdedit_setwidth(int w, int redraw_flg);
216 static void win_changed(int nsig)
218 struct winsize win = { 0, 0, 0, 0 };
219 static __sighandler_t previous_SIGWINCH_handler; /* for reset */
221 /* emulate || signal call */
222 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
223 ioctl(0, TIOCGWINSZ, &win);
224 if (win.ws_col > 0) {
225 cmdedit_setwidth(win.ws_col, nsig == SIGWINCH);
228 /* Unix not all standart in recall signal */
230 if (nsig == -SIGWINCH) /* save previous handler */
231 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
232 else if (nsig == SIGWINCH) /* signaled called handler */
233 signal(SIGWINCH, win_changed); /* set for next call */
235 /* set previous handler */
236 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
239 static void cmdedit_reset_term(void)
241 if ((handlers_sets & SET_RESET_TERM) != 0) {
242 /* sparc and other have broken termios support: use old termio handling. */
243 setTermSettings(fileno(stdin), (void *) &initial_settings);
244 handlers_sets &= ~SET_RESET_TERM;
246 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
247 /* reset SIGWINCH handler to previous (default) */
249 handlers_sets &= ~SET_WCHG_HANDLERS;
252 #ifdef BB_FEATURE_CLEAN_UP
256 while (his_front != his_end) {
267 /* special for recount position for scroll and remove terminal margin effect */
268 static void cmdedit_set_out_char(int next_char)
271 int c = command_ps[cursor];
274 c = ' '; /* destroy end char? */
275 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
276 if (!isprint(c)) { /* Inverse put non-printable characters */
277 if (((unsigned char) c) >= 128)
279 if (((unsigned char) c) < ' ')
283 printf("\033[7m%c\033[0m", c);
287 if (++cmdedit_x >= cmdedit_termw) {
288 /* terminal is scrolled down */
294 /* destroy "(auto)margin" */
301 /* Move to end line. Bonus: rewrite line from cursor */
302 static void input_end(void)
305 cmdedit_set_out_char(0);
308 /* Go to the next line */
309 static void goto_new_line(void)
317 static inline void out1str(const char *s)
321 static inline void beep(void)
326 /* Move back one charactor */
327 /* special for slow terminal */
328 static void input_backward(int num)
332 cursor -= num; /* new cursor (in command, not terminal) */
334 if (cmdedit_x >= num) { /* no to up line */
341 printf("\033[%dD", num);
346 putchar('\r'); /* back to first terminal pos. */
347 num -= cmdedit_x; /* set previous backward */
349 count_y = 1 + num / cmdedit_termw;
350 printf("\033[%dA", count_y);
351 cmdedit_y -= count_y;
352 /* require forward after uping */
353 cmdedit_x = cmdedit_termw * count_y - num;
354 printf("\033[%dC", cmdedit_x); /* set term cursor */
358 static void put_prompt(void)
360 out1str(cmdedit_prompt);
361 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
365 #ifdef BB_FEATURE_SIMPLE_PROMPT
366 static void parse_prompt(const char *prmt_ptr)
368 cmdedit_prompt = prmt_ptr;
369 cmdedit_prmt_len = strlen(prmt_ptr);
373 static void add_to_prompt(char **prmt_mem_ptr, int *alm,
374 int *prmt_len, const char *addb)
376 *prmt_len += strlen(addb);
377 if (*alm < (*prmt_len) + 1) {
378 *alm = (*prmt_len) + 1;
379 *prmt_mem_ptr = xrealloc(*prmt_mem_ptr, *alm);
381 strcat(*prmt_mem_ptr, addb);
384 static void parse_prompt(const char *prmt_ptr)
386 int alm = strlen(prmt_ptr) + 1; /* supposedly require memory */
389 int flg_not_length = '[';
390 char *prmt_mem_ptr = xstrdup(prmt_ptr);
391 char pwd_buf[PATH_MAX + 1];
406 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
408 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, user_buf);
412 if (hostname_buf[0] == 0) {
413 hostname_buf = xcalloc(256, 1);
414 if (gethostname(hostname_buf, 255) < 0) {
415 strcpy(hostname_buf, "?");
417 char *s = strchr(hostname_buf, '.');
423 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len,
427 c = my_euid == 0 ? '#' : '$';
429 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
431 if (pwd_buf[0] == 0) {
434 getcwd(pwd_buf, PATH_MAX);
435 l = strlen(home_pwd_buf);
436 if (home_pwd_buf[0] != 0 &&
437 strncmp(home_pwd_buf, pwd_buf, l) == 0) {
438 strcpy(pwd_buf + 1, pwd_buf + l);
442 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, pwd_buf);
446 snprintf(buf, sizeof(buf), "%d", num_ok_lines);
447 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
450 case 'E': /* \e \E = \033 */
470 for (l = 0; l < 3;) {
472 buf[l++] = *prmt_ptr;
474 ho = strtol(buf, &eho, c == 'x' ? 16 : 8);
475 if (ho > UCHAR_MAX || (eho - buf) < l) {
482 ho = strtol(buf, 0, c == 'x' ? 16 : 8);
483 c = ho == 0 ? '?' : (char) ho;
488 if (c == flg_not_length) {
489 flg_not_length = flg_not_length == '[' ? ']' : '[';
497 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
498 if (flg_not_length == ']')
502 cmdedit_prmt_len = prmt_len - sub_len;
503 cmdedit_prompt = prmt_ptr;
505 cmdedit_prompt = prmt_mem_ptr;
506 cmdedit_prmt_len = strlen(cmdedit_prompt);
512 /* draw promt, editor line, and clear tail */
513 static void redraw(int y, int back_cursor)
515 if (y > 0) /* up to start y */
516 printf("\033[%dA", y);
517 cmdedit_y = 0; /* new quasireal y */
520 input_end(); /* rewrite */
521 printf("\033[J"); /* destroy tail after cursor */
522 input_backward(back_cursor);
525 /* Delete the char in front of the cursor */
526 static void input_delete(void)
533 strcpy(command_ps + j, command_ps + j + 1);
535 input_end(); /* rewtite new line */
536 cmdedit_set_out_char(0); /* destroy end char */
537 input_backward(cursor - j); /* back to old pos cursor */
540 /* Delete the char in back of the cursor */
541 static void input_backspace(void)
550 /* Move forward one charactor */
551 static void input_forward(void)
554 cmdedit_set_out_char(command_ps[cursor + 1]);
558 static void clean_up_and_die(int sig)
562 exit(EXIT_SUCCESS); /* cmdedit_reset_term() called in atexit */
563 cmdedit_reset_term();
566 static void cmdedit_setwidth(int w, int redraw_flg)
568 cmdedit_termw = cmdedit_prmt_len + 2;
569 if (w <= cmdedit_termw) {
570 cmdedit_termw = cmdedit_termw % w;
572 if (w > cmdedit_termw) {
576 /* new y for current cursor */
577 int new_y = (cursor + cmdedit_prmt_len) / w;
580 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
586 extern void cmdedit_init(void)
588 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
589 /* emulate usage handler to set handler and call yours work */
590 win_changed(-SIGWINCH);
591 handlers_sets |= SET_WCHG_HANDLERS;
594 if ((handlers_sets & SET_ATEXIT) == 0) {
595 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
596 struct passwd *entry;
599 entry = getpwuid(my_euid);
601 user_buf = xstrdup(entry->pw_name);
602 home_pwd_buf = xstrdup(entry->pw_dir);
606 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
608 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
613 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
614 handlers_sets |= SET_ATEXIT;
615 atexit(cmdedit_reset_term); /* be sure to do this only once */
618 if ((handlers_sets & SET_TERM_HANDLERS) == 0) {
619 signal(SIGKILL, clean_up_and_die);
620 signal(SIGINT, clean_up_and_die);
621 signal(SIGQUIT, clean_up_and_die);
622 signal(SIGTERM, clean_up_and_die);
623 handlers_sets |= SET_TERM_HANDLERS;
628 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
630 static int is_execute(const struct stat *st)
632 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
633 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
634 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
635 (st->st_mode & S_IXOTH)) return TRUE;
639 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
641 static char **username_tab_completion(char *ud, int *num_matches)
643 struct passwd *entry;
648 ud++; /* ~user/... to user/... */
649 userlen = strlen(ud);
651 if (num_matches == 0) { /* "~/..." or "~user/..." */
652 char *sav_ud = ud - 1;
655 if (*ud == '/') { /* "~/..." */
659 temp = strchr(ud, '/');
660 *temp = 0; /* ~user\0 */
661 entry = getpwnam(ud);
662 *temp = '/'; /* restore ~user/... */
665 home = entry->pw_dir;
668 if ((userlen + strlen(home) + 1) < BUFSIZ) {
669 char temp2[BUFSIZ]; /* argument size */
672 sprintf(temp2, "%s%s", home, ud);
673 strcpy(sav_ud, temp2);
676 return 0; /* void, result save to argument :-) */
679 char **matches = (char **) NULL;
684 while ((entry = getpwent()) != NULL) {
685 /* Null usernames should result in all users as possible completions. */
686 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
688 temp = xmalloc(3 + strlen(entry->pw_name));
689 sprintf(temp, "~%s/", entry->pw_name);
690 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
692 matches[nm++] = temp;
701 #endif /* BB_FEATURE_COMMAND_USERNAME_COMPLETION */
709 static int path_parse(char ***p, int flags)
715 /* if not setenv PATH variable, to search cur dir "." */
716 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
717 /* PATH=<empty> or PATH=:<empty> */
718 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
726 npth++; /* count words is + 1 count ':' */
727 tmp = strchr(tmp, ':');
730 break; /* :<empty> */
735 *p = xmalloc(npth * sizeof(char *));
738 (*p)[0] = xstrdup(tmp);
739 npth = 1; /* count words is + 1 count ':' */
742 tmp = strchr(tmp, ':');
744 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
746 break; /* :<empty> */
749 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
755 static char *add_quote_for_spec_chars(char *found)
758 char *s = xmalloc((strlen(found) + 1) * 2);
761 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
769 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
777 int nm = *num_matches;
780 char **paths = path1;
783 char found[BUFSIZ + 4 + PATH_MAX];
784 char *pfind = strrchr(command, '/');
789 /* no dir, if flags==EXE_ONLY - get paths, else "." */
790 npaths = path_parse(&paths, type);
794 /* save for change */
795 strcpy(dirbuf, command);
797 dirbuf[(pfind - command) + 1] = 0;
798 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
799 if (dirbuf[0] == '~') /* ~/... or ~user/... */
800 username_tab_completion(dirbuf, 0);
802 /* "strip" dirname in command */
806 npaths = 1; /* only 1 dir */
809 for (i = 0; i < npaths; i++) {
811 dir = opendir(paths[i]);
812 if (!dir) /* Don't print an error */
815 while ((next = readdir(dir)) != NULL) {
816 const char *str_merge = "%s/%s";
817 char *str_found = next->d_name;
820 if (strncmp(str_found, pfind, strlen(pfind)))
822 /* not see .name without .match */
823 if (*str_found == '.' && *pfind == 0) {
824 if (*paths[i] == '/' && paths[i][1] == 0
825 && str_found[1] == 0) str_found = ""; /* only "/" */
829 if (paths[i][strlen(paths[i]) - 1] == '/')
831 sprintf(found, str_merge, paths[i], str_found);
832 /* hmm, remover in progress? */
833 if (stat(found, &st) < 0)
835 /* find with dirs ? */
836 if (paths[i] != dirbuf)
837 strcpy(found, next->d_name); /* only name */
838 if (S_ISDIR(st.st_mode)) {
839 /* name is directory */
840 /* algorithmic only "/" ? */
843 str_found = add_quote_for_spec_chars(found);
845 /* not put found file if search only dirs for cd */
846 if (type == FIND_DIR_ONLY)
848 str_found = add_quote_for_spec_chars(found);
849 if (type == FIND_FILE_ONLY ||
850 (type == FIND_EXE_ONLY && is_execute(&st) == TRUE))
851 strcat(str_found, " ");
853 /* Add it to the list */
854 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
856 matches[nm++] = str_found;
860 if (paths != path1) {
861 free(paths[0]); /* allocated memory only in first member */
868 static int match_compare(const void *a, const void *b)
870 return strcmp(*(char **) a, *(char **) b);
875 #define QUOT (UCHAR_MAX+1)
877 #define collapse_pos(is, in) { \
878 memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \
879 memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); }
881 static int find_match(char *matchBuf, int *len_with_quotes)
886 int int_buf[BUFSIZ + 1];
887 int pos_buf[BUFSIZ + 1];
889 /* set to integer dimension characters and own positions */
891 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
892 if (int_buf[i] == 0) {
893 pos_buf[i] = -1; /* indicator end line */
899 /* mask \+symbol and convert '\t' to ' ' */
900 for (i = j = 0; matchBuf[i]; i++, j++)
901 if (matchBuf[i] == '\\') {
902 collapse_pos(j, j + 1);
905 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
906 if (matchBuf[i] == '\t') /* algorithm equivalent */
907 int_buf[j] = ' ' | QUOT;
910 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
911 else if (matchBuf[i] == '\t')
915 /* mask "symbols" or 'symbols' */
917 for (i = 0; int_buf[i]; i++) {
919 if (c == '\'' || c == '"') {
928 } else if (c2 != 0 && c != '$')
932 /* skip commands with arguments if line have commands delimiters */
933 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
934 for (i = 0; int_buf[i]; i++) {
937 j = i ? int_buf[i - 1] : -1;
939 if (c == ';' || c == '&' || c == '|') {
940 command_mode = 1 + (c == c2);
942 if (j == '>' || j == '<')
944 } else if (c == '|' && j == '>')
948 collapse_pos(0, i + command_mode);
949 i = -1; /* hack incremet */
952 /* collapse `command...` */
953 for (i = 0; int_buf[i]; i++)
954 if (int_buf[i] == '`') {
955 for (j = i + 1; int_buf[j]; j++)
956 if (int_buf[j] == '`') {
957 collapse_pos(i, j + 1);
962 /* not found close ` - command mode, collapse all previous */
963 collapse_pos(0, i + 1);
966 i--; /* hack incremet */
969 /* collapse (command...(command...)...) or {command...{command...}...} */
970 c = 0; /* "recursive" level */
972 for (i = 0; int_buf[i]; i++)
973 if (int_buf[i] == '(' || int_buf[i] == '{') {
974 if (int_buf[i] == '(')
978 collapse_pos(0, i + 1);
979 i = -1; /* hack incremet */
981 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
982 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
983 if (int_buf[i] == ')')
987 collapse_pos(0, i + 1);
988 i = -1; /* hack incremet */
991 /* skip first not quote space */
992 for (i = 0; int_buf[i]; i++)
993 if (int_buf[i] != ' ')
998 /* set find mode for completion */
999 command_mode = FIND_EXE_ONLY;
1000 for (i = 0; int_buf[i]; i++)
1001 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
1002 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
1003 && matchBuf[pos_buf[0]]=='c'
1004 && matchBuf[pos_buf[1]]=='d' )
1005 command_mode = FIND_DIR_ONLY;
1007 command_mode = FIND_FILE_ONLY;
1012 for (i = 0; int_buf[i]; i++);
1013 /* find last word */
1014 for (--i; i >= 0; i--) {
1016 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
1017 collapse_pos(0, i + 1);
1021 /* skip first not quoted '\'' or '"' */
1022 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
1023 /* collapse quote or unquote // or /~ */
1024 while ((int_buf[i] & ~QUOT) == '/' &&
1025 ((int_buf[i + 1] & ~QUOT) == '/'
1026 || (int_buf[i + 1] & ~QUOT) == '~')) {
1033 /* set only match and destroy quotes */
1035 for (i = 0; pos_buf[i] >= 0; i++) {
1036 matchBuf[i] = matchBuf[pos_buf[i]];
1040 /* old lenght matchBuf with quotes symbols */
1041 *len_with_quotes = j ? j - pos_buf[0] : 0;
1043 return command_mode;
1047 static void input_tab(int *lastWasTab)
1049 /* Do TAB completion */
1050 static int num_matches;
1051 static char **matches;
1053 if (lastWasTab == 0) { /* free all memory */
1055 while (num_matches > 0)
1056 free(matches[--num_matches]);
1058 matches = (char **) NULL;
1062 if (*lastWasTab == FALSE) {
1066 char matchBuf[BUFSIZ];
1070 *lastWasTab = TRUE; /* flop trigger */
1072 /* Make a local copy of the string -- up
1073 * to the position of the cursor */
1074 tmp = strncpy(matchBuf, command_ps, cursor);
1077 find_type = find_match(matchBuf, &recalc_pos);
1079 /* Free up any memory already allocated */
1082 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
1083 /* If the word starts with `~' and there is no slash in the word,
1084 * then try completing this word as a username. */
1086 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1087 matches = username_tab_completion(matchBuf, &num_matches);
1089 /* Try to match any executable in our path and everything
1090 * in the current working directory that matches. */
1093 exe_n_cwd_tab_completion(matchBuf, &num_matches,
1096 /* Did we find exactly one match? */
1097 if (!matches || num_matches > 1) {
1102 return; /* not found */
1104 qsort(matches, num_matches, sizeof(char *), match_compare);
1106 /* find minimal match */
1107 tmp = xstrdup(matches[0]);
1108 for (tmp1 = tmp; *tmp1; tmp1++)
1109 for (len_found = 1; len_found < num_matches; len_found++)
1110 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1114 if (*tmp == 0) { /* have unique */
1118 } else { /* one match */
1120 /* for next completion current found */
1121 *lastWasTab = FALSE;
1124 len_found = strlen(tmp);
1125 /* have space to placed match? */
1126 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1128 /* before word for match */
1129 command_ps[cursor - recalc_pos] = 0;
1130 /* save tail line */
1131 strcpy(matchBuf, command_ps + cursor);
1133 strcat(command_ps, tmp);
1135 strcat(command_ps, matchBuf);
1136 /* back to begin word for match */
1137 input_backward(recalc_pos);
1139 recalc_pos = cursor + len_found;
1141 len = strlen(command_ps);
1142 /* write out the matched command */
1144 input_backward(cursor - recalc_pos);
1146 if (tmp != matches[0])
1149 /* Ok -- the last char was a TAB. Since they
1150 * just hit TAB again, print a list of all the
1151 * available choices... */
1152 if (matches && num_matches > 0) {
1154 int sav_cursor = cursor; /* change goto_new_line() */
1156 /* Go to the next line */
1158 for (i = 0, col = 0; i < num_matches; i++) {
1159 l = strlen(matches[i]);
1162 printf("%-14s ", matches[i]);
1169 col -= (col / cmdedit_termw) * cmdedit_termw;
1170 if (col > 60 && matches[i + 1] != NULL) {
1175 /* Go to the next line and rewrite */
1177 redraw(0, len - sav_cursor);
1181 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
1183 static void get_previous_history(struct history **hp, struct history *p)
1187 (*hp)->s = xstrdup(command_ps);
1191 static inline void get_next_history(struct history **hp)
1193 get_previous_history(hp, (*hp)->n);
1203 * This function is used to grab a character buffer
1204 * from the input file descriptor and allows you to
1205 * a string with full command editing (sortof like
1208 * The following standard commands are not implemented:
1209 * ESC-b -- Move back one word
1210 * ESC-f -- Move forward one word
1211 * ESC-d -- Delete back one word
1212 * ESC-h -- Delete forward one word
1213 * CTL-t -- Transpose two characters
1215 * Furthermore, the "vi" command editing keys are not implemented.
1219 extern void cmdedit_read_input(char *prompt, char command[BUFSIZ])
1222 int inputFd = fileno(stdin);
1225 int lastWasTab = FALSE;
1227 struct history *hp = his_end;
1229 /* prepare before init handlers */
1230 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1232 command_ps = command;
1234 if (new_settings.c_cc[VMIN] == 0) { /* first call */
1236 getTermSettings(inputFd, (void *) &initial_settings);
1237 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1239 new_settings.c_cc[VMIN] = 1;
1240 new_settings.c_cc[VTIME] = 0;
1241 /* Turn off CTRL-C, so we can trap it */
1242 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1243 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1244 /* Turn off echoing */
1245 new_settings.c_lflag &= ~(ECHO | ECHOCTL | ECHONL);
1250 setTermSettings(inputFd, (void *) &new_settings);
1251 handlers_sets |= SET_RESET_TERM;
1253 /* Print out the command prompt */
1254 parse_prompt(prompt);
1255 /* Now initialize things */
1260 fflush(stdout); /* buffered out to fast */
1262 if (read(inputFd, &c, 1) < 1)
1273 /* Control-a -- Beginning of line */
1274 input_backward(cursor);
1277 /* Control-b -- Move back one character */
1281 /* Control-c -- stop gathering input */
1283 /* Link into lash to reset context to 0 on ^C and such */
1286 /* Go to the next line */
1292 /* Control-d -- Delete one character, or exit
1293 * if the len=0 and no chars to delete */
1296 clean_up_and_die(0);
1302 /* Control-e -- End of line */
1306 /* Control-f -- Move forward one character */
1311 /* Control-h and DEL */
1315 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1316 input_tab(&lastWasTab);
1320 /* Control-n -- Get next command in history */
1321 if (hp && hp->n && hp->n->s) {
1322 get_next_history(&hp);
1329 /* Control-p -- Get previous command from history */
1331 get_previous_history(&hp, hp->p);
1338 /* Control-U -- Clear line before cursor */
1340 strcpy(command, command + cursor);
1341 redraw(cmdedit_y, len -= cursor);
1346 /* escape sequence follows */
1347 if (read(inputFd, &c, 1) < 1)
1349 /* different vt100 emulations */
1350 if (c == '[' || c == 'O') {
1351 if (read(inputFd, &c, 1) < 1)
1355 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1356 case '\t': /* Alt-Tab */
1358 input_tab(&lastWasTab);
1362 /* Up Arrow -- Get previous command from history */
1364 get_previous_history(&hp, hp->p);
1371 /* Down Arrow -- Get next command in history */
1372 if (hp && hp->n && hp->n->s) {
1373 get_next_history(&hp);
1380 /* Rewrite the line with the selected history item */
1382 /* change command */
1383 len = strlen(strcpy(command, hp->s));
1384 /* redraw and go to end line */
1385 redraw(cmdedit_y, 0);
1388 /* Right Arrow -- Move forward one character */
1392 /* Left Arrow -- Move back one character */
1402 input_backward(cursor);
1410 if (!(c >= '1' && c <= '9'))
1414 if (c >= '1' && c <= '9')
1416 if (read(inputFd, &c, 1) < 1)
1422 default: /* If it's regular input, do the normal thing */
1423 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1424 /* Control-V -- Add non-printable symbol */
1426 if (read(inputFd, &c, 1) < 1)
1434 if (!isprint(c)) /* Skip non-printable characters */
1437 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1442 if (cursor == (len - 1)) { /* Append if at the end of the line */
1443 *(command + cursor) = c;
1444 *(command + cursor + 1) = 0;
1445 cmdedit_set_out_char(0);
1446 } else { /* Insert otherwise */
1449 memmove(command + sc + 1, command + sc, len - sc);
1450 *(command + sc) = c;
1452 /* rewrite from cursor */
1454 /* to prev x pos + 1 */
1455 input_backward(cursor - sc);
1460 if (break_out) /* Enter is the command terminator, no more input. */
1467 setTermSettings(inputFd, (void *) &initial_settings);
1468 handlers_sets &= ~SET_RESET_TERM;
1470 /* Handle command history log */
1471 if (len) { /* no put empty line */
1473 struct history *h = his_end;
1476 ss = xstrdup(command); /* duplicate */
1479 /* No previous history -- this memory is never freed */
1480 h = his_front = xmalloc(sizeof(struct history));
1481 h->n = xmalloc(sizeof(struct history));
1491 /* Add a new history command -- this memory is never freed */
1492 h->n = xmalloc(sizeof(struct history));
1500 /* After max history, remove the oldest command */
1501 if (history_counter >= MAX_HISTORY) {
1503 struct history *p = his_front->n;
1513 #if !defined(BB_FEATURE_SIMPLE_PROMPT)
1517 command[len++] = '\n'; /* set '\n' */
1519 #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_COMMAND_TAB_COMPLETION)
1520 input_tab(0); /* strong free */
1522 #if !defined(BB_FEATURE_SIMPLE_PROMPT)
1523 free(cmdedit_prompt);
1529 /* Undo the effects of cmdedit_init(). */
1530 extern void cmdedit_terminate(void)
1532 cmdedit_reset_term();
1533 if ((handlers_sets & SET_TERM_HANDLERS) != 0) {
1534 signal(SIGKILL, SIG_DFL);
1535 signal(SIGINT, SIG_DFL);
1536 signal(SIGQUIT, SIG_DFL);
1537 signal(SIGTERM, SIG_DFL);
1538 signal(SIGWINCH, SIG_DFL);
1539 handlers_sets &= ~SET_TERM_HANDLERS;
1543 #endif /* BB_FEATURE_COMMAND_EDITING */
1548 unsigned int shell_context;
1550 int main(int argc, char **argv)
1554 #if !defined(BB_FEATURE_SIMPLE_PROMPT)
1555 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1556 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1557 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1565 cmdedit_read_input(prompt, buff);
1567 if(l > 0 && buff[l-1] == '\n')
1569 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1570 } while (shell_context);
1571 printf("*** cmdedit_read_input() detect ^C\n");