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 #if ENABLE_FEATURE_COMMAND_USERNAME_COMPLETION || ENABLE_FEATURE_SH_FANCY_PROMPT
62 #define ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR 1
65 /* Maximum length of the linked list for the command line history */
66 #if !ENABLE_FEATURE_COMMAND_HISTORY
67 #define MAX_HISTORY 15
69 #define MAX_HISTORY (CONFIG_FEATURE_COMMAND_HISTORY + 0)
73 static char *history[MAX_HISTORY+1]; /* history + current */
74 /* saved history lines */
76 /* current pointer to history line */
77 static int cur_history;
80 //#include <termios.h>
81 #define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp)
82 #define getTermSettings(fd,argp) tcgetattr(fd, argp);
84 /* Current termio and the previous termio before starting sh */
85 static struct termios initial_settings, new_settings;
89 volatile int cmdedit_termw = 80; /* actual terminal width */
91 volatile int handlers_sets = 0; /* Set next bites: */
94 SET_ATEXIT = 1, /* when atexit() has been called
95 and get euid,uid,gid to fast compare */
96 SET_WCHG_HANDLERS = 2, /* winchg signal handler */
97 SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */
101 static int cmdedit_x; /* real x terminal position */
102 static int cmdedit_y; /* pseudoreal y terminal position */
103 static int cmdedit_prmt_len; /* lenght prompt without colores string */
105 static int cursor; /* required globals for signal handler */
106 static int len; /* --- "" - - "" -- -"- --""-- --""--- */
107 static char *command_ps; /* --- "" - - "" -- -"- --""-- --""--- */
108 static SKIP_FEATURE_SH_FANCY_PROMPT(const) char *cmdedit_prompt; /* -- */
110 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
111 static char *user_buf = "";
112 static char *home_pwd_buf = "";
116 #if ENABLE_FEATURE_SH_FANCY_PROMPT
117 static char *hostname_buf;
118 static int num_ok_lines = 1;
122 #if ENABLE_FEATURE_COMMAND_TAB_COMPLETION
124 #if !ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
131 #endif /* FEATURE_COMMAND_TAB_COMPLETION */
133 static void cmdedit_setwidth(int w, int redraw_flg);
135 static void win_changed(int nsig)
137 static sighandler_t previous_SIGWINCH_handler; /* for reset */
139 /* emulate || signal call */
140 if (nsig == -SIGWINCH || nsig == SIGWINCH) {
142 get_terminal_width_height(0, &width, NULL);
143 cmdedit_setwidth(width, nsig == SIGWINCH);
145 /* Unix not all standard in recall signal */
147 if (nsig == -SIGWINCH) /* save previous handler */
148 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
149 else if (nsig == SIGWINCH) /* signaled called handler */
150 signal(SIGWINCH, win_changed); /* set for next call */
152 /* set previous handler */
153 signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */
156 static void cmdedit_reset_term(void)
158 if ((handlers_sets & SET_RESET_TERM) != 0) {
159 /* sparc and other have broken termios support: use old termio handling. */
160 setTermSettings(STDIN_FILENO, (void *) &initial_settings);
161 handlers_sets &= ~SET_RESET_TERM;
163 if ((handlers_sets & SET_WCHG_HANDLERS) != 0) {
164 /* reset SIGWINCH handler to previous (default) */
166 handlers_sets &= ~SET_WCHG_HANDLERS;
172 /* special for recount position for scroll and remove terminal margin effect */
173 static void cmdedit_set_out_char(int next_char)
176 int c = (int)((unsigned char) command_ps[cursor]);
179 c = ' '; /* destroy end char? */
180 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
181 if (!Isprint(c)) { /* Inverse put non-printable characters */
188 printf("\033[7m%c\033[0m", c);
191 if (initial_settings.c_lflag & ECHO) putchar(c);
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);
310 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
319 if (gethostname(pbuf, 255) < 0) {
322 char *s = strchr(pbuf, '.');
331 c = my_euid == 0 ? '#' : '$';
333 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
336 l = strlen(home_pwd_buf);
337 if (home_pwd_buf[0] != 0 &&
338 strncmp(home_pwd_buf, pbuf, l) == 0 &&
339 (pbuf[l]=='/' || pbuf[l]=='\0') &&
340 strlen(pwd_buf+l)<PATH_MAX) {
343 strcpy(pbuf+1, pwd_buf+l);
349 cp = strrchr(pbuf,'/');
350 if ( (cp != NULL) && (cp != pbuf) )
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) == 0) {
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) == 0) {
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;
542 static char *add_char_to_match;
544 static void add_match(char *matched, int add_char)
546 int nm = num_matches;
549 matches = xrealloc(matches, nm1 * sizeof(char *));
550 add_char_to_match = xrealloc(add_char_to_match, nm1);
551 matches[nm] = matched;
552 add_char_to_match[nm] = (char)add_char;
556 static int is_execute(const struct stat *st)
558 if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) ||
559 (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) ||
560 (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) ||
561 (st->st_mode & S_IXOTH)) return TRUE;
565 #if ENABLE_FEATURE_COMMAND_USERNAME_COMPLETION
567 static void username_tab_completion(char *ud, char *with_shash_flg)
569 struct passwd *entry;
572 ud++; /* ~user/... to user/... */
573 userlen = strlen(ud);
575 if (with_shash_flg) { /* "~/..." or "~user/..." */
576 char *sav_ud = ud - 1;
580 if (*ud == '/') { /* "~/..." */
584 temp = strchr(ud, '/');
585 *temp = 0; /* ~user\0 */
586 entry = getpwnam(ud);
587 *temp = '/'; /* restore ~user/... */
590 home = entry->pw_dir;
593 if ((userlen + strlen(home) + 1) < BUFSIZ) {
594 char temp2[BUFSIZ]; /* argument size */
597 sprintf(temp2, "%s%s", home, ud);
598 strcpy(sav_ud, temp2);
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)) {
608 add_match(xasprintf("~%s", entry->pw_name), '/');
615 #endif /* FEATURE_COMMAND_USERNAME_COMPLETION */
624 const char *cmdedit_path_lookup;
626 #define cmdedit_path_lookup getenv("PATH")
629 static int path_parse(char ***p, int flags)
635 /* if not setenv PATH variable, to search cur dir "." */
636 if (flags != FIND_EXE_ONLY || (pth = cmdedit_path_lookup) == 0 ||
637 /* PATH=<empty> or PATH=:<empty> */
638 *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) {
646 npth++; /* count words is + 1 count ':' */
647 tmp = strchr(tmp, ':');
650 break; /* :<empty> */
655 *p = xmalloc(npth * sizeof(char *));
658 (*p)[0] = xstrdup(tmp);
659 npth = 1; /* count words is + 1 count ':' */
662 tmp = strchr(tmp, ':');
664 (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */
666 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, int add)
678 char *s = xmalloc((strlen(found) + 1) * 2);
681 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
691 static void exe_n_cwd_tab_completion(char *command, int type)
698 char **paths = path1;
702 char *pfind = strrchr(command, '/');
707 /* no dir, if flags==EXE_ONLY - get paths, else "." */
708 npaths = path_parse(&paths, type);
712 /* save for change */
713 strcpy(dirbuf, command);
715 dirbuf[(pfind - command) + 1] = 0;
716 #if ENABLE_FEATURE_COMMAND_USERNAME_COMPLETION
717 if (dirbuf[0] == '~') /* ~/... or ~user/... */
718 username_tab_completion(dirbuf, dirbuf);
720 /* "strip" dirname in command */
724 npaths = 1; /* only 1 dir */
727 for (i = 0; i < npaths; i++) {
729 dir = opendir(paths[i]);
730 if (!dir) /* Don't print an error */
733 while ((next = readdir(dir)) != NULL) {
734 char *str_found = next->d_name;
738 if (strncmp(str_found, pfind, strlen(pfind)))
740 /* not see .name without .match */
741 if (*str_found == '.' && *pfind == 0) {
742 if (*paths[i] == '/' && paths[i][1] == 0
743 && str_found[1] == 0) str_found = ""; /* only "/" */
747 found = concat_path_file(paths[i], str_found);
748 /* hmm, remover in progress? */
749 if (stat(found, &st) < 0)
751 /* find with dirs ? */
752 if (paths[i] != dirbuf)
753 strcpy(found, next->d_name); /* only name */
754 if (S_ISDIR(st.st_mode)) {
755 /* name is directory */
756 char *e = found + strlen(found) - 1;
762 /* not put found file if search only dirs for cd */
763 if (type == FIND_DIR_ONLY)
765 if (type == FIND_FILE_ONLY ||
766 (type == FIND_EXE_ONLY && is_execute(&st)))
769 /* Add it to the list */
770 add_match(found, add_chr);
777 if (paths != path1) {
778 free(paths[0]); /* allocated memory only in first member */
784 #define QUOT (UCHAR_MAX+1)
786 #define collapse_pos(is, in) { \
787 memmove(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
788 memmove(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
790 static int find_match(char *matchBuf, int *len_with_quotes)
795 int int_buf[BUFSIZ + 1];
796 int pos_buf[BUFSIZ + 1];
798 /* set to integer dimension characters and own positions */
800 int_buf[i] = (int) ((unsigned char) matchBuf[i]);
801 if (int_buf[i] == 0) {
802 pos_buf[i] = -1; /* indicator end line */
808 /* mask \+symbol and convert '\t' to ' ' */
809 for (i = j = 0; matchBuf[i]; i++, j++)
810 if (matchBuf[i] == '\\') {
811 collapse_pos(j, j + 1);
814 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
815 if (matchBuf[i] == '\t') /* algorithm equivalent */
816 int_buf[j] = ' ' | QUOT;
819 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
820 else if (matchBuf[i] == '\t')
824 /* mask "symbols" or 'symbols' */
826 for (i = 0; int_buf[i]; i++) {
828 if (c == '\'' || c == '"') {
837 } else if (c2 != 0 && c != '$')
841 /* skip commands with arguments if line have commands delimiters */
842 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
843 for (i = 0; int_buf[i]; i++) {
846 j = i ? int_buf[i - 1] : -1;
848 if (c == ';' || c == '&' || c == '|') {
849 command_mode = 1 + (c == c2);
851 if (j == '>' || j == '<')
853 } else if (c == '|' && j == '>')
857 collapse_pos(0, i + command_mode);
858 i = -1; /* hack incremet */
861 /* collapse `command...` */
862 for (i = 0; int_buf[i]; i++)
863 if (int_buf[i] == '`') {
864 for (j = i + 1; int_buf[j]; j++)
865 if (int_buf[j] == '`') {
866 collapse_pos(i, j + 1);
871 /* not found close ` - command mode, collapse all previous */
872 collapse_pos(0, i + 1);
875 i--; /* hack incremet */
878 /* collapse (command...(command...)...) or {command...{command...}...} */
879 c = 0; /* "recursive" level */
881 for (i = 0; int_buf[i]; i++)
882 if (int_buf[i] == '(' || int_buf[i] == '{') {
883 if (int_buf[i] == '(')
887 collapse_pos(0, i + 1);
888 i = -1; /* hack incremet */
890 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
891 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
892 if (int_buf[i] == ')')
896 collapse_pos(0, i + 1);
897 i = -1; /* hack incremet */
900 /* skip first not quote space */
901 for (i = 0; int_buf[i]; i++)
902 if (int_buf[i] != ' ')
907 /* set find mode for completion */
908 command_mode = FIND_EXE_ONLY;
909 for (i = 0; int_buf[i]; i++)
910 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
911 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
912 && matchBuf[pos_buf[0]]=='c'
913 && matchBuf[pos_buf[1]]=='d' )
914 command_mode = FIND_DIR_ONLY;
916 command_mode = FIND_FILE_ONLY;
921 for (i = 0; int_buf[i]; i++);
923 for (--i; i >= 0; i--) {
925 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
926 collapse_pos(0, i + 1);
930 /* skip first not quoted '\'' or '"' */
931 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) == '/'
935 || (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;
965 /* find the longest file name- use that as the column width */
966 for (row = 0; row < nrows; row++) {
967 l = strlen(matches[row]);
968 if (add_char_to_match[row])
970 if (column_width < l)
973 column_width += 2; /* min space for columns */
974 ncols = cmdedit_termw / column_width;
979 nrows++; /* round up fractionals */
984 for (row = 0; row < nrows; row++) {
989 for(nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
990 str_add_chr[0] = add_char_to_match[n];
991 acol = str_add_chr[0] ? column_width - 1 : column_width;
992 printf("%s%s%-*s", matches[n], str_add_chr,
993 acol - strlen(matches[n]), "");
995 str_add_chr[0] = add_char_to_match[n];
996 printf("%s%s\n", matches[n], str_add_chr);
1000 static int match_compare(const void *a, const void *b)
1002 return strcmp(*(char**)a, *(char**)b);
1005 static void input_tab(int *lastWasTab)
1007 /* Do TAB completion */
1008 if (lastWasTab == 0) { /* free all memory */
1010 while (num_matches > 0)
1011 free(matches[--num_matches]);
1013 matches = (char **) NULL;
1014 free(add_char_to_match);
1015 add_char_to_match = NULL;
1019 if (! *lastWasTab) {
1022 char matchBuf[BUFSIZ];
1026 *lastWasTab = TRUE; /* flop trigger */
1028 /* Make a local copy of the string -- up
1029 * to the position of the cursor */
1030 tmp = strncpy(matchBuf, command_ps, cursor);
1033 find_type = find_match(matchBuf, &recalc_pos);
1035 /* Free up any memory already allocated */
1038 #if ENABLE_FEATURE_COMMAND_USERNAME_COMPLETION
1039 /* If the word starts with `~' and there is no slash in the word,
1040 * then try completing this word as a username. */
1042 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
1043 username_tab_completion(matchBuf, NULL);
1046 /* Try to match any executable in our path and everything
1047 * in the current working directory that matches. */
1048 exe_n_cwd_tab_completion(matchBuf, find_type);
1049 /* Sort, then remove any duplicates found */
1052 qsort(matches, num_matches, sizeof(char*), match_compare);
1053 for (i = 0; i < num_matches - 1; ++i) {
1054 if (matches[i] && matches[i+1]) {
1055 if (strcmp(matches[i], matches[i+1]) == 0) {
1059 add_char_to_match[n] = add_char_to_match[i];
1060 matches[n++] = matches[i];
1064 add_char_to_match[n] = add_char_to_match[num_matches-1];
1065 matches[n++] = matches[num_matches-1];
1068 /* Did we find exactly one match? */
1069 if (!matches || num_matches > 1) {
1072 return; /* not found */
1073 /* find minimal match */
1074 tmp1 = xstrdup(matches[0]);
1075 for (tmp = tmp1; *tmp; tmp++)
1076 for (len_found = 1; len_found < num_matches; len_found++)
1077 if (matches[len_found][(tmp - tmp1)] != *tmp) {
1081 if (*tmp1 == 0) { /* have unique */
1085 tmp = add_quote_for_spec_chars(tmp1, 0);
1087 } else { /* one match */
1088 tmp = add_quote_for_spec_chars(matches[0], add_char_to_match[0]);
1089 /* for next completion current found */
1090 *lastWasTab = FALSE;
1092 len_found = strlen(tmp);
1093 /* have space to placed match? */
1094 if ((len_found - strlen(matchBuf) + len) < BUFSIZ) {
1096 /* before word for match */
1097 command_ps[cursor - recalc_pos] = 0;
1098 /* save tail line */
1099 strcpy(matchBuf, command_ps + cursor);
1101 strcat(command_ps, tmp);
1103 strcat(command_ps, matchBuf);
1104 /* back to begin word for match */
1105 input_backward(recalc_pos);
1107 recalc_pos = cursor + len_found;
1109 len = strlen(command_ps);
1110 /* write out the matched command */
1111 redraw(cmdedit_y, len - recalc_pos);
1115 /* Ok -- the last char was a TAB. Since they
1116 * just hit TAB again, print a list of all the
1117 * available choices... */
1118 if (matches && num_matches > 0) {
1119 int sav_cursor = cursor; /* change goto_new_line() */
1121 /* Go to the next line */
1124 redraw(0, len - sav_cursor);
1128 #endif /* FEATURE_COMMAND_TAB_COMPLETION */
1131 static void get_previous_history(void)
1133 if (command_ps[0] != 0 || history[cur_history] == 0) {
1134 free(history[cur_history]);
1135 history[cur_history] = xstrdup(command_ps);
1140 static int get_next_history(void)
1142 int ch = cur_history;
1144 if (ch < n_history) {
1145 get_previous_history(); /* save the current history line */
1146 cur_history = ch + 1;
1154 #if ENABLE_FEATURE_COMMAND_SAVEHISTORY
1155 void load_history ( const char *fromfile )
1162 for(hi = n_history; hi > 0; ) {
1164 free ( history [hi] );
1167 if (( fp = fopen ( fromfile, "r" ))) {
1169 for ( hi = 0; hi < MAX_HISTORY; ) {
1170 char * hl = xmalloc_getline(fp);
1178 if (l == 0 || hl[0] == ' ') {
1182 history [hi++] = hl;
1186 cur_history = n_history = hi;
1189 void save_history ( const char *tofile )
1191 FILE *fp = fopen ( tofile, "w" );
1196 for ( i = 0; i < n_history; i++ ) {
1197 fprintf(fp, "%s\n", history [i]);
1213 * This function is used to grab a character buffer
1214 * from the input file descriptor and allows you to
1215 * a string with full command editing (sort of like
1218 * The following standard commands are not implemented:
1219 * ESC-b -- Move back one word
1220 * ESC-f -- Move forward one word
1221 * ESC-d -- Delete back one word
1222 * ESC-h -- Delete forward one word
1223 * CTL-t -- Transpose two characters
1225 * Minimalist vi-style command line editing available if configured.
1226 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
1230 #if ENABLE_FEATURE_COMMAND_EDITING_VI
1233 void setvimode ( int viflag )
1239 vi_Word_motion(char *command, int eat)
1241 while (cursor < len && !isspace(command[cursor]))
1243 if (eat) while (cursor < len && isspace(command[cursor]))
1248 vi_word_motion(char *command, int eat)
1250 if (isalnum(command[cursor]) || command[cursor] == '_') {
1251 while (cursor < len &&
1252 (isalnum(command[cursor+1]) ||
1253 command[cursor+1] == '_'))
1255 } else if (ispunct(command[cursor])) {
1256 while (cursor < len &&
1257 (ispunct(command[cursor+1])))
1264 if (eat && cursor < len && isspace(command[cursor]))
1265 while (cursor < len && isspace(command[cursor]))
1270 vi_End_motion(char *command)
1273 while (cursor < len && isspace(command[cursor]))
1275 while (cursor < len-1 && !isspace(command[cursor+1]))
1280 vi_end_motion(char *command)
1282 if (cursor >= len-1)
1285 while (cursor < len-1 && isspace(command[cursor]))
1287 if (cursor >= len-1)
1289 if (isalnum(command[cursor]) || command[cursor] == '_') {
1290 while (cursor < len-1 &&
1291 (isalnum(command[cursor+1]) ||
1292 command[cursor+1] == '_'))
1294 } else if (ispunct(command[cursor])) {
1295 while (cursor < len-1 &&
1296 (ispunct(command[cursor+1])))
1302 vi_Back_motion(char *command)
1304 while (cursor > 0 && isspace(command[cursor-1]))
1306 while (cursor > 0 && !isspace(command[cursor-1]))
1311 vi_back_motion(char *command)
1316 while (cursor > 0 && isspace(command[cursor]))
1320 if (isalnum(command[cursor]) || command[cursor] == '_') {
1321 while (cursor > 0 &&
1322 (isalnum(command[cursor-1]) ||
1323 command[cursor-1] == '_'))
1325 } else if (ispunct(command[cursor])) {
1326 while (cursor > 0 &&
1327 (ispunct(command[cursor-1])))
1334 * the emacs and vi modes share much of the code in the big
1335 * command loop. commands entered when in vi's command mode (aka
1336 * "escape mode") get an extra bit added to distinguish them --
1337 * this keeps them from being self-inserted. this clutters the
1338 * big switch a bit, but keeps all the code in one place.
1343 /* leave out the "vi-mode"-only case labels if vi editing isn't
1345 #define vi_case(caselabel) USE_FEATURE_COMMAND_EDITING(caselabel)
1347 /* convert uppercase ascii to equivalent control char, for readability */
1348 #define CNTRL(uc_char) ((uc_char) - 0x40)
1351 int cmdedit_read_input(char *prompt, char command[BUFSIZ])
1355 int lastWasTab = FALSE;
1358 #if ENABLE_FEATURE_COMMAND_EDITING_VI
1362 /* prepare before init handlers */
1363 cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */
1365 command_ps = command;
1367 getTermSettings(0, (void *) &initial_settings);
1368 memcpy(&new_settings, &initial_settings, sizeof(struct termios));
1369 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1370 /* Turn off echoing and CTRL-C, so we can trap it */
1371 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1372 /* Hmm, in linux c_cc[] not parsed if set ~ICANON */
1373 new_settings.c_cc[VMIN] = 1;
1374 new_settings.c_cc[VTIME] = 0;
1375 /* Turn off CTRL-C, so we can trap it */
1376 # ifndef _POSIX_VDISABLE
1377 # define _POSIX_VDISABLE '\0'
1379 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1382 setTermSettings(0, (void *) &new_settings);
1383 handlers_sets |= SET_RESET_TERM;
1385 /* Now initialize things */
1387 /* Print out the command prompt */
1388 parse_prompt(prompt);
1392 fflush(stdout); /* buffered out to fast */
1394 if (safe_read(0, &c, 1) < 1)
1395 /* if we can't read input then exit */
1396 goto prepare_to_die;
1400 #if ENABLE_FEATURE_COMMAND_EDITING_VI
1409 vi_case( case '\n'|vbit: )
1410 vi_case( case '\r'|vbit: )
1416 vi_case( case '0'|vbit: )
1417 /* Control-a -- Beginning of line */
1418 input_backward(cursor);
1421 vi_case( case 'h'|vbit: )
1422 vi_case( case '\b'|vbit: )
1423 vi_case( case DEL|vbit: )
1424 /* Control-b -- Move back one character */
1428 vi_case( case CNTRL('C')|vbit: )
1429 /* Control-c -- stop gathering input */
1438 break_out = -1; /* to control traps */
1442 /* Control-d -- Delete one character, or exit
1443 * if the len=0 and no chars to delete */
1450 /* cmdedit_reset_term() called in atexit */
1453 /* to control stopped jobs */
1454 len = break_out = -1;
1462 vi_case( case '$'|vbit: )
1463 /* Control-e -- End of line */
1467 vi_case( case 'l'|vbit: )
1468 vi_case( case ' '|vbit: )
1469 /* Control-f -- Move forward one character */
1474 /* Control-h and DEL */
1478 #if ENABLE_FEATURE_COMMAND_TAB_COMPLETION
1479 input_tab(&lastWasTab);
1483 /* Control-k -- clear to end of line */
1484 *(command + cursor) = 0;
1489 vi_case( case CNTRL('L')|vbit: )
1490 /* Control-l -- clear screen */
1492 redraw(0, len-cursor);
1496 vi_case( case CNTRL('N')|vbit: )
1497 vi_case( case 'j'|vbit: )
1498 /* Control-n -- Get next command in history */
1499 if (get_next_history())
1503 vi_case( case CNTRL('P')|vbit: )
1504 vi_case( case 'k'|vbit: )
1505 /* Control-p -- Get previous command from history */
1506 if (cur_history > 0) {
1507 get_previous_history();
1515 vi_case( case CNTRL('U')|vbit: )
1516 /* Control-U -- Clear line before cursor */
1518 strcpy(command, command + cursor);
1519 redraw(cmdedit_y, len -= cursor);
1523 vi_case( case CNTRL('W')|vbit: )
1524 /* Control-W -- Remove the last word */
1525 while (cursor > 0 && isspace(command[cursor-1]))
1527 while (cursor > 0 &&!isspace(command[cursor-1]))
1530 #if ENABLE_FEATURE_COMMAND_EDITING_VI
1535 input_backward(cursor);
1556 vi_Word_motion(command, 1);
1559 vi_word_motion(command, 1);
1562 vi_End_motion(command);
1565 vi_end_motion(command);
1568 vi_Back_motion(command);
1571 vi_back_motion(command);
1587 if (safe_read(0, &c, 1) < 1)
1588 goto prepare_to_die;
1589 if (c == (prevc & 0xff)) {
1591 input_backward(cursor);
1601 case 'w': /* "dw", "cw" */
1602 vi_word_motion(command, vi_cmdmode);
1604 case 'W': /* 'dW', 'cW' */
1605 vi_Word_motion(command, vi_cmdmode);
1607 case 'e': /* 'de', 'ce' */
1608 vi_end_motion(command);
1611 case 'E': /* 'dE', 'cE' */
1612 vi_End_motion(command);
1617 input_backward(cursor - sc);
1618 while (nc-- > cursor)
1621 case 'b': /* "db", "cb" */
1622 case 'B': /* implemented as B */
1624 vi_back_motion(command);
1626 vi_Back_motion(command);
1627 while (sc-- > cursor)
1630 case ' ': /* "d ", "c " */
1633 case '$': /* "d$", "c$" */
1635 while (cursor < len)
1648 if (safe_read(0, &c, 1) < 1)
1649 goto prepare_to_die;
1653 *(command + cursor) = c;
1658 #endif /* FEATURE_COMMAND_EDITING_VI */
1662 #if ENABLE_FEATURE_COMMAND_EDITING_VI
1664 /* ESC: insert mode --> command mode */
1670 /* escape sequence follows */
1671 if (safe_read(0, &c, 1) < 1)
1672 goto prepare_to_die;
1673 /* different vt100 emulations */
1674 if (c == '[' || c == 'O') {
1675 vi_case( case '['|vbit: )
1676 vi_case( case 'O'|vbit: )
1677 if (safe_read(0, &c, 1) < 1)
1678 goto prepare_to_die;
1680 if (c >= '1' && c <= '9') {
1681 unsigned char dummy;
1683 if (safe_read(0, &dummy, 1) < 1)
1684 goto prepare_to_die;
1689 #if ENABLE_FEATURE_COMMAND_TAB_COMPLETION
1690 case '\t': /* Alt-Tab */
1692 input_tab(&lastWasTab);
1697 /* Up Arrow -- Get previous command from history */
1698 if (cur_history > 0) {
1699 get_previous_history();
1706 /* Down Arrow -- Get next command in history */
1707 if (!get_next_history())
1709 /* Rewrite the line with the selected history item */
1711 /* change command */
1712 len = strlen(strcpy(command, history[cur_history]));
1713 /* redraw and go to eol (bol, in vi */
1714 #if ENABLE_FEATURE_COMMAND_EDITING_VI
1715 redraw(cmdedit_y, vi_mode ? 9999:0);
1717 redraw(cmdedit_y, 0);
1722 /* Right Arrow -- Move forward one character */
1726 /* Left Arrow -- Move back one character */
1736 input_backward(cursor);
1749 default: /* If it's regular input, do the normal thing */
1750 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1751 /* Control-V -- Add non-printable symbol */
1752 if (c == CNTRL('V')) {
1753 if (safe_read(0, &c, 1) < 1)
1754 goto prepare_to_die;
1762 #if ENABLE_FEATURE_COMMAND_EDITING_VI
1763 if (vi_cmdmode) /* don't self-insert */
1766 if (!Isprint(c)) /* Skip non-printable characters */
1770 if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */
1775 if (cursor == (len - 1)) { /* Append if at the end of the line */
1776 *(command + cursor) = c;
1777 *(command + cursor + 1) = 0;
1778 cmdedit_set_out_char(0);
1779 } else { /* Insert otherwise */
1782 memmove(command + sc + 1, command + sc, len - sc);
1783 *(command + sc) = c;
1785 /* rewrite from cursor */
1787 /* to prev x pos + 1 */
1788 input_backward(cursor - sc);
1793 if (break_out) /* Enter is the command terminator, no more input. */
1800 setTermSettings(0, (void *) &initial_settings);
1801 handlers_sets &= ~SET_RESET_TERM;
1804 /* Handle command history log */
1805 /* cleanup may be saved current command line */
1806 if (len> 0) { /* no put empty line */
1809 free(history[MAX_HISTORY]);
1810 history[MAX_HISTORY] = 0;
1811 /* After max history, remove the oldest command */
1812 if (i >= MAX_HISTORY) {
1814 for(i = 0; i < (MAX_HISTORY-1); i++)
1815 history[i] = history[i+1];
1817 history[i++] = xstrdup(command);
1820 #if ENABLE_FEATURE_SH_FANCY_PROMPT
1824 #else /* MAX_HISTORY == 0 */
1825 #if ENABLE_FEATURE_SH_FANCY_PROMPT
1826 if (len > 0) { /* no put empty line */
1830 #endif /* MAX_HISTORY > 0 */
1831 if (break_out > 0) {
1832 command[len++] = '\n'; /* set '\n' */
1835 #if ENABLE_FEATURE_CLEAN_UP && ENABLE_FEATURE_COMMAND_TAB_COMPLETION
1836 input_tab(0); /* strong free */
1838 #if ENABLE_FEATURE_SH_FANCY_PROMPT
1839 free(cmdedit_prompt);
1841 cmdedit_reset_term();
1845 #endif /* FEATURE_COMMAND_EDITING */
1850 const char *applet_name = "debug stuff usage";
1852 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1856 int main(int argc, char **argv)
1860 #if ENABLE_FEATURE_SH_FANCY_PROMPT
1861 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\
1862 \\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \
1863 \\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1868 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1869 setlocale(LC_ALL, "");
1873 l = cmdedit_read_input(prompt, buff);
1874 if (l > 0 && buff[l-1] == '\n') {
1876 printf("*** cmdedit_read_input() returned line =%s=\n", buff);
1881 printf("*** cmdedit_read_input() detect ^D\n");