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
39 #include <sys/ioctl.h>
46 #ifdef BB_LOCALE_SUPPORT
47 #define Isprint(c) isprint((c))
49 #define Isprint(c) ( (c) >= ' ' && (c) != ((unsigned char)'\233') )
58 #define BB_FEATURE_COMMAND_EDITING
59 #define BB_FEATURE_COMMAND_TAB_COMPLETION
60 #define BB_FEATURE_COMMAND_USERNAME_COMPLETION
61 #define BB_FEATURE_NONPRINTABLE_INVERSE_PUT
62 #define BB_FEATURE_CLEAN_UP
68 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
73 #ifdef BB_FEATURE_COMMAND_EDITING
75 #ifndef BB_FEATURE_COMMAND_TAB_COMPLETION
76 #undef BB_FEATURE_COMMAND_USERNAME_COMPLETION
79 #if defined(BB_FEATURE_COMMAND_USERNAME_COMPLETION) || !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
80 #define BB_FEATURE_GETUSERNAME_AND_HOMEDIR
83 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
85 #include "pwd_grp/pwd.h"
89 #endif /* advanced FEATURES */
99 /* Maximum length of the linked list for the command line history */
100 static const int MAX_HISTORY = 15;
102 /* First element in command line list */
103 static struct history *his_front = NULL;
105 /* Last element in command line list */
106 static struct history *his_end = NULL;
109 /* ED: sparc termios is broken: revert back to old termio handling. */
113 # define termios termio
114 # define setTermSettings(fd,argp) ioctl(fd,TCSETAF,argp)
115 # define getTermSettings(fd,argp) ioctl(fd,TCGETA,argp)
117 # include <termios.h>
118 # define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
119 # define getTermSettings(fd,argp) tcgetattr(fd, argp);
122 /* Current termio and the previous termio before starting sh */
123 static struct termios initial_settings, new_settings;
126 #ifndef _POSIX_VDISABLE
127 #define _POSIX_VDISABLE '\0'
132 volatile int cmdedit_termw = 80; /* actual terminal width */
133 static int history_counter = 0; /* Number of commands in history list */
135 volatile int handlers_sets = 0; /* Set next bites: */
138 SET_ATEXIT = 1, /* when atexit() has been called
139 and get euid,uid,gid to fast compare */
140 SET_TERM_HANDLERS = 2, /* set many terminates signal handlers */
141 SET_WCHG_HANDLERS = 4, /* winchg signal handler */
142 SET_RESET_TERM = 8, /* if the terminal needs to be reset upon exit */
146 static int cmdedit_x; /* real x terminal position */
147 static int cmdedit_y; /* pseudoreal y terminal position */
148 static int cmdedit_prmt_len; /* lenght prompt without colores string */
150 static int cursor; /* required global for signal handler */
151 static int len; /* --- "" - - "" - -"- --""-- --""--- */
152 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
154 #ifdef BB_FEATURE_SH_SIMPLE_PROMPT
157 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
159 /* Link into lash to reset context to 0 on ^C and such */
160 extern unsigned int shell_context;
163 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
164 static char *user_buf = "";
165 static char *home_pwd_buf = "";
169 #ifndef BB_FEATURE_SH_SIMPLE_PROMPT
170 static char *hostname_buf = "";
171 static int num_ok_lines = 1;
175 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
177 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
184 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
187 static void cmdedit_setwidth(int w, int redraw_flg);
189 static void win_changed(int nsig)
191 struct winsize win = { 0, 0, 0, 0 };
192 static __sighandler_t previous_SIGWINCH_handler; /* for reset */
194 /* emulate || signal call */
195 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
196 ioctl(0, TIOCGWINSZ, &win);
197 if (win.ws_col > 0) {
198 cmdedit_setwidth(win.ws_col, nsig == SIGWINCH);
201 /* Unix not all standart in recall signal */
203 if (nsig == -SIGWINCH) /* save previous handler */
204 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
205 else if (nsig == SIGWINCH) /* signaled called handler */
206 signal(SIGWINCH, win_changed); /* set for next call */
208 /* set previous handler */
209 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
212 static void cmdedit_reset_term(void)
214 if ((handlers_sets & SET_RESET_TERM) != 0) {
215 /* sparc and other have broken termios support: use old termio handling. */
216 setTermSettings(fileno(stdin), (void *) &initial_settings);
217 handlers_sets &= ~SET_RESET_TERM;
219 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
220 /* reset SIGWINCH handler to previous (default) */
222 handlers_sets &= ~SET_WCHG_HANDLERS;
225 #ifdef BB_FEATURE_CLEAN_UP
229 while (his_front != his_end) {
240 /* special for recount position for scroll and remove terminal margin effect */
241 static void cmdedit_set_out_char(int next_char)
244 int c = (int)((unsigned char) command_ps[cursor]);
247 c = ' '; /* destroy end char? */
248 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
249 if (!Isprint(c)) { /* Inverse put non-printable characters */
256 printf("\033[7m%c\033[0m", c);
260 if (++cmdedit_x >= cmdedit_termw) {
261 /* terminal is scrolled down */
267 /* destroy "(auto)margin" */
274 /* Move to end line. Bonus: rewrite line from cursor */
275 static void input_end(void)
278 cmdedit_set_out_char(0);
281 /* Go to the next line */
282 static void goto_new_line(void)
290 static inline void out1str(const char *s)
294 static inline void beep(void)
299 /* Move back one charactor */
300 /* special for slow terminal */
301 static void input_backward(int num)
305 cursor -= num; /* new cursor (in command, not terminal) */
307 if (cmdedit_x >= num) { /* no to up line */
314 printf("\033[%dD", num);
319 putchar('\r'); /* back to first terminal pos. */
320 num -= cmdedit_x; /* set previous backward */
322 count_y = 1 + num / cmdedit_termw;
323 printf("\033[%dA", count_y);
324 cmdedit_y -= count_y;
325 /* require forward after uping */
326 cmdedit_x = cmdedit_termw * count_y - num;
327 printf("\033[%dC", cmdedit_x); /* set term cursor */
331 static void put_prompt(void)
333 out1str(cmdedit_prompt);
334 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
338 #ifdef BB_FEATURE_SH_SIMPLE_PROMPT
339 static void parse_prompt(const char *prmt_ptr)
341 cmdedit_prompt = prmt_ptr;
342 cmdedit_prmt_len = strlen(prmt_ptr);
346 static void parse_prompt(const char *prmt_ptr)
350 char flg_not_length = '[';
351 char *prmt_mem_ptr = xcalloc(1, 1);
352 char *pwd_buf = xgetcwd(0);
353 char buf2[PATH_MAX + 1];
363 const char *cp = prmt_ptr;
366 c = process_escape_sequence(&prmt_ptr);
372 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
380 pbuf = xcalloc(256, 1);
381 if (gethostname(pbuf, 255) < 0) {
384 char *s = strchr(pbuf, '.');
393 c = my_euid == 0 ? '#' : '$';
395 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
398 l = strlen(home_pwd_buf);
399 if (home_pwd_buf[0] != 0 &&
400 strncmp(home_pwd_buf, pbuf, l) == 0 &&
401 (pbuf[l]=='/' || pbuf[l]=='\0') &&
402 strlen(pwd_buf+l)<PATH_MAX) {
405 strcpy(pbuf+1, pwd_buf+l);
411 cp = strrchr(pbuf,'/');
412 if ( (cp != NULL) && (cp != pbuf) )
416 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
418 case 'e': case 'E': /* \e \E = \033 */
422 for (l = 0; l < 3;) {
424 buf2[l++] = *prmt_ptr;
426 h = strtol(buf2, &pbuf, 16);
427 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
434 c = (char)strtol(buf2, 0, 16);
440 if (c == flg_not_length) {
441 flg_not_length = flg_not_length == '[' ? ']' : '[';
450 prmt_len += strlen(pbuf);
451 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
452 if (flg_not_length == ']')
456 cmdedit_prompt = prmt_mem_ptr;
457 cmdedit_prmt_len = prmt_len - sub_len;
463 /* draw promt, editor line, and clear tail */
464 static void redraw(int y, int back_cursor)
466 if (y > 0) /* up to start y */
467 printf("\033[%dA", y);
468 cmdedit_y = 0; /* new quasireal y */
471 input_end(); /* rewrite */
472 printf("\033[J"); /* destroy tail after cursor */
473 input_backward(back_cursor);
476 /* Delete the char in front of the cursor */
477 static void input_delete(void)
484 strcpy(command_ps + j, command_ps + j + 1);
486 input_end(); /* rewtite new line */
487 cmdedit_set_out_char(0); /* destroy end char */
488 input_backward(cursor - j); /* back to old pos cursor */
491 /* Delete the char in back of the cursor */
492 static void input_backspace(void)
501 /* Move forward one charactor */
502 static void input_forward(void)
505 cmdedit_set_out_char(command_ps[cursor + 1]);
509 static void clean_up_and_die(int sig)
513 exit(EXIT_SUCCESS); /* cmdedit_reset_term() called in atexit */
514 cmdedit_reset_term();
517 static void cmdedit_setwidth(int w, int redraw_flg)
519 cmdedit_termw = cmdedit_prmt_len + 2;
520 if (w <= cmdedit_termw) {
521 cmdedit_termw = cmdedit_termw % w;
523 if (w > cmdedit_termw) {
527 /* new y for current cursor */
528 int new_y = (cursor + cmdedit_prmt_len) / w;
531 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
537 extern void cmdedit_init(void)
539 cmdedit_prmt_len = 0;
540 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
541 /* emulate usage handler to set handler and call yours work */
542 win_changed(-SIGWINCH);
543 handlers_sets |= SET_WCHG_HANDLERS;
546 if ((handlers_sets & SET_ATEXIT) == 0) {
547 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
548 struct passwd *entry;
551 entry = getpwuid(my_euid);
553 user_buf = xstrdup(entry->pw_name);
554 home_pwd_buf = xstrdup(entry->pw_dir);
558 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
560 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
565 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
566 handlers_sets |= SET_ATEXIT;
567 atexit(cmdedit_reset_term); /* be sure to do this only once */
570 if ((handlers_sets & SET_TERM_HANDLERS) == 0) {
571 signal(SIGKILL, clean_up_and_die);
572 signal(SIGINT, clean_up_and_die);
573 signal(SIGQUIT, clean_up_and_die);
574 signal(SIGTERM, clean_up_and_die);
575 handlers_sets |= SET_TERM_HANDLERS;
580 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
582 static int is_execute(const struct stat *st)
584 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
585 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
586 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
587 (st->st_mode & S_IXOTH)) return TRUE;
591 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
593 static char **username_tab_completion(char *ud, int *num_matches)
595 struct passwd *entry;
600 ud++; /* ~user/... to user/... */
601 userlen = strlen(ud);
603 if (num_matches == 0) { /* "~/..." or "~user/..." */
604 char *sav_ud = ud - 1;
607 if (*ud == '/') { /* "~/..." */
611 temp = strchr(ud, '/');
612 *temp = 0; /* ~user\0 */
613 entry = getpwnam(ud);
614 *temp = '/'; /* restore ~user/... */
617 home = entry->pw_dir;
620 if ((userlen + strlen(home) + 1) < BUFSIZ) {
621 char temp2[BUFSIZ]; /* argument size */
624 sprintf(temp2, "%s%s", home, ud);
625 strcpy(sav_ud, temp2);
628 return 0; /* void, result save to argument :-) */
631 char **matches = (char **) NULL;
636 while ((entry = getpwent()) != NULL) {
637 /* Null usernames should result in all users as possible completions. */
638 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
640 temp = xmalloc(3 + strlen(entry->pw_name));
641 sprintf(temp, "~%s/", entry->pw_name);
642 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
644 matches[nm++] = temp;
653 #endif /* BB_FEATURE_COMMAND_USERNAME_COMPLETION */
661 static int path_parse(char ***p, int flags)
667 /* if not setenv PATH variable, to search cur dir "." */
668 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
669 /* PATH=<empty> or PATH=:<empty> */
670 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
678 npth++; /* count words is + 1 count ':' */
679 tmp = strchr(tmp, ':');
682 break; /* :<empty> */
687 *p = xmalloc(npth * sizeof(char *));
690 (*p)[0] = xstrdup(tmp);
691 npth = 1; /* count words is + 1 count ':' */
694 tmp = strchr(tmp, ':');
696 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
698 break; /* :<empty> */
701 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
707 static char *add_quote_for_spec_chars(char *found)
710 char *s = xmalloc((strlen(found) + 1) * 2);
713 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
721 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
729 int nm = *num_matches;
732 char **paths = path1;
736 char *pfind = strrchr(command, '/');
741 /* no dir, if flags==EXE_ONLY - get paths, else "." */
742 npaths = path_parse(&paths, type);
746 /* save for change */
747 strcpy(dirbuf, command);
749 dirbuf[(pfind - command) + 1] = 0;
750 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
751 if (dirbuf[0] == '~') /* ~/... or ~user/... */
752 username_tab_completion(dirbuf, 0);
754 /* "strip" dirname in command */
758 npaths = 1; /* only 1 dir */
761 for (i = 0; i < npaths; i++) {
763 dir = opendir(paths[i]);
764 if (!dir) /* Don't print an error */
767 while ((next = readdir(dir)) != NULL) {
768 char *str_found = next->d_name;
771 if (strncmp(str_found, pfind, strlen(pfind)))
773 /* not see .name without .match */
774 if (*str_found == '.' && *pfind == 0) {
775 if (*paths[i] == '/' && paths[i][1] == 0
776 && str_found[1] == 0) str_found = ""; /* only "/" */
780 found = concat_path_file(paths[i], str_found);
781 /* hmm, remover in progress? */
782 if (stat(found, &st) < 0)
784 /* find with dirs ? */
785 if (paths[i] != dirbuf)
786 strcpy(found, next->d_name); /* only name */
787 if (S_ISDIR(st.st_mode)) {
788 /* name is directory */
790 found = concat_path_file(found, "");
792 str_found = add_quote_for_spec_chars(found);
794 /* not put found file if search only dirs for cd */
795 if (type == FIND_DIR_ONLY)
797 str_found = add_quote_for_spec_chars(found);
798 if (type == FIND_FILE_ONLY ||
799 (type == FIND_EXE_ONLY && is_execute(&st) == TRUE))
800 strcat(str_found, " ");
802 /* Add it to the list */
803 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
805 matches[nm++] = str_found;
811 if (paths != path1) {
812 free(paths[0]); /* allocated memory only in first member */
819 static int match_compare(const void *a, const void *b)
821 return strcmp(*(char **) a, *(char **) b);
826 #define QUOT (UCHAR_MAX+1)
828 #define collapse_pos(is, in) { \
829 memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \
830 memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); }
832 static int find_match(char *matchBuf, int *len_with_quotes)
837 int int_buf[BUFSIZ + 1];
838 int pos_buf[BUFSIZ + 1];
840 /* set to integer dimension characters and own positions */
842 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
843 if (int_buf[i] == 0) {
844 pos_buf[i] = -1; /* indicator end line */
850 /* mask \+symbol and convert '\t' to ' ' */
851 for (i = j = 0; matchBuf[i]; i++, j++)
852 if (matchBuf[i] == '\\') {
853 collapse_pos(j, j + 1);
856 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
857 if (matchBuf[i] == '\t') /* algorithm equivalent */
858 int_buf[j] = ' ' | QUOT;
861 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
862 else if (matchBuf[i] == '\t')
866 /* mask "symbols" or 'symbols' */
868 for (i = 0; int_buf[i]; i++) {
870 if (c == '\'' || c == '"') {
879 } else if (c2 != 0 && c != '$')
883 /* skip commands with arguments if line have commands delimiters */
884 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
885 for (i = 0; int_buf[i]; i++) {
888 j = i ? int_buf[i - 1] : -1;
890 if (c == ';' || c == '&' || c == '|') {
891 command_mode = 1 + (c == c2);
893 if (j == '>' || j == '<')
895 } else if (c == '|' && j == '>')
899 collapse_pos(0, i + command_mode);
900 i = -1; /* hack incremet */
903 /* collapse `command...` */
904 for (i = 0; int_buf[i]; i++)
905 if (int_buf[i] == '`') {
906 for (j = i + 1; int_buf[j]; j++)
907 if (int_buf[j] == '`') {
908 collapse_pos(i, j + 1);
913 /* not found close ` - command mode, collapse all previous */
914 collapse_pos(0, i + 1);
917 i--; /* hack incremet */
920 /* collapse (command...(command...)...) or {command...{command...}...} */
921 c = 0; /* "recursive" level */
923 for (i = 0; int_buf[i]; i++)
924 if (int_buf[i] == '(' || int_buf[i] == '{') {
925 if (int_buf[i] == '(')
929 collapse_pos(0, i + 1);
930 i = -1; /* hack incremet */
932 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
933 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
934 if (int_buf[i] == ')')
938 collapse_pos(0, i + 1);
939 i = -1; /* hack incremet */
942 /* skip first not quote space */
943 for (i = 0; int_buf[i]; i++)
944 if (int_buf[i] != ' ')
949 /* set find mode for completion */
950 command_mode = FIND_EXE_ONLY;
951 for (i = 0; int_buf[i]; i++)
952 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
953 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
954 && matchBuf[pos_buf[0]]=='c'
955 && matchBuf[pos_buf[1]]=='d' )
956 command_mode = FIND_DIR_ONLY;
958 command_mode = FIND_FILE_ONLY;
963 for (i = 0; int_buf[i]; i++);
965 for (--i; i >= 0; i--) {
967 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
968 collapse_pos(0, i + 1);
972 /* skip first not quoted '\'' or '"' */
973 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
974 /* collapse quote or unquote // or /~ */
975 while ((int_buf[i] & ~QUOT) == '/' &&
976 ((int_buf[i + 1] & ~QUOT) == '/'
977 || (int_buf[i + 1] & ~QUOT) == '~')) {
984 /* set only match and destroy quotes */
986 for (i = 0; pos_buf[i] >= 0; i++) {
987 matchBuf[i] = matchBuf[pos_buf[i]];
991 /* old lenght matchBuf with quotes symbols */
992 *len_with_quotes = j ? j - pos_buf[0] : 0;
998 static void input_tab(int *lastWasTab)
1000 /* Do TAB completion */
1001 static int num_matches;
1002 static char **matches;
1004 if (lastWasTab == 0) { /* free all memory */
1006 while (num_matches > 0)
1007 free(matches[--num_matches]);
1009 matches = (char **) NULL;
1013 if (*lastWasTab == FALSE) {
1017 char matchBuf[BUFSIZ];
1021 *lastWasTab = TRUE; /* flop trigger */
1023 /* Make a local copy of the string -- up
1024 * to the position of the cursor */
1025 tmp = strncpy(matchBuf, command_ps, cursor);
1028 find_type = find_match(matchBuf, &recalc_pos);
1030 /* Free up any memory already allocated */
1033 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
1034 /* If the word starts with `~' and there is no slash in the word,
1035 * then try completing this word as a username. */
1037 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1038 matches = username_tab_completion(matchBuf, &num_matches);
1040 /* Try to match any executable in our path and everything
1041 * in the current working directory that matches. */
1044 exe_n_cwd_tab_completion(matchBuf, &num_matches,
1047 /* Did we find exactly one match? */
1048 if (!matches || num_matches > 1) {
1053 return; /* not found */
1055 qsort(matches, num_matches, sizeof(char *), match_compare);
1057 /* find minimal match */
1058 tmp = xstrdup(matches[0]);
1059 for (tmp1 = tmp; *tmp1; tmp1++)
1060 for (len_found = 1; len_found < num_matches; len_found++)
1061 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1065 if (*tmp == 0) { /* have unique */
1069 } else { /* one match */
1071 /* for next completion current found */
1072 *lastWasTab = FALSE;
1075 len_found = strlen(tmp);
1076 /* have space to placed match? */
1077 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1079 /* before word for match */
1080 command_ps[cursor - recalc_pos] = 0;
1081 /* save tail line */
1082 strcpy(matchBuf, command_ps + cursor);
1084 strcat(command_ps, tmp);
1086 strcat(command_ps, matchBuf);
1087 /* back to begin word for match */
1088 input_backward(recalc_pos);
1090 recalc_pos = cursor + len_found;
1092 len = strlen(command_ps);
1093 /* write out the matched command */
1095 input_backward(cursor - recalc_pos);
1097 if (tmp != matches[0])
1100 /* Ok -- the last char was a TAB. Since they
1101 * just hit TAB again, print a list of all the
1102 * available choices... */
1103 if (matches && num_matches > 0) {
1105 int sav_cursor = cursor; /* change goto_new_line() */
1107 /* Go to the next line */
1109 for (i = 0, col = 0; i < num_matches; i++) {
1110 l = strlen(matches[i]);
1113 printf("%-14s ", matches[i]);
1120 col -= (col / cmdedit_termw) * cmdedit_termw;
1121 if (col > 60 && matches[i + 1] != NULL) {
1126 /* Go to the next line and rewrite */
1128 redraw(0, len - sav_cursor);
1132 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
1134 static void get_previous_history(struct history **hp, struct history *p)
1138 (*hp)->s = xstrdup(command_ps);
1142 static inline void get_next_history(struct history **hp)
1144 get_previous_history(hp, (*hp)->n);
1154 * This function is used to grab a character buffer
1155 * from the input file descriptor and allows you to
1156 * a string with full command editing (sortof like
1159 * The following standard commands are not implemented:
1160 * ESC-b -- Move back one word
1161 * ESC-f -- Move forward one word
1162 * ESC-d -- Delete back one word
1163 * ESC-h -- Delete forward one word
1164 * CTL-t -- Transpose two characters
1166 * Furthermore, the "vi" command editing keys are not implemented.
1170 extern void cmdedit_read_input(char *prompt, char command[BUFSIZ])
1173 int inputFd = fileno(stdin);
1176 int lastWasTab = FALSE;
1177 unsigned char c = 0;
1178 struct history *hp = his_end;
1180 /* prepare before init handlers */
1181 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1183 command_ps = command;
1185 if (new_settings.c_cc[VMIN] == 0) { /* first call */
1187 getTermSettings(inputFd, (void *) &initial_settings);
1188 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1190 new_settings.c_cc[VMIN] = 1;
1191 new_settings.c_cc[VTIME] = 0;
1192 /* Turn off CTRL-C, so we can trap it */
1193 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1194 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1195 /* Turn off echoing */
1196 new_settings.c_lflag &= ~(ECHO | ECHOCTL | ECHONL);
1201 setTermSettings(inputFd, (void *) &new_settings);
1202 handlers_sets |= SET_RESET_TERM;
1204 /* Now initialize things */
1206 /* Print out the command prompt */
1207 parse_prompt(prompt);
1211 fflush(stdout); /* buffered out to fast */
1213 if (read(inputFd, &c, 1) < 1)
1214 /* if we can't read input then exit */
1215 goto prepare_to_die;
1225 /* Control-a -- Beginning of line */
1226 input_backward(cursor);
1229 /* Control-b -- Move back one character */
1233 /* Control-c -- stop gathering input */
1235 /* Link into lash to reset context to 0 on ^C and such */
1238 /* Go to the next line */
1244 /* Control-d -- Delete one character, or exit
1245 * if the len=0 and no chars to delete */
1249 clean_up_and_die(0);
1255 /* Control-e -- End of line */
1259 /* Control-f -- Move forward one character */
1264 /* Control-h and DEL */
1268 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1269 input_tab(&lastWasTab);
1273 /* Control-n -- Get next command in history */
1274 if (hp && hp->n && hp->n->s) {
1275 get_next_history(&hp);
1282 /* Control-p -- Get previous command from history */
1284 get_previous_history(&hp, hp->p);
1291 /* Control-U -- Clear line before cursor */
1293 strcpy(command, command + cursor);
1294 redraw(cmdedit_y, len -= cursor);
1299 /* escape sequence follows */
1300 if (read(inputFd, &c, 1) < 1)
1302 /* different vt100 emulations */
1303 if (c == '[' || c == 'O') {
1304 if (read(inputFd, &c, 1) < 1)
1308 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1309 case '\t': /* Alt-Tab */
1311 input_tab(&lastWasTab);
1315 /* Up Arrow -- Get previous command from history */
1317 get_previous_history(&hp, hp->p);
1324 /* Down Arrow -- Get next command in history */
1325 if (hp && hp->n && hp->n->s) {
1326 get_next_history(&hp);
1333 /* Rewrite the line with the selected history item */
1335 /* change command */
1336 len = strlen(strcpy(command, hp->s));
1337 /* redraw and go to end line */
1338 redraw(cmdedit_y, 0);
1341 /* Right Arrow -- Move forward one character */
1345 /* Left Arrow -- Move back one character */
1355 input_backward(cursor);
1363 if (!(c >= '1' && c <= '9'))
1367 if (c >= '1' && c <= '9')
1369 if (read(inputFd, &c, 1) < 1)
1375 default: /* If it's regular input, do the normal thing */
1376 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1377 /* Control-V -- Add non-printable symbol */
1379 if (read(inputFd, &c, 1) < 1)
1387 if (!Isprint(c)) /* Skip non-printable characters */
1390 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1395 if (cursor == (len - 1)) { /* Append if at the end of the line */
1396 *(command + cursor) = c;
1397 *(command + cursor + 1) = 0;
1398 cmdedit_set_out_char(0);
1399 } else { /* Insert otherwise */
1402 memmove(command + sc + 1, command + sc, len - sc);
1403 *(command + sc) = c;
1405 /* rewrite from cursor */
1407 /* to prev x pos + 1 */
1408 input_backward(cursor - sc);
1413 if (break_out) /* Enter is the command terminator, no more input. */
1420 setTermSettings(inputFd, (void *) &initial_settings);
1421 handlers_sets &= ~SET_RESET_TERM;
1423 /* Handle command history log */
1424 if (len) { /* no put empty line */
1426 struct history *h = his_end;
1429 ss = xstrdup(command); /* duplicate */
1432 /* No previous history -- this memory is never freed */
1433 h = his_front = xmalloc(sizeof(struct history));
1434 h->n = xmalloc(sizeof(struct history));
1444 /* Add a new history command -- this memory is never freed */
1445 h->n = xmalloc(sizeof(struct history));
1453 /* After max history, remove the oldest command */
1454 if (history_counter >= MAX_HISTORY) {
1456 struct history *p = his_front->n;
1466 #if !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
1470 command[len++] = '\n'; /* set '\n' */
1472 #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_COMMAND_TAB_COMPLETION)
1473 input_tab(0); /* strong free */
1475 #if !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
1476 free(cmdedit_prompt);
1482 /* Undo the effects of cmdedit_init(). */
1483 extern void cmdedit_terminate(void)
1485 cmdedit_reset_term();
1486 if ((handlers_sets & SET_TERM_HANDLERS) != 0) {
1487 signal(SIGKILL, SIG_DFL);
1488 signal(SIGINT, SIG_DFL);
1489 signal(SIGQUIT, SIG_DFL);
1490 signal(SIGTERM, SIG_DFL);
1491 signal(SIGWINCH, SIG_DFL);
1492 handlers_sets &= ~SET_TERM_HANDLERS;
1496 #endif /* BB_FEATURE_COMMAND_EDITING */
1501 const char *applet_name = "debug stuff usage";
1502 const char *memory_exhausted = "Memory exhausted";
1504 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1508 unsigned int shell_context;
1510 int main(int argc, char **argv)
1514 #if !defined(BB_FEATURE_SH_SIMPLE_PROMPT)
1515 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1516 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1517 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1522 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1523 setlocale(LC_ALL, "");
1528 cmdedit_read_input(prompt, buff);
1530 if(l > 0 && buff[l-1] == '\n')
1532 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1533 } while (shell_context);
1534 printf("*** cmdedit_read_input() detect ^C\n");