| 1 | --- a/libbb/lineedit.c |
| 2 | +++ b/libbb/lineedit.c |
| 3 | @@ -202,13 +202,23 @@ static void deinit_S(void) |
| 4 | |
| 5 | |
| 6 | #if ENABLE_UNICODE_SUPPORT |
| 7 | -static size_t load_string(const char *src, int maxsize) |
| 8 | +static size_t load_string(const char *src) |
| 9 | { |
| 10 | - ssize_t len = mbstowcs(command_ps, src, maxsize - 1); |
| 11 | - if (len < 0) |
| 12 | - len = 0; |
| 13 | - command_ps[len] = BB_NUL; |
| 14 | - return len; |
| 15 | + if (unicode_status == UNICODE_ON) { |
| 16 | + ssize_t len = mbstowcs(command_ps, src, S.maxsize - 1); |
| 17 | + if (len < 0) |
| 18 | + len = 0; |
| 19 | + command_ps[len] = BB_NUL; |
| 20 | + return len; |
| 21 | + } else { |
| 22 | + unsigned i = 0; |
| 23 | + while (src[i] && i < S.maxsize - 1) { |
| 24 | + command_ps[i] = src[i]; |
| 25 | + i++; |
| 26 | + } |
| 27 | + command_ps[i] = BB_NUL; |
| 28 | + return i; |
| 29 | + } |
| 30 | } |
| 31 | static unsigned save_string(char *dst, unsigned maxsize) |
| 32 | { |
| 33 | @@ -297,9 +307,9 @@ static wchar_t adjust_width_and_validate |
| 34 | return wc; |
| 35 | } |
| 36 | #else /* !UNICODE */ |
| 37 | -static size_t load_string(const char *src, int maxsize) |
| 38 | +static size_t load_string(const char *src) |
| 39 | { |
| 40 | - safe_strncpy(command_ps, src, maxsize); |
| 41 | + safe_strncpy(command_ps, src, S.maxsize); |
| 42 | return strlen(command_ps); |
| 43 | } |
| 44 | # if ENABLE_FEATURE_TAB_COMPLETION |
| 45 | @@ -1202,10 +1212,10 @@ static NOINLINE void input_tab(smallint |
| 46 | strcpy(match_buf, &command[cursor_mb]); |
| 47 | /* where do we want to have cursor after all? */ |
| 48 | strcpy(&command[cursor_mb], chosen_match + match_pfx_len); |
| 49 | - len = load_string(command, S.maxsize); |
| 50 | + len = load_string(command); |
| 51 | /* add match and tail */ |
| 52 | sprintf(&command[cursor_mb], "%s%s", chosen_match + match_pfx_len, match_buf); |
| 53 | - command_len = load_string(command, S.maxsize); |
| 54 | + command_len = load_string(command); |
| 55 | /* write out the matched command */ |
| 56 | /* paranoia: load_string can return 0 on conv error, |
| 57 | * prevent passing pos = (0 - 12) to redraw */ |
| 58 | @@ -1911,6 +1921,140 @@ static int isrtl_str(void) |
| 59 | #undef CTRL |
| 60 | #define CTRL(a) ((a) & ~0x40) |
| 61 | |
| 62 | +enum { |
| 63 | + VI_CMDMODE_BIT = 0x40000000, |
| 64 | + /* 0x80000000 bit flags KEYCODE_xxx */ |
| 65 | +}; |
| 66 | + |
| 67 | +#if ENABLE_FEATURE_REVERSE_SEARCH |
| 68 | +/* Mimic readline Ctrl-R reverse history search. |
| 69 | + * When invoked, it shows the following prompt: |
| 70 | + * (reverse-i-search)'': user_input [cursor pos unchanged by Ctrl-R] |
| 71 | + * and typing results in search being performed: |
| 72 | + * (reverse-i-search)'tmp': cd /tmp [cursor under t in /tmp] |
| 73 | + * Search is performed by looking at progressively older lines in history. |
| 74 | + * Ctrl-R again searches for the next match in history. |
| 75 | + * Backspace deletes last matched char. |
| 76 | + * Control keys exit search and return to normal editing (at current history line). |
| 77 | + */ |
| 78 | +static int32_t reverse_i_search(void) |
| 79 | +{ |
| 80 | + char match_buf[128]; /* for user input */ |
| 81 | + char read_key_buffer[KEYCODE_BUFFER_SIZE]; |
| 82 | + const char *matched_history_line; |
| 83 | + const char *saved_prompt; |
| 84 | + int32_t ic; |
| 85 | + |
| 86 | + matched_history_line = NULL; |
| 87 | + read_key_buffer[0] = 0; |
| 88 | + match_buf[0] = '\0'; |
| 89 | + |
| 90 | + /* Save and replace the prompt */ |
| 91 | + saved_prompt = cmdedit_prompt; |
| 92 | + goto set_prompt; |
| 93 | + |
| 94 | + while (1) { |
| 95 | + int h; |
| 96 | + unsigned match_buf_len = strlen(match_buf); |
| 97 | + |
| 98 | + fflush_all(); |
| 99 | +//FIXME: correct timeout? |
| 100 | + ic = lineedit_read_key(read_key_buffer); |
| 101 | + |
| 102 | + switch (ic) { |
| 103 | + case CTRL('R'): /* searching for the next match */ |
| 104 | + break; |
| 105 | + |
| 106 | + case '\b': |
| 107 | + case '\x7f': |
| 108 | + /* Backspace */ |
| 109 | + if (unicode_status == UNICODE_ON) { |
| 110 | + while (match_buf_len != 0) { |
| 111 | + uint8_t c = match_buf[--match_buf_len]; |
| 112 | + if ((c & 0xc0) != 0x80) /* start of UTF-8 char? */ |
| 113 | + break; /* yes */ |
| 114 | + } |
| 115 | + } else { |
| 116 | + if (match_buf_len != 0) |
| 117 | + match_buf_len--; |
| 118 | + } |
| 119 | + match_buf[match_buf_len] = '\0'; |
| 120 | + break; |
| 121 | + |
| 122 | + default: |
| 123 | + if (ic < ' ' |
| 124 | + || (!ENABLE_UNICODE_SUPPORT && ic >= 256) |
| 125 | + || (ENABLE_UNICODE_SUPPORT && ic >= VI_CMDMODE_BIT) |
| 126 | + ) { |
| 127 | + goto ret; |
| 128 | + } |
| 129 | + |
| 130 | + /* Append this char */ |
| 131 | +#if ENABLE_UNICODE_SUPPORT |
| 132 | + if (unicode_status == UNICODE_ON) { |
| 133 | + mbstate_t mbstate = { 0 }; |
| 134 | + char buf[MB_CUR_MAX + 1]; |
| 135 | + int len = wcrtomb(buf, ic, &mbstate); |
| 136 | + if (len > 0) { |
| 137 | + buf[len] = '\0'; |
| 138 | + if (match_buf_len + len < sizeof(match_buf)) |
| 139 | + strcpy(match_buf + match_buf_len, buf); |
| 140 | + } |
| 141 | + } else |
| 142 | +#endif |
| 143 | + if (match_buf_len < sizeof(match_buf) - 1) { |
| 144 | + match_buf[match_buf_len] = ic; |
| 145 | + match_buf[match_buf_len + 1] = '\0'; |
| 146 | + } |
| 147 | + break; |
| 148 | + } /* switch (ic) */ |
| 149 | + |
| 150 | + /* Search in history for match_buf */ |
| 151 | + h = state->cur_history; |
| 152 | + if (ic == CTRL('R')) |
| 153 | + h--; |
| 154 | + while (h >= 0) { |
| 155 | + if (state->history[h]) { |
| 156 | + char *match = strstr(state->history[h], match_buf); |
| 157 | + if (match) { |
| 158 | + state->cur_history = h; |
| 159 | + matched_history_line = state->history[h]; |
| 160 | + command_len = load_string(matched_history_line); |
| 161 | + cursor = match - matched_history_line; |
| 162 | +//FIXME: cursor position for Unicode case |
| 163 | + |
| 164 | + free((char*)cmdedit_prompt); |
| 165 | + set_prompt: |
| 166 | + cmdedit_prompt = xasprintf("(reverse-i-search)'%s': ", match_buf); |
| 167 | + cmdedit_prmt_len = strlen(cmdedit_prompt); |
| 168 | + goto do_redraw; |
| 169 | + } |
| 170 | + } |
| 171 | + h--; |
| 172 | + } |
| 173 | + |
| 174 | + /* Not found */ |
| 175 | + match_buf[match_buf_len] = '\0'; |
| 176 | + beep(); |
| 177 | + continue; |
| 178 | + |
| 179 | + do_redraw: |
| 180 | + redraw(cmdedit_y, command_len - cursor); |
| 181 | + } /* while (1) */ |
| 182 | + |
| 183 | + ret: |
| 184 | + if (matched_history_line) |
| 185 | + command_len = load_string(matched_history_line); |
| 186 | + |
| 187 | + free((char*)cmdedit_prompt); |
| 188 | + cmdedit_prompt = saved_prompt; |
| 189 | + cmdedit_prmt_len = strlen(cmdedit_prompt); |
| 190 | + redraw(cmdedit_y, command_len - cursor); |
| 191 | + |
| 192 | + return ic; |
| 193 | +} |
| 194 | +#endif |
| 195 | + |
| 196 | /* maxsize must be >= 2. |
| 197 | * Returns: |
| 198 | * -1 on read errors or EOF, or on bare Ctrl-D, |
| 199 | @@ -2026,15 +2170,14 @@ int FAST_FUNC read_line_input(const char |
| 200 | * clutters the big switch a bit, but keeps all the code |
| 201 | * in one place. |
| 202 | */ |
| 203 | - enum { |
| 204 | - VI_CMDMODE_BIT = 0x40000000, |
| 205 | - /* 0x80000000 bit flags KEYCODE_xxx */ |
| 206 | - }; |
| 207 | int32_t ic, ic_raw; |
| 208 | |
| 209 | fflush_all(); |
| 210 | ic = ic_raw = lineedit_read_key(read_key_buffer); |
| 211 | |
| 212 | +#if ENABLE_FEATURE_REVERSE_SEARCH |
| 213 | + again: |
| 214 | +#endif |
| 215 | #if ENABLE_FEATURE_EDITING_VI |
| 216 | newdelflag = 1; |
| 217 | if (vi_cmdmode) { |
| 218 | @@ -2138,6 +2281,11 @@ int FAST_FUNC read_line_input(const char |
| 219 | while (cursor > 0 && !BB_isspace(command_ps[cursor-1])) |
| 220 | input_backspace(); |
| 221 | break; |
| 222 | +#if ENABLE_FEATURE_REVERSE_SEARCH |
| 223 | + case CTRL('R'): |
| 224 | + ic = ic_raw = reverse_i_search(); |
| 225 | + goto again; |
| 226 | +#endif |
| 227 | |
| 228 | #if ENABLE_FEATURE_EDITING_VI |
| 229 | case 'i'|VI_CMDMODE_BIT: |
| 230 | @@ -2291,7 +2439,7 @@ int FAST_FUNC read_line_input(const char |
| 231 | /* Rewrite the line with the selected history item */ |
| 232 | /* change command */ |
| 233 | command_len = load_string(state->history[state->cur_history] ? |
| 234 | - state->history[state->cur_history] : "", maxsize); |
| 235 | + state->history[state->cur_history] : ""); |
| 236 | /* redraw and go to eol (bol, in vi) */ |
| 237 | redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0); |
| 238 | break; |
| 239 | --- a/libbb/Config.src |
| 240 | +++ b/libbb/Config.src |
| 241 | @@ -93,6 +93,14 @@ config FEATURE_EDITING_SAVEHISTORY |
| 242 | help |
| 243 | Enable history saving in shells. |
| 244 | |
| 245 | +config FEATURE_REVERSE_SEARCH |
| 246 | + bool "Reverse history search" |
| 247 | + default y |
| 248 | + depends on FEATURE_EDITING_SAVEHISTORY |
| 249 | + help |
| 250 | + Enable readline-like Ctrl-R combination for reverse history search. |
| 251 | + Increases code by about 0.5k. |
| 252 | + |
| 253 | config FEATURE_TAB_COMPLETION |
| 254 | bool "Tab completion" |
| 255 | default y |
| 256 | |