[PATCH] Readline's mimic for reverse history search

kyak bas at bmail.ru
Fri Jul 8 04:37:50 UTC 2011


Hi, just wanted to remind in case this patch got lost in the mailing 
list.

Thank you!

On Mon, 4 Jul 2011, kyak wrote:

> Implemented readline's mimic for reverse history search (Ctrl+r).
> Reworked based on Denys Vlasenko remarks.
>
> Signed-off-by: kyak <bas at bmail.ru>
> ---
> libbb/Config.src |    7 ++
> libbb/lineedit.c |  212 
> ++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 219 insertions(+), 0 deletions(-)
>
> diff --git a/libbb/Config.src b/libbb/Config.src
> index 0ea8f43..3b1487d 100644
> --- a/libbb/Config.src
> +++ b/libbb/Config.src
> @@ -94,6 +94,13 @@ config FEATURE_EDITING_SAVEHISTORY
> 	help
> 	  Enable history saving in shells.
>
> +config FEATURE_REVERSE_SEARCH
> +	bool "Reverse history search"
> +	default n
> +	depends on FEATURE_EDITING_SAVEHISTORY
> +	help
> +	  Enable readline-alike Ctrl+r combination for reverse history search
> +
> config FEATURE_TAB_COMPLETION
> 	bool "Tab completion"
> 	default y
> diff --git a/libbb/lineedit.c b/libbb/lineedit.c
> index 4e3bc0e..f18d9cb 100644
> --- a/libbb/lineedit.c
> +++ b/libbb/lineedit.c
> @@ -1948,6 +1948,213 @@ static int isrtl_str(void)
> #undef CTRL
> #define CTRL(a) ((a) & ~0x40)
>
> +#if ENABLE_FEATURE_REVERSE_SEARCH
> +/* Mimic readline's Ctrl+r behaviour for reverse history search.
> + * Search backward starting at the current line and moving `up'
> + * through the history as necessary. This is an incremental
> + * search, i.e. hitting Ctrl+r again would search for the next
> + * match in history. */
> +static smallint reverse_i_search(void)
> +{
> +	/* prepare the prompt */
> +	static const char prmt[] = "(reverse-i-search)`";
> +	int prmt_len = sizeof(prmt)-1;
> +	char *prmt_mem_ptr = xzalloc(1);
> +	/* save the real prompt */
> +	char *prmt_mem_ptr_save = xzalloc(1);
> +	/* user input is collected here */
> +	char *match_buf;
> +	/* matched lines from history are here */
> +	char *cmdline_buf;
> +	char read_key_buffer[KEYCODE_BUFFER_SIZE];
> +	int ic;
> +	wchar_t wc[MAX_LINELEN];
> +	ssize_t len, len_cmd, match_buf_len;
> +#if ENABLE_UNICODE_SUPPORT
> +	mbstate_t mbst = { 0 };
> +	char buf[MB_CUR_MAX + 1];
> +#endif
> +	smallint break_out, has_match;
> +	int cur, cur_match;
> +	int cmdedit_y_add, cmdedit_y_add_prev, cmdedit_y_add_cmp;
> +
> +	break_out = 0;
> +	has_match = 0;
> +	cur = state->cur_history;
> +	cur_match = state->cur_history;
> +	cmdedit_y_add = 0;
> +	cmdedit_y_add_prev = 0;
> +	cmdedit_y_add_cmp = 0;
> +	read_key_buffer[0] = 0;
> +	prmt_mem_ptr = xstrdup(prmt);
> +	/* save prompt */
> +	prmt_mem_ptr_save = xstrdup(cmdedit_prompt);
> +	/* overwrite the prompt */
> +	cmdedit_prompt = prmt_mem_ptr;
> +	cmdedit_prmt_len = prmt_len;
> +	/* save what's already typed.
> +	 * MAX_LINELEN * sizeof(int16_t) will be enough, see the comment
> +	 * in TAB completion function input_tab */
> +	match_buf = xmalloc(MAX_LINELEN * sizeof(int16_t));
> +	cmdline_buf = xmalloc(MAX_LINELEN * sizeof(int16_t));
> +	save_string(match_buf, MAX_LINELEN);
> +	command_len = load_string("", MAX_LINELEN);
> +	redraw(cmdedit_y, 0);
> +	fputs(match_buf, stdout);
> +	fputs("': ", stdout);
> +	fputs(match_buf, stdout);
> +	cmdedit_y_add_cmp = (prmt_len+1 + 2 * strlen(match_buf)) / 
> (cmdedit_termw);
> +	cmdedit_y_add_prev = cmdedit_y_add_cmp;
> +	/* switch to char-consumption mode... */
> +	while (1) {
> +		fflush_all();
> +		ic = lineedit_read_key(read_key_buffer, -1);
> +		switch (ic) {
> +			case KEYCODE_RIGHT: /* left-right keys act 
> differently from Enter */
> +			case KEYCODE_LEFT:
> +				if (has_match == 1) {
> +					command_len = 
> load_string(cmdline_buf, MAX_LINELEN);
> +				} else {
> +					command_len = load_string(match_buf, 
> MAX_LINELEN);
> +				}
> +				cmdedit_prompt = prmt_mem_ptr_save;
> +				cmdedit_prmt_len = strlen(cmdedit_prompt);
> +				redraw(cmdedit_y + cmdedit_y_add, 0);
> +				break_out = 0;
> +				break;
> +			case CTRL('R'): /* searching for the next match */
> +				break_out = 2;
> +				break;
> +			case CTRL('C'):
> +			case KEYCODE_UP:
> +			case KEYCODE_DOWN:
> +			case KEYCODE_HOME:
> +			case KEYCODE_END:
> +			case KEYCODE_DELETE:
> +			case KEYCODE_CTRL_RIGHT:
> +			case KEYCODE_CTRL_LEFT:
> +			case '\x1b': /* ESC */
> +				command_len = load_string("", MAX_LINELEN);
> +				cmdedit_prompt = prmt_mem_ptr_save;
> +				redraw(cmdedit_y + cmdedit_y_add, 0);
> +				break_out = 0;
> +				break;
> +			case '\b':   /* ^H */
> +			case '\x7f': /* DEL */
> +#if ENABLE_UNICODE_SUPPORT
> +				/* convert to wide char string,
> +				 * delete char, then convert back */
> +				len = mbstowcs(wc, match_buf, sizeof(wc));
> +				if (len < 0)
> +					len = 0;
> +				wc[len-1] = '\0';
> +				wcstombs(match_buf, wc, MAX_LINELEN);
> +#else
> +				match_buf[strlen(match_buf)-1] = '\0';
> +#endif
> +				break_out = 2;
> +				has_match = 0;
> +				break;
> +			case '\r':
> +			case '\n': /* Enter */
> +				if (has_match == 1) {
> +					command_len = 
> load_string(cmdline_buf, MAX_LINELEN);
> +				} else {
> +					command_len = load_string(match_buf, 
> MAX_LINELEN);
> +				}
> +				cmdedit_prompt = prmt_mem_ptr_save;
> +				redraw(cmdedit_y + cmdedit_y_add, 0);
> +				goto_new_line();
> +				break_out = 1;
> +				break;
> +			default: /* process the char */
> +#if ENABLE_UNICODE_SUPPORT
> +				len = wcrtomb(buf, ic, &mbst);
> +				if (len > 0) {
> +					buf[len] = '\0';
> +				}
> +				strncat(match_buf, buf, sizeof(match_buf)-1);
> +#else
> +				snprintf(&match_buf[strlen(match_buf)], 
> sizeof(match_buf)-1,
> +						"%c", ic);
> +#endif
> +				break_out = 2;
> +				break;
> +		}
> +		if (break_out != 2)
> +			break;
> +		/* if there is something in type buffer,
> +		 * do iterative search in history */
> +		if (match_buf[0] != '\0') {
> +			/* save current position in history */
> +			cur = state->cur_history;
> +			if (ic != CTRL('R')) {
> +				cur_match = state->cur_history;
> +			} else {
> +				/* if we hit Ctrl+r (again),
> +				 * start searching from the last matched 
> position */
> +				state->cur_history = cur_match;
> +			}
> +			while(get_previous_history()) {
> +				cmdline_buf = 
> state->history[state->cur_history];
> +				has_match = 0;
> +				match_buf_len = strlen(match_buf);
> +				for(int 
> i=0;i+match_buf_len<=strlen(cmdline_buf);i++) {
> +					if (strncmp(match_buf, cmdline_buf+i, 
> match_buf_len) == 0) {
> +						has_match = 1;
> +						/* save position of current 
> match */
> +						cur_match = 
> state->cur_history;
> +						goto break_out_search;
> +					}
> +				}
> +			}
> +		}
> + break_out_search:
> +		/* TODO: this could be done better. There are still some 
> (rare)
> +		 * cases of wrapping miscalculation */
> +		len = mbstowcs(wc, match_buf, sizeof(wc));
> +		if (len < 0)
> +			len = strlen(match_buf);
> +
> +		len_cmd = mbstowcs(wc, cmdline_buf, sizeof(wc));
> +		if (len_cmd < 0)
> +			len_cmd = strlen(cmdline_buf);
> +
> +		cmdedit_y_add = (prmt_len+1 + len + (has_match ? len_cmd : 
> len)) /
> +			(cmdedit_termw);
> +
> +		if (cmdedit_y_add > cmdedit_y_add_cmp) {
> +			int t = cmdedit_y_add - cmdedit_y_add_cmp;
> +			for (int j = 1; j <= t; j++) {
> +				/* scroll up */
> +				printf(ESC"D");
> +			}
> +			if (t > 1)
> +				redraw(cmdedit_y + t, 0);
> +			else
> +				redraw(cmdedit_y + cmdedit_y_add, 0);
> +			cmdedit_y_add_cmp = cmdedit_y_add;
> +		} else if (cmdedit_y_add != cmdedit_y_add_prev) {
> +			redraw(cmdedit_y + cmdedit_y_add_prev, 0);
> +		} else {
> +			redraw(cmdedit_y + cmdedit_y_add, 0);
> +		}
> +
> +		fputs(match_buf, stdout);
> +		fputs("': ", stdout);
> +		if (has_match == 0) {
> +			fputs(match_buf, stdout);
> +		} else {
> +			fputs(cmdline_buf, stdout);
> +		}
> +		cmdedit_y_add_prev = cmdedit_y_add;
> +		/* restore current position in history */
> +		state->cur_history=cur;
> +	} /* !while */
> +	return break_out;
> +}
> +#endif
> +
> /* maxsize must be >= 2.
>  * Returns:
>  * -1 on read errors or EOF, or on bare Ctrl-D,
> @@ -2174,6 +2381,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'):
> +			break_out = reverse_i_search();
> +			break;
> +#endif
>
> #if ENABLE_FEATURE_EDITING_VI
> 		case 'i'|VI_CMDMODE_BIT:
> -- 
> 1.7.1
>


More information about the busybox mailing list