Implemented readline's mimic for reverse history search (Ctrl+r). Reworked based on Denys Vlasenko remarks.
Signed-off-by: kyak <[email protected]> --- libbb/Config.src | 7 ++ libbb/lineedit.c | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 0 deletions(-) diff --git a/libbb/Config.src b/libbb/Config.src index 0ea8f43..3b1487d 100644 --- a/libbb/Config.src +++ b/libbb/Config.src @@ -94,6 +94,13 @@ config FEATURE_EDITING_SAVEHISTORY help Enable history saving in shells. +config FEATURE_REVERSE_SEARCH + bool "Reverse history search" + default n + depends on FEATURE_EDITING_SAVEHISTORY + help + Enable readline-alike Ctrl+r combination for reverse history search + config FEATURE_TAB_COMPLETION bool "Tab completion" default y diff --git a/libbb/lineedit.c b/libbb/lineedit.c index 4e3bc0e..f18d9cb 100644 --- a/libbb/lineedit.c +++ b/libbb/lineedit.c @@ -1948,6 +1948,213 @@ static int isrtl_str(void) #undef CTRL #define CTRL(a) ((a) & ~0x40) +#if ENABLE_FEATURE_REVERSE_SEARCH +/* Mimic readline's Ctrl+r behaviour for reverse history search. + * Search backward starting at the current line and moving `up' + * through the history as necessary. This is an incremental + * search, i.e. hitting Ctrl+r again would search for the next + * match in history. */ +static smallint reverse_i_search(void) +{ + /* prepare the prompt */ + static const char prmt[] = "(reverse-i-search)`"; + int prmt_len = sizeof(prmt)-1; + char *prmt_mem_ptr = xzalloc(1); + /* save the real prompt */ + char *prmt_mem_ptr_save = xzalloc(1); + /* user input is collected here */ + char *match_buf; + /* matched lines from history are here */ + char *cmdline_buf; + char read_key_buffer[KEYCODE_BUFFER_SIZE]; + int ic; + wchar_t wc[MAX_LINELEN]; + ssize_t len, len_cmd, match_buf_len; +#if ENABLE_UNICODE_SUPPORT + mbstate_t mbst = { 0 }; + char buf[MB_CUR_MAX + 1]; +#endif + smallint break_out, has_match; + int cur, cur_match; + int cmdedit_y_add, cmdedit_y_add_prev, cmdedit_y_add_cmp; + + break_out = 0; + has_match = 0; + cur = state->cur_history; + cur_match = state->cur_history; + cmdedit_y_add = 0; + cmdedit_y_add_prev = 0; + cmdedit_y_add_cmp = 0; + read_key_buffer[0] = 0; + prmt_mem_ptr = xstrdup(prmt); + /* save prompt */ + prmt_mem_ptr_save = xstrdup(cmdedit_prompt); + /* overwrite the prompt */ + cmdedit_prompt = prmt_mem_ptr; + cmdedit_prmt_len = prmt_len; + /* save what's already typed. + * MAX_LINELEN * sizeof(int16_t) will be enough, see the comment + * in TAB completion function input_tab */ + match_buf = xmalloc(MAX_LINELEN * sizeof(int16_t)); + cmdline_buf = xmalloc(MAX_LINELEN * sizeof(int16_t)); + save_string(match_buf, MAX_LINELEN); + command_len = load_string("", MAX_LINELEN); + redraw(cmdedit_y, 0); + fputs(match_buf, stdout); + fputs("': ", stdout); + fputs(match_buf, stdout); + cmdedit_y_add_cmp = (prmt_len+1 + 2 * strlen(match_buf)) / (cmdedit_termw); + cmdedit_y_add_prev = cmdedit_y_add_cmp; + /* switch to char-consumption mode... */ + while (1) { + fflush_all(); + ic = lineedit_read_key(read_key_buffer, -1); + switch (ic) { + case KEYCODE_RIGHT: /* left-right keys act differently from Enter */ + case KEYCODE_LEFT: + if (has_match == 1) { + command_len = load_string(cmdline_buf, MAX_LINELEN); + } else { + command_len = load_string(match_buf, MAX_LINELEN); + } + cmdedit_prompt = prmt_mem_ptr_save; + cmdedit_prmt_len = strlen(cmdedit_prompt); + redraw(cmdedit_y + cmdedit_y_add, 0); + break_out = 0; + break; + case CTRL('R'): /* searching for the next match */ + break_out = 2; + break; + case CTRL('C'): + case KEYCODE_UP: + case KEYCODE_DOWN: + case KEYCODE_HOME: + case KEYCODE_END: + case KEYCODE_DELETE: + case KEYCODE_CTRL_RIGHT: + case KEYCODE_CTRL_LEFT: + case '\x1b': /* ESC */ + command_len = load_string("", MAX_LINELEN); + cmdedit_prompt = prmt_mem_ptr_save; + redraw(cmdedit_y + cmdedit_y_add, 0); + break_out = 0; + break; + case '\b': /* ^H */ + case '\x7f': /* DEL */ +#if ENABLE_UNICODE_SUPPORT + /* convert to wide char string, + * delete char, then convert back */ + len = mbstowcs(wc, match_buf, sizeof(wc)); + if (len < 0) + len = 0; + wc[len-1] = '\0'; + wcstombs(match_buf, wc, MAX_LINELEN); +#else + match_buf[strlen(match_buf)-1] = '\0'; +#endif + break_out = 2; + has_match = 0; + break; + case '\r': + case '\n': /* Enter */ + if (has_match == 1) { + command_len = load_string(cmdline_buf, MAX_LINELEN); + } else { + command_len = load_string(match_buf, MAX_LINELEN); + } + cmdedit_prompt = prmt_mem_ptr_save; + redraw(cmdedit_y + cmdedit_y_add, 0); + goto_new_line(); + break_out = 1; + break; + default: /* process the char */ +#if ENABLE_UNICODE_SUPPORT + len = wcrtomb(buf, ic, &mbst); + if (len > 0) { + buf[len] = '\0'; + } + strncat(match_buf, buf, sizeof(match_buf)-1); +#else + snprintf(&match_buf[strlen(match_buf)], sizeof(match_buf)-1, + "%c", ic); +#endif + break_out = 2; + break; + } + if (break_out != 2) + break; + /* if there is something in type buffer, + * do iterative search in history */ + if (match_buf[0] != '\0') { + /* save current position in history */ + cur = state->cur_history; + if (ic != CTRL('R')) { + cur_match = state->cur_history; + } else { + /* if we hit Ctrl+r (again), + * start searching from the last matched position */ + state->cur_history = cur_match; + } + while(get_previous_history()) { + cmdline_buf = state->history[state->cur_history]; + has_match = 0; + match_buf_len = strlen(match_buf); + for(int i=0;i+match_buf_len<=strlen(cmdline_buf);i++) { + if (strncmp(match_buf, cmdline_buf+i, match_buf_len) == 0) { + has_match = 1; + /* save position of current match */ + cur_match = state->cur_history; + goto break_out_search; + } + } + } + } + break_out_search: + /* TODO: this could be done better. There are still some (rare) + * cases of wrapping miscalculation */ + len = mbstowcs(wc, match_buf, sizeof(wc)); + if (len < 0) + len = strlen(match_buf); + + len_cmd = mbstowcs(wc, cmdline_buf, sizeof(wc)); + if (len_cmd < 0) + len_cmd = strlen(cmdline_buf); + + cmdedit_y_add = (prmt_len+1 + len + (has_match ? len_cmd : len)) / + (cmdedit_termw); + + if (cmdedit_y_add > cmdedit_y_add_cmp) { + int t = cmdedit_y_add - cmdedit_y_add_cmp; + for (int j = 1; j <= t; j++) { + /* scroll up */ + printf(ESC"D"); + } + if (t > 1) + redraw(cmdedit_y + t, 0); + else + redraw(cmdedit_y + cmdedit_y_add, 0); + cmdedit_y_add_cmp = cmdedit_y_add; + } else if (cmdedit_y_add != cmdedit_y_add_prev) { + redraw(cmdedit_y + cmdedit_y_add_prev, 0); + } else { + redraw(cmdedit_y + cmdedit_y_add, 0); + } + + fputs(match_buf, stdout); + fputs("': ", stdout); + if (has_match == 0) { + fputs(match_buf, stdout); + } else { + fputs(cmdline_buf, stdout); + } + cmdedit_y_add_prev = cmdedit_y_add; + /* restore current position in history */ + state->cur_history=cur; + } /* !while */ + return break_out; +} +#endif + /* maxsize must be >= 2. * Returns: * -1 on read errors or EOF, or on bare Ctrl-D, @@ -2174,6 +2381,11 @@ int FAST_FUNC read_line_input(line_input_t *st, const char *prompt, char *comman while (cursor > 0 && !BB_isspace(command_ps[cursor-1])) input_backspace(); break; +#if ENABLE_FEATURE_REVERSE_SEARCH + case CTRL('R'): + break_out = reverse_i_search(); + break; +#endif #if ENABLE_FEATURE_EDITING_VI case 'i'|VI_CMDMODE_BIT: -- 1.7.1 _______________________________________________ busybox mailing list [email protected] http://lists.busybox.net/mailman/listinfo/busybox
