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 */
92 /* Maximum length of the linked list for the command line history */
93 #ifndef CONFIG_FEATURE_COMMAND_HISTORY
94 #define MAX_HISTORY 15
96 #define MAX_HISTORY CONFIG_FEATURE_COMMAND_HISTORY
100 #warning cmdedit: You set MAX_HISTORY < 1. The history algorithm switched off.
102 static char *history[MAX_HISTORY+1]; /* history + current */
103 /* saved history lines */
104 static int n_history;
105 /* current pointer to history line */
106 static int cur_history;
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 */
120 volatile int handlers_sets = 0; /* Set next bites: */
123 SET_ATEXIT = 1, /* when atexit() has been called
124 and get euid,uid,gid to fast compare */
125 SET_WCHG_HANDLERS = 2, /* winchg signal handler */
126 SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */
130 static int cmdedit_x; /* real x terminal position */
131 static int cmdedit_y; /* pseudoreal y terminal position */
132 static int cmdedit_prmt_len; /* lenght prompt without colores string */
134 static int cursor; /* required global for signal handler */
135 static int len; /* --- "" - - "" - -"- --""-- --""--- */
136 static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */
138 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
141 char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */
143 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
144 static char *user_buf = "";
145 static char *home_pwd_buf = "";
149 #ifdef CONFIG_FEATURE_SH_FANCY_PROMPT
150 static char *hostname_buf;
151 static int num_ok_lines = 1;
155 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
157 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
164 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
166 /* It seems that libc5 doesn't know what a sighandler_t is... */
167 #if (__GLIBC__ <= 2) && (__GLIBC_MINOR__ < 1)
168 typedef void (*sighandler_t) (int);
171 static void cmdedit_setwidth(int w, int redraw_flg);
173 static void win_changed(int nsig)
175 struct winsize win = { 0, 0, 0, 0 };
176 static sighandler_t previous_SIGWINCH_handler; /* for reset */
178 /* emulate || signal call */
179 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
180 ioctl(0, TIOCGWINSZ, &win);
181 if (win.ws_col > 0) {
182 cmdedit_setwidth(win.ws_col, nsig == SIGWINCH);
185 /* Unix not all standart in recall signal */
187 if (nsig == -SIGWINCH) /* save previous handler */
188 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
189 else if (nsig == SIGWINCH) /* signaled called handler */
190 signal(SIGWINCH, win_changed); /* set for next call */
192 /* set previous handler */
193 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
196 static void cmdedit_reset_term(void)
198 if ((handlers_sets & SET_RESET_TERM) != 0) {
199 /* sparc and other have broken termios support: use old termio handling. */
200 setTermSettings(fileno(stdin), (void *) &initial_settings);
201 handlers_sets &= ~SET_RESET_TERM;
203 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
204 /* reset SIGWINCH handler to previous (default) */
206 handlers_sets &= ~SET_WCHG_HANDLERS;
212 /* special for recount position for scroll and remove terminal margin effect */
213 static void cmdedit_set_out_char(int next_char)
216 int c = (int)((unsigned char) command_ps[cursor]);
219 c = ' '; /* destroy end char? */
220 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
221 if (!Isprint(c)) { /* Inverse put non-printable characters */
228 printf("\033[7m%c\033[0m", c);
232 if (++cmdedit_x >= cmdedit_termw) {
233 /* terminal is scrolled down */
239 /* destroy "(auto)margin" */
246 /* Move to end line. Bonus: rewrite line from cursor */
247 static void input_end(void)
250 cmdedit_set_out_char(0);
253 /* Go to the next line */
254 static void goto_new_line(void)
262 static inline void out1str(const char *s)
267 static inline void beep(void)
272 /* Move back one charactor */
273 /* special for slow terminal */
274 static void input_backward(int num)
278 cursor -= num; /* new cursor (in command, not terminal) */
280 if (cmdedit_x >= num) { /* no to up line */
287 printf("\033[%dD", num);
292 putchar('\r'); /* back to first terminal pos. */
293 num -= cmdedit_x; /* set previous backward */
295 count_y = 1 + num / cmdedit_termw;
296 printf("\033[%dA", count_y);
297 cmdedit_y -= count_y;
298 /* require forward after uping */
299 cmdedit_x = cmdedit_termw * count_y - num;
300 printf("\033[%dC", cmdedit_x); /* set term cursor */
304 static void put_prompt(void)
306 out1str(cmdedit_prompt);
307 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
309 cmdedit_y = 0; /* new quasireal y */
312 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
313 static void parse_prompt(const char *prmt_ptr)
315 cmdedit_prompt = prmt_ptr;
316 cmdedit_prmt_len = strlen(prmt_ptr);
320 static void parse_prompt(const char *prmt_ptr)
324 char flg_not_length = '[';
325 char *prmt_mem_ptr = xcalloc(1, 1);
326 char *pwd_buf = xgetcwd(0);
327 char buf2[PATH_MAX + 1];
333 pwd_buf=(char *)unknown;
341 const char *cp = prmt_ptr;
344 c = process_escape_sequence(&prmt_ptr);
350 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
358 pbuf = xcalloc(256, 1);
359 if (gethostname(pbuf, 255) < 0) {
362 char *s = strchr(pbuf, '.');
371 c = my_euid == 0 ? '#' : '$';
373 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
376 l = strlen(home_pwd_buf);
377 if (home_pwd_buf[0] != 0 &&
378 strncmp(home_pwd_buf, pbuf, l) == 0 &&
379 (pbuf[l]=='/' || pbuf[l]=='\0') &&
380 strlen(pwd_buf+l)<PATH_MAX) {
383 strcpy(pbuf+1, pwd_buf+l);
389 cp = strrchr(pbuf,'/');
390 if ( (cp != NULL) && (cp != pbuf) )
394 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
396 case 'e': case 'E': /* \e \E = \033 */
400 for (l = 0; l < 3;) {
402 buf2[l++] = *prmt_ptr;
404 h = strtol(buf2, &pbuf, 16);
405 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
412 c = (char)strtol(buf2, 0, 16);
418 if (c == flg_not_length) {
419 flg_not_length = flg_not_length == '[' ? ']' : '[';
428 prmt_len += strlen(pbuf);
429 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
430 if (flg_not_length == ']')
433 if(pwd_buf!=(char *)unknown)
435 cmdedit_prompt = prmt_mem_ptr;
436 cmdedit_prmt_len = prmt_len - sub_len;
442 /* draw promt, editor line, and clear tail */
443 static void redraw(int y, int back_cursor)
445 if (y > 0) /* up to start y */
446 printf("\033[%dA", y);
449 input_end(); /* rewrite */
450 printf("\033[J"); /* destroy tail after cursor */
451 input_backward(back_cursor);
454 /* Delete the char in front of the cursor */
455 static void input_delete(void)
462 strcpy(command_ps + j, command_ps + j + 1);
464 input_end(); /* rewtite new line */
465 cmdedit_set_out_char(0); /* destroy end char */
466 input_backward(cursor - j); /* back to old pos cursor */
469 /* Delete the char in back of the cursor */
470 static void input_backspace(void)
479 /* Move forward one charactor */
480 static void input_forward(void)
483 cmdedit_set_out_char(command_ps[cursor + 1]);
487 static void cmdedit_setwidth(int w, int redraw_flg)
489 cmdedit_termw = cmdedit_prmt_len + 2;
490 if (w <= cmdedit_termw) {
491 cmdedit_termw = cmdedit_termw % w;
493 if (w > cmdedit_termw) {
497 /* new y for current cursor */
498 int new_y = (cursor + cmdedit_prmt_len) / w;
501 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
507 static void cmdedit_init(void)
509 cmdedit_prmt_len = 0;
510 if ((handlers_sets & SET_WCHG_HANDLERS) == 0) {
511 /* emulate usage handler to set handler and call yours work */
512 win_changed(-SIGWINCH);
513 handlers_sets |= SET_WCHG_HANDLERS;
516 if ((handlers_sets & SET_ATEXIT) == 0) {
517 #ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
518 struct passwd *entry;
521 entry = getpwuid(my_euid);
523 user_buf = xstrdup(entry->pw_name);
524 home_pwd_buf = xstrdup(entry->pw_dir);
528 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
530 #ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR
535 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
536 handlers_sets |= SET_ATEXIT;
537 atexit(cmdedit_reset_term); /* be sure to do this only once */
541 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
543 static int is_execute(const struct stat *st)
545 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
546 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
547 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
548 (st->st_mode & S_IXOTH)) return TRUE;
552 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
554 static char **username_tab_completion(char *ud, int *num_matches)
556 struct passwd *entry;
561 ud++; /* ~user/... to user/... */
562 userlen = strlen(ud);
564 if (num_matches == 0) { /* "~/..." or "~user/..." */
565 char *sav_ud = ud - 1;
568 if (*ud == '/') { /* "~/..." */
572 temp = strchr(ud, '/');
573 *temp = 0; /* ~user\0 */
574 entry = getpwnam(ud);
575 *temp = '/'; /* restore ~user/... */
578 home = entry->pw_dir;
581 if ((userlen + strlen(home) + 1) < BUFSIZ) {
582 char temp2[BUFSIZ]; /* argument size */
585 sprintf(temp2, "%s%s", home, ud);
586 strcpy(sav_ud, temp2);
589 return 0; /* void, result save to argument :-) */
592 char **matches = (char **) NULL;
597 while ((entry = getpwent()) != NULL) {
598 /* Null usernames should result in all users as possible completions. */
599 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
601 bb_asprintf(&temp, "~%s/", entry->pw_name);
602 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
604 matches[nm++] = temp;
613 #endif /* CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION */
621 static int path_parse(char ***p, int flags)
627 /* if not setenv PATH variable, to search cur dir "." */
628 if (flags != FIND_EXE_ONLY || (pth = getenv("PATH")) == 0 ||
629 /* PATH=<empty> or PATH=:<empty> */
630 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
638 npth++; /* count words is + 1 count ':' */
639 tmp = strchr(tmp, ':');
642 break; /* :<empty> */
647 *p = xmalloc(npth * sizeof(char *));
650 (*p)[0] = xstrdup(tmp);
651 npth = 1; /* count words is + 1 count ':' */
654 tmp = strchr(tmp, ':');
656 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
658 break; /* :<empty> */
661 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
667 static char *add_quote_for_spec_chars(char *found)
670 char *s = xmalloc((strlen(found) + 1) * 2);
673 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
681 static char **exe_n_cwd_tab_completion(char *command, int *num_matches,
689 int nm = *num_matches;
692 char **paths = path1;
696 char *pfind = strrchr(command, '/');
701 /* no dir, if flags==EXE_ONLY - get paths, else "." */
702 npaths = path_parse(&paths, type);
706 /* save for change */
707 strcpy(dirbuf, command);
709 dirbuf[(pfind - command) + 1] = 0;
710 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
711 if (dirbuf[0] == '~') /* ~/... or ~user/... */
712 username_tab_completion(dirbuf, 0);
714 /* "strip" dirname in command */
718 npaths = 1; /* only 1 dir */
721 for (i = 0; i < npaths; i++) {
723 dir = opendir(paths[i]);
724 if (!dir) /* Don't print an error */
727 while ((next = readdir(dir)) != NULL) {
728 char *str_found = next->d_name;
731 if (strncmp(str_found, pfind, strlen(pfind)))
733 /* not see .name without .match */
734 if (*str_found == '.' && *pfind == 0) {
735 if (*paths[i] == '/' && paths[i][1] == 0
736 && str_found[1] == 0) str_found = ""; /* only "/" */
740 found = concat_path_file(paths[i], str_found);
741 /* hmm, remover in progress? */
742 if (stat(found, &st) < 0)
744 /* find with dirs ? */
745 if (paths[i] != dirbuf)
746 strcpy(found, next->d_name); /* only name */
747 if (S_ISDIR(st.st_mode)) {
748 /* name is directory */
750 found = concat_path_file(found, "");
752 str_found = add_quote_for_spec_chars(found);
754 /* not put found file if search only dirs for cd */
755 if (type == FIND_DIR_ONLY)
757 str_found = add_quote_for_spec_chars(found);
758 if (type == FIND_FILE_ONLY ||
759 (type == FIND_EXE_ONLY && is_execute(&st)))
760 strcat(str_found, " ");
762 /* Add it to the list */
763 matches = xrealloc(matches, (nm + 1) * sizeof(char *));
765 matches[nm++] = str_found;
771 if (paths != path1) {
772 free(paths[0]); /* allocated memory only in first member */
779 static int match_compare(const void *a, const void *b)
781 return strcmp(*(char **) a, *(char **) b);
786 #define QUOT (UCHAR_MAX+1)
788 #define collapse_pos(is, in) { \
789 memcpy(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
790 memcpy(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
792 static int find_match(char *matchBuf, int *len_with_quotes)
797 int int_buf[BUFSIZ + 1];
798 int pos_buf[BUFSIZ + 1];
800 /* set to integer dimension characters and own positions */
802 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
803 if (int_buf[i] == 0) {
804 pos_buf[i] = -1; /* indicator end line */
810 /* mask \+symbol and convert '\t' to ' ' */
811 for (i = j = 0; matchBuf[i]; i++, j++)
812 if (matchBuf[i] == '\\') {
813 collapse_pos(j, j + 1);
816 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
817 if (matchBuf[i] == '\t') /* algorithm equivalent */
818 int_buf[j] = ' ' | QUOT;
821 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
822 else if (matchBuf[i] == '\t')
826 /* mask "symbols" or 'symbols' */
828 for (i = 0; int_buf[i]; i++) {
830 if (c == '\'' || c == '"') {
839 } else if (c2 != 0 && c != '$')
843 /* skip commands with arguments if line have commands delimiters */
844 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
845 for (i = 0; int_buf[i]; i++) {
848 j = i ? int_buf[i - 1] : -1;
850 if (c == ';' || c == '&' || c == '|') {
851 command_mode = 1 + (c == c2);
853 if (j == '>' || j == '<')
855 } else if (c == '|' && j == '>')
859 collapse_pos(0, i + command_mode);
860 i = -1; /* hack incremet */
863 /* collapse `command...` */
864 for (i = 0; int_buf[i]; i++)
865 if (int_buf[i] == '`') {
866 for (j = i + 1; int_buf[j]; j++)
867 if (int_buf[j] == '`') {
868 collapse_pos(i, j + 1);
873 /* not found close ` - command mode, collapse all previous */
874 collapse_pos(0, i + 1);
877 i--; /* hack incremet */
880 /* collapse (command...(command...)...) or {command...{command...}...} */
881 c = 0; /* "recursive" level */
883 for (i = 0; int_buf[i]; i++)
884 if (int_buf[i] == '(' || int_buf[i] == '{') {
885 if (int_buf[i] == '(')
889 collapse_pos(0, i + 1);
890 i = -1; /* hack incremet */
892 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
893 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
894 if (int_buf[i] == ')')
898 collapse_pos(0, i + 1);
899 i = -1; /* hack incremet */
902 /* skip first not quote space */
903 for (i = 0; int_buf[i]; i++)
904 if (int_buf[i] != ' ')
909 /* set find mode for completion */
910 command_mode = FIND_EXE_ONLY;
911 for (i = 0; int_buf[i]; i++)
912 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
913 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
914 && matchBuf[pos_buf[0]]=='c'
915 && matchBuf[pos_buf[1]]=='d' )
916 command_mode = FIND_DIR_ONLY;
918 command_mode = FIND_FILE_ONLY;
923 for (i = 0; int_buf[i]; i++);
925 for (--i; i >= 0; i--) {
927 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
928 collapse_pos(0, i + 1);
932 /* skip first not quoted '\'' or '"' */
933 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++);
934 /* collapse quote or unquote // or /~ */
935 while ((int_buf[i] & ~QUOT) == '/' &&
936 ((int_buf[i + 1] & ~QUOT) == '/'
937 || (int_buf[i + 1] & ~QUOT) == '~')) {
941 /* set only match and destroy quotes */
943 for (c = 0; pos_buf[i] >= 0; i++) {
944 matchBuf[c++] = matchBuf[pos_buf[i]];
948 /* old lenght matchBuf with quotes symbols */
949 *len_with_quotes = j ? j - pos_buf[0] : 0;
955 static void input_tab(int *lastWasTab)
957 /* Do TAB completion */
958 static int num_matches;
959 static char **matches;
961 if (lastWasTab == 0) { /* free all memory */
963 while (num_matches > 0)
964 free(matches[--num_matches]);
966 matches = (char **) NULL;
974 char matchBuf[BUFSIZ];
978 *lastWasTab = TRUE; /* flop trigger */
980 /* Make a local copy of the string -- up
981 * to the position of the cursor */
982 tmp = strncpy(matchBuf, command_ps, cursor);
985 find_type = find_match(matchBuf, &recalc_pos);
987 /* Free up any memory already allocated */
990 #ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION
991 /* If the word starts with `~' and there is no slash in the word,
992 * then try completing this word as a username. */
994 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
995 matches = username_tab_completion(matchBuf, &num_matches);
997 /* Try to match any executable in our path and everything
998 * in the current working directory that matches. */
1001 exe_n_cwd_tab_completion(matchBuf,
1002 &num_matches, find_type);
1003 /* Remove duplicate found */
1007 for(i=0; i<(num_matches-1); i++)
1008 for(j=i+1; j<num_matches; j++)
1009 if(matches[i]!=0 && matches[j]!=0 &&
1010 strcmp(matches[i], matches[j])==0) {
1018 if(!strcmp(matches[i], "./"))
1020 else if(!strcmp(matches[i], "../"))
1022 matches[num_matches++]=matches[i];
1025 /* Did we find exactly one match? */
1026 if (!matches || num_matches > 1) {
1031 return; /* not found */
1033 qsort(matches, num_matches, sizeof(char *), match_compare);
1035 /* find minimal match */
1036 tmp = xstrdup(matches[0]);
1037 for (tmp1 = tmp; *tmp1; tmp1++)
1038 for (len_found = 1; len_found < num_matches; len_found++)
1039 if (matches[len_found][(tmp1 - tmp)] != *tmp1) {
1043 if (*tmp == 0) { /* have unique */
1047 } else { /* one match */
1049 /* for next completion current found */
1050 *lastWasTab = FALSE;
1053 len_found = strlen(tmp);
1054 /* have space to placed match? */
1055 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1057 /* before word for match */
1058 command_ps[cursor - recalc_pos] = 0;
1059 /* save tail line */
1060 strcpy(matchBuf, command_ps + cursor);
1062 strcat(command_ps, tmp);
1064 strcat(command_ps, matchBuf);
1065 /* back to begin word for match */
1066 input_backward(recalc_pos);
1068 recalc_pos = cursor + len_found;
1070 len = strlen(command_ps);
1071 /* write out the matched command */
1072 redraw(cmdedit_y, len - recalc_pos);
1074 if (tmp != matches[0])
1077 /* Ok -- the last char was a TAB. Since they
1078 * just hit TAB again, print a list of all the
1079 * available choices... */
1080 if (matches && num_matches > 0) {
1082 int sav_cursor = cursor; /* change goto_new_line() */
1084 /* Go to the next line */
1086 for (i = 0, col = 0; i < num_matches; i++) {
1087 l = strlen(matches[i]);
1090 printf("%-14s ", matches[i]);
1097 col -= (col / cmdedit_termw) * cmdedit_termw;
1098 if (col > 60 && matches[i + 1] != NULL) {
1103 /* Go to the next line and rewrite */
1105 redraw(0, len - sav_cursor);
1109 #endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */
1111 #if MAX_HISTORY >= 1
1112 static void get_previous_history(void)
1114 if(command_ps[0] != 0 || history[cur_history] == 0) {
1115 free(history[cur_history]);
1116 history[cur_history] = xstrdup(command_ps);
1121 static int get_next_history(void)
1123 int ch = cur_history;
1125 if (ch < n_history) {
1126 get_previous_history(); /* save the current history line */
1127 return (cur_history = ch+1);
1134 #ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY
1135 extern void load_history ( const char *fromfile )
1142 for(hi = n_history; hi > 0; ) {
1144 free ( history [hi] );
1147 if (( fp = fopen ( fromfile, "r" ))) {
1149 for ( hi = 0; hi < MAX_HISTORY; ) {
1150 char * hl = get_line_from_file(fp);
1159 if(l == 0 || hl[0] == ' ') {
1163 history [hi++] = hl;
1167 cur_history = n_history = hi;
1170 extern void save_history ( const char *tofile )
1172 FILE *fp = fopen ( tofile, "w" );
1177 for ( i = 0; i < n_history; i++ ) {
1178 fputs ( history [i], fp );
1195 * This function is used to grab a character buffer
1196 * from the input file descriptor and allows you to
1197 * a string with full command editing (sortof like
1200 * The following standard commands are not implemented:
1201 * ESC-b -- Move back one word
1202 * ESC-f -- Move forward one word
1203 * ESC-d -- Delete back one word
1204 * ESC-h -- Delete forward one word
1205 * CTL-t -- Transpose two characters
1207 * Furthermore, the "vi" command editing keys are not implemented.
1212 int cmdedit_read_input(char *prompt, char command[BUFSIZ])
1216 int lastWasTab = FALSE;
1217 unsigned char c = 0;
1219 /* prepare before init handlers */
1220 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1222 command_ps = command;
1224 getTermSettings(0, (void *) &initial_settings);
1225 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1226 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1227 /* Turn off echoing and CTRL-C, so we can trap it */
1228 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1230 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1231 new_settings.c_cc[VMIN] = 1;
1232 new_settings.c_cc[VTIME] = 0;
1233 /* Turn off CTRL-C, so we can trap it */
1234 # ifndef _POSIX_VDISABLE
1235 # define _POSIX_VDISABLE '\0'
1237 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1241 setTermSettings(0, (void *) &new_settings);
1242 handlers_sets |= SET_RESET_TERM;
1244 /* Now initialize things */
1246 /* Print out the command prompt */
1247 parse_prompt(prompt);
1251 fflush(stdout); /* buffered out to fast */
1253 if (safe_read(0, &c, 1) < 1)
1254 /* if we can't read input then exit */
1255 goto prepare_to_die;
1265 /* Control-a -- Beginning of line */
1266 input_backward(cursor);
1269 /* Control-b -- Move back one character */
1273 /* Control-c -- stop gathering input */
1281 /* Control-d -- Delete one character, or exit
1282 * if the len=0 and no chars to delete */
1285 #if !defined(CONFIG_ASH)
1288 /* cmdedit_reset_term() called in atexit */
1291 break_out = -1; /* for control stoped jobs */
1299 /* Control-e -- End of line */
1303 /* Control-f -- Move forward one character */
1308 /* Control-h and DEL */
1312 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1313 input_tab(&lastWasTab);
1317 /* Control-k -- clear to end of line */
1318 *(command + cursor) = 0;
1323 /* Control-l -- clear screen */
1325 redraw(0, len-cursor);
1327 #if MAX_HISTORY >= 1
1329 /* Control-n -- Get next command in history */
1330 if (get_next_history())
1334 /* Control-p -- Get previous command from history */
1335 if (cur_history > 0) {
1336 get_previous_history();
1344 /* Control-U -- Clear line before cursor */
1346 strcpy(command, command + cursor);
1347 redraw(cmdedit_y, len -= cursor);
1351 /* escape sequence follows */
1352 if (safe_read(0, &c, 1) < 1)
1353 goto prepare_to_die;
1354 /* different vt100 emulations */
1355 if (c == '[' || c == 'O') {
1356 if (safe_read(0, &c, 1) < 1)
1357 goto prepare_to_die;
1360 #ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION
1361 case '\t': /* Alt-Tab */
1363 input_tab(&lastWasTab);
1366 #if MAX_HISTORY >= 1
1368 /* Up Arrow -- Get previous command from history */
1369 if (cur_history > 0) {
1370 get_previous_history();
1377 /* Down Arrow -- Get next command in history */
1378 if (!get_next_history())
1380 /* Rewrite the line with the selected history item */
1382 /* change command */
1383 len = strlen(strcpy(command, history[cur_history]));
1384 /* redraw and go to end line */
1385 redraw(cmdedit_y, 0);
1389 /* Right Arrow -- Move forward one character */
1393 /* Left Arrow -- Move back one character */
1403 input_backward(cursor);
1411 if (!(c >= '1' && c <= '9'))
1415 if (c >= '1' && c <= '9')
1417 if (safe_read(0, &c, 1) < 1)
1418 goto prepare_to_die;
1423 default: /* If it's regular input, do the normal thing */
1424 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1425 /* Control-V -- Add non-printable symbol */
1427 if (safe_read(0, &c, 1) < 1)
1428 goto prepare_to_die;
1435 if (!Isprint(c)) /* Skip non-printable characters */
1438 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1443 if (cursor == (len - 1)) { /* Append if at the end of the line */
1444 *(command + cursor) = c;
1445 *(command + cursor + 1) = 0;
1446 cmdedit_set_out_char(0);
1447 } else { /* Insert otherwise */
1450 memmove(command + sc + 1, command + sc, len - sc);
1451 *(command + sc) = c;
1453 /* rewrite from cursor */
1455 /* to prev x pos + 1 */
1456 input_backward(cursor - sc);
1461 if (break_out) /* Enter is the command terminator, no more input. */
1468 setTermSettings(0, (void *) &initial_settings);
1469 handlers_sets &= ~SET_RESET_TERM;
1471 #if MAX_HISTORY >= 1
1472 /* Handle command history log */
1473 /* cleanup may be saved current command line */
1474 free(history[MAX_HISTORY]);
1475 history[MAX_HISTORY] = 0;
1476 if (len) { /* no put empty line */
1478 /* After max history, remove the oldest command */
1479 if (i >= MAX_HISTORY) {
1481 for(i = 0; i < (MAX_HISTORY-1); i++)
1482 history[i] = history[i+1];
1484 history[i++] = xstrdup(command);
1487 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1491 #else /* MAX_HISTORY < 1 */
1492 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1493 if (len) { /* no put empty line */
1497 #endif /* MAX_HISTORY >= 1 */
1499 command[len++] = '\n'; /* set '\n' */
1502 #if defined(CONFIG_FEATURE_CLEAN_UP) && defined(CONFIG_FEATURE_COMMAND_TAB_COMPLETION)
1503 input_tab(0); /* strong free */
1505 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1506 free(cmdedit_prompt);
1508 cmdedit_reset_term();
1514 #endif /* CONFIG_FEATURE_COMMAND_EDITING */
1519 const char *applet_name = "debug stuff usage";
1520 const char *memory_exhausted = "Memory exhausted";
1522 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1526 int main(int argc, char **argv)
1530 #if defined(CONFIG_FEATURE_SH_FANCY_PROMPT)
1531 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1532 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1533 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1538 #ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT
1539 setlocale(LC_ALL, "");
1543 cmdedit_read_input(prompt, buff);
1547 if(l > 0 && buff[l-1] == '\n')
1549 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1551 printf("*** cmdedit_read_input() detect ^C\n");