libbb/lineedit: implement optional Ctrl-R history search
authorDenys Vlasenko <vda.linux@googlemail.com>
Mon, 11 Jul 2011 05:36:59 +0000 (07:36 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Mon, 11 Jul 2011 05:36:59 +0000 (07:36 +0200)
function                                             old     new   delta
read_line_input                                     3433    3957    +524
load_string                                           77      90     +13
input_tab                                           1086    1069     -17
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/1 up/down: 537/-17)           Total: 520 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
libbb/Config.src
libbb/lineedit.c

index 0ea8f43ab50166e84201b23c43b16877693877ab..aa442365abeba2072f621c52c58f4b01593f5c80 100644 (file)
@@ -94,6 +94,14 @@ config FEATURE_EDITING_SAVEHISTORY
        help
          Enable history saving in shells.
 
+config FEATURE_REVERSE_SEARCH
+       bool "Reverse history search"
+       default y
+       depends on FEATURE_EDITING_SAVEHISTORY
+       help
+         Enable readline-like Ctrl-R combination for reverse history search.
+         Increases code by about 0.5k.
+
 config FEATURE_TAB_COMPLETION
        bool "Tab completion"
        default y
index 4e3bc0ef7ff17d86a98bd1ee4e16138e4beee903..10265192e609208ffb842f6e0bb1be98d9ebbf53 100644 (file)
@@ -207,18 +207,21 @@ 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)
 {
        if (unicode_status == UNICODE_ON) {
-               ssize_t len = mbstowcs(command_ps, src, maxsize - 1);
+               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 ((command_ps[i] = src[i]) != 0)
+               while (src[i] && i < S.maxsize - 1) {
+                       command_ps[i] = src[i];
                        i++;
+               }
+               command_ps[i] = BB_NUL;
                return i;
        }
 }
@@ -319,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
@@ -1224,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 */
@@ -1948,6 +1951,140 @@ 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,
@@ -2062,15 +2199,14 @@ int FAST_FUNC read_line_input(line_input_t *st, const char *prompt, char *comman
                 * 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, timeout);
 
+#if ENABLE_FEATURE_REVERSE_SEARCH
+ again:
+#endif
 #if ENABLE_FEATURE_EDITING_VI
                newdelflag = 1;
                if (vi_cmdmode) {
@@ -2174,6 +2310,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'):
+                       ic = ic_raw = reverse_i_search();
+                       goto again;
+#endif
 
 #if ENABLE_FEATURE_EDITING_VI
                case 'i'|VI_CMDMODE_BIT:
@@ -2327,7 +2468,7 @@ int FAST_FUNC read_line_input(line_input_t *st, const char *prompt, char *comman
                        /* 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;