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 if (pwd_buf[0] == 0) {
448 getcwd(pwd_buf, PATH_MAX);
449 z = strrchr(pwd_buf,'/');
450 if ( (z != NULL) && (z != pwd_buf) ) {
455 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, pwd_buf);
458 snprintf(buf, sizeof(buf), "%d", num_ok_lines);
459 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
462 case 'E': /* \e \E = \033 */
482 for (l = 0; l < 3;) {
484 buf[l++] = *prmt_ptr;
486 ho = strtol(buf, &eho, c == 'x' ? 16 : 8);
487 if (ho > UCHAR_MAX || (eho - buf) < l) {
494 ho = strtol(buf, 0, c == 'x' ? 16 : 8);
495 c = ho == 0 ? '?' : (char) ho;
500 if (c == flg_not_length) {
501 flg_not_length = flg_not_length == '[' ? ']' : '[';
509 add_to_prompt(&prmt_mem_ptr, &alm, &prmt_len, buf);
510 if (flg_not_length == ']')
513 cmdedit_prompt = prmt_mem_ptr;
514 cmdedit_prmt_len = prmt_len - sub_len;
520 /* draw promt, editor line, and clear tail */
521 static void redraw(int y, int back_cursor)
523 if (y > 0) /* up to start y */
524 printf("\033[%dA", y);
525 cmdedit_y = 0; /* new quasireal y */
528 input_end(); /* rewrite */
529 printf("\033[J"); /* destroy tail after cursor */
530 input_backward(back_cursor);
533 /* Delete the char in front of the cursor */
534 static void input_delete(void)
541 strcpy(command_ps + j, command_ps + j + 1);
543 input_end(); /* rewtite new line */
544 cmdedit_set_out_char(0); /* destroy end char */
545 input_backward(cursor - j); /* back to old pos cursor */
548 /* Delete the char in back of the cursor */
549 static void input_backspace(void)
558 /* Move forward one charactor */
559 static void input_forward(void)
562 cmdedit_set_out_char(command_ps[cursor + 1]);
566 static void clean_up_and_die(int sig)
570 exit(EXIT_SUCCESS); /* cmdedit_reset_term() called in atexit */
571 cmdedit_reset_term();
574 static void cmdedit_setwidth(int w, int redraw_flg)
576 cmdedit_termw = cmdedit_prmt_len + 2;
577 if (w <= cmdedit_termw) {
578 cmdedit_termw = cmdedit_termw % w;
580 if (w > cmdedit_termw) {
584 /* new y for current cursor */
585 int new_y = (cursor + cmdedit_prmt_len) / w;
588 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
594 extern void cmdedit_init(void)
596 cmdedit_prmt_len = 0;
597 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
598 /* emulate usage handler to set handler and call yours work */
599 win_changed(-SIGWINCH);
600 handlers_sets |= SET_WCHG_HANDLERS;
603 if ((handlers_sets & SET_ATEXIT) == 0) {
604 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
605 struct passwd *entry;
608 entry = getpwuid(my_euid);
610 user_buf = xstrdup(entry->pw_name);
611 home_pwd_buf = xstrdup(entry->pw_dir);
615 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
617 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
622 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
623 handlers_sets |= SET_ATEXIT;
624 atexit(cmdedit_reset_term); /* be sure to do this only once */
627 if ((handlers_sets & SET_TERM_HANDLERS) == 0) {
628 signal(SIGKILL, clean_up_and_die);
629 signal(SIGINT, clean_up_and_die);
630 signal(SIGQUIT, clean_up_and_die);
631 signal(SIGTERM, clean_up_and_die);
632 handlers_sets |= SET_TERM_HANDLERS;
637 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
639 static int is_execute(const struct stat *st)
641 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
642 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
643 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
644 (st->st_mode & S_IXOTH)) return TRUE;
648 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
650 static char **username_tab_completion(char *ud, int *num_matches)
652 struct passwd *entry;
657 ud++; /* ~user/... to user/... */
658 userlen = strlen(ud);
660 if (num_matches == 0) { /* "~/..." or "~user/..." */
661 char *sav_ud = ud - 1;
664 if (*ud == '/') { /* "~/..." */
668 temp = strchr(ud, '/');
669 *temp = 0; /* ~user\0 */
670 entry = getpwnam(ud);
671 *temp = '/'; /* restore ~user/... */
674 home = entry->pw_dir;
677 if ((userlen + strlen(home) + 1) < BUFSIZ) {
678 char temp2[BUFSIZ]; /* argument size */
681 sprintf(temp2, "%s%s", home, ud);
682 strcpy(sav_ud, temp2);
685 return 0; /* void, result save to argument :-) */
688 char **matches = (char **) NULL;
693 while ((entry = getpwent()) != NULL) {
694 /* Null usernames should result in all users as possible completions. */
695 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
697 temp = xmalloc(3 + strlen(entry->pw_name));
698 sprintf(temp, "~%s/", entry->pw_name);
699 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
701 matches[nm++] = temp;
710 #endif /* BB_FEATURE_COMMAND_USERNAME_COMPLETION */
718 static int path_parse(char ***p, int flags)
724 /* if not setenv PATH variable, to search cur dir "." */
725 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
726 /* PATH=<empty> or PATH=:<empty> */
727 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
735 npth++; /* count words is + 1 count ':' */
736 tmp = strchr(tmp, ':');
739 break; /* :<empty> */
744 *p = xmalloc(npth * sizeof(char *));
747 (*p)[0] = xstrdup(tmp);
748 npth = 1; /* count words is + 1 count ':' */
751 tmp = strchr(tmp, ':');
753 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
755 break; /* :<empty> */
758 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
764 static char *add_quote_for_spec_chars(char *found)
767 char *s = xmalloc((strlen(found) + 1) * 2);
770 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
778 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
786 int nm = *num_matches;
789 char **paths = path1;
792 char found[BUFSIZ + 4 + PATH_MAX];
793 char *pfind = strrchr(command, '/');
798 /* no dir, if flags==EXE_ONLY - get paths, else "." */
799 npaths = path_parse(&paths, type);
803 /* save for change */
804 strcpy(dirbuf, command);
806 dirbuf[(pfind - command) + 1] = 0;
807 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
808 if (dirbuf[0] == '~') /* ~/... or ~user/... */
809 username_tab_completion(dirbuf, 0);
811 /* "strip" dirname in command */
815 npaths = 1; /* only 1 dir */
818 for (i = 0; i < npaths; i++) {
820 dir = opendir(paths[i]);
821 if (!dir) /* Don't print an error */
824 while ((next = readdir(dir)) != NULL) {
825 const char *str_merge = "%s/%s";
826 char *str_found = next->d_name;
829 if (strncmp(str_found, pfind, strlen(pfind)))
831 /* not see .name without .match */
832 if (*str_found == '.' && *pfind == 0) {
833 if (*paths[i] == '/' && paths[i][1] == 0
834 && str_found[1] == 0) str_found = ""; /* only "/" */
838 if (paths[i][strlen(paths[i]) - 1] == '/')
840 sprintf(found, str_merge, paths[i], str_found);
841 /* hmm, remover in progress? */
842 if (stat(found, &st) < 0)
844 /* find with dirs ? */
845 if (paths[i] != dirbuf)
846 strcpy(found, next->d_name); /* only name */
847 if (S_ISDIR(st.st_mode)) {
848 /* name is directory */
849 /* algorithmic only "/" ? */
852 str_found = add_quote_for_spec_chars(found);
854 /* not put found file if search only dirs for cd */
855 if (type == FIND_DIR_ONLY)
857 str_found = add_quote_for_spec_chars(found);
858 if (type == FIND_FILE_ONLY ||
859 (type == FIND_EXE_ONLY && is_execute(&st) == TRUE))
860 strcat(str_found, " ");
862 /* Add it to the list */
863 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
865 matches[nm++] = str_found;
869 if (paths != path1) {
870 free(paths[0]); /* allocated memory only in first member */
877 static int match_compare(const void *a, const void *b)
879 return strcmp(*(char **) a, *(char **) b);
884 #define QUOT (UCHAR_MAX+1)
886 #define collapse_pos(is, in) { \
887 memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \
888 memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); }
890 static int find_match(char *matchBuf, int *len_with_quotes)
895 int int_buf[BUFSIZ + 1];
896 int pos_buf[BUFSIZ + 1];
898 /* set to integer dimension characters and own positions */
900 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
901 if (int_buf[i] == 0) {
902 pos_buf[i] = -1; /* indicator end line */
908 /* mask \+symbol and convert '\t' to ' ' */
909 for (i = j = 0; matchBuf[i]; i++, j++)
910 if (matchBuf[i] == '\\') {
911 collapse_pos(j, j + 1);
914 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
915 if (matchBuf[i] == '\t') /* algorithm equivalent */
916 int_buf[j] = ' ' | QUOT;
919 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
920 else if (matchBuf[i] == '\t')
924 /* mask "symbols" or 'symbols' */
926 for (i = 0; int_buf[i]; i++) {
928 if (c == '\'' || c == '"') {
937 } else if (c2 != 0 && c != '$')
941 /* skip commands with arguments if line have commands delimiters */
942 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
943 for (i = 0; int_buf[i]; i++) {
946 j = i ? int_buf[i - 1] : -1;
948 if (c == ';' || c == '&' || c == '|') {
949 command_mode = 1 + (c == c2);
951 if (j == '>' || j == '<')
953 } else if (c == '|' && j == '>')
957 collapse_pos(0, i + command_mode);
958 i = -1; /* hack incremet */
961 /* collapse `command...` */
962 for (i = 0; int_buf[i]; i++)
963 if (int_buf[i] == '`') {
964 for (j = i + 1; int_buf[j]; j++)
965 if (int_buf[j] == '`') {
966 collapse_pos(i, j + 1);
971 /* not found close ` - command mode, collapse all previous */
972 collapse_pos(0, i + 1);
975 i--; /* hack incremet */
978 /* collapse (command...(command...)...) or {command...{command...}...} */
979 c = 0; /* "recursive" level */
981 for (i = 0; int_buf[i]; i++)
982 if (int_buf[i] == '(' || int_buf[i] == '{') {
983 if (int_buf[i] == '(')
987 collapse_pos(0, i + 1);
988 i = -1; /* hack incremet */
990 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
991 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
992 if (int_buf[i] == ')')
996 collapse_pos(0, i + 1);
997 i = -1; /* hack incremet */
1000 /* skip first not quote space */
1001 for (i = 0; int_buf[i]; i++)
1002 if (int_buf[i] != ' ')
1007 /* set find mode for completion */
1008 command_mode = FIND_EXE_ONLY;
1009 for (i = 0; int_buf[i]; i++)
1010 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
1011 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
1012 && matchBuf[pos_buf[0]]=='c'
1013 && matchBuf[pos_buf[1]]=='d' )
1014 command_mode = FIND_DIR_ONLY;
1016 command_mode = FIND_FILE_ONLY;
1021 for (i = 0; int_buf[i]; i++);
1022 /* find last word */
1023 for (--i; i >= 0; i--) {
1025 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
1026 collapse_pos(0, i + 1);
1030 /* skip first not quoted '\'' or '"' */
1031 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
1032 /* collapse quote or unquote // or /~ */
1033 while ((int_buf[i] & ~QUOT) == '/' &&
1034 ((int_buf[i + 1] & ~QUOT) == '/'
1035 || (int_buf[i + 1] & ~QUOT) == '~')) {
1042 /* set only match and destroy quotes */
1044 for (i = 0; pos_buf[i] >= 0; i++) {
1045 matchBuf[i] = matchBuf[pos_buf[i]];
1049 /* old lenght matchBuf with quotes symbols */
1050 *len_with_quotes = j ? j - pos_buf[0] : 0;
1052 return command_mode;
1056 static void input_tab(int *lastWasTab)
1058 /* Do TAB completion */
1059 static int num_matches;
1060 static char **matches;
1062 if (lastWasTab == 0) { /* free all memory */
1064 while (num_matches > 0)
1065 free(matches[--num_matches]);
1067 matches = (char **) NULL;
1071 if (*lastWasTab == FALSE) {
1075 char matchBuf[BUFSIZ];
1079 *lastWasTab = TRUE; /* flop trigger */
1081 /* Make a local copy of the string -- up
1082 * to the position of the cursor */
1083 tmp = strncpy(matchBuf, command_ps, cursor);
1086 find_type = find_match(matchBuf, &recalc_pos);
1088 /* Free up any memory already allocated */
1091 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
1092 /* If the word starts with `~' and there is no slash in the word,
1093 * then try completing this word as a username. */
1095 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1096 matches = username_tab_completion(matchBuf, &num_matches);
1098 /* Try to match any executable in our path and everything
1099 * in the current working directory that matches. */
1102 exe_n_cwd_tab_completion(matchBuf, &num_matches,
1105 /* Did we find exactly one match? */
1106 if (!matches || num_matches > 1) {
1111 return; /* not found */
1113 qsort(matches, num_matches, sizeof(char *), match_compare);
1115 /* find minimal match */
1116 tmp = xstrdup(matches[0]);
1117 for (tmp1 = tmp; *tmp1; tmp1++)
1118 for (len_found = 1; len_found < num_matches; len_found++)
1119 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1123 if (*tmp == 0) { /* have unique */
1127 } else { /* one match */
1129 /* for next completion current found */
1130 *lastWasTab = FALSE;
1133 len_found = strlen(tmp);
1134 /* have space to placed match? */
1135 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1137 /* before word for match */
1138 command_ps[cursor - recalc_pos] = 0;
1139 /* save tail line */
1140 strcpy(matchBuf, command_ps + cursor);
1142 strcat(command_ps, tmp);
1144 strcat(command_ps, matchBuf);
1145 /* back to begin word for match */
1146 input_backward(recalc_pos);
1148 recalc_pos = cursor + len_found;
1150 len = strlen(command_ps);
1151 /* write out the matched command */
1153 input_backward(cursor - recalc_pos);
1155 if (tmp != matches[0])
1158 /* Ok -- the last char was a TAB. Since they
1159 * just hit TAB again, print a list of all the
1160 * available choices... */
1161 if (matches && num_matches > 0) {
1163 int sav_cursor = cursor; /* change goto_new_line() */
1165 /* Go to the next line */
1167 for (i = 0, col = 0; i < num_matches; i++) {
1168 l = strlen(matches[i]);
1171 printf("%-14s ", matches[i]);
1178 col -= (col / cmdedit_termw) * cmdedit_termw;
1179 if (col > 60 && matches[i + 1] != NULL) {
1184 /* Go to the next line and rewrite */
1186 redraw(0, len - sav_cursor);
1190 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
1192 static void get_previous_history(struct history **hp, struct history *p)
1196 (*hp)->s = xstrdup(command_ps);
1200 static inline void get_next_history(struct history **hp)
1202 get_previous_history(hp, (*hp)->n);
1212 * This function is used to grab a character buffer
1213 * from the input file descriptor and allows you to
1214 * a string with full command editing (sortof like
1217 * The following standard commands are not implemented:
1218 * ESC-b -- Move back one word
1219 * ESC-f -- Move forward one word
1220 * ESC-d -- Delete back one word
1221 * ESC-h -- Delete forward one word
1222 * CTL-t -- Transpose two characters
1224 * Furthermore, the "vi" command editing keys are not implemented.
1228 extern void cmdedit_read_input(char *prompt, char command[BUFSIZ])
1231 int inputFd = fileno(stdin);
1234 int lastWasTab = FALSE;
1235 unsigned char c = 0;
1236 struct history *hp = his_end;
1238 /* prepare before init handlers */
1239 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1241 command_ps = command;
1243 if (new_settings.c_cc[VMIN] == 0) { /* first call */
1245 getTermSettings(inputFd, (void *) &initial_settings);
1246 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1248 new_settings.c_cc[VMIN] = 1;
1249 new_settings.c_cc[VTIME] = 0;
1250 /* Turn off CTRL-C, so we can trap it */
1251 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1252 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1253 /* Turn off echoing */
1254 new_settings.c_lflag &= ~(ECHO | ECHOCTL | ECHONL);
1259 setTermSettings(inputFd, (void *) &new_settings);
1260 handlers_sets |= SET_RESET_TERM;
1262 /* Now initialize things */
1264 /* Print out the command prompt */
1265 parse_prompt(prompt);
1269 fflush(stdout); /* buffered out to fast */
1271 if (read(inputFd, &c, 1) < 1)
1282 /* Control-a -- Beginning of line */
1283 input_backward(cursor);
1286 /* Control-b -- Move back one character */
1290 /* Control-c -- stop gathering input */
1292 /* Link into lash to reset context to 0 on ^C and such */
1295 /* Go to the next line */
1301 /* Control-d -- Delete one character, or exit
1302 * if the len=0 and no chars to delete */
1305 clean_up_and_die(0);
1311 /* Control-e -- End of line */
1315 /* Control-f -- Move forward one character */
1320 /* Control-h and DEL */
1324 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1325 input_tab(&lastWasTab);
1329 /* Control-n -- Get next command in history */
1330 if (hp && hp->n && hp->n->s) {
1331 get_next_history(&hp);
1338 /* Control-p -- Get previous command from history */
1340 get_previous_history(&hp, hp->p);
1347 /* Control-U -- Clear line before cursor */
1349 strcpy(command, command + cursor);
1350 redraw(cmdedit_y, len -= cursor);
1355 /* escape sequence follows */
1356 if (read(inputFd, &c, 1) < 1)
1358 /* different vt100 emulations */
1359 if (c == '[' || c == 'O') {
1360 if (read(inputFd, &c, 1) < 1)
1364 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1365 case '\t': /* Alt-Tab */
1367 input_tab(&lastWasTab);
1371 /* Up Arrow -- Get previous command from history */
1373 get_previous_history(&hp, hp->p);
1380 /* Down Arrow -- Get next command in history */
1381 if (hp && hp->n && hp->n->s) {
1382 get_next_history(&hp);
1389 /* Rewrite the line with the selected history item */
1391 /* change command */
1392 len = strlen(strcpy(command, hp->s));
1393 /* redraw and go to end line */
1394 redraw(cmdedit_y, 0);
1397 /* Right Arrow -- Move forward one character */
1401 /* Left Arrow -- Move back one character */
1411 input_backward(cursor);
1419 if (!(c >= '1' && c <= '9'))
1423 if (c >= '1' && c <= '9')
1425 if (read(inputFd, &c, 1) < 1)
1431 default: /* If it's regular input, do the normal thing */
1432 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1433 /* Control-V -- Add non-printable symbol */
1435 if (read(inputFd, &c, 1) < 1)
1443 if (!isprint(c)) /* Skip non-printable characters */
1446 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1451 if (cursor == (len - 1)) { /* Append if at the end of the line */
1452 *(command + cursor) = c;
1453 *(command + cursor + 1) = 0;
1454 cmdedit_set_out_char(0);
1455 } else { /* Insert otherwise */
1458 memmove(command + sc + 1, command + sc, len - sc);
1459 *(command + sc) = c;
1461 /* rewrite from cursor */
1463 /* to prev x pos + 1 */
1464 input_backward(cursor - sc);
1469 if (break_out) /* Enter is the command terminator, no more input. */
1476 setTermSettings(inputFd, (void *) &initial_settings);
1477 handlers_sets &= ~SET_RESET_TERM;
1479 /* Handle command history log */
1480 if (len) { /* no put empty line */
1482 struct history *h = his_end;
1485 ss = xstrdup(command); /* duplicate */
1488 /* No previous history -- this memory is never freed */
1489 h = his_front = xmalloc(sizeof(struct history));
1490 h->n = xmalloc(sizeof(struct history));
1500 /* Add a new history command -- this memory is never freed */
1501 h->n = xmalloc(sizeof(struct history));
1509 /* After max history, remove the oldest command */
1510 if (history_counter >= MAX_HISTORY) {
1512 struct history *p = his_front->n;
1522 #if !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
1526 command[len++] = '\n'; /* set '\n' */
1528 #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_COMMAND_TAB_COMPLETION)
1529 input_tab(0); /* strong free */
1531 #if !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
1532 free(cmdedit_prompt);
1538 /* Undo the effects of cmdedit_init(). */
1539 extern void cmdedit_terminate(void)
1541 cmdedit_reset_term();
1542 if ((handlers_sets & SET_TERM_HANDLERS) != 0) {
1543 signal(SIGKILL, SIG_DFL);
1544 signal(SIGINT, SIG_DFL);
1545 signal(SIGQUIT, SIG_DFL);
1546 signal(SIGTERM, SIG_DFL);
1547 signal(SIGWINCH, SIG_DFL);
1548 handlers_sets &= ~SET_TERM_HANDLERS;
1552 #endif /* BB_FEATURE_COMMAND_EDITING */
1557 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1561 unsigned int shell_context;
1563 int main(int argc, char **argv)
1567 #if !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
1568 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1569 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1570 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1575 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1576 setlocale(LC_ALL, "");
1581 cmdedit_read_input(prompt, buff);
1583 if(l > 0 && buff[l-1] == '\n')
1585 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1586 } while (shell_context);
1587 printf("*** cmdedit_read_input() detect ^C\n");