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 CONFIG_LOCALE_SUPPORT
47 #define Isprint(c) isprint((c))
49 #define Isprint(c) ( (c) >= ' ' && (c) != ((unsigned char)'\233') )
58 #define CONFIG_FEATURE_COMMAND_EDITING
59 #define CONFIG_FEATURE_COMMAND_TAB_COMPLETION
60 #define CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
61 #define CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
62 #define CONFIG_FEATURE_CLEAN_UP
68 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
73 #ifdef CONFIG_FEATURE_COMMAND_EDITING
75 #ifndef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
76 #undef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
79 #if defined(CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION) || defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
80 #define CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
83 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
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 CONFIG_FEATURE_SH_FANCY_PROMPT
142 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
144 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
145 static char *user_buf = "";
146 static char *home_pwd_buf = "";
150 #ifdef CONFIG_FEATURE_SH_FANCY_PROMPT
151 static char *hostname_buf = "";
152 static int num_ok_lines = 1;
156 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
158 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
165 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
167 /* It seems that libc5 doesn't know what a sighandler_t is... */
168 #if (__GLIBC__ <= 2) && (__GLIBC_MINOR__ < 1)
169 typedef void (*sighandler_t) (int);
172 static void cmdedit_setwidth(int w, int redraw_flg);
174 static void win_changed(int nsig)
176 struct winsize win = { 0, 0, 0, 0 };
177 static sighandler_t previous_SIGWINCH_handler; /* for reset */
179 /* emulate || signal call */
180 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
181 ioctl(0, TIOCGWINSZ, &win);
182 if (win.ws_col > 0) {
183 cmdedit_setwidth(win.ws_col, nsig == SIGWINCH);
186 /* Unix not all standart in recall signal */
188 if (nsig == -SIGWINCH) /* save previous handler */
189 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
190 else if (nsig == SIGWINCH) /* signaled called handler */
191 signal(SIGWINCH, win_changed); /* set for next call */
193 /* set previous handler */
194 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
197 static void cmdedit_reset_term(void)
199 if ((handlers_sets & SET_RESET_TERM) != 0) {
200 /* sparc and other have broken termios support: use old termio handling. */
201 setTermSettings(fileno(stdin), (void *) &initial_settings);
202 handlers_sets &= ~SET_RESET_TERM;
204 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
205 /* reset SIGWINCH handler to previous (default) */
207 handlers_sets &= ~SET_WCHG_HANDLERS;
211 //#ifdef CONFIG_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 CONFIG_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)
281 static inline void beep(void)
286 /* Move back one charactor */
287 /* special for slow terminal */
288 static void input_backward(int num)
292 cursor -= num; /* new cursor (in command, not terminal) */
294 if (cmdedit_x >= num) { /* no to up line */
301 printf("\033[%dD", num);
306 putchar('\r'); /* back to first terminal pos. */
307 num -= cmdedit_x; /* set previous backward */
309 count_y = 1 + num / cmdedit_termw;
310 printf("\033[%dA", count_y);
311 cmdedit_y -= count_y;
312 /* require forward after uping */
313 cmdedit_x = cmdedit_termw * count_y - num;
314 printf("\033[%dC", cmdedit_x); /* set term cursor */
318 static void put_prompt(void)
320 out1str(cmdedit_prompt);
321 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
323 cmdedit_y = 0; /* new quasireal y */
326 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
327 static void parse_prompt(const char *prmt_ptr)
329 cmdedit_prompt = prmt_ptr;
330 cmdedit_prmt_len = strlen(prmt_ptr);
334 static void parse_prompt(const char *prmt_ptr)
338 char flg_not_length = '[';
339 char *prmt_mem_ptr = xcalloc(1, 1);
340 char *pwd_buf = xgetcwd(0);
341 char buf2[PATH_MAX + 1];
347 pwd_buf=(char *)unknown;
355 const char *cp = prmt_ptr;
358 c = process_escape_sequence(&prmt_ptr);
364 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
372 pbuf = xcalloc(256, 1);
373 if (gethostname(pbuf, 255) < 0) {
376 char *s = strchr(pbuf, '.');
385 c = my_euid == 0 ? '#' : '$';
387 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
390 l = strlen(home_pwd_buf);
391 if (home_pwd_buf[0] != 0 &&
392 strncmp(home_pwd_buf, pbuf, l) == 0 &&
393 (pbuf[l]=='/' || pbuf[l]=='\0') &&
394 strlen(pwd_buf+l)<PATH_MAX) {
397 strcpy(pbuf+1, pwd_buf+l);
403 cp = strrchr(pbuf,'/');
404 if ( (cp != NULL) && (cp != pbuf) )
408 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
410 case 'e': case 'E': /* \e \E = \033 */
414 for (l = 0; l < 3;) {
416 buf2[l++] = *prmt_ptr;
418 h = strtol(buf2, &pbuf, 16);
419 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
426 c = (char)strtol(buf2, 0, 16);
432 if (c == flg_not_length) {
433 flg_not_length = flg_not_length == '[' ? ']' : '[';
442 prmt_len += strlen(pbuf);
443 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
444 if (flg_not_length == ']')
447 if(pwd_buf!=(char *)unknown)
449 cmdedit_prompt = prmt_mem_ptr;
450 cmdedit_prmt_len = prmt_len - sub_len;
456 /* draw promt, editor line, and clear tail */
457 static void redraw(int y, int back_cursor)
459 if (y > 0) /* up to start y */
460 printf("\033[%dA", y);
463 input_end(); /* rewrite */
464 printf("\033[J"); /* destroy tail after cursor */
465 input_backward(back_cursor);
468 /* Delete the char in front of the cursor */
469 static void input_delete(void)
476 strcpy(command_ps + j, command_ps + j + 1);
478 input_end(); /* rewtite new line */
479 cmdedit_set_out_char(0); /* destroy end char */
480 input_backward(cursor - j); /* back to old pos cursor */
483 /* Delete the char in back of the cursor */
484 static void input_backspace(void)
493 /* Move forward one charactor */
494 static void input_forward(void)
497 cmdedit_set_out_char(command_ps[cursor + 1]);
501 static void cmdedit_setwidth(int w, int redraw_flg)
503 cmdedit_termw = cmdedit_prmt_len + 2;
504 if (w <= cmdedit_termw) {
505 cmdedit_termw = cmdedit_termw % w;
507 if (w > cmdedit_termw) {
511 /* new y for current cursor */
512 int new_y = (cursor + cmdedit_prmt_len) / w;
515 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
521 static void cmdedit_init(void)
523 cmdedit_prmt_len = 0;
524 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
525 /* emulate usage handler to set handler and call yours work */
526 win_changed(-SIGWINCH);
527 handlers_sets |= SET_WCHG_HANDLERS;
530 if ((handlers_sets & SET_ATEXIT) == 0) {
531 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
532 struct passwd *entry;
535 entry = getpwuid(my_euid);
537 user_buf = xstrdup(entry->pw_name);
538 home_pwd_buf = xstrdup(entry->pw_dir);
542 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
544 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
549 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
550 handlers_sets |= SET_ATEXIT;
551 atexit(cmdedit_reset_term); /* be sure to do this only once */
555 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
557 static int is_execute(const struct stat *st)
559 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
560 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
561 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
562 (st->st_mode & S_IXOTH)) return TRUE;
566 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
568 static char **username_tab_completion(char *ud, int *num_matches)
570 struct passwd *entry;
575 ud++; /* ~user/... to user/... */
576 userlen = strlen(ud);
578 if (num_matches == 0) { /* "~/..." or "~user/..." */
579 char *sav_ud = ud - 1;
582 if (*ud == '/') { /* "~/..." */
586 temp = strchr(ud, '/');
587 *temp = 0; /* ~user\0 */
588 entry = getpwnam(ud);
589 *temp = '/'; /* restore ~user/... */
592 home = entry->pw_dir;
595 if ((userlen + strlen(home) + 1) < BUFSIZ) {
596 char temp2[BUFSIZ]; /* argument size */
599 sprintf(temp2, "%s%s", home, ud);
600 strcpy(sav_ud, temp2);
603 return 0; /* void, result save to argument :-) */
606 char **matches = (char **) NULL;
611 while ((entry = getpwent()) != NULL) {
612 /* Null usernames should result in all users as possible completions. */
613 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
615 bb_asprintf(&temp, "~%s/", entry->pw_name);
616 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
618 matches[nm++] = temp;
627 #endif /* CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION */
635 static int path_parse(char ***p, int flags)
641 /* if not setenv PATH variable, to search cur dir "." */
642 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
643 /* PATH=<empty> or PATH=:<empty> */
644 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
652 npth++; /* count words is + 1 count ':' */
653 tmp = strchr(tmp, ':');
656 break; /* :<empty> */
661 *p = xmalloc(npth * sizeof(char *));
664 (*p)[0] = xstrdup(tmp);
665 npth = 1; /* count words is + 1 count ':' */
668 tmp = strchr(tmp, ':');
670 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
672 break; /* :<empty> */
675 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
681 static char *add_quote_for_spec_chars(char *found)
684 char *s = xmalloc((strlen(found) + 1) * 2);
687 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
695 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
703 int nm = *num_matches;
706 char **paths = path1;
710 char *pfind = strrchr(command, '/');
715 /* no dir, if flags==EXE_ONLY - get paths, else "." */
716 npaths = path_parse(&paths, type);
720 /* save for change */
721 strcpy(dirbuf, command);
723 dirbuf[(pfind - command) + 1] = 0;
724 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
725 if (dirbuf[0] == '~') /* ~/... or ~user/... */
726 username_tab_completion(dirbuf, 0);
728 /* "strip" dirname in command */
732 npaths = 1; /* only 1 dir */
735 for (i = 0; i < npaths; i++) {
737 dir = opendir(paths[i]);
738 if (!dir) /* Don't print an error */
741 while ((next = readdir(dir)) != NULL) {
742 char *str_found = next->d_name;
745 if (strncmp(str_found, pfind, strlen(pfind)))
747 /* not see .name without .match */
748 if (*str_found == '.' && *pfind == 0) {
749 if (*paths[i] == '/' && paths[i][1] == 0
750 && str_found[1] == 0) str_found = ""; /* only "/" */
754 found = concat_path_file(paths[i], str_found);
755 /* hmm, remover in progress? */
756 if (stat(found, &st) < 0)
758 /* find with dirs ? */
759 if (paths[i] != dirbuf)
760 strcpy(found, next->d_name); /* only name */
761 if (S_ISDIR(st.st_mode)) {
762 /* name is directory */
764 found = concat_path_file(found, "");
766 str_found = add_quote_for_spec_chars(found);
768 /* not put found file if search only dirs for cd */
769 if (type == FIND_DIR_ONLY)
771 str_found = add_quote_for_spec_chars(found);
772 if (type == FIND_FILE_ONLY ||
773 (type == FIND_EXE_ONLY && is_execute(&st)))
774 strcat(str_found, " ");
776 /* Add it to the list */
777 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
779 matches[nm++] = str_found;
785 if (paths != path1) {
786 free(paths[0]); /* allocated memory only in first member */
793 static int match_compare(const void *a, const void *b)
795 return strcmp(*(char **) a, *(char **) b);
800 #define QUOT (UCHAR_MAX+1)
802 #define collapse_pos(is, in) { \
803 memcpy(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
804 memcpy(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
806 static int find_match(char *matchBuf, int *len_with_quotes)
811 int int_buf[BUFSIZ + 1];
812 int pos_buf[BUFSIZ + 1];
814 /* set to integer dimension characters and own positions */
816 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
817 if (int_buf[i] == 0) {
818 pos_buf[i] = -1; /* indicator end line */
824 /* mask \+symbol and convert '\t' to ' ' */
825 for (i = j = 0; matchBuf[i]; i++, j++)
826 if (matchBuf[i] == '\\') {
827 collapse_pos(j, j + 1);
830 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
831 if (matchBuf[i] == '\t') /* algorithm equivalent */
832 int_buf[j] = ' ' | QUOT;
835 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
836 else if (matchBuf[i] == '\t')
840 /* mask "symbols" or 'symbols' */
842 for (i = 0; int_buf[i]; i++) {
844 if (c == '\'' || c == '"') {
853 } else if (c2 != 0 && c != '$')
857 /* skip commands with arguments if line have commands delimiters */
858 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
859 for (i = 0; int_buf[i]; i++) {
862 j = i ? int_buf[i - 1] : -1;
864 if (c == ';' || c == '&' || c == '|') {
865 command_mode = 1 + (c == c2);
867 if (j == '>' || j == '<')
869 } else if (c == '|' && j == '>')
873 collapse_pos(0, i + command_mode);
874 i = -1; /* hack incremet */
877 /* collapse `command...` */
878 for (i = 0; int_buf[i]; i++)
879 if (int_buf[i] == '`') {
880 for (j = i + 1; int_buf[j]; j++)
881 if (int_buf[j] == '`') {
882 collapse_pos(i, j + 1);
887 /* not found close ` - command mode, collapse all previous */
888 collapse_pos(0, i + 1);
891 i--; /* hack incremet */
894 /* collapse (command...(command...)...) or {command...{command...}...} */
895 c = 0; /* "recursive" level */
897 for (i = 0; int_buf[i]; i++)
898 if (int_buf[i] == '(' || int_buf[i] == '{') {
899 if (int_buf[i] == '(')
903 collapse_pos(0, i + 1);
904 i = -1; /* hack incremet */
906 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
907 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
908 if (int_buf[i] == ')')
912 collapse_pos(0, i + 1);
913 i = -1; /* hack incremet */
916 /* skip first not quote space */
917 for (i = 0; int_buf[i]; i++)
918 if (int_buf[i] != ' ')
923 /* set find mode for completion */
924 command_mode = FIND_EXE_ONLY;
925 for (i = 0; int_buf[i]; i++)
926 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
927 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
928 && matchBuf[pos_buf[0]]=='c'
929 && matchBuf[pos_buf[1]]=='d' )
930 command_mode = FIND_DIR_ONLY;
932 command_mode = FIND_FILE_ONLY;
937 for (i = 0; int_buf[i]; i++);
939 for (--i; i >= 0; i--) {
941 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
942 collapse_pos(0, i + 1);
946 /* skip first not quoted '\'' or '"' */
947 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
948 /* collapse quote or unquote // or /~ */
949 while ((int_buf[i] & ~QUOT) == '/' &&
950 ((int_buf[i + 1] & ~QUOT) == '/'
951 || (int_buf[i + 1] & ~QUOT) == '~')) {
955 /* set only match and destroy quotes */
957 for (c = 0; pos_buf[i] >= 0; i++) {
958 matchBuf[c++] = matchBuf[pos_buf[i]];
962 /* old lenght matchBuf with quotes symbols */
963 *len_with_quotes = j ? j - pos_buf[0] : 0;
969 static void input_tab(int *lastWasTab)
971 /* Do TAB completion */
972 static int num_matches;
973 static char **matches;
975 if (lastWasTab == 0) { /* free all memory */
977 while (num_matches > 0)
978 free(matches[--num_matches]);
980 matches = (char **) NULL;
988 char matchBuf[BUFSIZ];
992 *lastWasTab = TRUE; /* flop trigger */
994 /* Make a local copy of the string -- up
995 * to the position of the cursor */
996 tmp = strncpy(matchBuf, command_ps, cursor);
999 find_type = find_match(matchBuf, &recalc_pos);
1001 /* Free up any memory already allocated */
1004 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
1005 /* If the word starts with `~' and there is no slash in the word,
1006 * then try completing this word as a username. */
1008 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1009 matches = username_tab_completion(matchBuf, &num_matches);
1011 /* Try to match any executable in our path and everything
1012 * in the current working directory that matches. */
1015 exe_n_cwd_tab_completion(matchBuf,
1016 &num_matches, find_type);
1017 /* Remove duplicate found */
1021 for(i=0; i<(num_matches-1); i++)
1022 for(j=i+1; j<num_matches; j++)
1023 if(matches[i]!=0 && matches[j]!=0 &&
1024 strcmp(matches[i], matches[j])==0) {
1032 if(!strcmp(matches[i], "./"))
1034 else if(!strcmp(matches[i], "../"))
1036 matches[num_matches++]=matches[i];
1039 /* Did we find exactly one match? */
1040 if (!matches || num_matches > 1) {
1045 return; /* not found */
1047 qsort(matches, num_matches, sizeof(char *), match_compare);
1049 /* find minimal match */
1050 tmp = xstrdup(matches[0]);
1051 for (tmp1 = tmp; *tmp1; tmp1++)
1052 for (len_found = 1; len_found < num_matches; len_found++)
1053 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1057 if (*tmp == 0) { /* have unique */
1061 } else { /* one match */
1063 /* for next completion current found */
1064 *lastWasTab = FALSE;
1067 len_found = strlen(tmp);
1068 /* have space to placed match? */
1069 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1071 /* before word for match */
1072 command_ps[cursor - recalc_pos] = 0;
1073 /* save tail line */
1074 strcpy(matchBuf, command_ps + cursor);
1076 strcat(command_ps, tmp);
1078 strcat(command_ps, matchBuf);
1079 /* back to begin word for match */
1080 input_backward(recalc_pos);
1082 recalc_pos = cursor + len_found;
1084 len = strlen(command_ps);
1085 /* write out the matched command */
1086 redraw(cmdedit_y, len - recalc_pos);
1088 if (tmp != matches[0])
1091 /* Ok -- the last char was a TAB. Since they
1092 * just hit TAB again, print a list of all the
1093 * available choices... */
1094 if (matches && num_matches > 0) {
1096 int sav_cursor = cursor; /* change goto_new_line() */
1098 /* Go to the next line */
1100 for (i = 0, col = 0; i < num_matches; i++) {
1101 l = strlen(matches[i]);
1104 printf("%-14s ", matches[i]);
1111 col -= (col / cmdedit_termw) * cmdedit_termw;
1112 if (col > 60 && matches[i + 1] != NULL) {
1117 /* Go to the next line and rewrite */
1119 redraw(0, len - sav_cursor);
1123 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
1125 static void get_previous_history(struct history **hp, struct history *p)
1129 (*hp)->s = xstrdup(command_ps);
1133 static inline void get_next_history(struct history **hp)
1135 get_previous_history(hp, (*hp)->n);
1145 * This function is used to grab a character buffer
1146 * from the input file descriptor and allows you to
1147 * a string with full command editing (sortof like
1150 * The following standard commands are not implemented:
1151 * ESC-b -- Move back one word
1152 * ESC-f -- Move forward one word
1153 * ESC-d -- Delete back one word
1154 * ESC-h -- Delete forward one word
1155 * CTL-t -- Transpose two characters
1157 * Furthermore, the "vi" command editing keys are not implemented.
1162 int cmdedit_read_input(char *prompt, char command[BUFSIZ])
1166 int lastWasTab = FALSE;
1167 unsigned char c = 0;
1168 struct history *hp = his_end;
1170 /* prepare before init handlers */
1171 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1173 command_ps = command;
1175 getTermSettings(0, (void *) &initial_settings);
1176 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1177 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1178 /* Turn off echoing and CTRL-C, so we can trap it */
1179 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1181 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1182 new_settings.c_cc[VMIN] = 1;
1183 new_settings.c_cc[VTIME] = 0;
1184 /* Turn off CTRL-C, so we can trap it */
1185 # ifndef _POSIX_VDISABLE
1186 # define _POSIX_VDISABLE '\0'
1188 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1192 setTermSettings(0, (void *) &new_settings);
1193 handlers_sets |= SET_RESET_TERM;
1195 /* Now initialize things */
1197 /* Print out the command prompt */
1198 parse_prompt(prompt);
1202 fflush(stdout); /* buffered out to fast */
1204 if (safe_read(0, &c, 1) < 1)
1205 /* if we can't read input then exit */
1206 goto prepare_to_die;
1216 /* Control-a -- Beginning of line */
1217 input_backward(cursor);
1220 /* Control-b -- Move back one character */
1224 /* Control-c -- stop gathering input */
1232 /* Control-d -- Delete one character, or exit
1233 * if the len=0 and no chars to delete */
1236 #if !defined(CONFIG_ASH)
1239 /* cmdedit_reset_term() called in atexit */
1242 break_out = -1; /* for control stoped jobs */
1250 /* Control-e -- End of line */
1254 /* Control-f -- Move forward one character */
1259 /* Control-h and DEL */
1263 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1264 input_tab(&lastWasTab);
1268 /* Control-k -- clear to end of line */
1269 *(command + cursor) = 0;
1275 /* Control-l -- clear screen */
1276 int old_cursor = cursor;
1278 redraw(0, len-old_cursor);
1282 /* Control-n -- Get next command in history */
1283 if (hp && hp->n && hp->n->s) {
1284 get_next_history(&hp);
1291 /* Control-p -- Get previous command from history */
1293 get_previous_history(&hp, hp->p);
1300 /* Control-U -- Clear line before cursor */
1302 strcpy(command, command + cursor);
1303 redraw(cmdedit_y, len -= cursor);
1307 /* escape sequence follows */
1308 if (safe_read(0, &c, 1) < 1)
1309 goto prepare_to_die;
1310 /* different vt100 emulations */
1311 if (c == '[' || c == 'O') {
1312 if (safe_read(0, &c, 1) < 1)
1313 goto prepare_to_die;
1316 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1317 case '\t': /* Alt-Tab */
1319 input_tab(&lastWasTab);
1323 /* Up Arrow -- Get previous command from history */
1325 get_previous_history(&hp, hp->p);
1332 /* Down Arrow -- Get next command in history */
1333 if (hp && hp->n && hp->n->s) {
1334 get_next_history(&hp);
1341 /* Rewrite the line with the selected history item */
1343 /* change command */
1344 len = strlen(strcpy(command, hp->s));
1345 /* redraw and go to end line */
1346 redraw(cmdedit_y, 0);
1349 /* Right Arrow -- Move forward one character */
1353 /* Left Arrow -- Move back one character */
1363 input_backward(cursor);
1371 if (!(c >= '1' && c <= '9'))
1375 if (c >= '1' && c <= '9')
1377 if (safe_read(0, &c, 1) < 1)
1378 goto prepare_to_die;
1383 default: /* If it's regular input, do the normal thing */
1384 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1385 /* Control-V -- Add non-printable symbol */
1387 if (safe_read(0, &c, 1) < 1)
1388 goto prepare_to_die;
1395 if (!Isprint(c)) /* Skip non-printable characters */
1398 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1403 if (cursor == (len - 1)) { /* Append if at the end of the line */
1404 *(command + cursor) = c;
1405 *(command + cursor + 1) = 0;
1406 cmdedit_set_out_char(0);
1407 } else { /* Insert otherwise */
1410 memmove(command + sc + 1, command + sc, len - sc);
1411 *(command + sc) = c;
1413 /* rewrite from cursor */
1415 /* to prev x pos + 1 */
1416 input_backward(cursor - sc);
1421 if (break_out) /* Enter is the command terminator, no more input. */
1428 setTermSettings(0, (void *) &initial_settings);
1429 handlers_sets &= ~SET_RESET_TERM;
1431 /* Handle command history log */
1432 if (len) { /* no put empty line */
1434 struct history *h = his_end;
1437 ss = xstrdup(command); /* duplicate */
1440 /* No previous history -- this memory is never freed */
1441 h = his_front = xmalloc(sizeof(struct history));
1442 h->n = xmalloc(sizeof(struct history));
1452 /* Add a new history command -- this memory is never freed */
1453 h->n = xmalloc(sizeof(struct history));
1461 /* After max history, remove the oldest command */
1462 if (history_counter >= MAX_HISTORY) {
1464 struct history *p = his_front->n;
1474 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1479 command[len++] = '\n'; /* set '\n' */
1482 #if defined(CONFIG_FEATURE_CLEAN_UP) && defined(CONFIG_FEATURE_COMMAND_TAB_COMPLETION)
1483 input_tab(0); /* strong free */
1485 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1486 free(cmdedit_prompt);
1488 cmdedit_reset_term();
1494 #endif /* CONFIG_FEATURE_COMMAND_EDITING */
1499 const char *applet_name = "debug stuff usage";
1500 const char *memory_exhausted = "Memory exhausted";
1502 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1506 int main(int argc, char **argv)
1510 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1511 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1512 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1513 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1518 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1519 setlocale(LC_ALL, "");
1523 cmdedit_read_input(prompt, buff);
1527 if(l > 0 && buff[l-1] == '\n')
1529 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1531 printf("*** cmdedit_read_input() detect ^C\n");