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 #if ENABLE_FEATURE_TAB_COMPLETION
93 /* Put 'command_ps[cursor]', cursor++.
94 * Advance cursor on screen. If we reached right margin, scroll text up
95 * and remove terminal margin effect by printing 'next_char' */
96 static void cmdedit_set_out_char(int next_char)
98 int c = (unsigned char)command_ps[cursor];
101 /* erase character after end of input string */
104 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
105 /* Display non-printable characters in reverse */
113 printf("\033[7m%c\033[0m", c);
117 if (initial_settings.c_lflag & ECHO)
120 if (++cmdedit_x >= cmdedit_termw) {
121 /* terminal is scrolled down */
124 /* destroy "(auto)margin" */
128 // Huh? What if command_ps[cursor] == '\0' (we are at the end already?)
132 /* Move to end of line (by printing all chars till the end) */
133 static void input_end(void)
135 while (cursor < command_len)
136 cmdedit_set_out_char(' ');
139 /* Go to the next line */
140 static void goto_new_line(void)
148 static void out1str(const char *s)
154 static void beep(void)
159 /* Move back one character */
160 /* (optimized for slow terminals) */
161 static void input_backward(unsigned num)
171 if (cmdedit_x >= num) {
174 printf("\b\b\b\b" + (4-num));
177 printf("\033[%uD", num);
181 /* Need to go one or more lines up */
183 count_y = 1 + (num / cmdedit_termw);
184 cmdedit_y -= count_y;
185 cmdedit_x = cmdedit_termw * count_y - num;
186 /* go to 1st column; go up; go to correct column */
187 printf("\r" "\033[%dA" "\033[%dC", count_y, cmdedit_x);
190 static void put_prompt(void)
192 out1str(cmdedit_prompt);
193 cmdedit_x = cmdedit_prmt_len;
195 // Huh? what if cmdedit_prmt_len >= width?
196 cmdedit_y = 0; /* new quasireal y */
199 /* draw prompt, editor line, and clear tail */
200 static void redraw(int y, int back_cursor)
202 if (y > 0) /* up to start y */
203 printf("\033[%dA", y);
206 input_end(); /* rewrite */
207 printf("\033[J"); /* erase after cursor */
208 input_backward(back_cursor);
211 #if ENABLE_FEATURE_EDITING_VI
212 #define DELBUFSIZ 128
213 static char *delbuf; /* a (malloced) place to store deleted characters */
215 static char newdelflag; /* whether delbuf should be reused yet */
218 /* Delete the char in front of the cursor, optionally saving it
219 * for later putback */
220 static void input_delete(int save)
224 if (j == command_len)
227 #if ENABLE_FEATURE_EDITING_VI
231 delbuf = malloc(DELBUFSIZ);
232 /* safe if malloc fails */
236 if (delbuf && (delp - delbuf < DELBUFSIZ))
237 *delp++ = command_ps[j];
241 strcpy(command_ps + j, command_ps + j + 1);
243 input_end(); /* rewrite new line */
244 cmdedit_set_out_char(' '); /* erase char */
245 input_backward(cursor - j); /* back to old pos cursor */
248 #if ENABLE_FEATURE_EDITING_VI
249 static void put(void)
252 int j = delp - delbuf;
257 /* open hole and then fill it */
258 memmove(command_ps + cursor + j, command_ps + cursor, command_len - cursor + 1);
259 strncpy(command_ps + cursor, delbuf, j);
261 input_end(); /* rewrite new line */
262 input_backward(cursor - ocursor - j + 1); /* at end of new text */
266 /* Delete the char in back of the cursor */
267 static void input_backspace(void)
275 /* Move forward one character */
276 static void input_forward(void)
278 if (cursor < command_len)
279 cmdedit_set_out_char(command_ps[cursor + 1]);
283 #if ENABLE_FEATURE_TAB_COMPLETION
285 static char **matches;
286 static unsigned num_matches;
288 static void free_tab_completion_data(void)
292 free(matches[--num_matches]);
298 static void add_match(char *matched)
300 int nm = num_matches;
303 matches = xrealloc(matches, nm1 * sizeof(char *));
304 matches[nm] = matched;
308 #if ENABLE_FEATURE_USERNAME_COMPLETION
309 static void username_tab_completion(char *ud, char *with_shash_flg)
311 struct passwd *entry;
314 ud++; /* ~user/... to user/... */
315 userlen = strlen(ud);
317 if (with_shash_flg) { /* "~/..." or "~user/..." */
318 char *sav_ud = ud - 1;
322 if (*ud == '/') { /* "~/..." */
326 temp = strchr(ud, '/');
327 *temp = 0; /* ~user\0 */
328 entry = getpwnam(ud);
329 *temp = '/'; /* restore ~user/... */
332 home = entry->pw_dir;
335 if ((userlen + strlen(home) + 1) < BUFSIZ) {
336 char temp2[BUFSIZ]; /* argument size */
339 sprintf(temp2, "%s%s", home, ud);
340 strcpy(sav_ud, temp2);
345 /* Using _r function to avoid pulling in static buffers */
348 struct passwd *result;
351 while (!getpwent_r(&pwd, line_buff, sizeof(line_buff), &result)) {
352 /* Null usernames should result in all users as possible completions. */
353 if (/*!userlen || */ strncmp(ud, pwd.pw_name, userlen) == 0) {
354 add_match(xasprintf("~%s/", pwd.pw_name));
360 #endif /* FEATURE_COMMAND_USERNAME_COMPLETION */
368 static int path_parse(char ***p, int flags)
375 /* if not setenv PATH variable, to search cur dir "." */
376 if (flags != FIND_EXE_ONLY)
379 if (state->flags & WITH_PATH_LOOKUP)
380 pth = state->path_lookup;
382 pth = getenv("PATH");
383 /* PATH=<empty> or PATH=:<empty> */
384 if (!pth || !pth[0] || LONE_CHAR(pth, ':'))
388 npth = 1; /* path component count */
390 tmp = strchr(tmp, ':');
394 break; /* :<empty> */
398 res = xmalloc(npth * sizeof(char*));
399 res[0] = tmp = xstrdup(pth);
402 tmp = strchr(tmp, ':');
405 *tmp++ = '\0'; /* ':' -> '\0' */
407 break; /* :<empty> */
414 static void exe_n_cwd_tab_completion(char *command, int type)
421 char **paths = path1;
425 char *pfind = strrchr(command, '/');
428 path1[0] = (char*)".";
431 /* no dir, if flags==EXE_ONLY - get paths, else "." */
432 npaths = path_parse(&paths, type);
435 /* dirbuf = ".../.../.../" */
436 safe_strncpy(dirbuf, command, (pfind - command) + 2);
437 #if ENABLE_FEATURE_USERNAME_COMPLETION
438 if (dirbuf[0] == '~') /* ~/... or ~user/... */
439 username_tab_completion(dirbuf, dirbuf);
442 /* point to 'l' in "..../last_component" */
446 for (i = 0; i < npaths; i++) {
447 dir = opendir(paths[i]);
448 if (!dir) /* Don't print an error */
451 while ((next = readdir(dir)) != NULL) {
453 const char *str_found = next->d_name;
456 if (strncmp(str_found, pfind, strlen(pfind)))
458 /* not see .name without .match */
459 if (*str_found == '.' && *pfind == 0) {
460 if (NOT_LONE_CHAR(paths[i], '/') || str_found[1])
462 str_found = ""; /* only "/" */
464 found = concat_path_file(paths[i], str_found);
465 /* hmm, remover in progress? */
466 if (stat(found, &st) < 0)
468 /* find with dirs? */
469 if (paths[i] != dirbuf)
470 strcpy(found, next->d_name); /* only name */
472 len1 = strlen(found);
473 found = xrealloc(found, len1 + 2);
475 found[len1+1] = '\0';
477 if (S_ISDIR(st.st_mode)) {
478 /* name is directory */
479 if (found[len1-1] != '/') {
483 /* not put found file if search only dirs for cd */
484 if (type == FIND_DIR_ONLY)
487 /* Add it to the list */
495 if (paths != path1) {
496 free(paths[0]); /* allocated memory only in first member */
501 #define QUOT (UCHAR_MAX+1)
503 #define collapse_pos(is, in) { \
504 memmove(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \
505 memmove(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); }
507 static int find_match(char *matchBuf, int *len_with_quotes)
512 int int_buf[BUFSIZ + 1];
513 int pos_buf[BUFSIZ + 1];
515 /* set to integer dimension characters and own positions */
517 int_buf[i] = (unsigned char)matchBuf[i];
518 if (int_buf[i] == 0) {
519 pos_buf[i] = -1; /* indicator end line */
525 /* mask \+symbol and convert '\t' to ' ' */
526 for (i = j = 0; matchBuf[i]; i++, j++)
527 if (matchBuf[i] == '\\') {
528 collapse_pos(j, j + 1);
531 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
532 if (matchBuf[i] == '\t') /* algorithm equivalent */
533 int_buf[j] = ' ' | QUOT;
536 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
537 else if (matchBuf[i] == '\t')
541 /* mask "symbols" or 'symbols' */
543 for (i = 0; int_buf[i]; i++) {
545 if (c == '\'' || c == '"') {
554 } else if (c2 != 0 && c != '$')
558 /* skip commands with arguments if line has commands delimiters */
559 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */
560 for (i = 0; int_buf[i]; i++) {
563 j = i ? int_buf[i - 1] : -1;
565 if (c == ';' || c == '&' || c == '|') {
566 command_mode = 1 + (c == c2);
568 if (j == '>' || j == '<')
570 } else if (c == '|' && j == '>')
574 collapse_pos(0, i + command_mode);
575 i = -1; /* hack incremet */
578 /* collapse `command...` */
579 for (i = 0; int_buf[i]; i++)
580 if (int_buf[i] == '`') {
581 for (j = i + 1; int_buf[j]; j++)
582 if (int_buf[j] == '`') {
583 collapse_pos(i, j + 1);
588 /* not found close ` - command mode, collapse all previous */
589 collapse_pos(0, i + 1);
592 i--; /* hack incremet */
595 /* collapse (command...(command...)...) or {command...{command...}...} */
596 c = 0; /* "recursive" level */
598 for (i = 0; int_buf[i]; i++)
599 if (int_buf[i] == '(' || int_buf[i] == '{') {
600 if (int_buf[i] == '(')
604 collapse_pos(0, i + 1);
605 i = -1; /* hack incremet */
607 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++)
608 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
609 if (int_buf[i] == ')')
613 collapse_pos(0, i + 1);
614 i = -1; /* hack incremet */
617 /* skip first not quote space */
618 for (i = 0; int_buf[i]; i++)
619 if (int_buf[i] != ' ')
624 /* set find mode for completion */
625 command_mode = FIND_EXE_ONLY;
626 for (i = 0; int_buf[i]; i++)
627 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
628 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY
629 && matchBuf[pos_buf[0]]=='c'
630 && matchBuf[pos_buf[1]]=='d'
632 command_mode = FIND_DIR_ONLY;
634 command_mode = FIND_FILE_ONLY;
638 for (i = 0; int_buf[i]; i++)
641 for (--i; i >= 0; i--) {
643 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') {
644 collapse_pos(0, i + 1);
648 /* skip first not quoted '\'' or '"' */
649 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++)
651 /* collapse quote or unquote // or /~ */
652 while ((int_buf[i] & ~QUOT) == '/'
653 && ((int_buf[i+1] & ~QUOT) == '/' || (int_buf[i+1] & ~QUOT) == '~')
658 /* set only match and destroy quotes */
660 for (c = 0; pos_buf[i] >= 0; i++) {
661 matchBuf[c++] = matchBuf[pos_buf[i]];
665 /* old lenght matchBuf with quotes symbols */
666 *len_with_quotes = j ? j - pos_buf[0] : 0;
672 * display by column (original idea from ls applet,
673 * very optimized by me :)
675 static void showfiles(void)
678 int column_width = 0;
679 int nfiles = num_matches;
683 /* find the longest file name- use that as the column width */
684 for (row = 0; row < nrows; row++) {
685 l = strlen(matches[row]);
686 if (column_width < l)
689 column_width += 2; /* min space for columns */
690 ncols = cmdedit_termw / column_width;
695 nrows++; /* round up fractionals */
699 for (row = 0; row < nrows; row++) {
703 for (nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) {
704 printf("%s%-*s", matches[n],
705 (int)(column_width - strlen(matches[n])), "");
707 printf("%s\n", matches[n]);
711 static char *add_quote_for_spec_chars(char *found)
714 char *s = xmalloc((strlen(found) + 1) * 2);
717 if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found))
725 static int match_compare(const void *a, const void *b)
727 return strcmp(*(char**)a, *(char**)b);
730 /* Do TAB completion */
731 static void input_tab(int *lastWasTab)
733 if (!(state->flags & TAB_COMPLETION))
739 char matchBuf[BUFSIZ];
743 *lastWasTab = TRUE; /* flop trigger */
745 /* Make a local copy of the string -- up
746 * to the position of the cursor */
747 tmp = strncpy(matchBuf, command_ps, cursor);
750 find_type = find_match(matchBuf, &recalc_pos);
752 /* Free up any memory already allocated */
753 free_tab_completion_data();
755 #if ENABLE_FEATURE_USERNAME_COMPLETION
756 /* If the word starts with `~' and there is no slash in the word,
757 * then try completing this word as a username. */
758 if (state->flags & USERNAME_COMPLETION)
759 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0)
760 username_tab_completion(matchBuf, NULL);
762 /* Try to match any executable in our path and everything
763 * in the current working directory */
765 exe_n_cwd_tab_completion(matchBuf, find_type);
766 /* Sort, then remove any duplicates found */
769 qsort(matches, num_matches, sizeof(char*), match_compare);
770 for (i = 0; i < num_matches - 1; ++i) {
771 if (matches[i] && matches[i+1]) { /* paranoia */
772 if (strcmp(matches[i], matches[i+1]) == 0) {
774 matches[i] = NULL; /* paranoia */
776 matches[n++] = matches[i];
780 matches[n] = matches[i];
783 /* Did we find exactly one match? */
784 if (!matches || num_matches > 1) {
787 return; /* not found */
788 /* find minimal match */
789 tmp1 = xstrdup(matches[0]);
790 for (tmp = tmp1; *tmp; tmp++)
791 for (len_found = 1; len_found < num_matches; len_found++)
792 if (matches[len_found][(tmp - tmp1)] != *tmp) {
796 if (*tmp1 == '\0') { /* have unique */
800 tmp = add_quote_for_spec_chars(tmp1);
802 } else { /* one match */
803 tmp = add_quote_for_spec_chars(matches[0]);
804 /* for next completion current found */
807 len_found = strlen(tmp);
808 if (tmp[len_found-1] != '/') {
809 tmp[len_found] = ' ';
810 tmp[len_found+1] = '\0';
813 len_found = strlen(tmp);
814 /* have space to placed match? */
815 if ((len_found - strlen(matchBuf) + command_len) < BUFSIZ) {
816 /* before word for match */
817 command_ps[cursor - recalc_pos] = 0;
819 strcpy(matchBuf, command_ps + cursor);
821 strcat(command_ps, tmp);
823 strcat(command_ps, matchBuf);
824 /* back to begin word for match */
825 input_backward(recalc_pos);
827 recalc_pos = cursor + len_found;
829 command_len = strlen(command_ps);
830 /* write out the matched command */
831 redraw(cmdedit_y, command_len - recalc_pos);
835 /* Ok -- the last char was a TAB. Since they
836 * just hit TAB again, print a list of all the
837 * available choices... */
838 if (matches && num_matches > 0) {
839 int sav_cursor = cursor; /* change goto_new_line() */
841 /* Go to the next line */
844 redraw(0, command_len - sav_cursor);
850 #define input_tab(a) ((void)0)
851 #endif /* FEATURE_COMMAND_TAB_COMPLETION */
856 /* state->flags is already checked to be nonzero */
857 static void get_previous_history(void)
859 if (command_ps[0] != '\0' || state->history[state->cur_history] == NULL) {
860 free(state->history[state->cur_history]);
861 state->history[state->cur_history] = xstrdup(command_ps);
863 state->cur_history--;
866 static int get_next_history(void)
868 if (state->flags & DO_HISTORY) {
869 int ch = state->cur_history;
870 if (ch < state->cnt_history) {
871 get_previous_history(); /* save the current history line */
872 state->cur_history = ch + 1;
873 return state->cur_history;
880 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
881 /* state->flags is already checked to be nonzero */
882 static void load_history(const char *fromfile)
888 for (hi = state->cnt_history; hi > 0;) {
890 free(state->history[hi]);
893 fp = fopen(fromfile, "r");
895 for (hi = 0; hi < MAX_HISTORY;) {
896 char * hl = xmalloc_getline(fp);
904 if (l == 0 || hl[0] == ' ') {
908 state->history[hi++] = hl;
912 state->cur_history = state->cnt_history = hi;
915 /* state->flags is already checked to be nonzero */
916 static void save_history(const char *tofile)
920 fp = fopen(tofile, "w");
924 for (i = 0; i < state->cnt_history; i++) {
925 fprintf(fp, "%s\n", state->history[i]);
931 #define load_history(a) ((void)0)
932 #define save_history(a) ((void)0)
933 #endif /* FEATURE_COMMAND_SAVEHISTORY */
935 static void remember_in_history(const char *str)
939 if (!(state->flags & DO_HISTORY))
942 i = state->cnt_history;
943 free(state->history[MAX_HISTORY]);
944 state->history[MAX_HISTORY] = NULL;
945 /* After max history, remove the oldest command */
946 if (i >= MAX_HISTORY) {
947 free(state->history[0]);
948 for (i = 0; i < MAX_HISTORY-1; i++)
949 state->history[i] = state->history[i+1];
951 // Maybe "if (!i || strcmp(history[i-1], command) != 0) ..."
952 // (i.e. do not save dups?)
953 state->history[i++] = xstrdup(str);
954 state->cur_history = i;
955 state->cnt_history = i;
956 if (state->flags & SAVE_HISTORY)
957 save_history(state->hist_file);
958 USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;)
961 #else /* MAX_HISTORY == 0 */
962 #define remember_in_history(a) ((void)0)
963 #endif /* MAX_HISTORY */
967 * This function is used to grab a character buffer
968 * from the input file descriptor and allows you to
969 * a string with full command editing (sort of like
972 * The following standard commands are not implemented:
973 * ESC-b -- Move back one word
974 * ESC-f -- Move forward one word
975 * ESC-d -- Delete back one word
976 * ESC-h -- Delete forward one word
977 * CTL-t -- Transpose two characters
979 * Minimalist vi-style command line editing available if configured.
980 * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us>
983 #if ENABLE_FEATURE_EDITING_VI
985 vi_Word_motion(char *command, int eat)
987 while (cursor < command_len && !isspace(command[cursor]))
989 if (eat) while (cursor < command_len && isspace(command[cursor]))
994 vi_word_motion(char *command, int eat)
996 if (isalnum(command[cursor]) || command[cursor] == '_') {
997 while (cursor < command_len
998 && (isalnum(command[cursor+1]) || command[cursor+1] == '_'))
1000 } else if (ispunct(command[cursor])) {
1001 while (cursor < command_len && ispunct(command[cursor+1]))
1005 if (cursor < command_len)
1008 if (eat && cursor < command_len && isspace(command[cursor]))
1009 while (cursor < command_len && isspace(command[cursor]))
1014 vi_End_motion(char *command)
1017 while (cursor < command_len && isspace(command[cursor]))
1019 while (cursor < command_len-1 && !isspace(command[cursor+1]))
1024 vi_end_motion(char *command)
1026 if (cursor >= command_len-1)
1029 while (cursor < command_len-1 && isspace(command[cursor]))
1031 if (cursor >= command_len-1)
1033 if (isalnum(command[cursor]) || command[cursor] == '_') {
1034 while (cursor < command_len-1
1035 && (isalnum(command[cursor+1]) || command[cursor+1] == '_')
1039 } else if (ispunct(command[cursor])) {
1040 while (cursor < command_len-1 && ispunct(command[cursor+1]))
1046 vi_Back_motion(char *command)
1048 while (cursor > 0 && isspace(command[cursor-1]))
1050 while (cursor > 0 && !isspace(command[cursor-1]))
1055 vi_back_motion(char *command)
1060 while (cursor > 0 && isspace(command[cursor]))
1064 if (isalnum(command[cursor]) || command[cursor] == '_') {
1066 && (isalnum(command[cursor-1]) || command[cursor-1] == '_')
1070 } else if (ispunct(command[cursor])) {
1071 while (cursor > 0 && ispunct(command[cursor-1]))
1079 * read_line_input and its helpers
1082 #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
1083 static void parse_prompt(const char *prmt_ptr)
1085 cmdedit_prompt = prmt_ptr;
1086 cmdedit_prmt_len = strlen(prmt_ptr);
1090 static void parse_prompt(const char *prmt_ptr)
1093 size_t cur_prmt_len = 0;
1094 char flg_not_length = '[';
1095 char *prmt_mem_ptr = xzalloc(1);
1096 char *pwd_buf = xrealloc_getcwd_or_warn(NULL);
1097 char buf2[PATH_MAX + 1];
1102 cmdedit_prmt_len = 0;
1105 pwd_buf = (char *)bb_msg_unknown;
1113 const char *cp = prmt_ptr;
1116 c = bb_process_escape_sequence(&prmt_ptr);
1117 if (prmt_ptr == cp) {
1122 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1128 pbuf = hostname_buf;
1130 pbuf = xzalloc(256);
1131 if (gethostname(pbuf, 255) < 0) {
1134 char *s = strchr(pbuf, '.');
1138 hostname_buf = pbuf;
1142 c = (geteuid() == 0 ? '#' : '$');
1144 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1147 l = strlen(home_pwd_buf);
1148 if (home_pwd_buf[0] != 0
1149 && strncmp(home_pwd_buf, pbuf, l) == 0
1150 && (pbuf[l]=='/' || pbuf[l]=='\0')
1151 && strlen(pwd_buf+l)<PATH_MAX
1155 strcpy(pbuf+1, pwd_buf+l);
1161 cp = strrchr(pbuf,'/');
1162 if (cp != NULL && cp != pbuf)
1163 pbuf += (cp-pbuf) + 1;
1167 snprintf(buf2, sizeof(buf2), "%d", num_ok_lines);
1169 case 'e': case 'E': /* \e \E = \033 */
1173 for (l = 0; l < 3;) {
1175 buf2[l++] = *prmt_ptr;
1177 h = strtol(buf2, &pbuf, 16);
1178 if (h > UCHAR_MAX || (pbuf - buf2) < l) {
1185 c = (char)strtol(buf2, NULL, 16);
1191 if (c == flg_not_length) {
1192 flg_not_length = flg_not_length == '[' ? ']' : '[';
1201 cur_prmt_len = strlen(pbuf);
1202 prmt_len += cur_prmt_len;
1203 if (flg_not_length != ']')
1204 cmdedit_prmt_len += cur_prmt_len;
1205 prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
1207 if (pwd_buf != (char *)bb_msg_unknown)
1209 cmdedit_prompt = prmt_mem_ptr;
1214 #define setTermSettings(fd, argp) tcsetattr(fd, TCSANOW, argp)
1215 #define getTermSettings(fd, argp) tcgetattr(fd, argp);
1217 static sighandler_t previous_SIGWINCH_handler;
1219 static void cmdedit_setwidth(unsigned w, int redraw_flg)
1223 /* new y for current cursor */
1224 int new_y = (cursor + cmdedit_prmt_len) / w;
1226 redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), command_len - cursor);
1231 static void win_changed(int nsig)
1234 get_terminal_width_height(0, &width, NULL);
1235 cmdedit_setwidth(width, nsig /* - just a yes/no flag */);
1236 if (nsig == SIGWINCH)
1237 signal(SIGWINCH, win_changed); /* rearm ourself */
1241 * The emacs and vi modes share much of the code in the big
1242 * command loop. Commands entered when in vi's command mode (aka
1243 * "escape mode") get an extra bit added to distinguish them --
1244 * this keeps them from being self-inserted. This clutters the
1245 * big switch a bit, but keeps all the code in one place.
1250 /* leave out the "vi-mode"-only case labels if vi editing isn't
1252 #define vi_case(caselabel) USE_FEATURE_EDITING(case caselabel)
1254 /* convert uppercase ascii to equivalent control char, for readability */
1256 #define CTRL(a) ((a) & ~0x40)
1258 int read_line_input(const char* prompt, char* command, int maxsize, line_input_t *st)
1260 int lastWasTab = FALSE;
1263 smallint break_out = 0;
1264 #if ENABLE_FEATURE_EDITING_VI
1265 smallint vi_cmdmode = 0;
1269 // FIXME: audit & improve this
1270 if (maxsize > BUFSIZ)
1273 /* With null flags, no other fields are ever used */
1274 state = st ? st : (line_input_t*) &const_int_0;
1275 #if ENABLE_FEATURE_EDITING_SAVEHISTORY
1276 if (state->flags & SAVE_HISTORY)
1277 load_history(state->hist_file);
1280 /* prepare before init handlers */
1281 cmdedit_y = 0; /* quasireal y, not true if line > xt*yt */
1283 command_ps = command;
1286 getTermSettings(0, (void *) &initial_settings);
1287 memcpy(&new_settings, &initial_settings, sizeof(new_settings));
1288 new_settings.c_lflag &= ~ICANON; /* unbuffered input */
1289 /* Turn off echoing and CTRL-C, so we can trap it */
1290 new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG);
1291 /* Hmm, in linux c_cc[] is not parsed if ICANON is off */
1292 new_settings.c_cc[VMIN] = 1;
1293 new_settings.c_cc[VTIME] = 0;
1294 /* Turn off CTRL-C, so we can trap it */
1295 #ifndef _POSIX_VDISABLE
1296 #define _POSIX_VDISABLE '\0'
1298 new_settings.c_cc[VINTR] = _POSIX_VDISABLE;
1299 setTermSettings(0, (void *) &new_settings);
1301 /* Now initialize things */
1302 previous_SIGWINCH_handler = signal(SIGWINCH, win_changed);
1303 win_changed(0); /* do initial resizing */
1304 #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR
1306 struct passwd *entry;
1308 entry = getpwuid(geteuid());
1310 user_buf = xstrdup(entry->pw_name);
1311 home_pwd_buf = xstrdup(entry->pw_dir);
1315 #if ENABLE_FEATURE_TAB_COMPLETION
1319 /* Print out the command prompt */
1320 parse_prompt(prompt);
1325 if (safe_read(0, &c, 1) < 1) {
1326 /* if we can't read input then exit */
1327 goto prepare_to_die;
1332 #if ENABLE_FEATURE_EDITING_VI
1346 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1349 /* Control-a -- Beginning of line */
1350 input_backward(cursor);
1355 vi_case('\x7f'|vbit:) /* DEL */
1356 /* Control-b -- Move back one character */
1361 vi_case(CTRL('C')|vbit:)
1362 /* Control-c -- stop gathering input */
1365 break_out = -1; /* "do not append '\n'" */
1368 /* Control-d -- Delete one character, or exit
1369 * if the len=0 and no chars to delete */
1370 if (command_len == 0) {
1373 /* to control stopped jobs */
1374 break_out = command_len = -1;
1380 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1383 /* Control-e -- End of line */
1389 /* Control-f -- Move forward one character */
1395 case '\x7f': /* DEL */
1396 /* Control-h and DEL */
1401 input_tab(&lastWasTab);
1404 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1406 /* Control-k -- clear to end of line */
1407 command[cursor] = 0;
1408 command_len = cursor;
1412 vi_case(CTRL('L')|vbit:)
1413 /* Control-l -- clear screen */
1415 redraw(0, command_len - cursor);
1421 vi_case(CTRL('N')|vbit:)
1423 /* Control-n -- Get next command in history */
1424 if (get_next_history())
1428 vi_case(CTRL('P')|vbit:)
1430 /* Control-p -- Get previous command from history */
1431 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1432 get_previous_history();
1439 #if ENABLE_FEATURE_EDITING_FANCY_KEYS
1441 vi_case(CTRL('U')|vbit:)
1442 /* Control-U -- Clear line before cursor */
1444 strcpy(command, command + cursor);
1445 command_len -= cursor;
1446 redraw(cmdedit_y, command_len);
1451 vi_case(CTRL('W')|vbit:)
1452 /* Control-W -- Remove the last word */
1453 while (cursor > 0 && isspace(command[cursor-1]))
1455 while (cursor > 0 && !isspace(command[cursor-1]))
1459 #if ENABLE_FEATURE_EDITING_VI
1464 input_backward(cursor);
1485 vi_Word_motion(command, 1);
1488 vi_word_motion(command, 1);
1491 vi_End_motion(command);
1494 vi_end_motion(command);
1497 vi_Back_motion(command);
1500 vi_back_motion(command);
1515 if (safe_read(0, &c, 1) < 1)
1516 goto prepare_to_die;
1517 if (c == (prevc & 0xff)) {
1519 input_backward(cursor);
1529 case 'w': /* "dw", "cw" */
1530 vi_word_motion(command, vi_cmdmode);
1532 case 'W': /* 'dW', 'cW' */
1533 vi_Word_motion(command, vi_cmdmode);
1535 case 'e': /* 'de', 'ce' */
1536 vi_end_motion(command);
1539 case 'E': /* 'dE', 'cE' */
1540 vi_End_motion(command);
1545 input_backward(cursor - sc);
1546 while (nc-- > cursor)
1549 case 'b': /* "db", "cb" */
1550 case 'B': /* implemented as B */
1552 vi_back_motion(command);
1554 vi_Back_motion(command);
1555 while (sc-- > cursor)
1558 case ' ': /* "d ", "c " */
1561 case '$': /* "d$", "c$" */
1563 while (cursor < command_len)
1576 if (safe_read(0, &c, 1) < 1)
1577 goto prepare_to_die;
1581 *(command + cursor) = c;
1586 #endif /* FEATURE_COMMAND_EDITING_VI */
1588 case '\x1b': /* ESC */
1590 #if ENABLE_FEATURE_EDITING_VI
1591 if (state->flags & VI_MODE) {
1592 /* ESC: insert mode --> command mode */
1598 /* escape sequence follows */
1599 if (safe_read(0, &c, 1) < 1)
1600 goto prepare_to_die;
1601 /* different vt100 emulations */
1602 if (c == '[' || c == 'O') {
1605 if (safe_read(0, &c, 1) < 1)
1606 goto prepare_to_die;
1608 if (c >= '1' && c <= '9') {
1609 unsigned char dummy;
1611 if (safe_read(0, &dummy, 1) < 1)
1612 goto prepare_to_die;
1618 #if ENABLE_FEATURE_TAB_COMPLETION
1619 case '\t': /* Alt-Tab */
1620 input_tab(&lastWasTab);
1625 /* Up Arrow -- Get previous command from history */
1626 if ((state->flags & DO_HISTORY) && state->cur_history > 0) {
1627 get_previous_history();
1633 /* Down Arrow -- Get next command in history */
1634 if (!get_next_history())
1637 /* Rewrite the line with the selected history item */
1638 /* change command */
1639 command_len = strlen(strcpy(command, state->history[state->cur_history]));
1640 /* redraw and go to eol (bol, in vi */
1641 redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
1645 /* Right Arrow -- Move forward one character */
1649 /* Left Arrow -- Move back one character */
1659 input_backward(cursor);
1672 default: /* If it's regular input, do the normal thing */
1673 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1674 /* Control-V -- Add non-printable symbol */
1675 if (c == CTRL('V')) {
1676 if (safe_read(0, &c, 1) < 1)
1677 goto prepare_to_die;
1685 #if ENABLE_FEATURE_EDITING_VI
1686 if (vi_cmdmode) /* Don't self-insert */
1689 if (!Isprint(c)) /* Skip non-printable characters */
1692 if (command_len >= (maxsize - 2)) /* Need to leave space for enter */
1696 if (cursor == (command_len - 1)) { /* Append if at the end of the line */
1697 command[cursor] = c;
1698 command[cursor+1] = '\0';
1699 cmdedit_set_out_char(' ');
1700 } else { /* Insert otherwise */
1703 memmove(command + sc + 1, command + sc, command_len - sc);
1706 /* rewrite from cursor */
1708 /* to prev x pos + 1 */
1709 input_backward(cursor - sc);
1713 if (break_out) /* Enter is the command terminator, no more input. */
1720 if (command_len > 0)
1721 remember_in_history(command);
1723 if (break_out > 0) {
1724 command[command_len++] = '\n';
1725 command[command_len] = '\0';
1728 #if ENABLE_FEATURE_CLEAN_UP && ENABLE_FEATURE_TAB_COMPLETION
1729 free_tab_completion_data();
1732 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1733 free((char*)cmdedit_prompt);
1735 /* restore initial_settings */
1736 setTermSettings(STDIN_FILENO, (void *) &initial_settings);
1737 /* restore SIGWINCH handler */
1738 signal(SIGWINCH, previous_SIGWINCH_handler);
1743 line_input_t *new_line_input_t(int flags)
1745 line_input_t *n = xzalloc(sizeof(*n));
1752 #undef read_line_input
1753 int read_line_input(const char* prompt, char* command, int maxsize)
1755 fputs(prompt, stdout);
1757 fgets(command, maxsize, stdin);
1758 return strlen(command);
1761 #endif /* FEATURE_COMMAND_EDITING */
1772 const char *applet_name = "debug stuff usage";
1774 int main(int argc, char **argv)
1778 #if ENABLE_FEATURE_EDITING_FANCY_PROMPT
1779 "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:"
1780 "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] "
1781 "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]";
1786 #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT
1787 setlocale(LC_ALL, "");
1791 l = read_line_input(prompt, buff);
1792 if (l <= 0 || buff[l-1] != '\n')
1795 printf("*** read_line_input() returned line =%s=\n", buff);
1797 printf("*** read_line_input() detect ^D\n");