[PATCH] Readline's mimic for reverse history search
kyak
bas at bmail.ru
Mon Jul 4 07:10:30 UTC 2011
Implemented readline's mimic for reverse history search (Ctrl+r).
Reworked based on Denys Vlasenko remarks.
Signed-off-by: kyak <bas at bmail.ru>
---
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
More information about the busybox
mailing list