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_FANCY_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;
110 #define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
111 #define getTermSettings(fd,argp) tcgetattr(fd, argp);
113 /* Current termio and the previous termio before starting sh */
114 static struct termios initial_settings, new_settings;
118 volatile int cmdedit_termw = 80; /* actual terminal width */
119 static int history_counter = 0; /* Number of commands in history list */
121 volatile int handlers_sets = 0; /* Set next bites: */
124 SET_ATEXIT = 1, /* when atexit() has been called
125 and get euid,uid,gid to fast compare */
126 SET_TERM_HANDLERS = 2, /* set many terminates signal handlers */
127 SET_WCHG_HANDLERS = 4, /* winchg signal handler */
128 SET_RESET_TERM = 8, /* if the terminal needs to be reset upon exit */
132 static int cmdedit_x; /* real x terminal position */
133 static int cmdedit_y; /* pseudoreal y terminal position */
134 static int cmdedit_prmt_len; /* lenght prompt without colores string */
136 static int cursor; /* required global for signal handler */
137 static int len; /* --- "" - - "" - -"- --""-- --""--- */
138 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
140 #ifndef BB_FEATURE_SH_FANCY_PROMPT
143 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
145 /* Link into lash to reset context to 0 on ^C and such */
146 extern unsigned int shell_context;
149 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
150 static char *user_buf = "";
151 static char *home_pwd_buf = "";
155 #ifdef BB_FEATURE_SH_FANCY_PROMPT
156 static char *hostname_buf = "";
157 static int num_ok_lines = 1;
161 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
163 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
170 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
173 static void cmdedit_setwidth(int w, int redraw_flg);
175 static void win_changed(int nsig)
177 struct winsize win = { 0, 0, 0, 0 };
178 static __sighandler_t previous_SIGWINCH_handler; /* for reset */
180 /* emulate || signal call */
181 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
182 ioctl(0, TIOCGWINSZ, &win);
183 if (win.ws_col > 0) {
184 cmdedit_setwidth(win.ws_col, nsig == SIGWINCH);
187 /* Unix not all standart in recall signal */
189 if (nsig == -SIGWINCH) /* save previous handler */
190 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
191 else if (nsig == SIGWINCH) /* signaled called handler */
192 signal(SIGWINCH, win_changed); /* set for next call */
194 /* set previous handler */
195 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
198 static void cmdedit_reset_term(void)
200 if ((handlers_sets & SET_RESET_TERM) != 0) {
201 /* sparc and other have broken termios support: use old termio handling. */
202 setTermSettings(fileno(stdin), (void *) &initial_settings);
203 handlers_sets &= ~SET_RESET_TERM;
205 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
206 /* reset SIGWINCH handler to previous (default) */
208 handlers_sets &= ~SET_WCHG_HANDLERS;
211 #ifdef BB_FEATURE_CLEAN_UP
215 while (his_front != his_end) {
226 /* special for recount position for scroll and remove terminal margin effect */
227 static void cmdedit_set_out_char(int next_char)
230 int c = (int)((unsigned char) command_ps[cursor]);
233 c = ' '; /* destroy end char? */
234 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
235 if (!Isprint(c)) { /* Inverse put non-printable characters */
242 printf("\033[7m%c\033[0m", c);
246 if (++cmdedit_x >= cmdedit_termw) {
247 /* terminal is scrolled down */
253 /* destroy "(auto)margin" */
260 /* Move to end line. Bonus: rewrite line from cursor */
261 static void input_end(void)
264 cmdedit_set_out_char(0);
267 /* Go to the next line */
268 static void goto_new_line(void)
276 static inline void out1str(const char *s)
280 static inline void beep(void)
285 /* Move back one charactor */
286 /* special for slow terminal */
287 static void input_backward(int num)
291 cursor -= num; /* new cursor (in command, not terminal) */
293 if (cmdedit_x >= num) { /* no to up line */
300 printf("\033[%dD", num);
305 putchar('\r'); /* back to first terminal pos. */
306 num -= cmdedit_x; /* set previous backward */
308 count_y = 1 + num / cmdedit_termw;
309 printf("\033[%dA", count_y);
310 cmdedit_y -= count_y;
311 /* require forward after uping */
312 cmdedit_x = cmdedit_termw * count_y - num;
313 printf("\033[%dC", cmdedit_x); /* set term cursor */
317 static void put_prompt(void)
319 out1str(cmdedit_prompt);
320 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
324 #ifndef BB_FEATURE_SH_FANCY_PROMPT
325 static void parse_prompt(const char *prmt_ptr)
327 cmdedit_prompt = prmt_ptr;
328 cmdedit_prmt_len = strlen(prmt_ptr);
332 static void parse_prompt(const char *prmt_ptr)
336 char flg_not_length = '[';
337 char *prmt_mem_ptr = xcalloc(1, 1);
338 char *pwd_buf = xgetcwd(0);
339 char buf2[PATH_MAX + 1];
345 pwd_buf=(char *)unknown;
353 const char *cp = prmt_ptr;
356 c = process_escape_sequence(&prmt_ptr);
362 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
370 pbuf = xcalloc(256, 1);
371 if (gethostname(pbuf, 255) < 0) {
374 char *s = strchr(pbuf, '.');
383 c = my_euid == 0 ? '#' : '$';
385 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
388 l = strlen(home_pwd_buf);
389 if (home_pwd_buf[0] != 0 &&
390 strncmp(home_pwd_buf, pbuf, l) == 0 &&
391 (pbuf[l]=='/' || pbuf[l]=='\0') &&
392 strlen(pwd_buf+l)<PATH_MAX) {
395 strcpy(pbuf+1, pwd_buf+l);
401 cp = strrchr(pbuf,'/');
402 if ( (cp != NULL) && (cp != pbuf) )
406 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
408 case 'e': case 'E': /* \e \E = \033 */
412 for (l = 0; l < 3;) {
414 buf2[l++] = *prmt_ptr;
416 h = strtol(buf2, &pbuf, 16);
417 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
424 c = (char)strtol(buf2, 0, 16);
430 if (c == flg_not_length) {
431 flg_not_length = flg_not_length == '[' ? ']' : '[';
440 prmt_len += strlen(pbuf);
441 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
442 if (flg_not_length == ']')
445 if(pwd_buf!=(char *)unknown)
447 cmdedit_prompt = prmt_mem_ptr;
448 cmdedit_prmt_len = prmt_len - sub_len;
454 /* draw promt, editor line, and clear tail */
455 static void redraw(int y, int back_cursor)
457 if (y > 0) /* up to start y */
458 printf("\033[%dA", y);
459 cmdedit_y = 0; /* new quasireal y */
462 input_end(); /* rewrite */
463 printf("\033[J"); /* destroy tail after cursor */
464 input_backward(back_cursor);
467 /* Delete the char in front of the cursor */
468 static void input_delete(void)
475 strcpy(command_ps + j, command_ps + j + 1);
477 input_end(); /* rewtite new line */
478 cmdedit_set_out_char(0); /* destroy end char */
479 input_backward(cursor - j); /* back to old pos cursor */
482 /* Delete the char in back of the cursor */
483 static void input_backspace(void)
492 /* Move forward one charactor */
493 static void input_forward(void)
496 cmdedit_set_out_char(command_ps[cursor + 1]);
500 static void clean_up_and_die(int sig)
504 exit(EXIT_SUCCESS); /* cmdedit_reset_term() called in atexit */
505 cmdedit_reset_term();
508 static void cmdedit_setwidth(int w, int redraw_flg)
510 cmdedit_termw = cmdedit_prmt_len + 2;
511 if (w <= cmdedit_termw) {
512 cmdedit_termw = cmdedit_termw % w;
514 if (w > cmdedit_termw) {
518 /* new y for current cursor */
519 int new_y = (cursor + cmdedit_prmt_len) / w;
522 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
528 extern void cmdedit_init(void)
530 cmdedit_prmt_len = 0;
531 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
532 /* emulate usage handler to set handler and call yours work */
533 win_changed(-SIGWINCH);
534 handlers_sets |= SET_WCHG_HANDLERS;
537 if ((handlers_sets & SET_ATEXIT) == 0) {
538 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
539 struct passwd *entry;
542 entry = getpwuid(my_euid);
544 user_buf = xstrdup(entry->pw_name);
545 home_pwd_buf = xstrdup(entry->pw_dir);
549 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
551 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
556 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
557 handlers_sets |= SET_ATEXIT;
558 atexit(cmdedit_reset_term); /* be sure to do this only once */
561 if ((handlers_sets & SET_TERM_HANDLERS) == 0) {
562 signal(SIGKILL, clean_up_and_die);
563 signal(SIGINT, clean_up_and_die);
564 signal(SIGQUIT, clean_up_and_die);
565 signal(SIGTERM, clean_up_and_die);
566 handlers_sets |= SET_TERM_HANDLERS;
570 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
572 static int is_execute(const struct stat *st)
574 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
575 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
576 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
577 (st->st_mode & S_IXOTH)) return TRUE;
581 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
583 static char **username_tab_completion(char *ud, int *num_matches)
585 struct passwd *entry;
590 ud++; /* ~user/... to user/... */
591 userlen = strlen(ud);
593 if (num_matches == 0) { /* "~/..." or "~user/..." */
594 char *sav_ud = ud - 1;
597 if (*ud == '/') { /* "~/..." */
601 temp = strchr(ud, '/');
602 *temp = 0; /* ~user\0 */
603 entry = getpwnam(ud);
604 *temp = '/'; /* restore ~user/... */
607 home = entry->pw_dir;
610 if ((userlen + strlen(home) + 1) < BUFSIZ) {
611 char temp2[BUFSIZ]; /* argument size */
614 sprintf(temp2, "%s%s", home, ud);
615 strcpy(sav_ud, temp2);
618 return 0; /* void, result save to argument :-) */
621 char **matches = (char **) NULL;
626 while ((entry = getpwent()) != NULL) {
627 /* Null usernames should result in all users as possible completions. */
628 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
630 temp = xmalloc(3 + strlen(entry->pw_name));
631 sprintf(temp, "~%s/", entry->pw_name);
632 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
634 matches[nm++] = temp;
643 #endif /* BB_FEATURE_COMMAND_USERNAME_COMPLETION */
651 static int path_parse(char ***p, int flags)
657 /* if not setenv PATH variable, to search cur dir "." */
658 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
659 /* PATH=<empty> or PATH=:<empty> */
660 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
668 npth++; /* count words is + 1 count ':' */
669 tmp = strchr(tmp, ':');
672 break; /* :<empty> */
677 *p = xmalloc(npth * sizeof(char *));
680 (*p)[0] = xstrdup(tmp);
681 npth = 1; /* count words is + 1 count ':' */
684 tmp = strchr(tmp, ':');
686 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
688 break; /* :<empty> */
691 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
697 static char *add_quote_for_spec_chars(char *found)
700 char *s = xmalloc((strlen(found) + 1) * 2);
703 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
711 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
719 int nm = *num_matches;
722 char **paths = path1;
726 char *pfind = strrchr(command, '/');
731 /* no dir, if flags==EXE_ONLY - get paths, else "." */
732 npaths = path_parse(&paths, type);
736 /* save for change */
737 strcpy(dirbuf, command);
739 dirbuf[(pfind - command) + 1] = 0;
740 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
741 if (dirbuf[0] == '~') /* ~/... or ~user/... */
742 username_tab_completion(dirbuf, 0);
744 /* "strip" dirname in command */
748 npaths = 1; /* only 1 dir */
751 for (i = 0; i < npaths; i++) {
753 dir = opendir(paths[i]);
754 if (!dir) /* Don't print an error */
757 while ((next = readdir(dir)) != NULL) {
758 char *str_found = next->d_name;
761 if (strncmp(str_found, pfind, strlen(pfind)))
763 /* not see .name without .match */
764 if (*str_found == '.' && *pfind == 0) {
765 if (*paths[i] == '/' && paths[i][1] == 0
766 && str_found[1] == 0) str_found = ""; /* only "/" */
770 found = concat_path_file(paths[i], str_found);
771 /* hmm, remover in progress? */
772 if (stat(found, &st) < 0)
774 /* find with dirs ? */
775 if (paths[i] != dirbuf)
776 strcpy(found, next->d_name); /* only name */
777 if (S_ISDIR(st.st_mode)) {
778 /* name is directory */
780 found = concat_path_file(found, "");
782 str_found = add_quote_for_spec_chars(found);
784 /* not put found file if search only dirs for cd */
785 if (type == FIND_DIR_ONLY)
787 str_found = add_quote_for_spec_chars(found);
788 if (type == FIND_FILE_ONLY ||
789 (type == FIND_EXE_ONLY && is_execute(&st) == TRUE))
790 strcat(str_found, " ");
792 /* Add it to the list */
793 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
795 matches[nm++] = str_found;
801 if (paths != path1) {
802 free(paths[0]); /* allocated memory only in first member */
809 static int match_compare(const void *a, const void *b)
811 return strcmp(*(char **) a, *(char **) b);
816 #define QUOT (UCHAR_MAX+1)
818 #define collapse_pos(is, in) { \
819 memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \
820 memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); }
822 static int find_match(char *matchBuf, int *len_with_quotes)
827 int int_buf[BUFSIZ + 1];
828 int pos_buf[BUFSIZ + 1];
830 /* set to integer dimension characters and own positions */
832 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
833 if (int_buf[i] == 0) {
834 pos_buf[i] = -1; /* indicator end line */
840 /* mask \+symbol and convert '\t' to ' ' */
841 for (i = j = 0; matchBuf[i]; i++, j++)
842 if (matchBuf[i] == '\\') {
843 collapse_pos(j, j + 1);
846 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
847 if (matchBuf[i] == '\t') /* algorithm equivalent */
848 int_buf[j] = ' ' | QUOT;
851 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
852 else if (matchBuf[i] == '\t')
856 /* mask "symbols" or 'symbols' */
858 for (i = 0; int_buf[i]; i++) {
860 if (c == '\'' || c == '"') {
869 } else if (c2 != 0 && c != '$')
873 /* skip commands with arguments if line have commands delimiters */
874 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
875 for (i = 0; int_buf[i]; i++) {
878 j = i ? int_buf[i - 1] : -1;
880 if (c == ';' || c == '&' || c == '|') {
881 command_mode = 1 + (c == c2);
883 if (j == '>' || j == '<')
885 } else if (c == '|' && j == '>')
889 collapse_pos(0, i + command_mode);
890 i = -1; /* hack incremet */
893 /* collapse `command...` */
894 for (i = 0; int_buf[i]; i++)
895 if (int_buf[i] == '`') {
896 for (j = i + 1; int_buf[j]; j++)
897 if (int_buf[j] == '`') {
898 collapse_pos(i, j + 1);
903 /* not found close ` - command mode, collapse all previous */
904 collapse_pos(0, i + 1);
907 i--; /* hack incremet */
910 /* collapse (command...(command...)...) or {command...{command...}...} */
911 c = 0; /* "recursive" level */
913 for (i = 0; int_buf[i]; i++)
914 if (int_buf[i] == '(' || int_buf[i] == '{') {
915 if (int_buf[i] == '(')
919 collapse_pos(0, i + 1);
920 i = -1; /* hack incremet */
922 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
923 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
924 if (int_buf[i] == ')')
928 collapse_pos(0, i + 1);
929 i = -1; /* hack incremet */
932 /* skip first not quote space */
933 for (i = 0; int_buf[i]; i++)
934 if (int_buf[i] != ' ')
939 /* set find mode for completion */
940 command_mode = FIND_EXE_ONLY;
941 for (i = 0; int_buf[i]; i++)
942 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
943 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
944 && matchBuf[pos_buf[0]]=='c'
945 && matchBuf[pos_buf[1]]=='d' )
946 command_mode = FIND_DIR_ONLY;
948 command_mode = FIND_FILE_ONLY;
953 for (i = 0; int_buf[i]; i++);
955 for (--i; i >= 0; i--) {
957 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
958 collapse_pos(0, i + 1);
962 /* skip first not quoted '\'' or '"' */
963 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
964 /* collapse quote or unquote // or /~ */
965 while ((int_buf[i] & ~QUOT) == '/' &&
966 ((int_buf[i + 1] & ~QUOT) == '/'
967 || (int_buf[i + 1] & ~QUOT) == '~')) {
971 /* set only match and destroy quotes */
973 for (c = 0; pos_buf[i] >= 0; i++) {
974 matchBuf[c++] = matchBuf[pos_buf[i]];
978 /* old lenght matchBuf with quotes symbols */
979 *len_with_quotes = j ? j - pos_buf[0] : 0;
985 static void input_tab(int *lastWasTab)
987 /* Do TAB completion */
988 static int num_matches;
989 static char **matches;
991 if (lastWasTab == 0) { /* free all memory */
993 while (num_matches > 0)
994 free(matches[--num_matches]);
996 matches = (char **) NULL;
1000 if (*lastWasTab == FALSE) {
1004 char matchBuf[BUFSIZ];
1008 *lastWasTab = TRUE; /* flop trigger */
1010 /* Make a local copy of the string -- up
1011 * to the position of the cursor */
1012 tmp = strncpy(matchBuf, command_ps, cursor);
1015 find_type = find_match(matchBuf, &recalc_pos);
1017 /* Free up any memory already allocated */
1020 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
1021 /* If the word starts with `~' and there is no slash in the word,
1022 * then try completing this word as a username. */
1024 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1025 matches = username_tab_completion(matchBuf, &num_matches);
1027 /* Try to match any executable in our path and everything
1028 * in the current working directory that matches. */
1031 exe_n_cwd_tab_completion(matchBuf,
1032 &num_matches, find_type);
1033 /* Remove duplicate found */
1037 for(i=0; i<(num_matches-1); i++)
1038 for(j=i+1; j<num_matches; j++)
1039 if(matches[i]!=0 && matches[j]!=0 &&
1040 strcmp(matches[i], matches[j])==0) {
1048 if(!strcmp(matches[i], "./"))
1050 else if(!strcmp(matches[i], "../"))
1052 matches[num_matches++]=matches[i];
1055 /* Did we find exactly one match? */
1056 if (!matches || num_matches > 1) {
1061 return; /* not found */
1063 qsort(matches, num_matches, sizeof(char *), match_compare);
1065 /* find minimal match */
1066 tmp = xstrdup(matches[0]);
1067 for (tmp1 = tmp; *tmp1; tmp1++)
1068 for (len_found = 1; len_found < num_matches; len_found++)
1069 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1073 if (*tmp == 0) { /* have unique */
1077 } else { /* one match */
1079 /* for next completion current found */
1080 *lastWasTab = FALSE;
1083 len_found = strlen(tmp);
1084 /* have space to placed match? */
1085 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1087 /* before word for match */
1088 command_ps[cursor - recalc_pos] = 0;
1089 /* save tail line */
1090 strcpy(matchBuf, command_ps + cursor);
1092 strcat(command_ps, tmp);
1094 strcat(command_ps, matchBuf);
1095 /* back to begin word for match */
1096 input_backward(recalc_pos);
1098 recalc_pos = cursor + len_found;
1100 len = strlen(command_ps);
1101 /* write out the matched command */
1102 redraw(cmdedit_y, len - recalc_pos);
1104 if (tmp != matches[0])
1107 /* Ok -- the last char was a TAB. Since they
1108 * just hit TAB again, print a list of all the
1109 * available choices... */
1110 if (matches && num_matches > 0) {
1112 int sav_cursor = cursor; /* change goto_new_line() */
1114 /* Go to the next line */
1116 for (i = 0, col = 0; i < num_matches; i++) {
1117 l = strlen(matches[i]);
1120 printf("%-14s ", matches[i]);
1127 col -= (col / cmdedit_termw) * cmdedit_termw;
1128 if (col > 60 && matches[i + 1] != NULL) {
1133 /* Go to the next line and rewrite */
1135 redraw(0, len - sav_cursor);
1139 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
1141 static void get_previous_history(struct history **hp, struct history *p)
1145 (*hp)->s = xstrdup(command_ps);
1149 static inline void get_next_history(struct history **hp)
1151 get_previous_history(hp, (*hp)->n);
1161 * This function is used to grab a character buffer
1162 * from the input file descriptor and allows you to
1163 * a string with full command editing (sortof like
1166 * The following standard commands are not implemented:
1167 * ESC-b -- Move back one word
1168 * ESC-f -- Move forward one word
1169 * ESC-d -- Delete back one word
1170 * ESC-h -- Delete forward one word
1171 * CTL-t -- Transpose two characters
1173 * Furthermore, the "vi" command editing keys are not implemented.
1177 extern void cmdedit_read_input(char *prompt, char command[BUFSIZ])
1181 int lastWasTab = FALSE;
1182 unsigned char c = 0;
1183 struct history *hp = his_end;
1185 /* prepare before init handlers */
1186 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1188 command_ps = command;
1190 if (new_settings.c_cc[VERASE] == 0) { /* first call */
1192 getTermSettings(0, (void *) &initial_settings);
1193 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1194 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1195 /* Turn off echoing and CTRL-C, so we can trap it */
1196 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1198 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1199 new_settings.c_cc[VMIN] = 1;
1200 new_settings.c_cc[VTIME] = 0;
1201 /* Turn off CTRL-C, so we can trap it */
1202 # ifndef _POSIX_VDISABLE
1203 # define _POSIX_VDISABLE '\0'
1205 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1211 setTermSettings(0, (void *) &new_settings);
1212 handlers_sets |= SET_RESET_TERM;
1214 /* Now initialize things */
1216 /* Print out the command prompt */
1217 parse_prompt(prompt);
1221 fflush(stdout); /* buffered out to fast */
1223 if (read(0, &c, 1) < 1)
1224 /* if we can't read input then exit */
1225 goto prepare_to_die;
1235 /* Control-a -- Beginning of line */
1236 input_backward(cursor);
1239 /* Control-b -- Move back one character */
1243 /* Control-c -- stop gathering input */
1245 /* Link into lash to reset context to 0 on ^C and such */
1248 /* Go to the next line */
1254 /* Control-d -- Delete one character, or exit
1255 * if the len=0 and no chars to delete */
1259 clean_up_and_die(0);
1265 /* Control-e -- End of line */
1269 /* Control-f -- Move forward one character */
1274 /* Control-h and DEL */
1278 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1279 input_tab(&lastWasTab);
1283 /* Control-n -- Get next command in history */
1284 if (hp && hp->n && hp->n->s) {
1285 get_next_history(&hp);
1292 /* Control-p -- Get previous command from history */
1294 get_previous_history(&hp, hp->p);
1301 /* Control-U -- Clear line before cursor */
1303 strcpy(command, command + cursor);
1304 redraw(cmdedit_y, len -= cursor);
1309 /* escape sequence follows */
1310 if (read(0, &c, 1) < 1)
1312 /* different vt100 emulations */
1313 if (c == '[' || c == 'O') {
1314 if (read(0, &c, 1) < 1)
1318 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1319 case '\t': /* Alt-Tab */
1321 input_tab(&lastWasTab);
1325 /* Up Arrow -- Get previous command from history */
1327 get_previous_history(&hp, hp->p);
1334 /* Down Arrow -- Get next command in history */
1335 if (hp && hp->n && hp->n->s) {
1336 get_next_history(&hp);
1343 /* Rewrite the line with the selected history item */
1345 /* change command */
1346 len = strlen(strcpy(command, hp->s));
1347 /* redraw and go to end line */
1348 redraw(cmdedit_y, 0);
1351 /* Right Arrow -- Move forward one character */
1355 /* Left Arrow -- Move back one character */
1365 input_backward(cursor);
1373 if (!(c >= '1' && c <= '9'))
1377 if (c >= '1' && c <= '9')
1379 if (read(0, &c, 1) < 1)
1385 default: /* If it's regular input, do the normal thing */
1386 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1387 /* Control-V -- Add non-printable symbol */
1389 if (read(0, &c, 1) < 1)
1397 if (!Isprint(c)) /* Skip non-printable characters */
1400 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1405 if (cursor == (len - 1)) { /* Append if at the end of the line */
1406 *(command + cursor) = c;
1407 *(command + cursor + 1) = 0;
1408 cmdedit_set_out_char(0);
1409 } else { /* Insert otherwise */
1412 memmove(command + sc + 1, command + sc, len - sc);
1413 *(command + sc) = c;
1415 /* rewrite from cursor */
1417 /* to prev x pos + 1 */
1418 input_backward(cursor - sc);
1423 if (break_out) /* Enter is the command terminator, no more input. */
1430 setTermSettings(0, (void *) &initial_settings);
1431 handlers_sets &= ~SET_RESET_TERM;
1433 /* Handle command history log */
1434 if (len) { /* no put empty line */
1436 struct history *h = his_end;
1439 ss = xstrdup(command); /* duplicate */
1442 /* No previous history -- this memory is never freed */
1443 h = his_front = xmalloc(sizeof(struct history));
1444 h->n = xmalloc(sizeof(struct history));
1454 /* Add a new history command -- this memory is never freed */
1455 h->n = xmalloc(sizeof(struct history));
1463 /* After max history, remove the oldest command */
1464 if (history_counter >= MAX_HISTORY) {
1466 struct history *p = his_front->n;
1476 #if defined(BB_FEATURE_SH_FANCY_PROMPT)
1480 command[len++] = '\n'; /* set '\n' */
1482 #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_COMMAND_TAB_COMPLETION)
1483 input_tab(0); /* strong free */
1485 #if defined(BB_FEATURE_SH_FANCY_PROMPT)
1486 free(cmdedit_prompt);
1492 /* Undo the effects of cmdedit_init(). */
1493 extern void cmdedit_terminate(void)
1495 cmdedit_reset_term();
1496 if ((handlers_sets & SET_TERM_HANDLERS) != 0) {
1497 signal(SIGKILL, SIG_DFL);
1498 signal(SIGINT, SIG_DFL);
1499 signal(SIGQUIT, SIG_DFL);
1500 signal(SIGTERM, SIG_DFL);
1501 signal(SIGWINCH, SIG_DFL);
1502 handlers_sets &= ~SET_TERM_HANDLERS;
1506 #endif /* BB_FEATURE_COMMAND_EDITING */
1511 const char *applet_name = "debug stuff usage";
1512 const char *memory_exhausted = "Memory exhausted";
1514 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1518 unsigned int shell_context;
1520 int main(int argc, char **argv)
1524 #if defined(BB_FEATURE_SH_FANCY_PROMPT)
1525 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1526 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1527 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1532 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1533 setlocale(LC_ALL, "");
1538 cmdedit_read_input(prompt, buff);
1540 if(l > 0 && buff[l-1] == '\n')
1542 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1543 } while (shell_context);
1544 printf("*** cmdedit_read_input() detect ^C\n");