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
34 /* FIXME: obsolete CONFIG item? */
35 #define ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT 0
40 #define ENABLE_FEATURE_EDITING 0
41 #define ENABLE_FEATURE_TAB_COMPLETION 0
42 #define ENABLE_FEATURE_USERNAME_COMPLETION 0
43 #define ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT 0
44 #define ENABLE_FEATURE_CLEAN_UP 0
49 /* Entire file (except TESTing part) sits inside this #if */
50 #if ENABLE_FEATURE_EDITING
52 #if ENABLE_LOCALE_SUPPORT
53 #define Isprint(c) isprint(c)
55 #define Isprint(c) ((c) >= ' ' && (c) != ((unsigned char)'\233'))
58 #define ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR \
59 (ENABLE_FEATURE_USERNAME_COMPLETION || ENABLE_FEATURE_EDITING_FANCY_PROMPT)
61 enum { MAX_LINELEN = CONFIG_FEATURE_EDITING_MAX_LEN };
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) < MAX_LINELEN) {
331 char temp2[MAX_LINELEN]; /* 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)
413 char dirbuf[MAX_LINELEN];
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), (MAX_LINELEN+1-(is)-(in))*sizeof(int)); \
500 memmove(pos_buf+(is), pos_buf+(in), (MAX_LINELEN+1-(is)-(in))*sizeof(int)); }
502 static int find_match(char *matchBuf, int *len_with_quotes)
507 int int_buf[MAX_LINELEN + 1];
508 int pos_buf[MAX_LINELEN + 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[MAX_LINELEN];
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 // ash: yet another failure in trying to achieve "we don't die on OOM"
785 tmp1 = xstrdup(matches[0]);
786 for (tmp = tmp1; *tmp; tmp++)
787 for (len_found = 1; len_found < num_matches; len_found++)
788 if (matches[len_found][(tmp - tmp1)] != *tmp) {
792 if (*tmp1 == '\0') { /* have unique */
796 tmp = add_quote_for_spec_chars(tmp1);
798 } else { /* one match */
799 tmp = add_quote_for_spec_chars(matches[0]);
800 /* for next completion current found */
803 len_found = strlen(tmp);
804 if (tmp[len_found-1] != '/') {
805 tmp[len_found] = ' ';
806 tmp[len_found+1] = '\0';
809 len_found = strlen(tmp);
810 /* have space to placed match? */
811 if ((len_found - strlen(matchBuf) + command_len) < MAX_LINELEN) {
812 /* before word for match */
813 command_ps[cursor - recalc_pos] = 0;
815 strcpy(matchBuf, command_ps + cursor);
817 strcat(command_ps, tmp);
819 strcat(command_ps, matchBuf);
820 /* back to begin word for match */
821 input_backward(recalc_pos);
823 recalc_pos = cursor + len_found;
825 command_len = strlen(command_ps);
826 /* write out the matched command */
827 redraw(cmdedit_y, command_len - recalc_pos);
831 /* Ok -- the last char was a TAB. Since they
832 * just hit TAB again, print a list of all the
833 * available choices... */
834 if (matches && num_matches > 0) {
835 int sav_cursor = cursor; /* change goto_new_line() */
837 /* Go to the next line */
840 redraw(0, command_len - sav_cursor);
846 #define input_tab(a) ((void)0)
847 #endif /* FEATURE_COMMAND_TAB_COMPLETION */
852 /* state->flags is already checked to be nonzero */
853 static void get_previous_history(void)
855 if (command_ps[0] != '\0' || state->history[state->cur_history] == NULL) {
856 free(state->history[state->cur_history]);
857 state->history[state->cur_history] = xstrdup(command_ps);
859 state->cur_history--;
862 static int get_next_history(void)
864 if (state->flags & DO_HISTORY) {
865 int ch = state->cur_history;
866 if (ch < state->cnt_history) {
867 get_previous_history(); /* save the current history line */
868 state->cur_history = ch + 1;
869 return state->cur_history;
876 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
877 /* state->flags is already checked to be nonzero */
878 static void load_history(const char *fromfile)
884 for (hi = state->cnt_history; hi > 0;) {
886 free(state->history[hi]);
889 fp = fopen(fromfile, "r");
891 for (hi = 0; hi < MAX_HISTORY;) {
892 char *hl = xmalloc_getline(fp);
898 if (l >= MAX_LINELEN)
899 hl[MAX_LINELEN-1] = '\0';
900 if (l == 0 || hl[0] == ' ') {
904 state->history[hi++] = hl;
908 state->cur_history = state->cnt_history = hi;
911 /* state->flags is already checked to be nonzero */
912 static void save_history(const char *tofile)
916 fp = fopen(tofile, "w");
920 for (i = 0; i < state->cnt_history; i++) {
921 fprintf(fp, "%s\n", state->history[i]);
927 #define load_history(a) ((void)0)
928 #define save_history(a) ((void)0)
929 #endif /* FEATURE_COMMAND_SAVEHISTORY */
931 static void remember_in_history(const char *str)
935 if (!(state->flags & DO_HISTORY))
938 i = state->cnt_history;
939 free(state->history[MAX_HISTORY]);
940 state->history[MAX_HISTORY] = NULL;
941 /* After max history, remove the oldest command */
942 if (i >= MAX_HISTORY) {
943 free(state->history[0]);
944 for (i = 0; i < MAX_HISTORY-1; i++)
945 state->history[i] = state->history[i+1];
947 // Maybe "if (!i || strcmp(history[i-1], command) != 0) ..."
948 // (i.e. do not save dups?)
949 state->history[i++] = xstrdup(str);
950 state->cur_history = i;
951 state->cnt_history = i;
952 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
953 if ((state->flags & SAVE_HISTORY) && state->hist_file)
954 save_history(state->hist_file);
956 USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;)
959 #else /* MAX_HISTORY == 0 */
960 #define remember_in_history(a) ((void)0)
961 #endif /* MAX_HISTORY */
965 * This function is used to grab a character buffer
966 * from the input file descriptor and allows you to
967 * a string with full command editing (sort of like
970 * The following standard commands are not implemented:
971 * ESC-b -- Move back one word
972 * ESC-f -- Move forward one word
973 * ESC-d -- Delete back one word
974 * ESC-h -- Delete forward one word
975 * CTL-t -- Transpose two characters
977 * Minimalist vi-style command line editing available if configured.
978 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
981 #if ENABLE_FEATURE_EDITING_VI
983 vi_Word_motion(char *command, int eat)
985 while (cursor < command_len && !isspace(command[cursor]))
987 if (eat) while (cursor < command_len && isspace(command[cursor]))
992 vi_word_motion(char *command, int eat)
994 if (isalnum(command[cursor]) || command[cursor] == '_') {
995 while (cursor < command_len
996 && (isalnum(command[cursor+1]) || command[cursor+1] == '_'))
998 } else if (ispunct(command[cursor])) {
999 while (cursor < command_len && ispunct(command[cursor+1]))
1003 if (cursor < command_len)
1006 if (eat && cursor < command_len && isspace(command[cursor]))
1007 while (cursor < command_len && isspace(command[cursor]))
1012 vi_End_motion(char *command)
1015 while (cursor < command_len && isspace(command[cursor]))
1017 while (cursor < command_len-1 && !isspace(command[cursor+1]))
1022 vi_end_motion(char *command)
1024 if (cursor >= command_len-1)
1027 while (cursor < command_len-1 && isspace(command[cursor]))
1029 if (cursor >= command_len-1)
1031 if (isalnum(command[cursor]) || command[cursor] == '_') {
1032 while (cursor < command_len-1
1033 && (isalnum(command[cursor+1]) || command[cursor+1] == '_')
1037 } else if (ispunct(command[cursor])) {
1038 while (cursor < command_len-1 && ispunct(command[cursor+1]))
1044 vi_Back_motion(char *command)
1046 while (cursor > 0 && isspace(command[cursor-1]))
1048 while (cursor > 0 && !isspace(command[cursor-1]))
1053 vi_back_motion(char *command)
1058 while (cursor > 0 && isspace(command[cursor]))
1062 if (isalnum(command[cursor]) || command[cursor] == '_') {
1064 && (isalnum(command[cursor-1]) || command[cursor-1] == '_')
1068 } else if (ispunct(command[cursor])) {
1069 while (cursor > 0 && ispunct(command[cursor-1]))
1077 * read_line_input and its helpers
1080 #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
1081 static void parse_prompt(const char *prmt_ptr)
1083 cmdedit_prompt = prmt_ptr;
1084 cmdedit_prmt_len = strlen(prmt_ptr);
1088 static void parse_prompt(const char *prmt_ptr)
1091 size_t cur_prmt_len = 0;
1092 char flg_not_length = '[';
1093 char *prmt_mem_ptr = xzalloc(1);
1094 char *pwd_buf = xrealloc_getcwd_or_warn(NULL);
1095 char buf2[PATH_MAX + 1];
1100 cmdedit_prmt_len = 0;
1103 pwd_buf = (char *)bb_msg_unknown;
1111 const char *cp = prmt_ptr;
1114 c = bb_process_escape_sequence(&prmt_ptr);
1115 if (prmt_ptr == cp) {
1120 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1126 pbuf = hostname_buf;
1128 pbuf = xzalloc(256);
1129 if (gethostname(pbuf, 255) < 0) {
1132 char *s = strchr(pbuf, '.');
1136 hostname_buf = pbuf;
1140 c = (geteuid() == 0 ? '#' : '$');
1142 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1145 l = strlen(home_pwd_buf);
1146 if (home_pwd_buf[0] != 0
1147 && strncmp(home_pwd_buf, pbuf, l) == 0
1148 && (pbuf[l]=='/' || pbuf[l]=='\0')
1149 && strlen(pwd_buf+l)<PATH_MAX
1153 strcpy(pbuf+1, pwd_buf+l);
1159 cp = strrchr(pbuf, '/');
1160 if (cp != NULL && cp != pbuf)
1161 pbuf += (cp-pbuf) + 1;
1165 snprintf(buf2, sizeof(buf2), "%d", num_ok_lines);
1167 case 'e': case 'E': /* \e \E = \033 */
1171 for (l = 0; l < 3;) {
1173 buf2[l++] = *prmt_ptr;
1175 h = strtol(buf2, &pbuf, 16);
1176 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
1183 c = (char)strtol(buf2, NULL, 16);
1189 if (c == flg_not_length) {
1190 flg_not_length = flg_not_length == '[' ? ']' : '[';
1199 cur_prmt_len = strlen(pbuf);
1200 prmt_len += cur_prmt_len;
1201 if (flg_not_length != ']')
1202 cmdedit_prmt_len += cur_prmt_len;
1203 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
1205 if (pwd_buf != (char *)bb_msg_unknown)
1207 cmdedit_prompt = prmt_mem_ptr;
1212 #define setTermSettings(fd, argp) tcsetattr(fd, TCSANOW, argp)
1213 #define getTermSettings(fd, argp) tcgetattr(fd, argp);
1215 static sighandler_t previous_SIGWINCH_handler;
1217 static void cmdedit_setwidth(unsigned w, int redraw_flg)
1221 /* new y for current cursor */
1222 int new_y = (cursor + cmdedit_prmt_len) / w;
1224 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), command_len - cursor);
1229 static void win_changed(int nsig)
1232 get_terminal_width_height(0, &width, NULL);
1233 cmdedit_setwidth(width, nsig /* - just a yes/no flag */);
1234 if (nsig == SIGWINCH)
1235 signal(SIGWINCH, win_changed); /* rearm ourself */
1239 * The emacs and vi modes share much of the code in the big
1240 * command loop. Commands entered when in vi's command mode (aka
1241 * "escape mode") get an extra bit added to distinguish them --
1242 * this keeps them from being self-inserted. This clutters the
1243 * big switch a bit, but keeps all the code in one place.
1248 /* leave out the "vi-mode"-only case labels if vi editing isn't
1250 #define vi_case(caselabel) USE_FEATURE_EDITING(case caselabel)
1252 /* convert uppercase ascii to equivalent control char, for readability */
1254 #define CTRL(a) ((a) & ~0x40)
1256 int read_line_input(const char* prompt, char* command, int maxsize, line_input_t *st)
1258 int lastWasTab = FALSE;
1261 smallint break_out = 0;
1262 #if ENABLE_FEATURE_EDITING_VI
1263 smallint vi_cmdmode = 0;
1267 // FIXME: audit & improve this
1268 if (maxsize > MAX_LINELEN)
1269 maxsize = MAX_LINELEN;
1271 /* With null flags, no other fields are ever used */
1272 state = st ? st : (line_input_t*) &const_int_0;
1273 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1274 if ((state->flags & SAVE_HISTORY) && state->hist_file)
1275 load_history(state->hist_file);
1278 /* prepare before init handlers */
1279 cmdedit_y = 0; /* quasireal y, not true if line > xt*yt */
1281 command_ps = command;
1284 getTermSettings(0, (void *) &initial_settings);
1285 memcpy(&new_settings, &initial_settings, sizeof(new_settings));
1286 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1287 /* Turn off echoing and CTRL-C, so we can trap it */
1288 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1289 /* Hmm, in linux c_cc[] is not parsed if ICANON is off */
1290 new_settings.c_cc[VMIN] = 1;
1291 new_settings.c_cc[VTIME] = 0;
1292 /* Turn off CTRL-C, so we can trap it */
1293 #ifndef _POSIX_VDISABLE
1294 #define _POSIX_VDISABLE '\0'
1296 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1297 setTermSettings(0, (void *) &new_settings);
1299 /* Now initialize things */
1300 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
1301 win_changed(0); /* do initial resizing */
1302 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1304 struct passwd *entry;
1306 entry = getpwuid(geteuid());
1308 user_buf = xstrdup(entry->pw_name);
1309 home_pwd_buf = xstrdup(entry->pw_dir);
1313 /* Print out the command prompt */
1314 parse_prompt(prompt);
1319 if (safe_read(0, &c, 1) < 1) {
1320 /* if we can't read input then exit */
1321 goto prepare_to_die;
1326 #if ENABLE_FEATURE_EDITING_VI
1340 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1343 /* Control-a -- Beginning of line */
1344 input_backward(cursor);
1349 vi_case('\x7f'|vbit:) /* DEL */
1350 /* Control-b -- Move back one character */
1355 vi_case(CTRL('C')|vbit:)
1356 /* Control-c -- stop gathering input */
1359 break_out = -1; /* "do not append '\n'" */
1362 /* Control-d -- Delete one character, or exit
1363 * if the len=0 and no chars to delete */
1364 if (command_len == 0) {
1367 /* to control stopped jobs */
1368 break_out = command_len = -1;
1374 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1377 /* Control-e -- End of line */
1383 /* Control-f -- Move forward one character */
1389 case '\x7f': /* DEL */
1390 /* Control-h and DEL */
1395 input_tab(&lastWasTab);
1398 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1400 /* Control-k -- clear to end of line */
1401 command[cursor] = 0;
1402 command_len = cursor;
1406 vi_case(CTRL('L')|vbit:)
1407 /* Control-l -- clear screen */
1409 redraw(0, command_len - cursor);
1415 vi_case(CTRL('N')|vbit:)
1417 /* Control-n -- Get next command in history */
1418 if (get_next_history())
1422 vi_case(CTRL('P')|vbit:)
1424 /* Control-p -- Get previous command from history */
1425 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1426 get_previous_history();
1433 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1435 vi_case(CTRL('U')|vbit:)
1436 /* Control-U -- Clear line before cursor */
1438 strcpy(command, command + cursor);
1439 command_len -= cursor;
1440 redraw(cmdedit_y, command_len);
1445 vi_case(CTRL('W')|vbit:)
1446 /* Control-W -- Remove the last word */
1447 while (cursor > 0 && isspace(command[cursor-1]))
1449 while (cursor > 0 && !isspace(command[cursor-1]))
1453 #if ENABLE_FEATURE_EDITING_VI
1458 input_backward(cursor);
1479 vi_Word_motion(command, 1);
1482 vi_word_motion(command, 1);
1485 vi_End_motion(command);
1488 vi_end_motion(command);
1491 vi_Back_motion(command);
1494 vi_back_motion(command);
1509 if (safe_read(0, &c, 1) < 1)
1510 goto prepare_to_die;
1511 if (c == (prevc & 0xff)) {
1513 input_backward(cursor);
1523 case 'w': /* "dw", "cw" */
1524 vi_word_motion(command, vi_cmdmode);
1526 case 'W': /* 'dW', 'cW' */
1527 vi_Word_motion(command, vi_cmdmode);
1529 case 'e': /* 'de', 'ce' */
1530 vi_end_motion(command);
1533 case 'E': /* 'dE', 'cE' */
1534 vi_End_motion(command);
1539 input_backward(cursor - sc);
1540 while (nc-- > cursor)
1543 case 'b': /* "db", "cb" */
1544 case 'B': /* implemented as B */
1546 vi_back_motion(command);
1548 vi_Back_motion(command);
1549 while (sc-- > cursor)
1552 case ' ': /* "d ", "c " */
1555 case '$': /* "d$", "c$" */
1557 while (cursor < command_len)
1570 if (safe_read(0, &c, 1) < 1)
1571 goto prepare_to_die;
1575 *(command + cursor) = c;
1580 #endif /* FEATURE_COMMAND_EDITING_VI */
1582 case '\x1b': /* ESC */
1584 #if ENABLE_FEATURE_EDITING_VI
1585 if (state->flags & VI_MODE) {
1586 /* ESC: insert mode --> command mode */
1592 /* escape sequence follows */
1593 if (safe_read(0, &c, 1) < 1)
1594 goto prepare_to_die;
1595 /* different vt100 emulations */
1596 if (c == '[' || c == 'O') {
1599 if (safe_read(0, &c, 1) < 1)
1600 goto prepare_to_die;
1602 if (c >= '1' && c <= '9') {
1603 unsigned char dummy;
1605 if (safe_read(0, &dummy, 1) < 1)
1606 goto prepare_to_die;
1612 #if ENABLE_FEATURE_TAB_COMPLETION
1613 case '\t': /* Alt-Tab */
1614 input_tab(&lastWasTab);
1619 /* Up Arrow -- Get previous command from history */
1620 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1621 get_previous_history();
1627 /* Down Arrow -- Get next command in history */
1628 if (!get_next_history())
1631 /* Rewrite the line with the selected history item */
1632 /* change command */
1633 command_len = strlen(strcpy(command, state->history[state->cur_history]));
1634 /* redraw and go to eol (bol, in vi */
1635 redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
1639 /* Right Arrow -- Move forward one character */
1643 /* Left Arrow -- Move back one character */
1650 case '1': // vt100? linux vt? or what?
1651 case '7': // vt100? linux vt? or what?
1652 case 'H': /* xterm's <Home> */
1653 input_backward(cursor);
1655 case '4': // vt100? linux vt? or what?
1656 case '8': // vt100? linux vt? or what?
1657 case 'F': /* xterm's <End> */
1666 default: /* If it's regular input, do the normal thing */
1667 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1668 /* Control-V -- Add non-printable symbol */
1669 if (c == CTRL('V')) {
1670 if (safe_read(0, &c, 1) < 1)
1671 goto prepare_to_die;
1679 #if ENABLE_FEATURE_EDITING_VI
1680 if (vi_cmdmode) /* Don't self-insert */
1683 if (!Isprint(c)) /* Skip non-printable characters */
1686 if (command_len >= (maxsize - 2)) /* Need to leave space for enter */
1690 if (cursor == (command_len - 1)) { /* Append if at the end of the line */
1691 command[cursor] = c;
1692 command[cursor+1] = '\0';
1693 cmdedit_set_out_char(' ');
1694 } else { /* Insert otherwise */
1697 memmove(command + sc + 1, command + sc, command_len - sc);
1700 /* rewrite from cursor */
1702 /* to prev x pos + 1 */
1703 input_backward(cursor - sc);
1707 if (break_out) /* Enter is the command terminator, no more input. */
1714 if (command_len > 0)
1715 remember_in_history(command);
1717 if (break_out > 0) {
1718 command[command_len++] = '\n';
1719 command[command_len] = '\0';
1722 #if ENABLE_FEATURE_CLEAN_UP && ENABLE_FEATURE_TAB_COMPLETION
1723 free_tab_completion_data();
1726 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1727 free((char*)cmdedit_prompt);
1729 /* restore initial_settings */
1730 setTermSettings(STDIN_FILENO, (void *) &initial_settings);
1731 /* restore SIGWINCH handler */
1732 signal(SIGWINCH, previous_SIGWINCH_handler);
1737 line_input_t *new_line_input_t(int flags)
1739 line_input_t *n = xzalloc(sizeof(*n));
1746 #undef read_line_input
1747 int read_line_input(const char* prompt, char* command, int maxsize)
1749 fputs(prompt, stdout);
1751 fgets(command, maxsize, stdin);
1752 return strlen(command);
1755 #endif /* FEATURE_COMMAND_EDITING */
1766 const char *applet_name = "debug stuff usage";
1768 int main(int argc, char **argv)
1770 char buff[MAX_LINELEN];
1772 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1773 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:"
1774 "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] "
1775 "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1780 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1781 setlocale(LC_ALL, "");
1785 l = read_line_input(prompt, buff);
1786 if (l <= 0 || buff[l-1] != '\n')
1789 printf("*** read_line_input() returned line =%s=\n", buff);
1791 printf("*** read_line_input() detect ^D\n");