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 <dzo@simtreas.ru>
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_WCHG_HANDLERS = 2, /* winchg signal handler */
127 SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */
131 static int cmdedit_x; /* real x terminal position */
132 static int cmdedit_y; /* pseudoreal y terminal position */
133 static int cmdedit_prmt_len; /* lenght prompt without colores string */
135 static int cursor; /* required global for signal handler */
136 static int len; /* --- "" - - "" - -"- --""-- --""--- */
137 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
139 #ifndef BB_FEATURE_SH_FANCY_PROMPT
142 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
144 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
145 static char *user_buf = "";
146 static char *home_pwd_buf = "";
150 #ifdef BB_FEATURE_SH_FANCY_PROMPT
151 static char *hostname_buf = "";
152 static int num_ok_lines = 1;
156 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
158 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
165 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
168 static void cmdedit_setwidth(int w, int redraw_flg);
170 static void win_changed(int nsig)
172 struct winsize win = { 0, 0, 0, 0 };
173 static sighandler_t previous_SIGWINCH_handler; /* for reset */
175 /* emulate || signal call */
176 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
177 ioctl(0, TIOCGWINSZ, &win);
178 if (win.ws_col > 0) {
179 cmdedit_setwidth(win.ws_col, nsig == SIGWINCH);
182 /* Unix not all standart in recall signal */
184 if (nsig == -SIGWINCH) /* save previous handler */
185 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
186 else if (nsig == SIGWINCH) /* signaled called handler */
187 signal(SIGWINCH, win_changed); /* set for next call */
189 /* set previous handler */
190 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
193 static void cmdedit_reset_term(void)
195 if ((handlers_sets & SET_RESET_TERM) != 0) {
196 /* sparc and other have broken termios support: use old termio handling. */
197 setTermSettings(fileno(stdin), (void *) &initial_settings);
198 handlers_sets &= ~SET_RESET_TERM;
200 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
201 /* reset SIGWINCH handler to previous (default) */
203 handlers_sets &= ~SET_WCHG_HANDLERS;
206 #ifdef BB_FEATURE_CLEAN_UP
210 while (his_front != his_end) {
221 /* special for recount position for scroll and remove terminal margin effect */
222 static void cmdedit_set_out_char(int next_char)
225 int c = (int)((unsigned char) command_ps[cursor]);
228 c = ' '; /* destroy end char? */
229 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
230 if (!Isprint(c)) { /* Inverse put non-printable characters */
237 printf("\033[7m%c\033[0m", c);
241 if (++cmdedit_x >= cmdedit_termw) {
242 /* terminal is scrolled down */
248 /* destroy "(auto)margin" */
255 /* Move to end line. Bonus: rewrite line from cursor */
256 static void input_end(void)
259 cmdedit_set_out_char(0);
262 /* Go to the next line */
263 static void goto_new_line(void)
271 static inline void out1str(const char *s)
275 static inline void beep(void)
280 /* Move back one charactor */
281 /* special for slow terminal */
282 static void input_backward(int num)
286 cursor -= num; /* new cursor (in command, not terminal) */
288 if (cmdedit_x >= num) { /* no to up line */
295 printf("\033[%dD", num);
300 putchar('\r'); /* back to first terminal pos. */
301 num -= cmdedit_x; /* set previous backward */
303 count_y = 1 + num / cmdedit_termw;
304 printf("\033[%dA", count_y);
305 cmdedit_y -= count_y;
306 /* require forward after uping */
307 cmdedit_x = cmdedit_termw * count_y - num;
308 printf("\033[%dC", cmdedit_x); /* set term cursor */
312 static void put_prompt(void)
314 out1str(cmdedit_prompt);
315 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
317 cmdedit_y = 0; /* new quasireal y */
320 #ifndef BB_FEATURE_SH_FANCY_PROMPT
321 static void parse_prompt(const char *prmt_ptr)
323 cmdedit_prompt = prmt_ptr;
324 cmdedit_prmt_len = strlen(prmt_ptr);
328 static void parse_prompt(const char *prmt_ptr)
332 char flg_not_length = '[';
333 char *prmt_mem_ptr = xcalloc(1, 1);
334 char *pwd_buf = xgetcwd(0);
335 char buf2[PATH_MAX + 1];
341 pwd_buf=(char *)unknown;
349 const char *cp = prmt_ptr;
352 c = process_escape_sequence(&prmt_ptr);
358 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
366 pbuf = xcalloc(256, 1);
367 if (gethostname(pbuf, 255) < 0) {
370 char *s = strchr(pbuf, '.');
379 c = my_euid == 0 ? '#' : '$';
381 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
384 l = strlen(home_pwd_buf);
385 if (home_pwd_buf[0] != 0 &&
386 strncmp(home_pwd_buf, pbuf, l) == 0 &&
387 (pbuf[l]=='/' || pbuf[l]=='\0') &&
388 strlen(pwd_buf+l)<PATH_MAX) {
391 strcpy(pbuf+1, pwd_buf+l);
397 cp = strrchr(pbuf,'/');
398 if ( (cp != NULL) && (cp != pbuf) )
402 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
404 case 'e': case 'E': /* \e \E = \033 */
408 for (l = 0; l < 3;) {
410 buf2[l++] = *prmt_ptr;
412 h = strtol(buf2, &pbuf, 16);
413 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
420 c = (char)strtol(buf2, 0, 16);
426 if (c == flg_not_length) {
427 flg_not_length = flg_not_length == '[' ? ']' : '[';
436 prmt_len += strlen(pbuf);
437 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
438 if (flg_not_length == ']')
441 if(pwd_buf!=(char *)unknown)
443 cmdedit_prompt = prmt_mem_ptr;
444 cmdedit_prmt_len = prmt_len - sub_len;
450 /* draw promt, editor line, and clear tail */
451 static void redraw(int y, int back_cursor)
453 if (y > 0) /* up to start y */
454 printf("\033[%dA", y);
457 input_end(); /* rewrite */
458 printf("\033[J"); /* destroy tail after cursor */
459 input_backward(back_cursor);
462 /* Delete the char in front of the cursor */
463 static void input_delete(void)
470 strcpy(command_ps + j, command_ps + j + 1);
472 input_end(); /* rewtite new line */
473 cmdedit_set_out_char(0); /* destroy end char */
474 input_backward(cursor - j); /* back to old pos cursor */
477 /* Delete the char in back of the cursor */
478 static void input_backspace(void)
487 /* Move forward one charactor */
488 static void input_forward(void)
491 cmdedit_set_out_char(command_ps[cursor + 1]);
495 static void cmdedit_setwidth(int w, int redraw_flg)
497 cmdedit_termw = cmdedit_prmt_len + 2;
498 if (w <= cmdedit_termw) {
499 cmdedit_termw = cmdedit_termw % w;
501 if (w > cmdedit_termw) {
505 /* new y for current cursor */
506 int new_y = (cursor + cmdedit_prmt_len) / w;
509 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
515 static void cmdedit_init(void)
517 cmdedit_prmt_len = 0;
518 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
519 /* emulate usage handler to set handler and call yours work */
520 win_changed(-SIGWINCH);
521 handlers_sets |= SET_WCHG_HANDLERS;
524 if ((handlers_sets & SET_ATEXIT) == 0) {
525 #ifdef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
526 struct passwd *entry;
529 entry = getpwuid(my_euid);
531 user_buf = xstrdup(entry->pw_name);
532 home_pwd_buf = xstrdup(entry->pw_dir);
536 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
538 #ifndef BB_FEATURE_GETUSERNAME_AND_HOMEDIR
543 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
544 handlers_sets |= SET_ATEXIT;
545 atexit(cmdedit_reset_term); /* be sure to do this only once */
549 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
551 static int is_execute(const struct stat *st)
553 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
554 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
555 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
556 (st->st_mode & S_IXOTH)) return TRUE;
560 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
562 static char **username_tab_completion(char *ud, int *num_matches)
564 struct passwd *entry;
569 ud++; /* ~user/... to user/... */
570 userlen = strlen(ud);
572 if (num_matches == 0) { /* "~/..." or "~user/..." */
573 char *sav_ud = ud - 1;
576 if (*ud == '/') { /* "~/..." */
580 temp = strchr(ud, '/');
581 *temp = 0; /* ~user\0 */
582 entry = getpwnam(ud);
583 *temp = '/'; /* restore ~user/... */
586 home = entry->pw_dir;
589 if ((userlen + strlen(home) + 1) < BUFSIZ) {
590 char temp2[BUFSIZ]; /* argument size */
593 sprintf(temp2, "%s%s", home, ud);
594 strcpy(sav_ud, temp2);
597 return 0; /* void, result save to argument :-) */
600 char **matches = (char **) NULL;
605 while ((entry = getpwent()) != NULL) {
606 /* Null usernames should result in all users as possible completions. */
607 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
609 temp = xmalloc(3 + strlen(entry->pw_name));
610 sprintf(temp, "~%s/", entry->pw_name);
611 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
613 matches[nm++] = temp;
622 #endif /* BB_FEATURE_COMMAND_USERNAME_COMPLETION */
630 static int path_parse(char ***p, int flags)
636 /* if not setenv PATH variable, to search cur dir "." */
637 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
638 /* PATH=<empty> or PATH=:<empty> */
639 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
647 npth++; /* count words is + 1 count ':' */
648 tmp = strchr(tmp, ':');
651 break; /* :<empty> */
656 *p = xmalloc(npth * sizeof(char *));
659 (*p)[0] = xstrdup(tmp);
660 npth = 1; /* count words is + 1 count ':' */
663 tmp = strchr(tmp, ':');
665 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
667 break; /* :<empty> */
670 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
676 static char *add_quote_for_spec_chars(char *found)
679 char *s = xmalloc((strlen(found) + 1) * 2);
682 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
690 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
698 int nm = *num_matches;
701 char **paths = path1;
705 char *pfind = strrchr(command, '/');
710 /* no dir, if flags==EXE_ONLY - get paths, else "." */
711 npaths = path_parse(&paths, type);
715 /* save for change */
716 strcpy(dirbuf, command);
718 dirbuf[(pfind - command) + 1] = 0;
719 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
720 if (dirbuf[0] == '~') /* ~/... or ~user/... */
721 username_tab_completion(dirbuf, 0);
723 /* "strip" dirname in command */
727 npaths = 1; /* only 1 dir */
730 for (i = 0; i < npaths; i++) {
732 dir = opendir(paths[i]);
733 if (!dir) /* Don't print an error */
736 while ((next = readdir(dir)) != NULL) {
737 char *str_found = next->d_name;
740 if (strncmp(str_found, pfind, strlen(pfind)))
742 /* not see .name without .match */
743 if (*str_found == '.' && *pfind == 0) {
744 if (*paths[i] == '/' && paths[i][1] == 0
745 && str_found[1] == 0) str_found = ""; /* only "/" */
749 found = concat_path_file(paths[i], str_found);
750 /* hmm, remover in progress? */
751 if (stat(found, &st) < 0)
753 /* find with dirs ? */
754 if (paths[i] != dirbuf)
755 strcpy(found, next->d_name); /* only name */
756 if (S_ISDIR(st.st_mode)) {
757 /* name is directory */
759 found = concat_path_file(found, "");
761 str_found = add_quote_for_spec_chars(found);
763 /* not put found file if search only dirs for cd */
764 if (type == FIND_DIR_ONLY)
766 str_found = add_quote_for_spec_chars(found);
767 if (type == FIND_FILE_ONLY ||
768 (type == FIND_EXE_ONLY && is_execute(&st) == TRUE))
769 strcat(str_found, " ");
771 /* Add it to the list */
772 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
774 matches[nm++] = str_found;
780 if (paths != path1) {
781 free(paths[0]); /* allocated memory only in first member */
788 static int match_compare(const void *a, const void *b)
790 return strcmp(*(char **) a, *(char **) b);
795 #define QUOT (UCHAR_MAX+1)
797 #define collapse_pos(is, in) { \
798 memcpy(int_buf+is, int_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); \
799 memcpy(pos_buf+is, pos_buf+in, (BUFSIZ+1-is-in)*sizeof(int)); }
801 static int find_match(char *matchBuf, int *len_with_quotes)
806 int int_buf[BUFSIZ + 1];
807 int pos_buf[BUFSIZ + 1];
809 /* set to integer dimension characters and own positions */
811 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
812 if (int_buf[i] == 0) {
813 pos_buf[i] = -1; /* indicator end line */
819 /* mask \+symbol and convert '\t' to ' ' */
820 for (i = j = 0; matchBuf[i]; i++, j++)
821 if (matchBuf[i] == '\\') {
822 collapse_pos(j, j + 1);
825 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
826 if (matchBuf[i] == '\t') /* algorithm equivalent */
827 int_buf[j] = ' ' | QUOT;
830 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
831 else if (matchBuf[i] == '\t')
835 /* mask "symbols" or 'symbols' */
837 for (i = 0; int_buf[i]; i++) {
839 if (c == '\'' || c == '"') {
848 } else if (c2 != 0 && c != '$')
852 /* skip commands with arguments if line have commands delimiters */
853 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
854 for (i = 0; int_buf[i]; i++) {
857 j = i ? int_buf[i - 1] : -1;
859 if (c == ';' || c == '&' || c == '|') {
860 command_mode = 1 + (c == c2);
862 if (j == '>' || j == '<')
864 } else if (c == '|' && j == '>')
868 collapse_pos(0, i + command_mode);
869 i = -1; /* hack incremet */
872 /* collapse `command...` */
873 for (i = 0; int_buf[i]; i++)
874 if (int_buf[i] == '`') {
875 for (j = i + 1; int_buf[j]; j++)
876 if (int_buf[j] == '`') {
877 collapse_pos(i, j + 1);
882 /* not found close ` - command mode, collapse all previous */
883 collapse_pos(0, i + 1);
886 i--; /* hack incremet */
889 /* collapse (command...(command...)...) or {command...{command...}...} */
890 c = 0; /* "recursive" level */
892 for (i = 0; int_buf[i]; i++)
893 if (int_buf[i] == '(' || int_buf[i] == '{') {
894 if (int_buf[i] == '(')
898 collapse_pos(0, i + 1);
899 i = -1; /* hack incremet */
901 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
902 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
903 if (int_buf[i] == ')')
907 collapse_pos(0, i + 1);
908 i = -1; /* hack incremet */
911 /* skip first not quote space */
912 for (i = 0; int_buf[i]; i++)
913 if (int_buf[i] != ' ')
918 /* set find mode for completion */
919 command_mode = FIND_EXE_ONLY;
920 for (i = 0; int_buf[i]; i++)
921 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
922 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
923 && matchBuf[pos_buf[0]]=='c'
924 && matchBuf[pos_buf[1]]=='d' )
925 command_mode = FIND_DIR_ONLY;
927 command_mode = FIND_FILE_ONLY;
932 for (i = 0; int_buf[i]; i++);
934 for (--i; i >= 0; i--) {
936 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
937 collapse_pos(0, i + 1);
941 /* skip first not quoted '\'' or '"' */
942 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
943 /* collapse quote or unquote // or /~ */
944 while ((int_buf[i] & ~QUOT) == '/' &&
945 ((int_buf[i + 1] & ~QUOT) == '/'
946 || (int_buf[i + 1] & ~QUOT) == '~')) {
950 /* set only match and destroy quotes */
952 for (c = 0; pos_buf[i] >= 0; i++) {
953 matchBuf[c++] = matchBuf[pos_buf[i]];
957 /* old lenght matchBuf with quotes symbols */
958 *len_with_quotes = j ? j - pos_buf[0] : 0;
964 static void input_tab(int *lastWasTab)
966 /* Do TAB completion */
967 static int num_matches;
968 static char **matches;
970 if (lastWasTab == 0) { /* free all memory */
972 while (num_matches > 0)
973 free(matches[--num_matches]);
975 matches = (char **) NULL;
979 if (*lastWasTab == FALSE) {
983 char matchBuf[BUFSIZ];
987 *lastWasTab = TRUE; /* flop trigger */
989 /* Make a local copy of the string -- up
990 * to the position of the cursor */
991 tmp = strncpy(matchBuf, command_ps, cursor);
994 find_type = find_match(matchBuf, &recalc_pos);
996 /* Free up any memory already allocated */
999 #ifdef BB_FEATURE_COMMAND_USERNAME_COMPLETION
1000 /* If the word starts with `~' and there is no slash in the word,
1001 * then try completing this word as a username. */
1003 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1004 matches = username_tab_completion(matchBuf, &num_matches);
1006 /* Try to match any executable in our path and everything
1007 * in the current working directory that matches. */
1010 exe_n_cwd_tab_completion(matchBuf,
1011 &num_matches, find_type);
1012 /* Remove duplicate found */
1016 for(i=0; i<(num_matches-1); i++)
1017 for(j=i+1; j<num_matches; j++)
1018 if(matches[i]!=0 && matches[j]!=0 &&
1019 strcmp(matches[i], matches[j])==0) {
1027 if(!strcmp(matches[i], "./"))
1029 else if(!strcmp(matches[i], "../"))
1031 matches[num_matches++]=matches[i];
1034 /* Did we find exactly one match? */
1035 if (!matches || num_matches > 1) {
1040 return; /* not found */
1042 qsort(matches, num_matches, sizeof(char *), match_compare);
1044 /* find minimal match */
1045 tmp = xstrdup(matches[0]);
1046 for (tmp1 = tmp; *tmp1; tmp1++)
1047 for (len_found = 1; len_found < num_matches; len_found++)
1048 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1052 if (*tmp == 0) { /* have unique */
1056 } else { /* one match */
1058 /* for next completion current found */
1059 *lastWasTab = FALSE;
1062 len_found = strlen(tmp);
1063 /* have space to placed match? */
1064 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1066 /* before word for match */
1067 command_ps[cursor - recalc_pos] = 0;
1068 /* save tail line */
1069 strcpy(matchBuf, command_ps + cursor);
1071 strcat(command_ps, tmp);
1073 strcat(command_ps, matchBuf);
1074 /* back to begin word for match */
1075 input_backward(recalc_pos);
1077 recalc_pos = cursor + len_found;
1079 len = strlen(command_ps);
1080 /* write out the matched command */
1081 redraw(cmdedit_y, len - recalc_pos);
1083 if (tmp != matches[0])
1086 /* Ok -- the last char was a TAB. Since they
1087 * just hit TAB again, print a list of all the
1088 * available choices... */
1089 if (matches && num_matches > 0) {
1091 int sav_cursor = cursor; /* change goto_new_line() */
1093 /* Go to the next line */
1095 for (i = 0, col = 0; i < num_matches; i++) {
1096 l = strlen(matches[i]);
1099 printf("%-14s ", matches[i]);
1106 col -= (col / cmdedit_termw) * cmdedit_termw;
1107 if (col > 60 && matches[i + 1] != NULL) {
1112 /* Go to the next line and rewrite */
1114 redraw(0, len - sav_cursor);
1118 #endif /* BB_FEATURE_COMMAND_TAB_COMPLETION */
1120 static void get_previous_history(struct history **hp, struct history *p)
1124 (*hp)->s = xstrdup(command_ps);
1128 static inline void get_next_history(struct history **hp)
1130 get_previous_history(hp, (*hp)->n);
1140 * This function is used to grab a character buffer
1141 * from the input file descriptor and allows you to
1142 * a string with full command editing (sortof like
1145 * The following standard commands are not implemented:
1146 * ESC-b -- Move back one word
1147 * ESC-f -- Move forward one word
1148 * ESC-d -- Delete back one word
1149 * ESC-h -- Delete forward one word
1150 * CTL-t -- Transpose two characters
1152 * Furthermore, the "vi" command editing keys are not implemented.
1157 int cmdedit_read_input(char *prompt, char command[BUFSIZ])
1161 int lastWasTab = FALSE;
1162 unsigned char c = 0;
1163 struct history *hp = his_end;
1165 /* prepare before init handlers */
1166 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1168 command_ps = command;
1170 getTermSettings(0, (void *) &initial_settings);
1171 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1172 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1173 /* Turn off echoing and CTRL-C, so we can trap it */
1174 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1176 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1177 new_settings.c_cc[VMIN] = 1;
1178 new_settings.c_cc[VTIME] = 0;
1179 /* Turn off CTRL-C, so we can trap it */
1180 # ifndef _POSIX_VDISABLE
1181 # define _POSIX_VDISABLE '\0'
1183 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1187 setTermSettings(0, (void *) &new_settings);
1188 handlers_sets |= SET_RESET_TERM;
1190 /* Now initialize things */
1192 /* Print out the command prompt */
1193 parse_prompt(prompt);
1197 fflush(stdout); /* buffered out to fast */
1199 if (safe_read(0, &c, 1) < 1)
1200 /* if we can't read input then exit */
1201 goto prepare_to_die;
1211 /* Control-a -- Beginning of line */
1212 input_backward(cursor);
1215 /* Control-b -- Move back one character */
1219 /* Control-c -- stop gathering input */
1227 /* Control-d -- Delete one character, or exit
1228 * if the len=0 and no chars to delete */
1231 #if !defined(BB_ASH)
1234 /* cmdedit_reset_term() called in atexit */
1237 break_out = -1; /* for control stoped jobs */
1245 /* Control-e -- End of line */
1249 /* Control-f -- Move forward one character */
1254 /* Control-h and DEL */
1258 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1259 input_tab(&lastWasTab);
1263 /* Control-n -- Get next command in history */
1264 if (hp && hp->n && hp->n->s) {
1265 get_next_history(&hp);
1272 /* Control-p -- Get previous command from history */
1274 get_previous_history(&hp, hp->p);
1281 /* Control-U -- Clear line before cursor */
1283 strcpy(command, command + cursor);
1284 redraw(cmdedit_y, len -= cursor);
1289 /* escape sequence follows */
1290 if (safe_read(0, &c, 1) < 1)
1291 goto prepare_to_die;
1292 /* different vt100 emulations */
1293 if (c == '[' || c == 'O') {
1294 if (safe_read(0, &c, 1) < 1)
1295 goto prepare_to_die;
1298 #ifdef BB_FEATURE_COMMAND_TAB_COMPLETION
1299 case '\t': /* Alt-Tab */
1301 input_tab(&lastWasTab);
1305 /* Up Arrow -- Get previous command from history */
1307 get_previous_history(&hp, hp->p);
1314 /* Down Arrow -- Get next command in history */
1315 if (hp && hp->n && hp->n->s) {
1316 get_next_history(&hp);
1323 /* Rewrite the line with the selected history item */
1325 /* change command */
1326 len = strlen(strcpy(command, hp->s));
1327 /* redraw and go to end line */
1328 redraw(cmdedit_y, 0);
1331 /* Right Arrow -- Move forward one character */
1335 /* Left Arrow -- Move back one character */
1345 input_backward(cursor);
1353 if (!(c >= '1' && c <= '9'))
1357 if (c >= '1' && c <= '9')
1359 if (safe_read(0, &c, 1) < 1)
1360 goto prepare_to_die;
1365 default: /* If it's regular input, do the normal thing */
1366 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1367 /* Control-V -- Add non-printable symbol */
1369 if (safe_read(0, &c, 1) < 1)
1370 goto prepare_to_die;
1377 if (!Isprint(c)) /* Skip non-printable characters */
1380 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1385 if (cursor == (len - 1)) { /* Append if at the end of the line */
1386 *(command + cursor) = c;
1387 *(command + cursor + 1) = 0;
1388 cmdedit_set_out_char(0);
1389 } else { /* Insert otherwise */
1392 memmove(command + sc + 1, command + sc, len - sc);
1393 *(command + sc) = c;
1395 /* rewrite from cursor */
1397 /* to prev x pos + 1 */
1398 input_backward(cursor - sc);
1403 if (break_out) /* Enter is the command terminator, no more input. */
1410 setTermSettings(0, (void *) &initial_settings);
1411 handlers_sets &= ~SET_RESET_TERM;
1413 /* Handle command history log */
1414 if (len) { /* no put empty line */
1416 struct history *h = his_end;
1419 ss = xstrdup(command); /* duplicate */
1422 /* No previous history -- this memory is never freed */
1423 h = his_front = xmalloc(sizeof(struct history));
1424 h->n = xmalloc(sizeof(struct history));
1434 /* Add a new history command -- this memory is never freed */
1435 h->n = xmalloc(sizeof(struct history));
1443 /* After max history, remove the oldest command */
1444 if (history_counter >= MAX_HISTORY) {
1446 struct history *p = his_front->n;
1456 #if defined(BB_FEATURE_SH_FANCY_PROMPT)
1461 command[len++] = '\n'; /* set '\n' */
1464 #if defined(BB_FEATURE_CLEAN_UP) && defined(BB_FEATURE_COMMAND_TAB_COMPLETION)
1465 input_tab(0); /* strong free */
1467 #if defined(BB_FEATURE_SH_FANCY_PROMPT)
1468 free(cmdedit_prompt);
1470 cmdedit_reset_term();
1476 #endif /* BB_FEATURE_COMMAND_EDITING */
1481 const char *applet_name = "debug stuff usage";
1482 const char *memory_exhausted = "Memory exhausted";
1484 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1488 int main(int argc, char **argv)
1492 #if defined(BB_FEATURE_SH_FANCY_PROMPT)
1493 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1494 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1495 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1500 #ifdef BB_FEATURE_NONPRINTABLE_INVERSE_PUT
1501 setlocale(LC_ALL, "");
1505 cmdedit_read_input(prompt, buff);
1509 if(l > 0 && buff[l-1] == '\n')
1511 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1513 printf("*** cmdedit_read_input() detect ^C\n");