libbb/lineedit: implement optional Ctrl-R history search

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>
This commit is contained in:
Denys Vlasenko 2011-07-11 07:36:59 +02:00
parent 98f213ed79
commit a669eca3a2
2 changed files with 161 additions and 12 deletions

View File

@ -94,6 +94,14 @@ config FEATURE_EDITING_SAVEHISTORY
help help
Enable history saving in shells. 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 config FEATURE_TAB_COMPLETION
bool "Tab completion" bool "Tab completion"
default y default y

View File

@ -207,18 +207,21 @@ static void deinit_S(void)
#if ENABLE_UNICODE_SUPPORT #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) { 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) if (len < 0)
len = 0; len = 0;
command_ps[len] = BB_NUL; command_ps[len] = BB_NUL;
return len; return len;
} else { } else {
unsigned i = 0; unsigned i = 0;
while ((command_ps[i] = src[i]) != 0) while (src[i] && i < S.maxsize - 1) {
command_ps[i] = src[i];
i++; i++;
}
command_ps[i] = BB_NUL;
return i; return i;
} }
} }
@ -319,9 +322,9 @@ static wchar_t adjust_width_and_validate_wc(wchar_t wc)
return wc; return wc;
} }
#else /* !UNICODE */ #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); return strlen(command_ps);
} }
# if ENABLE_FEATURE_TAB_COMPLETION # if ENABLE_FEATURE_TAB_COMPLETION
@ -1224,10 +1227,10 @@ static NOINLINE void input_tab(smallint *lastWasTab)
strcpy(match_buf, &command[cursor_mb]); strcpy(match_buf, &command[cursor_mb]);
/* where do we want to have cursor after all? */ /* where do we want to have cursor after all? */
strcpy(&command[cursor_mb], chosen_match + match_pfx_len); strcpy(&command[cursor_mb], chosen_match + match_pfx_len);
len = load_string(command, S.maxsize); len = load_string(command);
/* add match and tail */ /* add match and tail */
sprintf(&command[cursor_mb], "%s%s", chosen_match + match_pfx_len, match_buf); 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 */ /* write out the matched command */
/* paranoia: load_string can return 0 on conv error, /* paranoia: load_string can return 0 on conv error,
* prevent passing pos = (0 - 12) to redraw */ * prevent passing pos = (0 - 12) to redraw */
@ -1948,6 +1951,140 @@ static int isrtl_str(void)
#undef CTRL #undef CTRL
#define CTRL(a) ((a) & ~0x40) #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. /* maxsize must be >= 2.
* Returns: * Returns:
* -1 on read errors or EOF, or on bare Ctrl-D, * -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 * clutters the big switch a bit, but keeps all the code
* in one place. * in one place.
*/ */
enum {
VI_CMDMODE_BIT = 0x40000000,
/* 0x80000000 bit flags KEYCODE_xxx */
};
int32_t ic, ic_raw; int32_t ic, ic_raw;
fflush_all(); fflush_all();
ic = ic_raw = lineedit_read_key(read_key_buffer, timeout); ic = ic_raw = lineedit_read_key(read_key_buffer, timeout);
#if ENABLE_FEATURE_REVERSE_SEARCH
again:
#endif
#if ENABLE_FEATURE_EDITING_VI #if ENABLE_FEATURE_EDITING_VI
newdelflag = 1; newdelflag = 1;
if (vi_cmdmode) { 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])) while (cursor > 0 && !BB_isspace(command_ps[cursor-1]))
input_backspace(); input_backspace();
break; break;
#if ENABLE_FEATURE_REVERSE_SEARCH
case CTRL('R'):
ic = ic_raw = reverse_i_search();
goto again;
#endif
#if ENABLE_FEATURE_EDITING_VI #if ENABLE_FEATURE_EDITING_VI
case 'i'|VI_CMDMODE_BIT: 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 */ /* Rewrite the line with the selected history item */
/* change command */ /* change command */
command_len = load_string(state->history[state->cur_history] ? 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 and go to eol (bol, in vi) */
redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0); redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
break; break;