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
31 //#include <sys/ioctl.h>
35 /* FIXME: obsolete CONFIG item? */
36 #define ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT 0
41 #define ENABLE_FEATURE_EDITING 0
42 #define ENABLE_FEATURE_TAB_COMPLETION 0
43 #define ENABLE_FEATURE_USERNAME_COMPLETION 0
44 #define ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT 0
45 #define ENABLE_FEATURE_CLEAN_UP 0
50 /* Entire file (except TESTing part) sits inside this #if */
51 #if ENABLE_FEATURE_EDITING
53 #if ENABLE_LOCALE_SUPPORT
54 #define Isprint(c) isprint(c)
56 #define Isprint(c) ((c) >= ' ' && (c) != ((unsigned char)'\233'))
59 #define ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR \
60 (ENABLE_FEATURE_USERNAME_COMPLETION || ENABLE_FEATURE_EDITING_FANCY_PROMPT)
63 static line_input_t *state;
65 static struct termios initial_settings, new_settings;
67 static volatile unsigned cmdedit_termw = 80; /* actual terminal width */
69 static int cmdedit_x; /* real x terminal position */
70 static int cmdedit_y; /* pseudoreal y terminal position */
71 static int cmdedit_prmt_len; /* length of prompt (without colors etc) */
73 static unsigned cursor;
74 static unsigned command_len;
75 static char *command_ps;
76 static const char *cmdedit_prompt;
78 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
79 static char *hostname_buf;
80 static int num_ok_lines = 1;
83 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
84 static char *user_buf = (char*)"";
85 static char *home_pwd_buf = (char*)"";
88 /* Put 'command_ps[cursor]', cursor++.
89 * Advance cursor on screen. If we reached right margin, scroll text up
90 * and remove terminal margin effect by printing 'next_char' */
91 static void cmdedit_set_out_char(int next_char)
93 int c = (unsigned char)command_ps[cursor];
96 /* erase character after end of input string */
99 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
100 /* Display non-printable characters in reverse */
108 printf("\033[7m%c\033[0m", c);
112 if (initial_settings.c_lflag & ECHO)
115 if (++cmdedit_x >= cmdedit_termw) {
116 /* terminal is scrolled down */
119 /* destroy "(auto)margin" */
123 // Huh? What if command_ps[cursor] == '\0' (we are at the end already?)
127 /* Move to end of line (by printing all chars till the end) */
128 static void input_end(void)
130 while (cursor < command_len)
131 cmdedit_set_out_char(' ');
134 /* Go to the next line */
135 static void goto_new_line(void)
143 static void out1str(const char *s)
149 static void beep(void)
154 /* Move back one character */
155 /* (optimized for slow terminals) */
156 static void input_backward(unsigned num)
166 if (cmdedit_x >= num) {
169 printf("\b\b\b\b" + (4-num));
172 printf("\033[%uD", num);
176 /* Need to go one or more lines up */
178 count_y = 1 + (num / cmdedit_termw);
179 cmdedit_y -= count_y;
180 cmdedit_x = cmdedit_termw * count_y - num;
181 /* go to 1st column; go up; go to correct column */
182 printf("\r" "\033[%dA" "\033[%dC", count_y, cmdedit_x);
185 static void put_prompt(void)
187 out1str(cmdedit_prompt);
188 cmdedit_x = cmdedit_prmt_len;
190 // Huh? what if cmdedit_prmt_len >= width?
191 cmdedit_y = 0; /* new quasireal y */
194 /* draw prompt, editor line, and clear tail */
195 static void redraw(int y, int back_cursor)
197 if (y > 0) /* up to start y */
198 printf("\033[%dA", y);
201 input_end(); /* rewrite */
202 printf("\033[J"); /* erase after cursor */
203 input_backward(back_cursor);
206 #if ENABLE_FEATURE_EDITING_VI
207 #define DELBUFSIZ 128
208 static char *delbuf; /* a (malloced) place to store deleted characters */
210 static char newdelflag; /* whether delbuf should be reused yet */
213 /* Delete the char in front of the cursor, optionally saving it
214 * for later putback */
215 static void input_delete(int save)
219 if (j == command_len)
222 #if ENABLE_FEATURE_EDITING_VI
226 delbuf = malloc(DELBUFSIZ);
227 /* safe if malloc fails */
231 if (delbuf && (delp - delbuf < DELBUFSIZ))
232 *delp++ = command_ps[j];
236 strcpy(command_ps + j, command_ps + j + 1);
238 input_end(); /* rewrite new line */
239 cmdedit_set_out_char(' '); /* erase char */
240 input_backward(cursor - j); /* back to old pos cursor */
243 #if ENABLE_FEATURE_EDITING_VI
244 static void put(void)
247 int j = delp - delbuf;
252 /* open hole and then fill it */
253 memmove(command_ps + cursor + j, command_ps + cursor, command_len - cursor + 1);
254 strncpy(command_ps + cursor, delbuf, j);
256 input_end(); /* rewrite new line */
257 input_backward(cursor - ocursor - j + 1); /* at end of new text */
261 /* Delete the char in back of the cursor */
262 static void input_backspace(void)
270 /* Move forward one character */
271 static void input_forward(void)
273 if (cursor < command_len)
274 cmdedit_set_out_char(command_ps[cursor + 1]);
278 #if ENABLE_FEATURE_TAB_COMPLETION
280 static char **matches;
281 static unsigned num_matches;
283 static void free_tab_completion_data(void)
287 free(matches[--num_matches]);
293 static void add_match(char *matched)
295 int nm = num_matches;
298 matches = xrealloc(matches, nm1 * sizeof(char *));
299 matches[nm] = matched;
303 #if ENABLE_FEATURE_USERNAME_COMPLETION
304 static void username_tab_completion(char *ud, char *with_shash_flg)
306 struct passwd *entry;
309 ud++; /* ~user/... to user/... */
310 userlen = strlen(ud);
312 if (with_shash_flg) { /* "~/..." or "~user/..." */
313 char *sav_ud = ud - 1;
317 if (*ud == '/') { /* "~/..." */
321 temp = strchr(ud, '/');
322 *temp = 0; /* ~user\0 */
323 entry = getpwnam(ud);
324 *temp = '/'; /* restore ~user/... */
327 home = entry->pw_dir;
330 if ((userlen + strlen(home) + 1) < BUFSIZ) {
331 char temp2[BUFSIZ]; /* argument size */
334 sprintf(temp2, "%s%s", home, ud);
335 strcpy(sav_ud, temp2);
340 /* Using _r function to avoid pulling in static buffers */
343 struct passwd *result;
346 while (!getpwent_r(&pwd, line_buff, sizeof(line_buff), &result)) {
347 /* Null usernames should result in all users as possible completions. */
348 if (/*!userlen || */ strncmp(ud, pwd.pw_name, userlen) == 0) {
349 add_match(xasprintf("~%s/", pwd.pw_name));
355 #endif /* FEATURE_COMMAND_USERNAME_COMPLETION */
363 static int path_parse(char ***p, int flags)
370 /* if not setenv PATH variable, to search cur dir "." */
371 if (flags != FIND_EXE_ONLY)
374 if (state->flags & WITH_PATH_LOOKUP)
375 pth = state->path_lookup;
377 pth = getenv("PATH");
378 /* PATH=<empty> or PATH=:<empty> */
379 if (!pth || !pth[0] || LONE_CHAR(pth, ':'))
383 npth = 1; /* path component count */
385 tmp = strchr(tmp, ':');
389 break; /* :<empty> */
393 res = xmalloc(npth * sizeof(char*));
394 res[0] = tmp = xstrdup(pth);
397 tmp = strchr(tmp, ':');
400 *tmp++ = '\0'; /* ':' -> '\0' */
402 break; /* :<empty> */
409 static void exe_n_cwd_tab_completion(char *command, int type)
416 char **paths = path1;
420 char *pfind = strrchr(command, '/');
423 path1[0] = (char*)".";
426 /* no dir, if flags==EXE_ONLY - get paths, else "." */
427 npaths = path_parse(&paths, type);
430 /* dirbuf = ".../.../.../" */
431 safe_strncpy(dirbuf, command, (pfind - command) + 2);
432 #if ENABLE_FEATURE_USERNAME_COMPLETION
433 if (dirbuf[0] == '~') /* ~/... or ~user/... */
434 username_tab_completion(dirbuf, dirbuf);
437 /* point to 'l' in "..../last_component" */
441 for (i = 0; i < npaths; i++) {
442 dir = opendir(paths[i]);
443 if (!dir) /* Don't print an error */
446 while ((next = readdir(dir)) != NULL) {
448 const char *str_found = next->d_name;
451 if (strncmp(str_found, pfind, strlen(pfind)))
453 /* not see .name without .match */
454 if (*str_found == '.' && *pfind == 0) {
455 if (NOT_LONE_CHAR(paths[i], '/') || str_found[1])
457 str_found = ""; /* only "/" */
459 found = concat_path_file(paths[i], str_found);
460 /* hmm, remover in progress? */
461 if (stat(found, &st) < 0)
463 /* find with dirs? */
464 if (paths[i] != dirbuf)
465 strcpy(found, next->d_name); /* only name */
467 len1 = strlen(found);
468 found = xrealloc(found, len1 + 2);
470 found[len1+1] = '\0';
472 if (S_ISDIR(st.st_mode)) {
473 /* name is directory */
474 if (found[len1-1] != '/') {
478 /* not put found file if search only dirs for cd */
479 if (type == FIND_DIR_ONLY)
482 /* Add it to the list */
490 if (paths != path1) {
491 free(paths[0]); /* allocated memory only in first member */
496 #define QUOT (UCHAR_MAX+1)
498 #define collapse_pos(is, in) { \
499 memmove(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
500 memmove(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
502 static int find_match(char *matchBuf, int *len_with_quotes)
507 int int_buf[BUFSIZ + 1];
508 int pos_buf[BUFSIZ + 1];
510 /* set to integer dimension characters and own positions */
512 int_buf[i] = (unsigned char)matchBuf[i];
513 if (int_buf[i] == 0) {
514 pos_buf[i] = -1; /* indicator end line */
520 /* mask \+symbol and convert '\t' to ' ' */
521 for (i = j = 0; matchBuf[i]; i++, j++)
522 if (matchBuf[i] == '\\') {
523 collapse_pos(j, j + 1);
526 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
527 if (matchBuf[i] == '\t') /* algorithm equivalent */
528 int_buf[j] = ' ' | QUOT;
531 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
532 else if (matchBuf[i] == '\t')
536 /* mask "symbols" or 'symbols' */
538 for (i = 0; int_buf[i]; i++) {
540 if (c == '\'' || c == '"') {
549 } else if (c2 != 0 && c != '$')
553 /* skip commands with arguments if line has commands delimiters */
554 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
555 for (i = 0; int_buf[i]; i++) {
558 j = i ? int_buf[i - 1] : -1;
560 if (c == ';' || c == '&' || c == '|') {
561 command_mode = 1 + (c == c2);
563 if (j == '>' || j == '<')
565 } else if (c == '|' && j == '>')
569 collapse_pos(0, i + command_mode);
570 i = -1; /* hack incremet */
573 /* collapse `command...` */
574 for (i = 0; int_buf[i]; i++)
575 if (int_buf[i] == '`') {
576 for (j = i + 1; int_buf[j]; j++)
577 if (int_buf[j] == '`') {
578 collapse_pos(i, j + 1);
583 /* not found close ` - command mode, collapse all previous */
584 collapse_pos(0, i + 1);
587 i--; /* hack incremet */
590 /* collapse (command...(command...)...) or {command...{command...}...} */
591 c = 0; /* "recursive" level */
593 for (i = 0; int_buf[i]; i++)
594 if (int_buf[i] == '(' || int_buf[i] == '{') {
595 if (int_buf[i] == '(')
599 collapse_pos(0, i + 1);
600 i = -1; /* hack incremet */
602 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
603 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
604 if (int_buf[i] == ')')
608 collapse_pos(0, i + 1);
609 i = -1; /* hack incremet */
612 /* skip first not quote space */
613 for (i = 0; int_buf[i]; i++)
614 if (int_buf[i] != ' ')
619 /* set find mode for completion */
620 command_mode = FIND_EXE_ONLY;
621 for (i = 0; int_buf[i]; i++)
622 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
623 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
624 && matchBuf[pos_buf[0]]=='c'
625 && matchBuf[pos_buf[1]]=='d'
627 command_mode = FIND_DIR_ONLY;
629 command_mode = FIND_FILE_ONLY;
633 for (i = 0; int_buf[i]; i++)
636 for (--i; i >= 0; i--) {
638 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
639 collapse_pos(0, i + 1);
643 /* skip first not quoted '\'' or '"' */
644 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++)
646 /* collapse quote or unquote // or /~ */
647 while ((int_buf[i] & ~QUOT) == '/'
648 && ((int_buf[i+1] & ~QUOT) == '/' || (int_buf[i+1] & ~QUOT) == '~')
653 /* set only match and destroy quotes */
655 for (c = 0; pos_buf[i] >= 0; i++) {
656 matchBuf[c++] = matchBuf[pos_buf[i]];
660 /* old lenght matchBuf with quotes symbols */
661 *len_with_quotes = j ? j - pos_buf[0] : 0;
667 * display by column (original idea from ls applet,
668 * very optimized by me :)
670 static void showfiles(void)
673 int column_width = 0;
674 int nfiles = num_matches;
678 /* find the longest file name- use that as the column width */
679 for (row = 0; row < nrows; row++) {
680 l = strlen(matches[row]);
681 if (column_width < l)
684 column_width += 2; /* min space for columns */
685 ncols = cmdedit_termw / column_width;
690 nrows++; /* round up fractionals */
694 for (row = 0; row < nrows; row++) {
698 for (nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
699 printf("%s%-*s", matches[n],
700 (int)(column_width - strlen(matches[n])), "");
702 printf("%s\n", matches[n]);
706 static char *add_quote_for_spec_chars(char *found)
709 char *s = xmalloc((strlen(found) + 1) * 2);
712 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
720 static int match_compare(const void *a, const void *b)
722 return strcmp(*(char**)a, *(char**)b);
725 /* Do TAB completion */
726 static void input_tab(int *lastWasTab)
728 if (!(state->flags & TAB_COMPLETION))
734 char matchBuf[BUFSIZ];
738 *lastWasTab = TRUE; /* flop trigger */
740 /* Make a local copy of the string -- up
741 * to the position of the cursor */
742 tmp = strncpy(matchBuf, command_ps, cursor);
745 find_type = find_match(matchBuf, &recalc_pos);
747 /* Free up any memory already allocated */
748 free_tab_completion_data();
750 #if ENABLE_FEATURE_USERNAME_COMPLETION
751 /* If the word starts with `~' and there is no slash in the word,
752 * then try completing this word as a username. */
753 if (state->flags & USERNAME_COMPLETION)
754 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
755 username_tab_completion(matchBuf, NULL);
757 /* Try to match any executable in our path and everything
758 * in the current working directory */
760 exe_n_cwd_tab_completion(matchBuf, find_type);
761 /* Sort, then remove any duplicates found */
764 qsort(matches, num_matches, sizeof(char*), match_compare);
765 for (i = 0; i < num_matches - 1; ++i) {
766 if (matches[i] && matches[i+1]) { /* paranoia */
767 if (strcmp(matches[i], matches[i+1]) == 0) {
769 matches[i] = NULL; /* paranoia */
771 matches[n++] = matches[i];
775 matches[n] = matches[i];
778 /* Did we find exactly one match? */
779 if (!matches || num_matches > 1) {
782 return; /* not found */
783 /* find minimal match */
784 tmp1 = xstrdup(matches[0]);
785 for (tmp = tmp1; *tmp; tmp++)
786 for (len_found = 1; len_found < num_matches; len_found++)
787 if (matches[len_found][(tmp - tmp1)] != *tmp) {
791 if (*tmp1 == '\0') { /* have unique */
795 tmp = add_quote_for_spec_chars(tmp1);
797 } else { /* one match */
798 tmp = add_quote_for_spec_chars(matches[0]);
799 /* for next completion current found */
802 len_found = strlen(tmp);
803 if (tmp[len_found-1] != '/') {
804 tmp[len_found] = ' ';
805 tmp[len_found+1] = '\0';
808 len_found = strlen(tmp);
809 /* have space to placed match? */
810 if ((len_found - strlen(matchBuf) + command_len) < BUFSIZ) {
811 /* before word for match */
812 command_ps[cursor - recalc_pos] = 0;
814 strcpy(matchBuf, command_ps + cursor);
816 strcat(command_ps, tmp);
818 strcat(command_ps, matchBuf);
819 /* back to begin word for match */
820 input_backward(recalc_pos);
822 recalc_pos = cursor + len_found;
824 command_len = strlen(command_ps);
825 /* write out the matched command */
826 redraw(cmdedit_y, command_len - recalc_pos);
830 /* Ok -- the last char was a TAB. Since they
831 * just hit TAB again, print a list of all the
832 * available choices... */
833 if (matches && num_matches > 0) {
834 int sav_cursor = cursor; /* change goto_new_line() */
836 /* Go to the next line */
839 redraw(0, command_len - sav_cursor);
845 #define input_tab(a) ((void)0)
846 #endif /* FEATURE_COMMAND_TAB_COMPLETION */
851 /* state->flags is already checked to be nonzero */
852 static void get_previous_history(void)
854 if (command_ps[0] != '\0' || state->history[state->cur_history] == NULL) {
855 free(state->history[state->cur_history]);
856 state->history[state->cur_history] = xstrdup(command_ps);
858 state->cur_history--;
861 static int get_next_history(void)
863 if (state->flags & DO_HISTORY) {
864 int ch = state->cur_history;
865 if (ch < state->cnt_history) {
866 get_previous_history(); /* save the current history line */
867 state->cur_history = ch + 1;
868 return state->cur_history;
875 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
876 /* state->flags is already checked to be nonzero */
877 static void load_history(const char *fromfile)
883 for (hi = state->cnt_history; hi > 0;) {
885 free(state->history[hi]);
888 fp = fopen(fromfile, "r");
890 for (hi = 0; hi < MAX_HISTORY;) {
891 char * hl = xmalloc_getline(fp);
899 if (l == 0 || hl[0] == ' ') {
903 state->history[hi++] = hl;
907 state->cur_history = state->cnt_history = hi;
910 /* state->flags is already checked to be nonzero */
911 static void save_history(const char *tofile)
915 fp = fopen(tofile, "w");
919 for (i = 0; i < state->cnt_history; i++) {
920 fprintf(fp, "%s\n", state->history[i]);
926 #define load_history(a) ((void)0)
927 #define save_history(a) ((void)0)
928 #endif /* FEATURE_COMMAND_SAVEHISTORY */
930 static void remember_in_history(const char *str)
934 if (!(state->flags & DO_HISTORY))
937 i = state->cnt_history;
938 free(state->history[MAX_HISTORY]);
939 state->history[MAX_HISTORY] = NULL;
940 /* After max history, remove the oldest command */
941 if (i >= MAX_HISTORY) {
942 free(state->history[0]);
943 for (i = 0; i < MAX_HISTORY-1; i++)
944 state->history[i] = state->history[i+1];
946 // Maybe "if (!i || strcmp(history[i-1], command) != 0) ..."
947 // (i.e. do not save dups?)
948 state->history[i++] = xstrdup(str);
949 state->cur_history = i;
950 state->cnt_history = i;
951 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
952 if ((state->flags & SAVE_HISTORY) && state->hist_file)
953 save_history(state->hist_file);
955 USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;)
958 #else /* MAX_HISTORY == 0 */
959 #define remember_in_history(a) ((void)0)
960 #endif /* MAX_HISTORY */
964 * This function is used to grab a character buffer
965 * from the input file descriptor and allows you to
966 * a string with full command editing (sort of like
969 * The following standard commands are not implemented:
970 * ESC-b -- Move back one word
971 * ESC-f -- Move forward one word
972 * ESC-d -- Delete back one word
973 * ESC-h -- Delete forward one word
974 * CTL-t -- Transpose two characters
976 * Minimalist vi-style command line editing available if configured.
977 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
980 #if ENABLE_FEATURE_EDITING_VI
982 vi_Word_motion(char *command, int eat)
984 while (cursor < command_len && !isspace(command[cursor]))
986 if (eat) while (cursor < command_len && isspace(command[cursor]))
991 vi_word_motion(char *command, int eat)
993 if (isalnum(command[cursor]) || command[cursor] == '_') {
994 while (cursor < command_len
995 && (isalnum(command[cursor+1]) || command[cursor+1] == '_'))
997 } else if (ispunct(command[cursor])) {
998 while (cursor < command_len && ispunct(command[cursor+1]))
1002 if (cursor < command_len)
1005 if (eat && cursor < command_len && isspace(command[cursor]))
1006 while (cursor < command_len && isspace(command[cursor]))
1011 vi_End_motion(char *command)
1014 while (cursor < command_len && isspace(command[cursor]))
1016 while (cursor < command_len-1 && !isspace(command[cursor+1]))
1021 vi_end_motion(char *command)
1023 if (cursor >= command_len-1)
1026 while (cursor < command_len-1 && isspace(command[cursor]))
1028 if (cursor >= command_len-1)
1030 if (isalnum(command[cursor]) || command[cursor] == '_') {
1031 while (cursor < command_len-1
1032 && (isalnum(command[cursor+1]) || command[cursor+1] == '_')
1036 } else if (ispunct(command[cursor])) {
1037 while (cursor < command_len-1 && ispunct(command[cursor+1]))
1043 vi_Back_motion(char *command)
1045 while (cursor > 0 && isspace(command[cursor-1]))
1047 while (cursor > 0 && !isspace(command[cursor-1]))
1052 vi_back_motion(char *command)
1057 while (cursor > 0 && isspace(command[cursor]))
1061 if (isalnum(command[cursor]) || command[cursor] == '_') {
1063 && (isalnum(command[cursor-1]) || command[cursor-1] == '_')
1067 } else if (ispunct(command[cursor])) {
1068 while (cursor > 0 && ispunct(command[cursor-1]))
1076 * read_line_input and its helpers
1079 #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
1080 static void parse_prompt(const char *prmt_ptr)
1082 cmdedit_prompt = prmt_ptr;
1083 cmdedit_prmt_len = strlen(prmt_ptr);
1087 static void parse_prompt(const char *prmt_ptr)
1090 size_t cur_prmt_len = 0;
1091 char flg_not_length = '[';
1092 char *prmt_mem_ptr = xzalloc(1);
1093 char *pwd_buf = xrealloc_getcwd_or_warn(NULL);
1094 char buf2[PATH_MAX + 1];
1099 cmdedit_prmt_len = 0;
1102 pwd_buf = (char *)bb_msg_unknown;
1110 const char *cp = prmt_ptr;
1113 c = bb_process_escape_sequence(&prmt_ptr);
1114 if (prmt_ptr == cp) {
1119 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1125 pbuf = hostname_buf;
1127 pbuf = xzalloc(256);
1128 if (gethostname(pbuf, 255) < 0) {
1131 char *s = strchr(pbuf, '.');
1135 hostname_buf = pbuf;
1139 c = (geteuid() == 0 ? '#' : '$');
1141 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1144 l = strlen(home_pwd_buf);
1145 if (home_pwd_buf[0] != 0
1146 && strncmp(home_pwd_buf, pbuf, l) == 0
1147 && (pbuf[l]=='/' || pbuf[l]=='\0')
1148 && strlen(pwd_buf+l)<PATH_MAX
1152 strcpy(pbuf+1, pwd_buf+l);
1158 cp = strrchr(pbuf,'/');
1159 if (cp != NULL && cp != pbuf)
1160 pbuf += (cp-pbuf) + 1;
1164 snprintf(buf2, sizeof(buf2), "%d", num_ok_lines);
1166 case 'e': case 'E': /* \e \E = \033 */
1170 for (l = 0; l < 3;) {
1172 buf2[l++] = *prmt_ptr;
1174 h = strtol(buf2, &pbuf, 16);
1175 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
1182 c = (char)strtol(buf2, NULL, 16);
1188 if (c == flg_not_length) {
1189 flg_not_length = flg_not_length == '[' ? ']' : '[';
1198 cur_prmt_len = strlen(pbuf);
1199 prmt_len += cur_prmt_len;
1200 if (flg_not_length != ']')
1201 cmdedit_prmt_len += cur_prmt_len;
1202 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
1204 if (pwd_buf != (char *)bb_msg_unknown)
1206 cmdedit_prompt = prmt_mem_ptr;
1211 #define setTermSettings(fd, argp) tcsetattr(fd, TCSANOW, argp)
1212 #define getTermSettings(fd, argp) tcgetattr(fd, argp);
1214 static sighandler_t previous_SIGWINCH_handler;
1216 static void cmdedit_setwidth(unsigned w, int redraw_flg)
1220 /* new y for current cursor */
1221 int new_y = (cursor + cmdedit_prmt_len) / w;
1223 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), command_len - cursor);
1228 static void win_changed(int nsig)
1231 get_terminal_width_height(0, &width, NULL);
1232 cmdedit_setwidth(width, nsig /* - just a yes/no flag */);
1233 if (nsig == SIGWINCH)
1234 signal(SIGWINCH, win_changed); /* rearm ourself */
1238 * The emacs and vi modes share much of the code in the big
1239 * command loop. Commands entered when in vi's command mode (aka
1240 * "escape mode") get an extra bit added to distinguish them --
1241 * this keeps them from being self-inserted. This clutters the
1242 * big switch a bit, but keeps all the code in one place.
1247 /* leave out the "vi-mode"-only case labels if vi editing isn't
1249 #define vi_case(caselabel) USE_FEATURE_EDITING(case caselabel)
1251 /* convert uppercase ascii to equivalent control char, for readability */
1253 #define CTRL(a) ((a) & ~0x40)
1255 int read_line_input(const char* prompt, char* command, int maxsize, line_input_t *st)
1257 int lastWasTab = FALSE;
1260 smallint break_out = 0;
1261 #if ENABLE_FEATURE_EDITING_VI
1262 smallint vi_cmdmode = 0;
1266 // FIXME: audit & improve this
1267 if (maxsize > BUFSIZ)
1270 /* With null flags, no other fields are ever used */
1271 state = st ? st : (line_input_t*) &const_int_0;
1272 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1273 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1274 load_history(state->hist_file);
1277 /* prepare before init handlers */
1278 cmdedit_y = 0; /* quasireal y, not true if line > xt*yt */
1280 command_ps = command;
1283 getTermSettings(0, (void *) &initial_settings);
1284 memcpy(&new_settings, &initial_settings, sizeof(new_settings));
1285 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1286 /* Turn off echoing and CTRL-C, so we can trap it */
1287 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1288 /* Hmm, in linux c_cc[] is not parsed if ICANON is off */
1289 new_settings.c_cc[VMIN] = 1;
1290 new_settings.c_cc[VTIME] = 0;
1291 /* Turn off CTRL-C, so we can trap it */
1292 #ifndef _POSIX_VDISABLE
1293 #define _POSIX_VDISABLE '\0'
1295 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1296 setTermSettings(0, (void *) &new_settings);
1298 /* Now initialize things */
1299 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
1300 win_changed(0); /* do initial resizing */
1301 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1303 struct passwd *entry;
1305 entry = getpwuid(geteuid());
1307 user_buf = xstrdup(entry->pw_name);
1308 home_pwd_buf = xstrdup(entry->pw_dir);
1312 /* Print out the command prompt */
1313 parse_prompt(prompt);
1318 if (safe_read(0, &c, 1) < 1) {
1319 /* if we can't read input then exit */
1320 goto prepare_to_die;
1325 #if ENABLE_FEATURE_EDITING_VI
1339 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1342 /* Control-a -- Beginning of line */
1343 input_backward(cursor);
1348 vi_case('\x7f'|vbit:) /* DEL */
1349 /* Control-b -- Move back one character */
1354 vi_case(CTRL('C')|vbit:)
1355 /* Control-c -- stop gathering input */
1358 break_out = -1; /* "do not append '\n'" */
1361 /* Control-d -- Delete one character, or exit
1362 * if the len=0 and no chars to delete */
1363 if (command_len == 0) {
1366 /* to control stopped jobs */
1367 break_out = command_len = -1;
1373 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1376 /* Control-e -- End of line */
1382 /* Control-f -- Move forward one character */
1388 case '\x7f': /* DEL */
1389 /* Control-h and DEL */
1394 input_tab(&lastWasTab);
1397 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1399 /* Control-k -- clear to end of line */
1400 command[cursor] = 0;
1401 command_len = cursor;
1405 vi_case(CTRL('L')|vbit:)
1406 /* Control-l -- clear screen */
1408 redraw(0, command_len - cursor);
1414 vi_case(CTRL('N')|vbit:)
1416 /* Control-n -- Get next command in history */
1417 if (get_next_history())
1421 vi_case(CTRL('P')|vbit:)
1423 /* Control-p -- Get previous command from history */
1424 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1425 get_previous_history();
1432 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1434 vi_case(CTRL('U')|vbit:)
1435 /* Control-U -- Clear line before cursor */
1437 strcpy(command, command + cursor);
1438 command_len -= cursor;
1439 redraw(cmdedit_y, command_len);
1444 vi_case(CTRL('W')|vbit:)
1445 /* Control-W -- Remove the last word */
1446 while (cursor > 0 && isspace(command[cursor-1]))
1448 while (cursor > 0 && !isspace(command[cursor-1]))
1452 #if ENABLE_FEATURE_EDITING_VI
1457 input_backward(cursor);
1478 vi_Word_motion(command, 1);
1481 vi_word_motion(command, 1);
1484 vi_End_motion(command);
1487 vi_end_motion(command);
1490 vi_Back_motion(command);
1493 vi_back_motion(command);
1508 if (safe_read(0, &c, 1) < 1)
1509 goto prepare_to_die;
1510 if (c == (prevc & 0xff)) {
1512 input_backward(cursor);
1522 case 'w': /* "dw", "cw" */
1523 vi_word_motion(command, vi_cmdmode);
1525 case 'W': /* 'dW', 'cW' */
1526 vi_Word_motion(command, vi_cmdmode);
1528 case 'e': /* 'de', 'ce' */
1529 vi_end_motion(command);
1532 case 'E': /* 'dE', 'cE' */
1533 vi_End_motion(command);
1538 input_backward(cursor - sc);
1539 while (nc-- > cursor)
1542 case 'b': /* "db", "cb" */
1543 case 'B': /* implemented as B */
1545 vi_back_motion(command);
1547 vi_Back_motion(command);
1548 while (sc-- > cursor)
1551 case ' ': /* "d ", "c " */
1554 case '$': /* "d$", "c$" */
1556 while (cursor < command_len)
1569 if (safe_read(0, &c, 1) < 1)
1570 goto prepare_to_die;
1574 *(command + cursor) = c;
1579 #endif /* FEATURE_COMMAND_EDITING_VI */
1581 case '\x1b': /* ESC */
1583 #if ENABLE_FEATURE_EDITING_VI
1584 if (state->flags & VI_MODE) {
1585 /* ESC: insert mode --> command mode */
1591 /* escape sequence follows */
1592 if (safe_read(0, &c, 1) < 1)
1593 goto prepare_to_die;
1594 /* different vt100 emulations */
1595 if (c == '[' || c == 'O') {
1598 if (safe_read(0, &c, 1) < 1)
1599 goto prepare_to_die;
1601 if (c >= '1' && c <= '9') {
1602 unsigned char dummy;
1604 if (safe_read(0, &dummy, 1) < 1)
1605 goto prepare_to_die;
1611 #if ENABLE_FEATURE_TAB_COMPLETION
1612 case '\t': /* Alt-Tab */
1613 input_tab(&lastWasTab);
1618 /* Up Arrow -- Get previous command from history */
1619 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1620 get_previous_history();
1626 /* Down Arrow -- Get next command in history */
1627 if (!get_next_history())
1630 /* Rewrite the line with the selected history item */
1631 /* change command */
1632 command_len = strlen(strcpy(command, state->history[state->cur_history]));
1633 /* redraw and go to eol (bol, in vi */
1634 redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
1638 /* Right Arrow -- Move forward one character */
1642 /* Left Arrow -- Move back one character */
1652 input_backward(cursor);
1665 default: /* If it's regular input, do the normal thing */
1666 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1667 /* Control-V -- Add non-printable symbol */
1668 if (c == CTRL('V')) {
1669 if (safe_read(0, &c, 1) < 1)
1670 goto prepare_to_die;
1678 #if ENABLE_FEATURE_EDITING_VI
1679 if (vi_cmdmode) /* Don't self-insert */
1682 if (!Isprint(c)) /* Skip non-printable characters */
1685 if (command_len >= (maxsize - 2)) /* Need to leave space for enter */
1689 if (cursor == (command_len - 1)) { /* Append if at the end of the line */
1690 command[cursor] = c;
1691 command[cursor+1] = '\0';
1692 cmdedit_set_out_char(' ');
1693 } else { /* Insert otherwise */
1696 memmove(command + sc + 1, command + sc, command_len - sc);
1699 /* rewrite from cursor */
1701 /* to prev x pos + 1 */
1702 input_backward(cursor - sc);
1706 if (break_out) /* Enter is the command terminator, no more input. */
1713 if (command_len > 0)
1714 remember_in_history(command);
1716 if (break_out > 0) {
1717 command[command_len++] = '\n';
1718 command[command_len] = '\0';
1721 #if ENABLE_FEATURE_CLEAN_UP && ENABLE_FEATURE_TAB_COMPLETION
1722 free_tab_completion_data();
1725 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1726 free((char*)cmdedit_prompt);
1728 /* restore initial_settings */
1729 setTermSettings(STDIN_FILENO, (void *) &initial_settings);
1730 /* restore SIGWINCH handler */
1731 signal(SIGWINCH, previous_SIGWINCH_handler);
1736 line_input_t *new_line_input_t(int flags)
1738 line_input_t *n = xzalloc(sizeof(*n));
1745 #undef read_line_input
1746 int read_line_input(const char* prompt, char* command, int maxsize)
1748 fputs(prompt, stdout);
1750 fgets(command, maxsize, stdin);
1751 return strlen(command);
1754 #endif /* FEATURE_COMMAND_EDITING */
1765 const char *applet_name = "debug stuff usage";
1767 int main(int argc, char **argv)
1771 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1772 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:"
1773 "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] "
1774 "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1779 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1780 setlocale(LC_ALL, "");
1784 l = read_line_input(prompt, buff);
1785 if (l <= 0 || buff[l-1] != '\n')
1788 printf("*** read_line_input() returned line =%s=\n", buff);
1790 printf("*** read_line_input() detect ^D\n");