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 #define BB_FEATURE_CLEAN_UP
71 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
76 #ifdef BB_FEATURE_COMMAND_EDITING
78 #ifndef BB_FEATURE_COMMAND_TAB_COMPLETION
79 #undef BB_FEATURE_COMMAND_USERNAME_COMPLETION
82 #if defined(BB_FEATURE_COMMAND_USERNAME_COMPLETION) || !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
83 #define BB_FEATURE_GETUSERNAME_AND_HOMEDIR
86 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
88 #include "pwd_grp/pwd.h"
92 #endif /* advanced FEATURES */
96 void *xrealloc(void *old, size_t size)
98 return realloc(old, size);
101 void *xmalloc(size_t size)
105 char *xstrdup(const char *s)
110 void *xcalloc(size_t size, size_t se)
112 return calloc(size, se);
115 #define error_msg(s, d) fprintf(stderr, s, d)
125 /* Maximum length of the linked list for the command line history */
126 static const int MAX_HISTORY = 15;
128 /* First element in command line list */
129 static struct history *his_front = NULL;
131 /* Last element in command line list */
132 static struct history *his_end = NULL;
135 /* ED: sparc termios is broken: revert back to old termio handling. */
139 # define termios termio
140 # define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
141 # define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
143 # include <termios.h>
144 # define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
145 # define getTermSettings(fd,argp) tcgetattr(fd, argp);
148 /* Current termio and the previous termio before starting sh */
149 static struct termios initial_settings, new_settings;
152 #ifndef _POSIX_VDISABLE
153 #define _POSIX_VDISABLE '\0'
158 volatile int cmdedit_termw = 80; /* actual terminal width */
159 static int history_counter = 0; /* Number of commands in history list */
161 volatile int handlers_sets = 0; /* Set next bites: */
164 SET_ATEXIT = 1, /* when atexit() has been called
165 and get euid,uid,gid to fast compare */
166 SET_TERM_HANDLERS = 2, /* set many terminates signal handlers */
167 SET_WCHG_HANDLERS = 4, /* winchg signal handler */
168 SET_RESET_TERM = 8, /* if the terminal needs to be reset upon exit */
172 static int cmdedit_x; /* real x terminal position */
173 static int cmdedit_y; /* pseudoreal y terminal position */
174 static int cmdedit_prmt_len; /* lenght prompt without colores string */
176 static int cursor; /* required global for signal handler */
177 static int len; /* --- "" - - "" - -"- --""-- --""--- */
178 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
180 #ifdef BB_FEATURE_SH_SIMPLE_PROMPT
183 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
185 /* Link into lash to reset context to 0 on ^C and such */
186 extern unsigned int shell_context;
189 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
190 static char *user_buf = "";
191 static char *home_pwd_buf = "";
195 #ifndef BB_FEATURE_SH_SIMPLE_PROMPT
196 static char *hostname_buf = "";
197 static int num_ok_lines = 1;
201 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
203 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
210 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
213 static void cmdedit_setwidth(int w, int redraw_flg);
215 static void win_changed(int nsig)
217 struct winsize win = { 0, 0, 0, 0 };
218 static __sighandler_t previous_SIGWINCH_handler; /* for reset */
220 /* emulate || signal call */
221 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
222 ioctl(0, TIOCGWINSZ, &win);
223 if (win.ws_col > 0) {
224 cmdedit_setwidth(win.ws_col, nsig == SIGWINCH);
227 /* Unix not all standart in recall signal */
229 if (nsig == -SIGWINCH) /* save previous handler */
230 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
231 else if (nsig == SIGWINCH) /* signaled called handler */
232 signal(SIGWINCH, win_changed); /* set for next call */
234 /* set previous handler */
235 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
238 static void cmdedit_reset_term(void)
240 if ((handlers_sets & SET_RESET_TERM) != 0) {
241 /* sparc and other have broken termios support: use old termio handling. */
242 setTermSettings(fileno(stdin), (void *) &initial_settings);
243 handlers_sets &= ~SET_RESET_TERM;
245 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
246 /* reset SIGWINCH handler to previous (default) */
248 handlers_sets &= ~SET_WCHG_HANDLERS;
251 #ifdef BB_FEATURE_CLEAN_UP
255 while (his_front != his_end) {
266 /* special for recount position for scroll and remove terminal margin effect */
267 static void cmdedit_set_out_char(int next_char)
270 int c = (int)((unsigned char) command_ps[cursor]);
273 c = ' '; /* destroy end char? */
274 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
275 if (!isprint(c)) { /* Inverse put non-printable characters */
282 printf("\033[7m%c\033[0m", c);
286 if (++cmdedit_x >= cmdedit_termw) {
287 /* terminal is scrolled down */
293 /* destroy "(auto)margin" */
300 /* Move to end line. Bonus: rewrite line from cursor */
301 static void input_end(void)
304 cmdedit_set_out_char(0);
307 /* Go to the next line */
308 static void goto_new_line(void)
316 static inline void out1str(const char *s)
320 static inline void beep(void)
325 /* Move back one charactor */
326 /* special for slow terminal */
327 static void input_backward(int num)
331 cursor -= num; /* new cursor (in command, not terminal) */
333 if (cmdedit_x >= num) { /* no to up line */
340 printf("\033[%dD", num);
345 putchar('\r'); /* back to first terminal pos. */
346 num -= cmdedit_x; /* set previous backward */
348 count_y = 1 + num / cmdedit_termw;
349 printf("\033[%dA", count_y);
350 cmdedit_y -= count_y;
351 /* require forward after uping */
352 cmdedit_x = cmdedit_termw * count_y - num;
353 printf("\033[%dC", cmdedit_x); /* set term cursor */
357 static void put_prompt(void)
359 out1str(cmdedit_prompt);
360 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
364 #ifdef BB_FEATURE_SH_SIMPLE_PROMPT
365 static void parse_prompt(const char *prmt_ptr)
367 cmdedit_prompt = prmt_ptr;
368 cmdedit_prmt_len = strlen(prmt_ptr);
372 static void add_to_prompt(char **prmt_mem_ptr, int *alm,
373 int *prmt_len, const char *addb)
375 *prmt_len += strlen(addb);
376 if (*alm < (*prmt_len) + 1) {
377 *alm = (*prmt_len) + 1;
378 *prmt_mem_ptr = xrealloc(*prmt_mem_ptr, *alm);
380 strcat(*prmt_mem_ptr, addb);
383 static void parse_prompt(const char *prmt_ptr)
385 int alm = strlen(prmt_ptr) + 1; /* supposedly require memory */
388 int flg_not_length = '[';
389 char *prmt_mem_ptr = xstrdup(prmt_ptr);
390 char pwd_buf[PATH_MAX + 1];
405 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
407 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, user_buf);
411 if (hostname_buf[0] == 0) {
412 hostname_buf = xcalloc(256, 1);
413 if (gethostname(hostname_buf, 255) < 0) {
414 strcpy(hostname_buf, "?");
416 char *s = strchr(hostname_buf, '.');
422 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len,
426 c = my_euid == 0 ? '#' : '$';
428 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
430 if (pwd_buf[0] == 0) {
433 getcwd(pwd_buf, PATH_MAX);
434 l = strlen(home_pwd_buf);
435 if (home_pwd_buf[0] != 0 &&
436 strncmp(home_pwd_buf, pwd_buf, l) == 0) {
437 strcpy(pwd_buf + 1, pwd_buf + l);
441 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, pwd_buf);
445 snprintf(buf, sizeof(buf), "%d", num_ok_lines);
446 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
449 case 'E': /* \e \E = \033 */
469 for (l = 0; l < 3;) {
471 buf[l++] = *prmt_ptr;
473 ho = strtol(buf, &eho, c == 'x' ? 16 : 8);
474 if (ho > UCHAR_MAX || (eho - buf) < l) {
481 ho = strtol(buf, 0, c == 'x' ? 16 : 8);
482 c = ho == 0 ? '?' : (char) ho;
487 if (c == flg_not_length) {
488 flg_not_length = flg_not_length == '[' ? ']' : '[';
496 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
497 if (flg_not_length == ']')
500 cmdedit_prompt = prmt_mem_ptr;
501 cmdedit_prmt_len = prmt_len - sub_len;
507 /* draw promt, editor line, and clear tail */
508 static void redraw(int y, int back_cursor)
510 if (y > 0) /* up to start y */
511 printf("\033[%dA", y);
512 cmdedit_y = 0; /* new quasireal y */
515 input_end(); /* rewrite */
516 printf("\033[J"); /* destroy tail after cursor */
517 input_backward(back_cursor);
520 /* Delete the char in front of the cursor */
521 static void input_delete(void)
528 strcpy(command_ps + j, command_ps + j + 1);
530 input_end(); /* rewtite new line */
531 cmdedit_set_out_char(0); /* destroy end char */
532 input_backward(cursor - j); /* back to old pos cursor */
535 /* Delete the char in back of the cursor */
536 static void input_backspace(void)
545 /* Move forward one charactor */
546 static void input_forward(void)
549 cmdedit_set_out_char(command_ps[cursor + 1]);
553 static void clean_up_and_die(int sig)
557 exit(EXIT_SUCCESS); /* cmdedit_reset_term() called in atexit */
558 cmdedit_reset_term();
561 static void cmdedit_setwidth(int w, int redraw_flg)
563 cmdedit_termw = cmdedit_prmt_len + 2;
564 if (w <= cmdedit_termw) {
565 cmdedit_termw = cmdedit_termw % w;
567 if (w > cmdedit_termw) {
571 /* new y for current cursor */
572 int new_y = (cursor + cmdedit_prmt_len) / w;
575 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
581 extern void cmdedit_init(void)
583 cmdedit_prmt_len = 0;
584 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
585 /* emulate usage handler to set handler and call yours work */
586 win_changed(-SIGWINCH);
587 handlers_sets |= SET_WCHG_HANDLERS;
590 if ((handlers_sets & SET_ATEXIT) == 0) {
591 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
592 struct passwd *entry;
595 entry = getpwuid(my_euid);
597 user_buf = xstrdup(entry->pw_name);
598 home_pwd_buf = xstrdup(entry->pw_dir);
602 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
604 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
609 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
610 handlers_sets |= SET_ATEXIT;
611 atexit(cmdedit_reset_term); /* be sure to do this only once */
614 if ((handlers_sets & SET_TERM_HANDLERS) == 0) {
615 signal(SIGKILL, clean_up_and_die);
616 signal(SIGINT, clean_up_and_die);
617 signal(SIGQUIT, clean_up_and_die);
618 signal(SIGTERM, clean_up_and_die);
619 handlers_sets |= SET_TERM_HANDLERS;
624 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
626 static int is_execute(const struct stat *st)
628 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
629 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
630 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
631 (st->st_mode & S_IXOTH)) return TRUE;
635 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
637 static char **username_tab_completion(char *ud, int *num_matches)
639 struct passwd *entry;
644 ud++; /* ~user/... to user/... */
645 userlen = strlen(ud);
647 if (num_matches == 0) { /* "~/..." or "~user/..." */
648 char *sav_ud = ud - 1;
651 if (*ud == '/') { /* "~/..." */
655 temp = strchr(ud, '/');
656 *temp = 0; /* ~user\0 */
657 entry = getpwnam(ud);
658 *temp = '/'; /* restore ~user/... */
661 home = entry->pw_dir;
664 if ((userlen + strlen(home) + 1) < BUFSIZ) {
665 char temp2[BUFSIZ]; /* argument size */
668 sprintf(temp2, "%s%s", home, ud);
669 strcpy(sav_ud, temp2);
672 return 0; /* void, result save to argument :-) */
675 char **matches = (char **) NULL;
680 while ((entry = getpwent()) != NULL) {
681 /* Null usernames should result in all users as possible completions. */
682 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
684 temp = xmalloc(3 + strlen(entry->pw_name));
685 sprintf(temp, "~%s/", entry->pw_name);
686 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
688 matches[nm++] = temp;
697 #endif /* BB_FEATURE_COMMAND_USERNAME_COMPLETION */
705 static int path_parse(char ***p, int flags)
711 /* if not setenv PATH variable, to search cur dir "." */
712 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
713 /* PATH=<empty> or PATH=:<empty> */
714 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
722 npth++; /* count words is + 1 count ':' */
723 tmp = strchr(tmp, ':');
726 break; /* :<empty> */
731 *p = xmalloc(npth * sizeof(char *));
734 (*p)[0] = xstrdup(tmp);
735 npth = 1; /* count words is + 1 count ':' */
738 tmp = strchr(tmp, ':');
740 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
742 break; /* :<empty> */
745 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
751 static char *add_quote_for_spec_chars(char *found)
754 char *s = xmalloc((strlen(found) + 1) * 2);
757 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
765 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
773 int nm = *num_matches;
776 char **paths = path1;
779 char found[BUFSIZ + 4 + PATH_MAX];
780 char *pfind = strrchr(command, '/');
785 /* no dir, if flags==EXE_ONLY - get paths, else "." */
786 npaths = path_parse(&paths, type);
790 /* save for change */
791 strcpy(dirbuf, command);
793 dirbuf[(pfind - command) + 1] = 0;
794 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
795 if (dirbuf[0] == '~') /* ~/... or ~user/... */
796 username_tab_completion(dirbuf, 0);
798 /* "strip" dirname in command */
802 npaths = 1; /* only 1 dir */
805 for (i = 0; i < npaths; i++) {
807 dir = opendir(paths[i]);
808 if (!dir) /* Don't print an error */
811 while ((next = readdir(dir)) != NULL) {
812 const char *str_merge = "%s/%s";
813 char *str_found = next->d_name;
816 if (strncmp(str_found, pfind, strlen(pfind)))
818 /* not see .name without .match */
819 if (*str_found == '.' && *pfind == 0) {
820 if (*paths[i] == '/' && paths[i][1] == 0
821 && str_found[1] == 0) str_found = ""; /* only "/" */
825 if (paths[i][strlen(paths[i]) - 1] == '/')
827 sprintf(found, str_merge, paths[i], str_found);
828 /* hmm, remover in progress? */
829 if (stat(found, &st) < 0)
831 /* find with dirs ? */
832 if (paths[i] != dirbuf)
833 strcpy(found, next->d_name); /* only name */
834 if (S_ISDIR(st.st_mode)) {
835 /* name is directory */
836 /* algorithmic only "/" ? */
839 str_found = add_quote_for_spec_chars(found);
841 /* not put found file if search only dirs for cd */
842 if (type == FIND_DIR_ONLY)
844 str_found = add_quote_for_spec_chars(found);
845 if (type == FIND_FILE_ONLY ||
846 (type == FIND_EXE_ONLY && is_execute(&st) == TRUE))
847 strcat(str_found, " ");
849 /* Add it to the list */
850 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
852 matches[nm++] = str_found;
856 if (paths != path1) {
857 free(paths[0]); /* allocated memory only in first member */
864 static int match_compare(const void *a, const void *b)
866 return strcmp(*(char **) a, *(char **) b);
871 #define QUOT (UCHAR_MAX+1)
873 #define collapse_pos(is, in) { \
874 memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \
875 memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); }
877 static int find_match(char *matchBuf, int *len_with_quotes)
882 int int_buf[BUFSIZ + 1];
883 int pos_buf[BUFSIZ + 1];
885 /* set to integer dimension characters and own positions */
887 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
888 if (int_buf[i] == 0) {
889 pos_buf[i] = -1; /* indicator end line */
895 /* mask \+symbol and convert '\t' to ' ' */
896 for (i = j = 0; matchBuf[i]; i++, j++)
897 if (matchBuf[i] == '\\') {
898 collapse_pos(j, j + 1);
901 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
902 if (matchBuf[i] == '\t') /* algorithm equivalent */
903 int_buf[j] = ' ' | QUOT;
906 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
907 else if (matchBuf[i] == '\t')
911 /* mask "symbols" or 'symbols' */
913 for (i = 0; int_buf[i]; i++) {
915 if (c == '\'' || c == '"') {
924 } else if (c2 != 0 && c != '$')
928 /* skip commands with arguments if line have commands delimiters */
929 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
930 for (i = 0; int_buf[i]; i++) {
933 j = i ? int_buf[i - 1] : -1;
935 if (c == ';' || c == '&' || c == '|') {
936 command_mode = 1 + (c == c2);
938 if (j == '>' || j == '<')
940 } else if (c == '|' && j == '>')
944 collapse_pos(0, i + command_mode);
945 i = -1; /* hack incremet */
948 /* collapse `command...` */
949 for (i = 0; int_buf[i]; i++)
950 if (int_buf[i] == '`') {
951 for (j = i + 1; int_buf[j]; j++)
952 if (int_buf[j] == '`') {
953 collapse_pos(i, j + 1);
958 /* not found close ` - command mode, collapse all previous */
959 collapse_pos(0, i + 1);
962 i--; /* hack incremet */
965 /* collapse (command...(command...)...) or {command...{command...}...} */
966 c = 0; /* "recursive" level */
968 for (i = 0; int_buf[i]; i++)
969 if (int_buf[i] == '(' || int_buf[i] == '{') {
970 if (int_buf[i] == '(')
974 collapse_pos(0, i + 1);
975 i = -1; /* hack incremet */
977 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
978 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
979 if (int_buf[i] == ')')
983 collapse_pos(0, i + 1);
984 i = -1; /* hack incremet */
987 /* skip first not quote space */
988 for (i = 0; int_buf[i]; i++)
989 if (int_buf[i] != ' ')
994 /* set find mode for completion */
995 command_mode = FIND_EXE_ONLY;
996 for (i = 0; int_buf[i]; i++)
997 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
998 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
999 && matchBuf[pos_buf[0]]=='c'
1000 && matchBuf[pos_buf[1]]=='d' )
1001 command_mode = FIND_DIR_ONLY;
1003 command_mode = FIND_FILE_ONLY;
1008 for (i = 0; int_buf[i]; i++);
1009 /* find last word */
1010 for (--i; i >= 0; i--) {
1012 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
1013 collapse_pos(0, i + 1);
1017 /* skip first not quoted '\'' or '"' */
1018 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
1019 /* collapse quote or unquote // or /~ */
1020 while ((int_buf[i] & ~QUOT) == '/' &&
1021 ((int_buf[i + 1] & ~QUOT) == '/'
1022 || (int_buf[i + 1] & ~QUOT) == '~')) {
1029 /* set only match and destroy quotes */
1031 for (i = 0; pos_buf[i] >= 0; i++) {
1032 matchBuf[i] = matchBuf[pos_buf[i]];
1036 /* old lenght matchBuf with quotes symbols */
1037 *len_with_quotes = j ? j - pos_buf[0] : 0;
1039 return command_mode;
1043 static void input_tab(int *lastWasTab)
1045 /* Do TAB completion */
1046 static int num_matches;
1047 static char **matches;
1049 if (lastWasTab == 0) { /* free all memory */
1051 while (num_matches > 0)
1052 free(matches[--num_matches]);
1054 matches = (char **) NULL;
1058 if (*lastWasTab == FALSE) {
1062 char matchBuf[BUFSIZ];
1066 *lastWasTab = TRUE; /* flop trigger */
1068 /* Make a local copy of the string -- up
1069 * to the position of the cursor */
1070 tmp = strncpy(matchBuf, command_ps, cursor);
1073 find_type = find_match(matchBuf, &recalc_pos);
1075 /* Free up any memory already allocated */
1078 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
1079 /* If the word starts with `~' and there is no slash in the word,
1080 * then try completing this word as a username. */
1082 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1083 matches = username_tab_completion(matchBuf, &num_matches);
1085 /* Try to match any executable in our path and everything
1086 * in the current working directory that matches. */
1089 exe_n_cwd_tab_completion(matchBuf, &num_matches,
1092 /* Did we find exactly one match? */
1093 if (!matches || num_matches > 1) {
1098 return; /* not found */
1100 qsort(matches, num_matches, sizeof(char *), match_compare);
1102 /* find minimal match */
1103 tmp = xstrdup(matches[0]);
1104 for (tmp1 = tmp; *tmp1; tmp1++)
1105 for (len_found = 1; len_found < num_matches; len_found++)
1106 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1110 if (*tmp == 0) { /* have unique */
1114 } else { /* one match */
1116 /* for next completion current found */
1117 *lastWasTab = FALSE;
1120 len_found = strlen(tmp);
1121 /* have space to placed match? */
1122 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1124 /* before word for match */
1125 command_ps[cursor - recalc_pos] = 0;
1126 /* save tail line */
1127 strcpy(matchBuf, command_ps + cursor);
1129 strcat(command_ps, tmp);
1131 strcat(command_ps, matchBuf);
1132 /* back to begin word for match */
1133 input_backward(recalc_pos);
1135 recalc_pos = cursor + len_found;
1137 len = strlen(command_ps);
1138 /* write out the matched command */
1140 input_backward(cursor - recalc_pos);
1142 if (tmp != matches[0])
1145 /* Ok -- the last char was a TAB. Since they
1146 * just hit TAB again, print a list of all the
1147 * available choices... */
1148 if (matches && num_matches > 0) {
1150 int sav_cursor = cursor; /* change goto_new_line() */
1152 /* Go to the next line */
1154 for (i = 0, col = 0; i < num_matches; i++) {
1155 l = strlen(matches[i]);
1158 printf("%-14s ", matches[i]);
1165 col -= (col / cmdedit_termw) * cmdedit_termw;
1166 if (col > 60 && matches[i + 1] != NULL) {
1171 /* Go to the next line and rewrite */
1173 redraw(0, len - sav_cursor);
1177 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
1179 static void get_previous_history(struct history **hp, struct history *p)
1183 (*hp)->s = xstrdup(command_ps);
1187 static inline void get_next_history(struct history **hp)
1189 get_previous_history(hp, (*hp)->n);
1199 * This function is used to grab a character buffer
1200 * from the input file descriptor and allows you to
1201 * a string with full command editing (sortof like
1204 * The following standard commands are not implemented:
1205 * ESC-b -- Move back one word
1206 * ESC-f -- Move forward one word
1207 * ESC-d -- Delete back one word
1208 * ESC-h -- Delete forward one word
1209 * CTL-t -- Transpose two characters
1211 * Furthermore, the "vi" command editing keys are not implemented.
1215 extern void cmdedit_read_input(char *prompt, char command[BUFSIZ])
1218 int inputFd = fileno(stdin);
1221 int lastWasTab = FALSE;
1222 unsigned char c = 0;
1223 struct history *hp = his_end;
1225 /* prepare before init handlers */
1226 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1228 command_ps = command;
1230 if (new_settings.c_cc[VMIN] == 0) { /* first call */
1232 getTermSettings(inputFd, (void *) &initial_settings);
1233 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1235 new_settings.c_cc[VMIN] = 1;
1236 new_settings.c_cc[VTIME] = 0;
1237 /* Turn off CTRL-C, so we can trap it */
1238 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1239 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1240 /* Turn off echoing */
1241 new_settings.c_lflag &= ~(ECHO | ECHOCTL | ECHONL);
1246 setTermSettings(inputFd, (void *) &new_settings);
1247 handlers_sets |= SET_RESET_TERM;
1249 /* Now initialize things */
1251 /* Print out the command prompt */
1252 parse_prompt(prompt);
1256 fflush(stdout); /* buffered out to fast */
1258 if (read(inputFd, &c, 1) < 1)
1269 /* Control-a -- Beginning of line */
1270 input_backward(cursor);
1273 /* Control-b -- Move back one character */
1277 /* Control-c -- stop gathering input */
1279 /* Link into lash to reset context to 0 on ^C and such */
1282 /* Go to the next line */
1288 /* Control-d -- Delete one character, or exit
1289 * if the len=0 and no chars to delete */
1292 clean_up_and_die(0);
1298 /* Control-e -- End of line */
1302 /* Control-f -- Move forward one character */
1307 /* Control-h and DEL */
1311 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1312 input_tab(&lastWasTab);
1316 /* Control-n -- Get next command in history */
1317 if (hp && hp->n && hp->n->s) {
1318 get_next_history(&hp);
1325 /* Control-p -- Get previous command from history */
1327 get_previous_history(&hp, hp->p);
1334 /* Control-U -- Clear line before cursor */
1336 strcpy(command, command + cursor);
1337 redraw(cmdedit_y, len -= cursor);
1342 /* escape sequence follows */
1343 if (read(inputFd, &c, 1) < 1)
1345 /* different vt100 emulations */
1346 if (c == '[' || c == 'O') {
1347 if (read(inputFd, &c, 1) < 1)
1351 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1352 case '\t': /* Alt-Tab */
1354 input_tab(&lastWasTab);
1358 /* Up Arrow -- Get previous command from history */
1360 get_previous_history(&hp, hp->p);
1367 /* Down Arrow -- Get next command in history */
1368 if (hp && hp->n && hp->n->s) {
1369 get_next_history(&hp);
1376 /* Rewrite the line with the selected history item */
1378 /* change command */
1379 len = strlen(strcpy(command, hp->s));
1380 /* redraw and go to end line */
1381 redraw(cmdedit_y, 0);
1384 /* Right Arrow -- Move forward one character */
1388 /* Left Arrow -- Move back one character */
1398 input_backward(cursor);
1406 if (!(c >= '1' && c <= '9'))
1410 if (c >= '1' && c <= '9')
1412 if (read(inputFd, &c, 1) < 1)
1418 default: /* If it's regular input, do the normal thing */
1419 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1420 /* Control-V -- Add non-printable symbol */
1422 if (read(inputFd, &c, 1) < 1)
1430 if (!isprint(c)) /* Skip non-printable characters */
1433 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1438 if (cursor == (len - 1)) { /* Append if at the end of the line */
1439 *(command + cursor) = c;
1440 *(command + cursor + 1) = 0;
1441 cmdedit_set_out_char(0);
1442 } else { /* Insert otherwise */
1445 memmove(command + sc + 1, command + sc, len - sc);
1446 *(command + sc) = c;
1448 /* rewrite from cursor */
1450 /* to prev x pos + 1 */
1451 input_backward(cursor - sc);
1456 if (break_out) /* Enter is the command terminator, no more input. */
1463 setTermSettings(inputFd, (void *) &initial_settings);
1464 handlers_sets &= ~SET_RESET_TERM;
1466 /* Handle command history log */
1467 if (len) { /* no put empty line */
1469 struct history *h = his_end;
1472 ss = xstrdup(command); /* duplicate */
1475 /* No previous history -- this memory is never freed */
1476 h = his_front = xmalloc(sizeof(struct history));
1477 h->n = xmalloc(sizeof(struct history));
1487 /* Add a new history command -- this memory is never freed */
1488 h->n = xmalloc(sizeof(struct history));
1496 /* After max history, remove the oldest command */
1497 if (history_counter >= MAX_HISTORY) {
1499 struct history *p = his_front->n;
1509 #if !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
1513 command[len++] = '\n'; /* set '\n' */
1515 #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_COMMAND_TAB_COMPLETION)
1516 input_tab(0); /* strong free */
1518 #if !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
1519 free(cmdedit_prompt);
1525 /* Undo the effects of cmdedit_init(). */
1526 extern void cmdedit_terminate(void)
1528 cmdedit_reset_term();
1529 if ((handlers_sets & SET_TERM_HANDLERS) != 0) {
1530 signal(SIGKILL, SIG_DFL);
1531 signal(SIGINT, SIG_DFL);
1532 signal(SIGQUIT, SIG_DFL);
1533 signal(SIGTERM, SIG_DFL);
1534 signal(SIGWINCH, SIG_DFL);
1535 handlers_sets &= ~SET_TERM_HANDLERS;
1539 #endif /* BB_FEATURE_COMMAND_EDITING */
1544 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1548 unsigned int shell_context;
1550 int main(int argc, char **argv)
1554 #if !defined(BB_FEATURE_SH_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\\]";
1562 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1563 setlocale(LC_ALL, "");
1568 cmdedit_read_input(prompt, buff);
1570 if(l > 0 && buff[l-1] == '\n')
1572 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1573 } while (shell_context);
1574 printf("*** cmdedit_read_input() detect ^C\n");