1 libbb/lineedit: implement optional Ctrl-R history search
2 Source: http://git.busybox.net/busybox/commit/?id=a669eca3a230e35e4a6894a30168a047000f3b75
7 @@ -93,6 +93,14 @@ config FEATURE_EDITING_SAVEHISTORY
9 Enable history saving in shells.
11 +config FEATURE_REVERSE_SEARCH
12 + bool "Reverse history search"
14 + depends on FEATURE_EDITING_SAVEHISTORY
16 + Enable readline-like Ctrl-R combination for reverse history search.
17 + Increases code by about 0.5k.
19 config FEATURE_TAB_COMPLETION
22 --- a/libbb/lineedit.c
23 +++ b/libbb/lineedit.c
24 @@ -202,9 +202,9 @@ static void deinit_S(void)
27 #if ENABLE_UNICODE_SUPPORT
28 -static size_t load_string(const char *src, int maxsize)
29 +static size_t load_string(const char *src)
31 - ssize_t len = mbstowcs(command_ps, src, maxsize - 1);
32 + ssize_t len = mbstowcs(command_ps, src, S.maxsize - 1);
35 command_ps[len] = BB_NUL;
36 @@ -297,9 +297,9 @@ static wchar_t adjust_width_and_validate
40 -static size_t load_string(const char *src, int maxsize)
41 +static size_t load_string(const char *src)
43 - safe_strncpy(command_ps, src, maxsize);
44 + safe_strncpy(command_ps, src, S.maxsize);
45 return strlen(command_ps);
47 # if ENABLE_FEATURE_TAB_COMPLETION
48 @@ -1202,10 +1202,10 @@ static NOINLINE void input_tab(smallint
49 strcpy(match_buf, &command[cursor_mb]);
50 /* where do we want to have cursor after all? */
51 strcpy(&command[cursor_mb], chosen_match + match_pfx_len);
52 - len = load_string(command, S.maxsize);
53 + len = load_string(command);
54 /* add match and tail */
55 sprintf(&command[cursor_mb], "%s%s", chosen_match + match_pfx_len, match_buf);
56 - command_len = load_string(command, S.maxsize);
57 + command_len = load_string(command);
58 /* write out the matched command */
59 /* paranoia: load_string can return 0 on conv error,
60 * prevent passing pos = (0 - 12) to redraw */
61 @@ -1911,6 +1911,140 @@ static int isrtl_str(void)
63 #define CTRL(a) ((a) & ~0x40)
66 + VI_CMDMODE_BIT = 0x40000000,
67 + /* 0x80000000 bit flags KEYCODE_xxx */
70 +#if ENABLE_FEATURE_REVERSE_SEARCH
71 +/* Mimic readline Ctrl-R reverse history search.
72 + * When invoked, it shows the following prompt:
73 + * (reverse-i-search)'': user_input [cursor pos unchanged by Ctrl-R]
74 + * and typing results in search being performed:
75 + * (reverse-i-search)'tmp': cd /tmp [cursor under t in /tmp]
76 + * Search is performed by looking at progressively older lines in history.
77 + * Ctrl-R again searches for the next match in history.
78 + * Backspace deletes last matched char.
79 + * Control keys exit search and return to normal editing (at current history line).
81 +static int32_t reverse_i_search(void)
83 + char match_buf[128]; /* for user input */
84 + char read_key_buffer[KEYCODE_BUFFER_SIZE];
85 + const char *matched_history_line;
86 + const char *saved_prompt;
89 + matched_history_line = NULL;
90 + read_key_buffer[0] = 0;
91 + match_buf[0] = '\0';
93 + /* Save and replace the prompt */
94 + saved_prompt = cmdedit_prompt;
99 + unsigned match_buf_len = strlen(match_buf);
102 +//FIXME: correct timeout?
103 + ic = lineedit_read_key(read_key_buffer);
106 + case CTRL('R'): /* searching for the next match */
112 + if (unicode_status == UNICODE_ON) {
113 + while (match_buf_len != 0) {
114 + uint8_t c = match_buf[--match_buf_len];
115 + if ((c & 0xc0) != 0x80) /* start of UTF-8 char? */
119 + if (match_buf_len != 0)
122 + match_buf[match_buf_len] = '\0';
127 + || (!ENABLE_UNICODE_SUPPORT && ic >= 256)
128 + || (ENABLE_UNICODE_SUPPORT && ic >= VI_CMDMODE_BIT)
133 + /* Append this char */
134 +#if ENABLE_UNICODE_SUPPORT
135 + if (unicode_status == UNICODE_ON) {
136 + mbstate_t mbstate = { 0 };
137 + char buf[MB_CUR_MAX + 1];
138 + int len = wcrtomb(buf, ic, &mbstate);
141 + if (match_buf_len + len < sizeof(match_buf))
142 + strcpy(match_buf + match_buf_len, buf);
146 + if (match_buf_len < sizeof(match_buf) - 1) {
147 + match_buf[match_buf_len] = ic;
148 + match_buf[match_buf_len + 1] = '\0';
151 + } /* switch (ic) */
153 + /* Search in history for match_buf */
154 + h = state->cur_history;
155 + if (ic == CTRL('R'))
158 + if (state->history[h]) {
159 + char *match = strstr(state->history[h], match_buf);
161 + state->cur_history = h;
162 + matched_history_line = state->history[h];
163 + command_len = load_string(matched_history_line);
164 + cursor = match - matched_history_line;
165 +//FIXME: cursor position for Unicode case
167 + free((char*)cmdedit_prompt);
169 + cmdedit_prompt = xasprintf("(reverse-i-search)'%s': ", match_buf);
170 + cmdedit_prmt_len = strlen(cmdedit_prompt);
178 + match_buf[match_buf_len] = '\0';
183 + redraw(cmdedit_y, command_len - cursor);
187 + if (matched_history_line)
188 + command_len = load_string(matched_history_line);
190 + free((char*)cmdedit_prompt);
191 + cmdedit_prompt = saved_prompt;
192 + cmdedit_prmt_len = strlen(cmdedit_prompt);
193 + redraw(cmdedit_y, command_len - cursor);
199 /* maxsize must be >= 2.
201 * -1 on read errors or EOF, or on bare Ctrl-D,
202 @@ -2026,15 +2160,14 @@ int FAST_FUNC read_line_input(const char
203 * clutters the big switch a bit, but keeps all the code
207 - VI_CMDMODE_BIT = 0x40000000,
208 - /* 0x80000000 bit flags KEYCODE_xxx */
213 ic = ic_raw = lineedit_read_key(read_key_buffer);
215 +#if ENABLE_FEATURE_REVERSE_SEARCH
218 #if ENABLE_FEATURE_EDITING_VI
221 @@ -2138,6 +2271,11 @@ int FAST_FUNC read_line_input(const char
222 while (cursor > 0 && !BB_isspace(command_ps[cursor-1]))
225 +#if ENABLE_FEATURE_REVERSE_SEARCH
227 + ic = ic_raw = reverse_i_search();
231 #if ENABLE_FEATURE_EDITING_VI
232 case 'i'|VI_CMDMODE_BIT:
233 @@ -2291,7 +2429,7 @@ int FAST_FUNC read_line_input(const char
234 /* Rewrite the line with the selected history item */
236 command_len = load_string(state->history[state->cur_history] ?
237 - state->history[state->cur_history] : "", maxsize);
238 + state->history[state->cur_history] : "");
239 /* redraw and go to eol (bol, in vi) */
240 redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);