1 /* vi: set sw=4 ts=4: */
3 * Termios command line History and Editing.
5 * Copyright (c) 1986-2003 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 <andersen@codepoet.org> (Majorly adjusted for busybox)
14 * This code is 'as is' with no warranty.
19 Terminal key codes are not extensive, and more will probably
20 need to be added. This version was created on Debian GNU/Linux 2.x.
21 Delete, Backspace, Home, End, and the arrow keys were tested
22 to work in an Xterm and console. Ctrl-A also works as Home.
23 Ctrl-E also works as End.
25 Small bugs (simple effect):
26 - not true viewing if terminal size (x*y symbols) less
27 size (prompt + editor`s line + 2 symbols)
28 - not true viewing if length prompt less terminal width
32 #include <sys/ioctl.h>
37 #if ENABLE_LOCALE_SUPPORT
38 #define Isprint(c) isprint(c)
40 #define Isprint(c) ((c) >= ' ' && (c) != ((unsigned char)'\233'))
44 /* FIXME: obsolete CONFIG item? */
45 #define ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT 0
50 #define ENABLE_FEATURE_COMMAND_EDITING 0
51 #define ENABLE_FEATURE_COMMAND_TAB_COMPLETION 0
52 #define ENABLE_FEATURE_COMMAND_USERNAME_COMPLETION 0
53 #define ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT 0
54 #define ENABLE_FEATURE_CLEAN_UP 0
59 #if ENABLE_FEATURE_COMMAND_EDITING
61 #define ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR \
62 (ENABLE_FEATURE_COMMAND_USERNAME_COMPLETION || ENABLE_FEATURE_SH_FANCY_PROMPT)
64 /* Maximum length of the linked list for the command line history */
65 #if !ENABLE_FEATURE_COMMAND_HISTORY
66 #define MAX_HISTORY 15
68 #define MAX_HISTORY (CONFIG_FEATURE_COMMAND_HISTORY + 0)
72 static char *history[MAX_HISTORY+1]; /* history + current */
73 /* saved history lines */
75 /* current pointer to history line */
76 static int cur_history;
79 #define setTermSettings(fd,argp) tcsetattr(fd, TCSANOW, argp)
80 #define getTermSettings(fd,argp) tcgetattr(fd, argp);
82 /* Current termio and the previous termio before starting sh */
83 static struct termios initial_settings, new_settings;
87 volatile int cmdedit_termw = 80; /* actual terminal width */
89 volatile int handlers_sets = 0; /* Set next bites: */
92 SET_ATEXIT = 1, /* when atexit() has been called
93 and get euid,uid,gid to fast compare */
94 SET_WCHG_HANDLERS = 2, /* winchg signal handler */
95 SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */
99 static int cmdedit_x; /* real x terminal position */
100 static int cmdedit_y; /* pseudoreal y terminal position */
101 static int cmdedit_prmt_len; /* lenght prompt without colores string */
103 static int cursor; /* required globals for signal handler */
104 static int len; /* --- "" - - "" -- -"- --""-- --""--- */
105 static char *command_ps; /* --- "" - - "" -- -"- --""-- --""--- */
106 static SKIP_FEATURE_SH_FANCY_PROMPT(const) char *cmdedit_prompt; /* -- */
108 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
109 static char *user_buf = "";
110 static char *home_pwd_buf = "";
114 #if ENABLE_FEATURE_SH_FANCY_PROMPT
115 static char *hostname_buf;
116 static int num_ok_lines = 1;
120 #if ENABLE_FEATURE_COMMAND_TAB_COMPLETION
122 #if !ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
129 #endif /* FEATURE_COMMAND_TAB_COMPLETION */
131 static void cmdedit_setwidth(int w, int redraw_flg);
133 static void win_changed(int nsig)
135 static sighandler_t previous_SIGWINCH_handler; /* for reset */
137 /* emulate || signal call */
138 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
140 get_terminal_width_height(0, &width, NULL);
141 cmdedit_setwidth(width, nsig == SIGWINCH);
143 /* Unix not all standard in recall signal */
145 if (nsig == -SIGWINCH) /* save previous handler */
146 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
147 else if (nsig == SIGWINCH) /* signaled called handler */
148 signal(SIGWINCH, win_changed); /* set for next call */
150 /* set previous handler */
151 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
154 static void cmdedit_reset_term(void)
156 if (handlers_sets & SET_RESET_TERM) {
157 /* sparc and other have broken termios support: use old termio handling. */
158 setTermSettings(STDIN_FILENO, (void *) &initial_settings);
159 handlers_sets &= ~SET_RESET_TERM;
161 if (handlers_sets & SET_WCHG_HANDLERS) {
162 /* reset SIGWINCH handler to previous (default) */
164 handlers_sets &= ~SET_WCHG_HANDLERS;
170 /* special for recount position for scroll and remove terminal margin effect */
171 static void cmdedit_set_out_char(int next_char)
173 int c = (unsigned char)command_ps[cursor];
176 c = ' '; /* destroy end char? */
177 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
178 if (!Isprint(c)) { /* Inverse put non-printable characters */
185 printf("\033[7m%c\033[0m", c);
189 if (initial_settings.c_lflag & ECHO)
192 if (++cmdedit_x >= cmdedit_termw) {
193 /* terminal is scrolled down */
199 /* destroy "(auto)margin" */
206 /* Move to end line. Bonus: rewrite line from cursor */
207 static void input_end(void)
210 cmdedit_set_out_char(0);
213 /* Go to the next line */
214 static void goto_new_line(void)
222 static void out1str(const char *s)
228 static void beep(void)
233 /* Move back one character */
234 /* special for slow terminal */
235 static void input_backward(int num)
239 cursor -= num; /* new cursor (in command, not terminal) */
241 if (cmdedit_x >= num) { /* no to up line */
247 printf("\033[%dD", num);
252 putchar('\r'); /* back to first terminal pos. */
253 num -= cmdedit_x; /* set previous backward */
255 count_y = 1 + num / cmdedit_termw;
256 printf("\033[%dA", count_y);
257 cmdedit_y -= count_y;
258 /* require forward after uping */
259 cmdedit_x = cmdedit_termw * count_y - num;
260 printf("\033[%dC", cmdedit_x); /* set term cursor */
264 static void put_prompt(void)
266 out1str(cmdedit_prompt);
267 cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */
269 cmdedit_y = 0; /* new quasireal y */
272 #if !ENABLE_FEATURE_SH_FANCY_PROMPT
273 static void parse_prompt(const char *prmt_ptr)
275 cmdedit_prompt = prmt_ptr;
276 cmdedit_prmt_len = strlen(prmt_ptr);
280 static void parse_prompt(const char *prmt_ptr)
283 size_t cur_prmt_len = 0;
284 char flg_not_length = '[';
285 char *prmt_mem_ptr = xzalloc(1);
286 char *pwd_buf = xgetcwd(0);
287 char buf2[PATH_MAX + 1];
293 pwd_buf = (char *)bb_msg_unknown;
301 const char *cp = prmt_ptr;
304 c = bb_process_escape_sequence(&prmt_ptr);
305 if (prmt_ptr == cp) {
310 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
319 if (gethostname(pbuf, 255) < 0) {
322 char *s = strchr(pbuf, '.');
330 c = (my_euid == 0 ? '#' : '$');
332 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
335 l = strlen(home_pwd_buf);
336 if (home_pwd_buf[0] != 0
337 && strncmp(home_pwd_buf, pbuf, l) == 0
338 && (pbuf[l]=='/' || pbuf[l]=='\0')
339 && strlen(pwd_buf+l)<PATH_MAX
343 strcpy(pbuf+1, pwd_buf+l);
349 cp = strrchr(pbuf,'/');
350 if (cp != NULL && cp != pbuf)
351 pbuf += (cp-pbuf) + 1;
354 snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines);
356 case 'e': case 'E': /* \e \E = \033 */
360 for (l = 0; l < 3;) {
362 buf2[l++] = *prmt_ptr;
364 h = strtol(buf2, &pbuf, 16);
365 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
372 c = (char)strtol(buf2, 0, 16);
378 if (c == flg_not_length) {
379 flg_not_length = flg_not_length == '[' ? ']' : '[';
388 cur_prmt_len = strlen(pbuf);
389 prmt_len += cur_prmt_len;
390 if (flg_not_length != ']')
391 cmdedit_prmt_len += cur_prmt_len;
392 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
394 if (pwd_buf!=(char *)bb_msg_unknown)
396 cmdedit_prompt = prmt_mem_ptr;
402 /* draw prompt, editor line, and clear tail */
403 static void redraw(int y, int back_cursor)
405 if (y > 0) /* up to start y */
406 printf("\033[%dA", y);
409 input_end(); /* rewrite */
410 printf("\033[J"); /* destroy tail after cursor */
411 input_backward(back_cursor);
414 #if ENABLE_FEATURE_COMMAND_EDITING_VI
415 #define DELBUFSIZ 128
416 static char *delbuf; /* a (malloced) place to store deleted characters */
418 static char newdelflag; /* whether delbuf should be reused yet */
421 /* Delete the char in front of the cursor, optionally saving it
422 * for later putback */
423 static void input_delete(int save)
430 #if ENABLE_FEATURE_COMMAND_EDITING_VI
434 delbuf = malloc(DELBUFSIZ);
435 /* safe if malloc fails */
439 if (delbuf && (delp - delbuf < DELBUFSIZ))
440 *delp++ = command_ps[j];
444 strcpy(command_ps + j, command_ps + j + 1);
446 input_end(); /* rewrite new line */
447 cmdedit_set_out_char(0); /* destroy end char */
448 input_backward(cursor - j); /* back to old pos cursor */
451 #if ENABLE_FEATURE_COMMAND_EDITING_VI
452 static void put(void)
454 int ocursor, j = delp - delbuf;
458 /* open hole and then fill it */
459 memmove(command_ps + cursor + j, command_ps + cursor, len - cursor + 1);
460 strncpy(command_ps + cursor, delbuf, j);
462 input_end(); /* rewrite new line */
463 input_backward(cursor - ocursor - j + 1); /* at end of new text */
467 /* Delete the char in back of the cursor */
468 static void input_backspace(void)
477 /* Move forward one character */
478 static void input_forward(void)
481 cmdedit_set_out_char(command_ps[cursor + 1]);
484 static void cmdedit_setwidth(int w, int redraw_flg)
486 cmdedit_termw = cmdedit_prmt_len + 2;
487 if (w <= cmdedit_termw) {
488 cmdedit_termw = cmdedit_termw % w;
490 if (w > cmdedit_termw) {
494 /* new y for current cursor */
495 int new_y = (cursor + cmdedit_prmt_len) / w;
498 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor);
504 static void cmdedit_init(void)
506 cmdedit_prmt_len = 0;
507 if (!(handlers_sets & SET_WCHG_HANDLERS)) {
508 /* emulate usage handler to set handler and call yours work */
509 win_changed(-SIGWINCH);
510 handlers_sets |= SET_WCHG_HANDLERS;
513 if (!(handlers_sets & SET_ATEXIT)) {
514 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
515 struct passwd *entry;
518 entry = getpwuid(my_euid);
520 user_buf = xstrdup(entry->pw_name);
521 home_pwd_buf = xstrdup(entry->pw_dir);
525 #if ENABLE_FEATURE_COMMAND_TAB_COMPLETION
527 #if !ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
532 #endif /* FEATURE_COMMAND_TAB_COMPLETION */
533 handlers_sets |= SET_ATEXIT;
534 atexit(cmdedit_reset_term); /* be sure to do this only once */
538 #if ENABLE_FEATURE_COMMAND_TAB_COMPLETION
540 static char **matches;
541 static int num_matches;
543 static void add_match(char *matched)
545 int nm = num_matches;
548 matches = xrealloc(matches, nm1 * sizeof(char *));
549 matches[nm] = matched;
554 static int is_execute(const struct stat *st)
556 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
557 || (my_uid == st->st_uid && (st->st_mode & S_IXUSR))
558 || (my_gid == st->st_gid && (st->st_mode & S_IXGRP))
559 || (st->st_mode & S_IXOTH)
567 #if ENABLE_FEATURE_COMMAND_USERNAME_COMPLETION
569 static void username_tab_completion(char *ud, char *with_shash_flg)
571 struct passwd *entry;
574 ud++; /* ~user/... to user/... */
575 userlen = strlen(ud);
577 if (with_shash_flg) { /* "~/..." or "~user/..." */
578 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);
607 while ((entry = getpwent()) != NULL) {
608 /* Null usernames should result in all users as possible completions. */
609 if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) {
610 add_match(xasprintf("~%s/", entry->pw_name));
617 #endif /* FEATURE_COMMAND_USERNAME_COMPLETION */
626 const char *cmdedit_path_lookup;
628 #define cmdedit_path_lookup getenv("PATH")
631 static int path_parse(char ***p, int flags)
635 const char *pth = cmdedit_path_lookup;
637 /* if not setenv PATH variable, to search cur dir "." */
638 if (flags != FIND_EXE_ONLY)
640 /* PATH=<empty> or PATH=:<empty> */
641 if (!pth || !pth[0] || LONE_CHAR(pth, ':'))
648 npth++; /* count words is + 1 count ':' */
649 tmp = strchr(tmp, ':');
653 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, ':');
666 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
668 break; /* :<empty> */
669 (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */
675 static char *add_quote_for_spec_chars(char *found)
678 char *s = xmalloc((strlen(found) + 1) * 2);
681 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
689 static void exe_n_cwd_tab_completion(char *command, int type)
696 char **paths = path1;
700 char *pfind = strrchr(command, '/');
705 /* no dir, if flags==EXE_ONLY - get paths, else "." */
706 npaths = path_parse(&paths, type);
710 /* save for change */
711 strcpy(dirbuf, command);
713 dirbuf[(pfind - command) + 1] = 0;
714 #if ENABLE_FEATURE_COMMAND_USERNAME_COMPLETION
715 if (dirbuf[0] == '~') /* ~/... or ~user/... */
716 username_tab_completion(dirbuf, dirbuf);
718 /* "strip" dirname in command */
722 npaths = 1; /* only 1 dir */
725 for (i = 0; i < npaths; i++) {
727 dir = opendir(paths[i]);
728 if (!dir) /* Don't print an error */
731 while ((next = readdir(dir)) != NULL) {
733 char *str_found = next->d_name;
736 if (strncmp(str_found, pfind, strlen(pfind)))
738 /* not see .name without .match */
739 if (*str_found == '.' && *pfind == 0) {
740 if (NOT_LONE_CHAR(paths[i], '/') || str_found[1])
742 str_found = ""; /* only "/" */
744 found = concat_path_file(paths[i], str_found);
745 /* hmm, remover in progress? */
746 if (stat(found, &st) < 0)
748 /* find with dirs? */
749 if (paths[i] != dirbuf)
750 strcpy(found, next->d_name); /* only name */
752 len1 = strlen(found);
753 found = xrealloc(found, len1 + 2);
755 found[len1+1] = '\0';
757 if (S_ISDIR(st.st_mode)) {
758 /* name is directory */
759 if (found[len1-1] != '/') {
763 /* not put found file if search only dirs for cd */
764 if (type == FIND_DIR_ONLY)
767 /* Add it to the list */
775 if (paths != path1) {
776 free(paths[0]); /* allocated memory only in first member */
782 #define QUOT (UCHAR_MAX+1)
784 #define collapse_pos(is, in) { \
785 memmove(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
786 memmove(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
788 static int find_match(char *matchBuf, int *len_with_quotes)
793 int int_buf[BUFSIZ + 1];
794 int pos_buf[BUFSIZ + 1];
796 /* set to integer dimension characters and own positions */
798 int_buf[i] = (unsigned char)matchBuf[i];
799 if (int_buf[i] == 0) {
800 pos_buf[i] = -1; /* indicator end line */
806 /* mask \+symbol and convert '\t' to ' ' */
807 for (i = j = 0; matchBuf[i]; i++, j++)
808 if (matchBuf[i] == '\\') {
809 collapse_pos(j, j + 1);
812 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
813 if (matchBuf[i] == '\t') /* algorithm equivalent */
814 int_buf[j] = ' ' | QUOT;
817 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
818 else if (matchBuf[i] == '\t')
822 /* mask "symbols" or 'symbols' */
824 for (i = 0; int_buf[i]; i++) {
826 if (c == '\'' || c == '"') {
835 } else if (c2 != 0 && c != '$')
839 /* skip commands with arguments if line has commands delimiters */
840 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
841 for (i = 0; int_buf[i]; i++) {
844 j = i ? int_buf[i - 1] : -1;
846 if (c == ';' || c == '&' || c == '|') {
847 command_mode = 1 + (c == c2);
849 if (j == '>' || j == '<')
851 } else if (c == '|' && j == '>')
855 collapse_pos(0, i + command_mode);
856 i = -1; /* hack incremet */
859 /* collapse `command...` */
860 for (i = 0; int_buf[i]; i++)
861 if (int_buf[i] == '`') {
862 for (j = i + 1; int_buf[j]; j++)
863 if (int_buf[j] == '`') {
864 collapse_pos(i, j + 1);
869 /* not found close ` - command mode, collapse all previous */
870 collapse_pos(0, i + 1);
873 i--; /* hack incremet */
876 /* collapse (command...(command...)...) or {command...{command...}...} */
877 c = 0; /* "recursive" level */
879 for (i = 0; int_buf[i]; i++)
880 if (int_buf[i] == '(' || int_buf[i] == '{') {
881 if (int_buf[i] == '(')
885 collapse_pos(0, i + 1);
886 i = -1; /* hack incremet */
888 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
889 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
890 if (int_buf[i] == ')')
894 collapse_pos(0, i + 1);
895 i = -1; /* hack incremet */
898 /* skip first not quote space */
899 for (i = 0; int_buf[i]; i++)
900 if (int_buf[i] != ' ')
905 /* set find mode for completion */
906 command_mode = FIND_EXE_ONLY;
907 for (i = 0; int_buf[i]; i++)
908 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
909 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
910 && matchBuf[pos_buf[0]]=='c'
911 && matchBuf[pos_buf[1]]=='d'
913 command_mode = FIND_DIR_ONLY;
915 command_mode = FIND_FILE_ONLY;
919 for (i = 0; int_buf[i]; i++)
922 for (--i; i >= 0; i--) {
924 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
925 collapse_pos(0, i + 1);
929 /* skip first not quoted '\'' or '"' */
930 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++)
932 /* collapse quote or unquote // or /~ */
933 while ((int_buf[i] & ~QUOT) == '/'
934 && ((int_buf[i+1] & ~QUOT) == '/' || (int_buf[i+1] & ~QUOT) == '~')
939 /* set only match and destroy quotes */
941 for (c = 0; pos_buf[i] >= 0; i++) {
942 matchBuf[c++] = matchBuf[pos_buf[i]];
946 /* old lenght matchBuf with quotes symbols */
947 *len_with_quotes = j ? j - pos_buf[0] : 0;
953 display by column original ideas from ls applet,
954 very optimize by my :)
956 static void showfiles(void)
959 int column_width = 0;
960 int nfiles = num_matches;
964 /* find the longest file name- use that as the column width */
965 for (row = 0; row < nrows; row++) {
966 l = strlen(matches[row]);
967 if (column_width < l)
970 column_width += 2; /* min space for columns */
971 ncols = cmdedit_termw / column_width;
976 nrows++; /* round up fractionals */
980 for (row = 0; row < nrows; row++) {
984 for (nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
985 printf("%s%-*s", matches[n],
986 column_width - strlen(matches[n]), "");
988 printf("%s\n", matches[n]);
992 static int match_compare(const void *a, const void *b)
994 return strcmp(*(char**)a, *(char**)b);
997 static void input_tab(int *lastWasTab)
999 /* Do TAB completion */
1000 if (lastWasTab == 0) { /* free all memory */
1002 while (num_matches > 0)
1003 free(matches[--num_matches]);
1005 matches = (char **) NULL;
1012 char matchBuf[BUFSIZ];
1016 *lastWasTab = TRUE; /* flop trigger */
1018 /* Make a local copy of the string -- up
1019 * to the position of the cursor */
1020 tmp = strncpy(matchBuf, command_ps, cursor);
1023 find_type = find_match(matchBuf, &recalc_pos);
1025 /* Free up any memory already allocated */
1028 #if ENABLE_FEATURE_COMMAND_USERNAME_COMPLETION
1029 /* If the word starts with `~' and there is no slash in the word,
1030 * then try completing this word as a username. */
1032 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1033 username_tab_completion(matchBuf, NULL);
1036 /* Try to match any executable in our path and everything
1037 * in the current working directory that matches. */
1038 exe_n_cwd_tab_completion(matchBuf, find_type);
1039 /* Sort, then remove any duplicates found */
1042 qsort(matches, num_matches, sizeof(char*), match_compare);
1043 for (i = 0; i < num_matches - 1; ++i) {
1044 if (matches[i] && matches[i+1]) {
1045 if (strcmp(matches[i], matches[i+1]) == 0) {
1049 matches[n++] = matches[i];
1053 matches[n++] = matches[num_matches-1];
1056 /* Did we find exactly one match? */
1057 if (!matches || num_matches > 1) {
1060 return; /* not found */
1061 /* find minimal match */
1062 tmp1 = xstrdup(matches[0]);
1063 for (tmp = tmp1; *tmp; tmp++)
1064 for (len_found = 1; len_found < num_matches; len_found++)
1065 if (matches[len_found][(tmp - tmp1)] != *tmp) {
1069 if (*tmp1 == 0) { /* have unique */
1073 tmp = add_quote_for_spec_chars(tmp1);
1075 } else { /* one match */
1076 tmp = add_quote_for_spec_chars(matches[0]);
1077 /* for next completion current found */
1078 *lastWasTab = FALSE;
1080 len_found = strlen(tmp);
1081 if (tmp[len_found-1] != '/') {
1082 tmp[len_found] = ' ';
1083 tmp[len_found+1] = '\0';
1086 len_found = strlen(tmp);
1087 /* have space to placed match? */
1088 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1090 /* before word for match */
1091 command_ps[cursor - recalc_pos] = 0;
1092 /* save tail line */
1093 strcpy(matchBuf, command_ps + cursor);
1095 strcat(command_ps, tmp);
1097 strcat(command_ps, matchBuf);
1098 /* back to begin word for match */
1099 input_backward(recalc_pos);
1101 recalc_pos = cursor + len_found;
1103 len = strlen(command_ps);
1104 /* write out the matched command */
1105 redraw(cmdedit_y, len - recalc_pos);
1109 /* Ok -- the last char was a TAB. Since they
1110 * just hit TAB again, print a list of all the
1111 * available choices... */
1112 if (matches && num_matches > 0) {
1113 int sav_cursor = cursor; /* change goto_new_line() */
1115 /* Go to the next line */
1118 redraw(0, len - sav_cursor);
1122 #endif /* FEATURE_COMMAND_TAB_COMPLETION */
1125 static void get_previous_history(void)
1127 if (command_ps[0] != 0 || history[cur_history] == 0) {
1128 free(history[cur_history]);
1129 history[cur_history] = xstrdup(command_ps);
1134 static int get_next_history(void)
1136 int ch = cur_history;
1138 if (ch < n_history) {
1139 get_previous_history(); /* save the current history line */
1140 cur_history = ch + 1;
1148 #if ENABLE_FEATURE_COMMAND_SAVEHISTORY
1149 void load_history(const char *fromfile)
1156 for (hi = n_history; hi > 0;) {
1161 fp = fopen(fromfile, "r");
1163 for (hi = 0; hi < MAX_HISTORY;) {
1164 char * hl = xmalloc_getline(fp);
1172 if (l == 0 || hl[0] == ' ') {
1180 cur_history = n_history = hi;
1183 void save_history (const char *tofile)
1185 FILE *fp = fopen(tofile, "w");
1190 for (i = 0; i < n_history; i++) {
1191 fprintf(fp, "%s\n", history[i]);
1207 * This function is used to grab a character buffer
1208 * from the input file descriptor and allows you to
1209 * a string with full command editing (sort of like
1212 * The following standard commands are not implemented:
1213 * ESC-b -- Move back one word
1214 * ESC-f -- Move forward one word
1215 * ESC-d -- Delete back one word
1216 * ESC-h -- Delete forward one word
1217 * CTL-t -- Transpose two characters
1219 * Minimalist vi-style command line editing available if configured.
1220 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
1224 #if ENABLE_FEATURE_COMMAND_EDITING_VI
1227 void setvimode ( int viflag )
1233 vi_Word_motion(char *command, int eat)
1235 while (cursor < len && !isspace(command[cursor]))
1237 if (eat) while (cursor < len && isspace(command[cursor]))
1242 vi_word_motion(char *command, int eat)
1244 if (isalnum(command[cursor]) || command[cursor] == '_') {
1246 && (isalnum(command[cursor+1]) || command[cursor+1] == '_'))
1248 } else if (ispunct(command[cursor])) {
1249 while (cursor < len && ispunct(command[cursor+1]))
1256 if (eat && cursor < len && isspace(command[cursor]))
1257 while (cursor < len && isspace(command[cursor]))
1262 vi_End_motion(char *command)
1265 while (cursor < len && isspace(command[cursor]))
1267 while (cursor < len-1 && !isspace(command[cursor+1]))
1272 vi_end_motion(char *command)
1274 if (cursor >= len-1)
1277 while (cursor < len-1 && isspace(command[cursor]))
1279 if (cursor >= len-1)
1281 if (isalnum(command[cursor]) || command[cursor] == '_') {
1282 while (cursor < len-1
1283 && (isalnum(command[cursor+1]) || command[cursor+1] == '_')
1287 } else if (ispunct(command[cursor])) {
1288 while (cursor < len-1 && ispunct(command[cursor+1]))
1294 vi_Back_motion(char *command)
1296 while (cursor > 0 && isspace(command[cursor-1]))
1298 while (cursor > 0 && !isspace(command[cursor-1]))
1303 vi_back_motion(char *command)
1308 while (cursor > 0 && isspace(command[cursor]))
1312 if (isalnum(command[cursor]) || command[cursor] == '_') {
1314 && (isalnum(command[cursor-1]) || command[cursor-1] == '_')
1318 } else if (ispunct(command[cursor])) {
1319 while (cursor > 0 && ispunct(command[cursor-1]))
1326 * the emacs and vi modes share much of the code in the big
1327 * command loop. commands entered when in vi's command mode (aka
1328 * "escape mode") get an extra bit added to distinguish them --
1329 * this keeps them from being self-inserted. this clutters the
1330 * big switch a bit, but keeps all the code in one place.
1335 /* leave out the "vi-mode"-only case labels if vi editing isn't
1337 #define vi_case(caselabel) USE_FEATURE_COMMAND_EDITING(caselabel)
1339 /* convert uppercase ascii to equivalent control char, for readability */
1340 #define CNTRL(uc_char) ((uc_char) - 0x40)
1343 int cmdedit_read_input(char *prompt, char command[BUFSIZ])
1346 int lastWasTab = FALSE;
1349 #if ENABLE_FEATURE_COMMAND_EDITING_VI
1353 /* prepare before init handlers */
1354 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1356 command_ps = command;
1358 getTermSettings(0, (void *) &initial_settings);
1359 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1360 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1361 /* Turn off echoing and CTRL-C, so we can trap it */
1362 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1363 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1364 new_settings.c_cc[VMIN] = 1;
1365 new_settings.c_cc[VTIME] = 0;
1366 /* Turn off CTRL-C, so we can trap it */
1367 # ifndef _POSIX_VDISABLE
1368 # define _POSIX_VDISABLE '\0'
1370 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1373 setTermSettings(0, (void *) &new_settings);
1374 handlers_sets |= SET_RESET_TERM;
1376 /* Now initialize things */
1378 /* Print out the command prompt */
1379 parse_prompt(prompt);
1382 fflush(stdout); /* buffered out to fast */
1384 if (safe_read(0, &c, 1) < 1)
1385 /* if we can't read input then exit */
1386 goto prepare_to_die;
1390 #if ENABLE_FEATURE_COMMAND_EDITING_VI
1398 vi_case( case '\n'|vbit: )
1399 vi_case( case '\r'|vbit: )
1405 vi_case( case '0'|vbit: )
1406 /* Control-a -- Beginning of line */
1407 input_backward(cursor);
1410 vi_case( case 'h'|vbit: )
1411 vi_case( case '\b'|vbit: )
1412 vi_case( case DEL|vbit: )
1413 /* Control-b -- Move back one character */
1417 vi_case( case CNTRL('C')|vbit: )
1418 /* Control-c -- stop gathering input */
1427 break_out = -1; /* to control traps */
1431 /* Control-d -- Delete one character, or exit
1432 * if the len=0 and no chars to delete */
1439 /* cmdedit_reset_term() called in atexit */
1442 /* to control stopped jobs */
1443 len = break_out = -1;
1451 vi_case( case '$'|vbit: )
1452 /* Control-e -- End of line */
1456 vi_case( case 'l'|vbit: )
1457 vi_case( case ' '|vbit: )
1458 /* Control-f -- Move forward one character */
1463 /* Control-h and DEL */
1467 #if ENABLE_FEATURE_COMMAND_TAB_COMPLETION
1468 input_tab(&lastWasTab);
1472 /* Control-k -- clear to end of line */
1473 command[cursor] = 0;
1478 vi_case( case CNTRL('L')|vbit: )
1479 /* Control-l -- clear screen */
1481 redraw(0, len - cursor);
1485 vi_case( case CNTRL('N')|vbit: )
1486 vi_case( case 'j'|vbit: )
1487 /* Control-n -- Get next command in history */
1488 if (get_next_history())
1492 vi_case( case CNTRL('P')|vbit: )
1493 vi_case( case 'k'|vbit: )
1494 /* Control-p -- Get previous command from history */
1495 if (cur_history > 0) {
1496 get_previous_history();
1504 vi_case( case CNTRL('U')|vbit: )
1505 /* Control-U -- Clear line before cursor */
1507 strcpy(command, command + cursor);
1508 redraw(cmdedit_y, len -= cursor);
1512 vi_case( case CNTRL('W')|vbit: )
1513 /* Control-W -- Remove the last word */
1514 while (cursor > 0 && isspace(command[cursor-1]))
1516 while (cursor > 0 &&!isspace(command[cursor-1]))
1519 #if ENABLE_FEATURE_COMMAND_EDITING_VI
1524 input_backward(cursor);
1545 vi_Word_motion(command, 1);
1548 vi_word_motion(command, 1);
1551 vi_End_motion(command);
1554 vi_end_motion(command);
1557 vi_Back_motion(command);
1560 vi_back_motion(command);
1575 if (safe_read(0, &c, 1) < 1)
1576 goto prepare_to_die;
1577 if (c == (prevc & 0xff)) {
1579 input_backward(cursor);
1589 case 'w': /* "dw", "cw" */
1590 vi_word_motion(command, vi_cmdmode);
1592 case 'W': /* 'dW', 'cW' */
1593 vi_Word_motion(command, vi_cmdmode);
1595 case 'e': /* 'de', 'ce' */
1596 vi_end_motion(command);
1599 case 'E': /* 'dE', 'cE' */
1600 vi_End_motion(command);
1605 input_backward(cursor - sc);
1606 while (nc-- > cursor)
1609 case 'b': /* "db", "cb" */
1610 case 'B': /* implemented as B */
1612 vi_back_motion(command);
1614 vi_Back_motion(command);
1615 while (sc-- > cursor)
1618 case ' ': /* "d ", "c " */
1621 case '$': /* "d$", "c$" */
1623 while (cursor < len)
1636 if (safe_read(0, &c, 1) < 1)
1637 goto prepare_to_die;
1641 *(command + cursor) = c;
1646 #endif /* FEATURE_COMMAND_EDITING_VI */
1650 #if ENABLE_FEATURE_COMMAND_EDITING_VI
1652 /* ESC: insert mode --> command mode */
1658 /* escape sequence follows */
1659 if (safe_read(0, &c, 1) < 1)
1660 goto prepare_to_die;
1661 /* different vt100 emulations */
1662 if (c == '[' || c == 'O') {
1663 vi_case( case '['|vbit: )
1664 vi_case( case 'O'|vbit: )
1665 if (safe_read(0, &c, 1) < 1)
1666 goto prepare_to_die;
1668 if (c >= '1' && c <= '9') {
1669 unsigned char dummy;
1671 if (safe_read(0, &dummy, 1) < 1)
1672 goto prepare_to_die;
1677 #if ENABLE_FEATURE_COMMAND_TAB_COMPLETION
1678 case '\t': /* Alt-Tab */
1680 input_tab(&lastWasTab);
1685 /* Up Arrow -- Get previous command from history */
1686 if (cur_history > 0) {
1687 get_previous_history();
1694 /* Down Arrow -- Get next command in history */
1695 if (!get_next_history())
1697 /* Rewrite the line with the selected history item */
1699 /* change command */
1700 len = strlen(strcpy(command, history[cur_history]));
1701 /* redraw and go to eol (bol, in vi */
1702 #if ENABLE_FEATURE_COMMAND_EDITING_VI
1703 redraw(cmdedit_y, vi_mode ? 9999:0);
1705 redraw(cmdedit_y, 0);
1710 /* Right Arrow -- Move forward one character */
1714 /* Left Arrow -- Move back one character */
1724 input_backward(cursor);
1737 default: /* If it's regular input, do the normal thing */
1738 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1739 /* Control-V -- Add non-printable symbol */
1740 if (c == CNTRL('V')) {
1741 if (safe_read(0, &c, 1) < 1)
1742 goto prepare_to_die;
1750 #if ENABLE_FEATURE_COMMAND_EDITING_VI
1751 if (vi_cmdmode) /* don't self-insert */
1754 if (!Isprint(c)) /* Skip non-printable characters */
1758 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1763 if (cursor == (len - 1)) { /* Append if at the end of the line */
1764 *(command + cursor) = c;
1765 *(command + cursor + 1) = 0;
1766 cmdedit_set_out_char(0);
1767 } else { /* Insert otherwise */
1770 memmove(command + sc + 1, command + sc, len - sc);
1771 *(command + sc) = c;
1773 /* rewrite from cursor */
1775 /* to prev x pos + 1 */
1776 input_backward(cursor - sc);
1781 if (break_out) /* Enter is the command terminator, no more input. */
1788 setTermSettings(0, (void *) &initial_settings);
1789 handlers_sets &= ~SET_RESET_TERM;
1792 /* Handle command history log */
1793 /* cleanup may be saved current command line */
1794 if (len > 0) { /* no put empty line */
1797 free(history[MAX_HISTORY]);
1798 history[MAX_HISTORY] = 0;
1799 /* After max history, remove the oldest command */
1800 if (i >= MAX_HISTORY) {
1802 for(i = 0; i < MAX_HISTORY-1; i++)
1803 history[i] = history[i+1];
1805 history[i++] = xstrdup(command);
1808 #if ENABLE_FEATURE_SH_FANCY_PROMPT
1812 #else /* MAX_HISTORY == 0 */
1813 #if ENABLE_FEATURE_SH_FANCY_PROMPT
1814 if (len > 0) { /* no put empty line */
1818 #endif /* MAX_HISTORY > 0 */
1819 if (break_out > 0) {
1820 command[len++] = '\n'; /* set '\n' */
1823 #if ENABLE_FEATURE_CLEAN_UP && ENABLE_FEATURE_COMMAND_TAB_COMPLETION
1824 input_tab(0); /* strong free */
1826 #if ENABLE_FEATURE_SH_FANCY_PROMPT
1827 free(cmdedit_prompt);
1829 cmdedit_reset_term();
1833 #endif /* FEATURE_COMMAND_EDITING */
1838 const char *applet_name = "debug stuff usage";
1840 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1844 int main(int argc, char **argv)
1848 #if ENABLE_FEATURE_SH_FANCY_PROMPT
1849 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:"
1850 "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] "
1851 "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1856 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1857 setlocale(LC_ALL, "");
1861 l = cmdedit_read_input(prompt, buff);
1862 if (l <= 0 || buff[l-1] != '\n')
1865 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1867 printf("*** cmdedit_read_input() detect ^D\n");