X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=libbb%2Flineedit.c;h=db8416712f40f5a8cc55adaadde7fe4ede6e2290;hb=eced0c78a54bbecc61f1717d33f64ee7d99804bb;hp=a917c5f925d69eab62516829abfa853ca2fe485b;hpb=76939e7b72937a5a2d00be3f62a6d2fa2757bdc9;p=oweals%2Fbusybox.git diff --git a/libbb/lineedit.c b/libbb/lineedit.c index a917c5f92..db8416712 100644 --- a/libbb/lineedit.c +++ b/libbb/lineedit.c @@ -41,6 +41,10 @@ */ #include "libbb.h" #include "unicode.h" +#ifndef _POSIX_VDISABLE +# define _POSIX_VDISABLE '\0' +#endif + #ifdef TEST # define ENABLE_FEATURE_EDITING 0 @@ -94,8 +98,10 @@ static bool BB_ispunct(CHAR_T c) { return ((unsigned)c < 256 && ispunct(c)); } #endif -#define SEQ_CLEAR_TILL_END_OF_SCREEN "\033[J" -//#define SEQ_CLEAR_TILL_END_OF_LINE "\033[K" +#define ESC "\033" + +#define SEQ_CLEAR_TILL_END_OF_SCREEN ESC"[J" +//#define SEQ_CLEAR_TILL_END_OF_LINE ESC"[K" enum { @@ -182,6 +188,7 @@ extern struct lineedit_statics *const lineedit_ptr_to_statics; IF_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines = 1;) \ IF_USERNAME_OR_HOMEDIR(home_pwd_buf = (char*)null_str;) \ } while (0) + static void deinit_S(void) { #if ENABLE_FEATURE_EDITING_FANCY_PROMPT @@ -200,67 +207,87 @@ static void deinit_S(void) #if ENABLE_UNICODE_SUPPORT -static size_t load_string(const char *src, int maxsize) +static size_t load_string(const char *src) { - ssize_t len = mbstowcs(command_ps, src, maxsize - 1); - if (len < 0) - len = 0; - command_ps[len] = BB_NUL; - return len; + if (unicode_status == UNICODE_ON) { + ssize_t len = mbstowcs(command_ps, src, S.maxsize - 1); + if (len < 0) + len = 0; + command_ps[len] = BB_NUL; + return len; + } else { + unsigned i = 0; + while (src[i] && i < S.maxsize - 1) { + command_ps[i] = src[i]; + i++; + } + command_ps[i] = BB_NUL; + return i; + } } static unsigned save_string(char *dst, unsigned maxsize) { + if (unicode_status == UNICODE_ON) { # if !ENABLE_UNICODE_PRESERVE_BROKEN - ssize_t len = wcstombs(dst, command_ps, maxsize - 1); - if (len < 0) - len = 0; - dst[len] = '\0'; - return len; + ssize_t len = wcstombs(dst, command_ps, maxsize - 1); + if (len < 0) + len = 0; + dst[len] = '\0'; + return len; # else - unsigned dstpos = 0; - unsigned srcpos = 0; + unsigned dstpos = 0; + unsigned srcpos = 0; - maxsize--; - while (dstpos < maxsize) { - wchar_t wc; - int n = srcpos; + maxsize--; + while (dstpos < maxsize) { + wchar_t wc; + int n = srcpos; - /* Convert up to 1st invalid byte (or up to end) */ - while ((wc = command_ps[srcpos]) != BB_NUL - && !unicode_is_raw_byte(wc) - ) { + /* Convert up to 1st invalid byte (or up to end) */ + while ((wc = command_ps[srcpos]) != BB_NUL + && !unicode_is_raw_byte(wc) + ) { + srcpos++; + } + command_ps[srcpos] = BB_NUL; + n = wcstombs(dst + dstpos, command_ps + n, maxsize - dstpos); + if (n < 0) /* should not happen */ + break; + dstpos += n; + if (wc == BB_NUL) /* usually is */ + break; + + /* We do have invalid byte here! */ + command_ps[srcpos] = wc; /* restore it */ srcpos++; + if (dstpos == maxsize) + break; + dst[dstpos++] = (char) wc; } - command_ps[srcpos] = BB_NUL; - n = wcstombs(dst + dstpos, command_ps + n, maxsize - dstpos); - if (n < 0) /* should not happen */ - break; - dstpos += n; - if (wc == BB_NUL) /* usually is */ - break; - - /* We do have invalid byte here! */ - command_ps[srcpos] = wc; /* restore it */ - srcpos++; - if (dstpos == maxsize) - break; - dst[dstpos++] = (char) wc; - } - dst[dstpos] = '\0'; - return dstpos; + dst[dstpos] = '\0'; + return dstpos; # endif + } else { + unsigned i = 0; + while ((dst[i] = command_ps[i]) != 0) + i++; + return i; + } } /* I thought just fputwc(c, stdout) would work. But no... */ static void BB_PUTCHAR(wchar_t c) { - char buf[MB_CUR_MAX + 1]; - mbstate_t mbst = { 0 }; - ssize_t len; - - len = wcrtomb(buf, c, &mbst); - if (len > 0) { - buf[len] = '\0'; - fputs(buf, stdout); + if (unicode_status == UNICODE_ON) { + char buf[MB_CUR_MAX + 1]; + mbstate_t mbst = { 0 }; + ssize_t len = wcrtomb(buf, c, &mbst); + if (len > 0) { + buf[len] = '\0'; + fputs(buf, stdout); + } + } else { + /* In this case, c is always one byte */ + putchar(c); } } # if ENABLE_UNICODE_COMBINING_WCHARS || ENABLE_UNICODE_WIDE_WCHARS @@ -295,9 +322,9 @@ static wchar_t adjust_width_and_validate_wc(wchar_t wc) return wc; } #else /* !UNICODE */ -static size_t load_string(const char *src, int maxsize) +static size_t load_string(const char *src) { - safe_strncpy(command_ps, src, maxsize); + safe_strncpy(command_ps, src, S.maxsize); return strlen(command_ps); } # if ENABLE_FEATURE_TAB_COMPLETION @@ -446,7 +473,7 @@ static void input_backward(unsigned num) } while (--num); return; } - printf("\033[%uD", num); + printf(ESC"[%uD", num); return; } @@ -471,7 +498,7 @@ static void input_backward(unsigned num) */ unsigned sv_cursor; /* go to 1st column; go up to first line */ - printf("\r" "\033[%uA", cmdedit_y); + printf("\r" ESC"[%uA", cmdedit_y); cmdedit_y = 0; sv_cursor = cursor; put_prompt(); /* sets cursor to 0 */ @@ -488,12 +515,12 @@ static void input_backward(unsigned num) cmdedit_x = (width * cmdedit_y - num) % width; cmdedit_y -= lines_up; /* go to 1st column; go up */ - printf("\r" "\033[%uA", lines_up); + printf("\r" ESC"[%uA", lines_up); /* go to correct column. * xterm, konsole, Linux VT interpret 0 as 1 below! wow. * need to *make sure* we skip it if cmdedit_x == 0 */ if (cmdedit_x) - printf("\033[%uC", cmdedit_x); + printf(ESC"[%uC", cmdedit_x); } } @@ -501,7 +528,7 @@ static void input_backward(unsigned num) static void redraw(int y, int back_cursor) { if (y > 0) /* up y lines */ - printf("\033[%uA", y); + printf(ESC"[%uA", y); bb_putchar('\r'); put_prompt(); put_till_end_and_adv_cursor(); @@ -582,6 +609,12 @@ static void input_forward(void) #if ENABLE_FEATURE_TAB_COMPLETION +//FIXME: +//needs to be more clever: currently it thinks that "foo\ b +//matches the file named "foo bar", which is untrue. +//Also, perhaps "foo b needs to complete to "foo bar" , +//not "foo bar ... + static void free_tab_completion_data(void) { if (matches) { @@ -599,7 +632,7 @@ static void add_match(char *matched) num_matches++; } -#if ENABLE_FEATURE_USERNAME_COMPLETION +# if ENABLE_FEATURE_USERNAME_COMPLETION /* Replace "~user/..." with "/homedir/...". * The parameter is malloced, free it or return it * unchanged if no user is matched. @@ -655,7 +688,7 @@ static NOINLINE unsigned complete_username(const char *ud) return 1 + userlen; } -#endif /* FEATURE_USERNAME_COMPLETION */ +# endif /* FEATURE_USERNAME_COMPLETION */ enum { FIND_EXE_ONLY = 0, @@ -732,10 +765,10 @@ static NOINLINE unsigned complete_cmd_dir_file(const char *command, int type) pfind++; /* dirbuf = ".../.../.../" */ dirbuf = xstrndup(command, pfind - command); -#if ENABLE_FEATURE_USERNAME_COMPLETION +# if ENABLE_FEATURE_USERNAME_COMPLETION if (dirbuf[0] == '~') /* ~/... or ~user/... */ dirbuf = username_path_completion(dirbuf); -#endif +# endif path1[0] = dirbuf; } pf_len = strlen(pfind); @@ -751,6 +784,7 @@ static NOINLINE unsigned complete_cmd_dir_file(const char *command, int type) continue; /* don't print an error */ while ((next = readdir(dir)) != NULL) { + unsigned len; const char *name_found = next->d_name; /* .../: bash 3.2.0 shows dotfiles, but not . and .. */ @@ -767,18 +801,15 @@ static NOINLINE unsigned complete_cmd_dir_file(const char *command, int type) if (stat(found, &st) && lstat(found, &st)) goto cont; /* hmm, remove in progress? */ - /* save only name if we scan PATH */ - if (paths[i] != dirbuf) - strcpy(found, name_found); + /* Save only name */ + len = strlen(name_found); + found = xrealloc(found, len + 2); /* +2: for slash and NUL */ + strcpy(found, name_found); if (S_ISDIR(st.st_mode)) { - unsigned len1 = strlen(found); - /* name is a directory */ - if (found[len1-1] != '/') { - found = xrealloc(found, len1 + 2); - found[len1] = '/'; - found[len1 + 1] = '\0'; - } + /* name is a directory, add slash */ + found[len] = '/'; + found[len + 1] = '\0'; } else { /* skip files if looking for dirs only (example: cd) */ if (type == FIND_DIR_ONLY) @@ -796,10 +827,8 @@ static NOINLINE unsigned complete_cmd_dir_file(const char *command, int type) if (paths != path1) { free(paths[0]); /* allocated memory is only in first member */ free(paths); - } else if (dirbuf) { - pf_len += strlen(dirbuf); - free(dirbuf); } + free(dirbuf); return pf_len; } @@ -1017,13 +1046,18 @@ static void showfiles(void) } } -static char *add_quote_for_spec_chars(char *found) +static const char *is_special_char(char c) +{ + return strchr(" `\"#$%^&*()=+{}[]:;'|\\<>", c); +} + +static char *quote_special_chars(char *found) { int l = 0; char *s = xzalloc((strlen(found) + 1) * 2); while (*found) { - if (strchr(" `\"#$%^&*()=+{}[]:;'|\\<>", *found)) + if (is_special_char(*found)) s[l++] = '\\'; s[l++] = *found++; } @@ -1040,10 +1074,10 @@ static NOINLINE void input_tab(smallint *lastWasTab) /* Length of string used for matching */ unsigned match_pfx_len = match_pfx_len; int find_type; -#if ENABLE_UNICODE_SUPPORT +# if ENABLE_UNICODE_SUPPORT /* cursor pos in command converted to multibyte form */ int cursor_mb; -#endif +# endif if (!(state->flags & TAB_COMPLETION)) return; @@ -1070,9 +1104,9 @@ static NOINLINE void input_tab(smallint *lastWasTab) * (we then also (ab)use this extra space later - see (**)) */ match_buf = xmalloc(MAX_LINELEN * sizeof(int16_t)); -#if !ENABLE_UNICODE_SUPPORT +# if !ENABLE_UNICODE_SUPPORT save_string(match_buf, cursor + 1); /* +1 for NUL */ -#else +# else { CHAR_T wc = command_ps[cursor]; command_ps[cursor] = BB_NUL; @@ -1080,26 +1114,37 @@ static NOINLINE void input_tab(smallint *lastWasTab) command_ps[cursor] = wc; cursor_mb = strlen(match_buf); } -#endif +# endif find_type = build_match_prefix(match_buf); /* Free up any memory already allocated */ free_tab_completion_data(); -#if ENABLE_FEATURE_USERNAME_COMPLETION - /* If the word starts with `~' and there is no slash in the word, +# if ENABLE_FEATURE_USERNAME_COMPLETION + /* If the word starts with ~ and there is no slash in the word, * then try completing this word as a username. */ if (state->flags & USERNAME_COMPLETION) if (match_buf[0] == '~' && strchr(match_buf, '/') == NULL) match_pfx_len = complete_username(match_buf); -#endif - /* Try to match a command in $PATH, or a directory, or a file */ +# endif + /* If complete_username() did not match, + * try to match a command in $PATH, or a directory, or a file */ if (!matches) match_pfx_len = complete_cmd_dir_file(match_buf, find_type); + + /* Account for backslashes which will be inserted + * by quote_special_chars() later */ + { + const char *e = match_buf + strlen(match_buf); + const char *s = e - match_pfx_len; + while (s < e) + if (is_special_char(*s++)) + match_pfx_len++; + } + /* Remove duplicates */ if (matches) { - unsigned i; - unsigned n = 0; + unsigned i, n = 0; qsort_string_vector(matches, num_matches); for (i = 0; i < num_matches - 1; ++i) { //if (matches[i] && matches[i+1]) { /* paranoia */ @@ -1114,6 +1159,7 @@ static NOINLINE void input_tab(smallint *lastWasTab) matches[n++] = matches[i]; num_matches = n; } + /* Did we find exactly one match? */ if (num_matches != 1) { /* no */ char *cp; @@ -1135,7 +1181,7 @@ static NOINLINE void input_tab(smallint *lastWasTab) goto ret; /* no */ } *cp = '\0'; - cp = add_quote_for_spec_chars(chosen_match); + cp = quote_special_chars(chosen_match); free(chosen_match); chosen_match = cp; len_found = strlen(chosen_match); @@ -1143,7 +1189,7 @@ static NOINLINE void input_tab(smallint *lastWasTab) /* Next is not a double-tab */ *lastWasTab = 0; - chosen_match = add_quote_for_spec_chars(matches[0]); + chosen_match = quote_special_chars(matches[0]); len_found = strlen(chosen_match); if (chosen_match[len_found-1] != '/') { chosen_match[len_found] = ' '; @@ -1151,7 +1197,7 @@ static NOINLINE void input_tab(smallint *lastWasTab) } } -#if !ENABLE_UNICODE_SUPPORT +# if !ENABLE_UNICODE_SUPPORT /* Have space to place the match? */ /* The result consists of three parts with these lengths: */ /* cursor + (len_found - match_pfx_len) + (command_len - cursor) */ @@ -1168,7 +1214,7 @@ static NOINLINE void input_tab(smallint *lastWasTab) /* write out the matched command */ redraw(cmdedit_y, command_len - pos); } -#else +# else { /* Use 2nd half of match_buf as scratch space - see (**) */ char *command = match_buf + MAX_LINELEN; @@ -1181,10 +1227,10 @@ static NOINLINE void input_tab(smallint *lastWasTab) strcpy(match_buf, &command[cursor_mb]); /* where do we want to have cursor after all? */ strcpy(&command[cursor_mb], chosen_match + match_pfx_len); - len = load_string(command, S.maxsize); + len = load_string(command); /* add match and tail */ sprintf(&command[cursor_mb], "%s%s", chosen_match + match_pfx_len, match_buf); - command_len = load_string(command, S.maxsize); + command_len = load_string(command); /* write out the matched command */ /* paranoia: load_string can return 0 on conv error, * prevent passing pos = (0 - 12) to redraw */ @@ -1192,7 +1238,7 @@ static NOINLINE void input_tab(smallint *lastWasTab) redraw(cmdedit_y, pos >= 0 ? pos : 0); } } -#endif +# endif ret: free(chosen_match); free(match_buf); @@ -1205,12 +1251,26 @@ line_input_t* FAST_FUNC new_line_input_t(int flags) { line_input_t *n = xzalloc(sizeof(*n)); n->flags = flags; + n->max_history = MAX_HISTORY; return n; } #if MAX_HISTORY > 0 +unsigned size_from_HISTFILESIZE(const char *hp) +{ + int size = MAX_HISTORY; + if (hp) { + size = atoi(hp); + if (size <= 0) + return 1; + if (size > MAX_HISTORY) + return MAX_HISTORY; + } + return size; +} + static void save_command_ps_at_cur_history(void) { if (command_ps[0] != BB_NUL) { @@ -1291,7 +1351,9 @@ static void load_history(line_input_t *st_parm) /* fill temp_h[], retaining only last MAX_HISTORY lines */ memset(temp_h, 0, sizeof(temp_h)); - st_parm->cnt_history_in_file = idx = 0; + idx = 0; + if (!ENABLE_FEATURE_EDITING_SAVE_ON_EXIT) + st_parm->cnt_history_in_file = 0; while ((line = xmalloc_fgetline(fp)) != NULL) { if (line[0] == '\0') { free(line); @@ -1299,9 +1361,10 @@ static void load_history(line_input_t *st_parm) } free(temp_h[idx]); temp_h[idx] = line; - st_parm->cnt_history_in_file++; + if (!ENABLE_FEATURE_EDITING_SAVE_ON_EXIT) + st_parm->cnt_history_in_file++; idx++; - if (idx == MAX_HISTORY) + if (idx == st_parm->max_history) idx = 0; } fclose(fp); @@ -1310,18 +1373,18 @@ static void load_history(line_input_t *st_parm) if (st_parm->cnt_history_in_file) { while (temp_h[idx] == NULL) { idx++; - if (idx == MAX_HISTORY) + if (idx == st_parm->max_history) idx = 0; } } /* copy temp_h[] to st_parm->history[] */ - for (i = 0; i < MAX_HISTORY;) { + for (i = 0; i < st_parm->max_history;) { line = temp_h[idx]; if (!line) break; idx++; - if (idx == MAX_HISTORY) + if (idx == st_parm->max_history) idx = 0; line_len = strlen(line); if (line_len >= MAX_LINELEN) @@ -1329,16 +1392,63 @@ static void load_history(line_input_t *st_parm) st_parm->history[i++] = line; } st_parm->cnt_history = i; + if (ENABLE_FEATURE_EDITING_SAVE_ON_EXIT) + st_parm->cnt_history_in_file = i; } } -/* state->flags is already checked to be nonzero */ +# if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT +void save_history(line_input_t *st) +{ + FILE *fp; + + if (!st->hist_file) + return; + if (st->cnt_history <= st->cnt_history_in_file) + return; + + fp = fopen(st->hist_file, "a"); + if (fp) { + int i, fd; + char *new_name; + line_input_t *st_temp; + + for (i = st->cnt_history_in_file; i < st->cnt_history; i++) + fprintf(fp, "%s\n", st->history[i]); + fclose(fp); + + /* we may have concurrently written entries from others. + * load them */ + st_temp = new_line_input_t(st->flags); + st_temp->hist_file = st->hist_file; + st_temp->max_history = st->max_history; + load_history(st_temp); + + /* write out temp file and replace hist_file atomically */ + new_name = xasprintf("%s.%u.new", st->hist_file, (int) getpid()); + fd = open(new_name, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd >= 0) { + fp = xfdopen_for_write(fd); + for (i = 0; i < st_temp->cnt_history; i++) + fprintf(fp, "%s\n", st_temp->history[i]); + fclose(fp); + if (rename(new_name, st->hist_file) == 0) + st->cnt_history_in_file = st_temp->cnt_history; + } + free(new_name); + free_line_input_t(st_temp); + } +} +# else static void save_history(char *str) { int fd; int len, len2; - fd = open(state->hist_file, O_WRONLY | O_CREAT | O_APPEND, 0666); + if (!state->hist_file) + return; + + fd = open(state->hist_file, O_WRONLY | O_CREAT | O_APPEND, 0600); if (fd < 0) return; xlseek(fd, 0, SEEK_END); /* paranoia */ @@ -1352,22 +1462,25 @@ static void save_history(char *str) /* did we write so much that history file needs trimming? */ state->cnt_history_in_file++; - if (state->cnt_history_in_file > MAX_HISTORY * 4) { - FILE *fp; + if (state->cnt_history_in_file > state->max_history * 4) { char *new_name; line_input_t *st_temp; - int i; /* we may have concurrently written entries from others. * load them */ st_temp = new_line_input_t(state->flags); st_temp->hist_file = state->hist_file; + st_temp->max_history = state->max_history; load_history(st_temp); /* write out temp file and replace hist_file atomically */ new_name = xasprintf("%s.%u.new", state->hist_file, (int) getpid()); - fp = fopen_for_write(new_name); - if (fp) { + fd = open(new_name, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd >= 0) { + FILE *fp; + int i; + + fp = xfdopen_for_write(fd); for (i = 0; i < st_temp->cnt_history; i++) fprintf(fp, "%s\n", st_temp->history[i]); fclose(fp); @@ -1378,6 +1491,7 @@ static void save_history(char *str) free_line_input_t(st_temp); } } +# endif # else # define load_history(a) ((void)0) # define save_history(a) ((void)0) @@ -1396,25 +1510,28 @@ static void remember_in_history(char *str) if (i && strcmp(state->history[i-1], str) == 0) return; - free(state->history[MAX_HISTORY]); /* redundant, paranoia */ - state->history[MAX_HISTORY] = NULL; /* redundant, paranoia */ + free(state->history[state->max_history]); /* redundant, paranoia */ + state->history[state->max_history] = NULL; /* redundant, paranoia */ /* If history[] is full, remove the oldest command */ - /* we need to keep history[MAX_HISTORY] empty, hence >=, not > */ - if (i >= MAX_HISTORY) { + /* we need to keep history[state->max_history] empty, hence >=, not > */ + if (i >= state->max_history) { free(state->history[0]); - for (i = 0; i < MAX_HISTORY-1; i++) + for (i = 0; i < state->max_history-1; i++) state->history[i] = state->history[i+1]; - /* i == MAX_HISTORY-1 */ + /* i == state->max_history-1 */ +# if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT + if (state->cnt_history_in_file) + state->cnt_history_in_file--; +# endif } - /* i <= MAX_HISTORY-1 */ + /* i <= state->max_history-1 */ state->history[i++] = xstrdup(str); - /* i <= MAX_HISTORY */ + /* i <= state->max_history */ state->cur_history = i; state->cnt_history = i; -# if MAX_HISTORY > 0 && ENABLE_FEATURE_EDITING_SAVEHISTORY - if ((state->flags & SAVE_HISTORY) && state->hist_file) - save_history(str); +# if ENABLE_FEATURE_EDITING_SAVEHISTORY && !ENABLE_FEATURE_EDITING_SAVE_ON_EXIT + save_history(str); # endif IF_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;) } @@ -1621,7 +1738,7 @@ static void ask_terminal(void) * write(1, "~/srcdevel/bbox/fix/busybox.t4 # ", 33) = 33 * poll([{fd=0, events=POLLIN}], 1, 0) = 0 (Timeout) <-- no input exists * write(1, "\33[6n", 4) = 4 <-- send the ESC sequence, quick! - * poll([{fd=0, events=POLLIN}], 1, 4294967295) = 1 ([{fd=0, revents=POLLIN}]) + * poll([{fd=0, events=POLLIN}], 1, -1) = 1 ([{fd=0, revents=POLLIN}]) * read(0, "\n", 1) = 1 <-- oh crap, user's input got in first */ struct pollfd pfd; @@ -1630,7 +1747,7 @@ static void ask_terminal(void) pfd.events = POLLIN; if (safe_poll(&pfd, 1, 0) == 0) { S.sent_ESC_br6n = 1; - fputs("\033" "[6n", stdout); + fputs(ESC"[6n", stdout); fflush_all(); /* make terminal see it ASAP! */ } } @@ -1779,17 +1896,17 @@ static void win_changed(int nsig) { int sv_errno = errno; unsigned width; + get_terminal_width_height(0, &width, NULL); - cmdedit_setwidth(width, nsig /* - just a yes/no flag */); - if (nsig == SIGWINCH) - signal(SIGWINCH, win_changed); /* rearm ourself */ +//FIXME: cmdedit_setwidth() -> redraw() -> printf() -> KABOOM! (we are in signal handler!) + cmdedit_setwidth(width, /*redraw_flg:*/ nsig); + errno = sv_errno; } -static int lineedit_read_key(char *read_key_buffer) +static int lineedit_read_key(char *read_key_buffer, int timeout) { int64_t ic; - int timeout = -1; #if ENABLE_UNICODE_SUPPORT char unicode_buf[MB_CUR_MAX + 1]; int unicode_idx = 0; @@ -1888,13 +2005,147 @@ static int isrtl_str(void) #undef CTRL #define CTRL(a) ((a) & ~0x40) +enum { + VI_CMDMODE_BIT = 0x40000000, + /* 0x80000000 bit flags KEYCODE_xxx */ +}; + +#if ENABLE_FEATURE_REVERSE_SEARCH +/* Mimic readline Ctrl-R reverse history search. + * When invoked, it shows the following prompt: + * (reverse-i-search)'': user_input [cursor pos unchanged by Ctrl-R] + * and typing results in search being performed: + * (reverse-i-search)'tmp': cd /tmp [cursor under t in /tmp] + * Search is performed by looking at progressively older lines in history. + * Ctrl-R again searches for the next match in history. + * Backspace deletes last matched char. + * Control keys exit search and return to normal editing (at current history line). + */ +static int32_t reverse_i_search(void) +{ + char match_buf[128]; /* for user input */ + char read_key_buffer[KEYCODE_BUFFER_SIZE]; + const char *matched_history_line; + const char *saved_prompt; + int32_t ic; + + matched_history_line = NULL; + read_key_buffer[0] = 0; + match_buf[0] = '\0'; + + /* Save and replace the prompt */ + saved_prompt = cmdedit_prompt; + goto set_prompt; + + while (1) { + int h; + unsigned match_buf_len = strlen(match_buf); + + fflush_all(); +//FIXME: correct timeout? + ic = lineedit_read_key(read_key_buffer, -1); + + switch (ic) { + case CTRL('R'): /* searching for the next match */ + break; + + case '\b': + case '\x7f': + /* Backspace */ + if (unicode_status == UNICODE_ON) { + while (match_buf_len != 0) { + uint8_t c = match_buf[--match_buf_len]; + if ((c & 0xc0) != 0x80) /* start of UTF-8 char? */ + break; /* yes */ + } + } else { + if (match_buf_len != 0) + match_buf_len--; + } + match_buf[match_buf_len] = '\0'; + break; + + default: + if (ic < ' ' + || (!ENABLE_UNICODE_SUPPORT && ic >= 256) + || (ENABLE_UNICODE_SUPPORT && ic >= VI_CMDMODE_BIT) + ) { + goto ret; + } + + /* Append this char */ +#if ENABLE_UNICODE_SUPPORT + if (unicode_status == UNICODE_ON) { + mbstate_t mbstate = { 0 }; + char buf[MB_CUR_MAX + 1]; + int len = wcrtomb(buf, ic, &mbstate); + if (len > 0) { + buf[len] = '\0'; + if (match_buf_len + len < sizeof(match_buf)) + strcpy(match_buf + match_buf_len, buf); + } + } else +#endif + if (match_buf_len < sizeof(match_buf) - 1) { + match_buf[match_buf_len] = ic; + match_buf[match_buf_len + 1] = '\0'; + } + break; + } /* switch (ic) */ + + /* Search in history for match_buf */ + h = state->cur_history; + if (ic == CTRL('R')) + h--; + while (h >= 0) { + if (state->history[h]) { + char *match = strstr(state->history[h], match_buf); + if (match) { + state->cur_history = h; + matched_history_line = state->history[h]; + command_len = load_string(matched_history_line); + cursor = match - matched_history_line; +//FIXME: cursor position for Unicode case + + free((char*)cmdedit_prompt); + set_prompt: + cmdedit_prompt = xasprintf("(reverse-i-search)'%s': ", match_buf); + cmdedit_prmt_len = strlen(cmdedit_prompt); + goto do_redraw; + } + } + h--; + } + + /* Not found */ + match_buf[match_buf_len] = '\0'; + beep(); + continue; + + do_redraw: + redraw(cmdedit_y, command_len - cursor); + } /* while (1) */ + + ret: + if (matched_history_line) + command_len = load_string(matched_history_line); + + free((char*)cmdedit_prompt); + cmdedit_prompt = saved_prompt; + cmdedit_prmt_len = strlen(cmdedit_prompt); + redraw(cmdedit_y, command_len - cursor); + + return ic; +} +#endif + /* maxsize must be >= 2. * Returns: * -1 on read errors or EOF, or on bare Ctrl-D, * 0 on ctrl-C (the line entered is still returned in 'command'), * >0 length of input string, including terminating '\n' */ -int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, line_input_t *st) +int FAST_FUNC read_line_input(line_input_t *st, const char *prompt, char *command, int maxsize, int timeout) { int len; #if ENABLE_FEATURE_TAB_COMPLETION @@ -1931,11 +2182,11 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li maxsize = MAX_LINELEN; S.maxsize = maxsize; - /* With null flags, no other fields are ever used */ + /* With zero flags, no other fields are ever used */ state = st ? st : (line_input_t*) &const_int_0; #if MAX_HISTORY > 0 # if ENABLE_FEATURE_EDITING_SAVEHISTORY - if ((state->flags & SAVE_HISTORY) && state->hist_file) + if (state->hist_file) if (state->cnt_history == 0) load_history(state); # endif @@ -1955,22 +2206,19 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li #define command command_must_not_be_used new_settings = initial_settings; - new_settings.c_lflag &= ~ICANON; /* unbuffered input */ - /* Turn off echoing and CTRL-C, so we can trap it */ - new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG); - /* Hmm, in linux c_cc[] is not parsed if ICANON is off */ + /* ~ICANON: unbuffered input (most c_cc[] are disabled, VMIN/VTIME are enabled) */ + /* ~ECHO, ~ECHONL: turn off echoing, including newline echoing */ + /* ~ISIG: turn off INTR (ctrl-C), QUIT, SUSP */ + new_settings.c_lflag &= ~(ICANON | ECHO | ECHONL | ISIG); + /* reads would block only if < 1 char is available */ new_settings.c_cc[VMIN] = 1; + /* no timeout (reads block forever) */ new_settings.c_cc[VTIME] = 0; - /* Turn off CTRL-C, so we can trap it */ -#ifndef _POSIX_VDISABLE -# define _POSIX_VDISABLE '\0' -#endif - new_settings.c_cc[VINTR] = _POSIX_VDISABLE; + /* Should be not needed if ISIG is off: */ + /* Turn off CTRL-C */ + /* new_settings.c_cc[VINTR] = _POSIX_VDISABLE; */ tcsetattr_stdin_TCSANOW(&new_settings); - /* Now initialize things */ - previous_SIGWINCH_handler = signal(SIGWINCH, win_changed); - win_changed(0); /* do initial resizing */ #if ENABLE_USERNAME_OR_HOMEDIR { struct passwd *entry; @@ -1984,7 +2232,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li #endif #if 0 - for (i = 0; i <= MAX_HISTORY; i++) + for (i = 0; i <= state->max_history; i++) bb_error_msg("history[%d]:'%s'", i, state->history[i]); bb_error_msg("cur_history:%d cnt_history:%d", state->cur_history, state->cnt_history); #endif @@ -1993,6 +2241,11 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li parse_and_put_prompt(prompt); ask_terminal(); + /* Install window resize handler (NB: after *all* init is complete) */ +//FIXME: save entire sigaction! + previous_SIGWINCH_handler = signal(SIGWINCH, win_changed); + win_changed(0); /* get initial window size */ + read_key_buffer[0] = 0; while (1) { /* @@ -2003,15 +2256,14 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li * clutters the big switch a bit, but keeps all the code * in one place. */ - enum { - VI_CMDMODE_BIT = 0x40000000, - /* 0x80000000 bit flags KEYCODE_xxx */ - }; int32_t ic, ic_raw; fflush_all(); - ic = ic_raw = lineedit_read_key(read_key_buffer); + ic = ic_raw = lineedit_read_key(read_key_buffer, timeout); +#if ENABLE_FEATURE_REVERSE_SEARCH + again: +#endif #if ENABLE_FEATURE_EDITING_VI newdelflag = 1; if (vi_cmdmode) { @@ -2078,7 +2330,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li case CTRL('L'): vi_case(CTRL('L')|VI_CMDMODE_BIT:) /* Control-l -- clear screen */ - printf("\033[H"); /* cursor to top,left */ + printf(ESC"[H"); /* cursor to top,left */ redraw(0, command_len - cursor); break; #if MAX_HISTORY > 0 @@ -2115,6 +2367,11 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li while (cursor > 0 && !BB_isspace(command_ps[cursor-1])) input_backspace(); break; +#if ENABLE_FEATURE_REVERSE_SEARCH + case CTRL('R'): + ic = ic_raw = reverse_i_search(); + goto again; +#endif #if ENABLE_FEATURE_EDITING_VI case 'i'|VI_CMDMODE_BIT: @@ -2171,9 +2428,9 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li case 'd'|VI_CMDMODE_BIT: { int nc, sc; - ic = lineedit_read_key(read_key_buffer); + ic = lineedit_read_key(read_key_buffer, timeout); if (errno) /* error */ - goto prepare_to_die; + goto return_error_indicator; if (ic == ic_raw) { /* "cc", "dd" */ input_backward(cursor); goto clear_to_eol; @@ -2235,9 +2492,9 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li break; case 'r'|VI_CMDMODE_BIT: //FIXME: unicode case? - ic = lineedit_read_key(read_key_buffer); + ic = lineedit_read_key(read_key_buffer, timeout); if (errno) /* error */ - goto prepare_to_die; + goto return_error_indicator; if (ic < ' ' || ic > 255) { beep(); } else { @@ -2252,6 +2509,44 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li vi_cmdmode = 1; input_backward(1); } + /* Handle a few ESC- combinations the same way + * standard readline bindings (IOW: bash) do. + * Often, Alt- generates ESC-. + */ + ic = lineedit_read_key(read_key_buffer, timeout); + switch (ic) { + //case KEYCODE_LEFT: - bash doesn't do this + case 'b': + ctrl_left(); + break; + //case KEYCODE_RIGHT: - bash doesn't do this + case 'f': + ctrl_right(); + break; + //case KEYCODE_DELETE: - bash doesn't do this + case 'd': /* Alt-D */ + { + /* Delete word forward */ + int nc, sc = cursor; + ctrl_right(); + nc = cursor; + input_backward(cursor - sc); + while (--nc >= cursor) + input_delete(1); + break; + } + case '\b': /* Alt-Backspace(?) */ + case '\x7f': /* Alt-Backspace(?) */ + //case 'w': - bash doesn't do this + { + /* Delete word backward */ + int sc = cursor; + ctrl_left(); + while (sc-- > cursor) + input_delete(1); + break; + } + } break; #endif /* FEATURE_COMMAND_EDITING_VI */ @@ -2268,7 +2563,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li /* Rewrite the line with the selected history item */ /* change command */ command_len = load_string(state->history[state->cur_history] ? - state->history[state->cur_history] : "", maxsize); + state->history[state->cur_history] : ""); /* redraw and go to eol (bol, in vi) */ redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0); break; @@ -2280,9 +2575,11 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li input_backward(1); break; case KEYCODE_CTRL_LEFT: + case KEYCODE_ALT_LEFT: /* bash doesn't do it */ ctrl_left(); break; case KEYCODE_CTRL_RIGHT: + case KEYCODE_ALT_RIGHT: /* bash doesn't do it */ ctrl_right(); break; case KEYCODE_HOME: @@ -2309,9 +2606,9 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li * or exit if len=0 and no chars to delete */ if (command_len == 0) { errno = 0; -#if ENABLE_FEATURE_EDITING_VI - prepare_to_die: -#endif + + case -1: /* error (e.g. EIO when tty is destroyed) */ + IF_FEATURE_EDITING_VI(return_error_indicator:) break_out = command_len = -1; break; } @@ -2321,7 +2618,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li // /* Control-V -- force insert of next char */ // if (c == CTRL('V')) { // if (safe_read(STDIN_FILENO, &c, 1) < 1) -// goto prepare_to_die; +// goto return_error_indicator; // if (c == 0) { // beep(); // break;