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:
parent
98f213ed79
commit
a669eca3a2
@ -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
|
||||||
|
165
libbb/lineedit.c
165
libbb/lineedit.c
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user