Unverified Commit 8a05f009 authored by ClaytonNorthey92's avatar ClaytonNorthey92 Committed by GitHub
Browse files

Add reverse history search in redis-cli (linenoise) (#12543)

added reverse history search to redis-cli, use it with the following:

* CTRL+R : enable search backward mode, and search next one when
pressing CTRL+R again until reach index 0.
```
127.0.0.1:6379> keys one
127.0.0.1:6379> keys two
(reverse-i-search):                   # press CTRL+R
(reverse-i-search): keys two          # input `keys`
(reverse-i-search): keys one          # press CTRL+R again
(reverse-i-search): keys one          # press CTRL+R again, still `keys one` due to reaching index 0
(i-search): keys two                  # press CTRL+S, enable search forward
(i-search): keys two                  # press CTRL+S, still `keys one` due to reaching index 1
```

* CTRL+S : enable search forward mode, and search next one when pressing
CTRL+S again until reach index 0.
```
127.0.0.1:6379> keys one
127.0.0.1:6379> keys two
(i-search):                       # press CTRL+S
(i-search): keys one              # input `keys`
(i-search): keys two              # press CTRL+S again
(i-search): keys two              # press CTRL+R again, still `keys two` due to reaching index 0
(reverse-i-search): keys one      # press CTRL+R, enable search backward
(reverse-i-search): keys one      # press CTRL+S, still `keys one` due to reaching index 1
```

* CTRL+G : disable
```
127.0.0.1:6379> keys one
127.0.0.1:6379> keys two
(reverse-i-search):                   # press CTRL+R
(reverse-i-search): keys two          # input `keys`
127.0.0.1:6379>                       # press CTRL+G
```

* CTRL+C : disable
```
127.0.0.1:6379> keys one
127.0.0.1:6379> keys two
(reverse-i-search):                   # press CTRL+R
(reverse-i-search): keys two          # input `keys`
127.0.0.1:6379>                       # press CTRL+G
```

* TAB : use the current search result and exit search mode
```
127.0.0.1:6379> keys one
127.0.0.1:6379> keys two
(reverse-i-search):                # press CTRL+R
(reverse-i-search): keys two       # input `keys`
127.0.0.1:6379> keys two           # press TAB
```

* ENTER : use the current search result and execute the command
```
127.0.0.1:6379> keys one
127.0.0.1:6379> keys two
(reverse-i-search):                 # press CTRL+R
(reverse-i-search): keys two        # input `keys`
127.0.0.1:6379> keys two            # press ENTER
(empty array)
127.0.0.1:6379>
```

* any arrow key will disable reverse search

your result will have the search match bolded, you can press enter to
execute the full result

note: I have _only added this for multi-line mode_, as it seems to be
forced that way when `repl` is called

Closes: https://github.com/redis/redis/issues/8277



---------
Co-authored-by: default avatarClayton Northey <clayton@knowbl.com>
Co-authored-by: default avatarViktor Söderqvist <viktor.soderqvist@est.tech>
Co-authored-by: default avatardebing.sun <debing.sun@redis.com>
Co-authored-by: default avatarBjorn Svensson <bjorn.a.svensson@est.tech>
Co-authored-by: default avatarViktor Söderqvist <viktor@zuiderkwast.se>
parent 0e1de78f
......@@ -117,6 +117,7 @@
#include <sys/types.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <assert.h>
#include "linenoise.h"
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
......@@ -137,6 +138,16 @@ static char **history = NULL;
static int *history_sensitive = NULL; /* An array records whether each line in
* history is sensitive. */
static int reverse_search_mode_enabled = 0;
static int reverse_search_direction = 0; /* 1 means forward, -1 means backward. */
static int cycle_to_next_search = 0; /* indicates whether to continue the search with CTRL+S or CTRL+R. */
static char search_result[LINENOISE_MAX_LINE];
static char search_result_friendly[LINENOISE_MAX_LINE];
static int search_result_history_index = 0;
static int search_result_start_offset = 0;
static int ignore_once_hint = 0; /* Flag to ignore hint once, preventing it from interfering
* with search results right after exiting search mode. */
/* The linenoiseState structure represents the state during line editing.
* We pass this state to functions implementing specific editing
* functionalities. */
......@@ -145,6 +156,7 @@ struct linenoiseState {
int ofd; /* Terminal stdout file descriptor. */
char *buf; /* Edited line buffer. */
size_t buflen; /* Edited line buffer size. */
const char *origin_prompt; /* Original prompt, used to restore when exiting search mode. */
const char *prompt; /* Prompt to display. */
size_t plen; /* Prompt length. */
size_t pos; /* Current cursor position. */
......@@ -155,6 +167,13 @@ struct linenoiseState {
int history_index; /* The history index we are currently editing. */
};
typedef struct {
int len; /* Length of the result string. */
char *result; /* Search result string. */
int search_term_index; /* Position of the search term in the history record. */
int search_term_len; /* Length of the search term. */
} linenoiseHistorySearchResult;
enum KEY_ACTION{
KEY_NULL = 0, /* NULL */
CTRL_A = 1, /* Ctrl+a */
......@@ -163,6 +182,7 @@ enum KEY_ACTION{
CTRL_D = 4, /* Ctrl-d */
CTRL_E = 5, /* Ctrl-e */
CTRL_F = 6, /* Ctrl-f */
CTRL_G = 7, /* Ctrl-g */
CTRL_H = 8, /* Ctrl-h */
TAB = 9, /* Tab */
NL = 10, /* Enter typed before raw mode was enabled */
......@@ -171,6 +191,8 @@ enum KEY_ACTION{
ENTER = 13, /* Enter */
CTRL_N = 14, /* Ctrl-n */
CTRL_P = 16, /* Ctrl-p */
CTRL_R = 18, /* Ctrl-r */
CTRL_S = 19, /* Ctrl-s */
CTRL_T = 20, /* Ctrl-t */
CTRL_U = 21, /* Ctrl+u */
CTRL_W = 23, /* Ctrl+w */
......@@ -181,6 +203,12 @@ enum KEY_ACTION{
static void linenoiseAtExit(void);
int linenoiseHistoryAdd(const char *line, int is_sensitive);
static void refreshLine(struct linenoiseState *l);
static void refreshSearchResult(struct linenoiseState *ls);
static inline void resetSearchResult(void) {
memset(search_result, 0, sizeof(search_result));
memset(search_result_friendly, 0, sizeof(search_result_friendly));
}
/* Debugging macro. */
#if 0
......@@ -221,6 +249,41 @@ void linenoiseSetMultiLine(int ml) {
mlmode = ml;
}
#define REVERSE_SEARCH_PROMPT(direction) ((direction) == -1 ? "(reverse-i-search): " : "(i-search): ")
/* Enables the reverse search mode and refreshes the prompt. */
static void enableReverseSearchMode(struct linenoiseState *l) {
assert(reverse_search_mode_enabled != 1);
reverse_search_mode_enabled = 1;
l->origin_prompt = l->prompt;
l->prompt = REVERSE_SEARCH_PROMPT(reverse_search_direction);
refreshLine(l);
}
/* This function disables the reverse search mode and returns the terminal to its original state.
* If the 'discard' parameter is true, it discards the user's input search keyword and search result.
* Otherwise, it copies the search result into 'buf', If there is no search result, it copies the
* input search keyword instead. */
static void disableReverseSearchMode(struct linenoiseState *l, char *buf, size_t buflen, int discard) {
if (discard) {
buf[0] = '\0';
l->pos = l->len = 0;
} else {
ignore_once_hint = 1;
if (strlen(search_result)) {
strncpy(buf, search_result, buflen);
buf[buflen-1] = '\0';
l->pos = l->len = strlen(buf);
}
}
/* Reset the state to non-search state. */
reverse_search_mode_enabled = 0;
l->prompt = l->origin_prompt;
resetSearchResult();
refreshLine(l);
}
/* Return true if the terminal name is in the list of terminals we know are
* not able to understand basic escape sequences. */
static int isUnsupportedTerm(void) {
......@@ -235,6 +298,10 @@ static int isUnsupportedTerm(void) {
/* Raw mode: 1960 magic shit. */
static int enableRawMode(int fd) {
if (getenv("FAKETTY_WITH_PROMPT") != NULL) {
return 0;
}
struct termios raw;
if (!isatty(STDIN_FILENO)) goto fatal;
......@@ -303,6 +370,9 @@ static int getCursorPosition(int ifd, int ofd) {
/* Try to get the number of columns in the current terminal, or assume 80
* if it fails. */
static int getColumns(int ifd, int ofd) {
if (getenv("FAKETTY_WITH_PROMPT") != NULL) {
goto failed;
}
struct winsize ws;
if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
......@@ -494,6 +564,13 @@ static void abFree(struct abuf *ab) {
* to the right of the prompt. */
void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) {
char seq[64];
/* Show hits when not in reverse search mode and not instructed to ignore once. */
if (reverse_search_mode_enabled || ignore_once_hint) {
ignore_once_hint = 0;
return;
}
if (hintsCallback && plen+l->len < l->cols) {
int color = -1, bold = 0;
char *hint = hintsCallback(l->buf,&color,&bold);
......@@ -606,7 +683,12 @@ static void refreshMultiLine(struct linenoiseState *l) {
unsigned int i;
for (i = 0; i < l->len; i++) abAppend(&ab,"*",1);
} else {
abAppend(&ab,l->buf,l->len);
refreshSearchResult(l);
if (strlen(search_result) > 0) {
abAppend(&ab, search_result_friendly, strlen(search_result_friendly));
} else {
abAppend(&ab,l->buf,l->len);
}
}
/* Show hits if any. */
......@@ -639,6 +721,9 @@ static void refreshMultiLine(struct linenoiseState *l) {
/* Set column. */
col = (plen+(int)l->pos) % (int)l->cols;
if (strlen(search_result) > 0) {
col += search_result_start_offset;
}
lndebug("set col %d", 1+col);
if (col)
snprintf(seq,64,"\r\x1b[%dC", col);
......@@ -834,7 +919,7 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
/* Only autocomplete when the callback is set. It returns < 0 when
* there was an error reading from fd. Otherwise it will return the
* character that should be handled next. */
if (c == 9 && completionCallback != NULL) {
if (c == TAB && completionCallback != NULL && !reverse_search_mode_enabled) {
c = completeLine(&l);
/* Return on errors */
if (c < 0) return l.len;
......@@ -845,6 +930,9 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
switch(c) {
case NL: /* enter, typed before raw mode was enabled */
break;
case TAB:
if (reverse_search_mode_enabled) disableReverseSearchMode(&l, buf, buflen, 0);
break;
case ENTER: /* enter */
history_len--;
free(history[history_len]);
......@@ -857,8 +945,14 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
refreshLine(&l);
hintsCallback = hc;
}
if (reverse_search_mode_enabled) disableReverseSearchMode(&l, buf, buflen, 0);
return (int)l.len;
case CTRL_C: /* ctrl-c */
if (reverse_search_mode_enabled) {
disableReverseSearchMode(&l, buf, buflen, 1);
break;
}
errno = EAGAIN;
return -1;
case BACKSPACE: /* backspace */
......@@ -893,6 +987,23 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
case CTRL_P: /* ctrl-p */
linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
break;
case CTRL_R:
case CTRL_S:
reverse_search_direction = c == CTRL_R ? -1 : 1;
if (reverse_search_mode_enabled) {
/* cycle search results */
cycle_to_next_search = 1;
l.prompt = REVERSE_SEARCH_PROMPT(reverse_search_direction);
refreshLine(&l);
break;
}
buf[0] = '\0';
l.pos = l.len = 0;
enableReverseSearchMode(&l);
break;
case CTRL_G:
if (reverse_search_mode_enabled) disableReverseSearchMode(&l, buf, buflen, 1);
break;
case CTRL_N: /* ctrl-n */
linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
break;
......@@ -903,6 +1014,11 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
if (read(l.ifd,seq,1) == -1) break;
if (read(l.ifd,seq+1,1) == -1) break;
if (reverse_search_mode_enabled) {
disableReverseSearchMode(&l, buf, buflen, 1);
break;
}
/* ESC [ sequences. */
if (seq[0] == '[') {
if (seq[1] >= '0' && seq[1] <= '9') {
......@@ -1069,14 +1185,14 @@ static char *linenoiseNoTTY(void) {
* editing function or uses dummy fgets() so that you will be able to type
* something even in the most desperate of the conditions. */
char *linenoise(const char *prompt) {
char buf[LINENOISE_MAX_LINE];
char buf[LINENOISE_MAX_LINE] = {0};
int count;
if (!isatty(STDIN_FILENO)) {
if (getenv("FAKETTY_WITH_PROMPT") == NULL && !isatty(STDIN_FILENO)) {
/* Not a tty: read from file / pipe. In this mode we don't want any
* limit to the line size, so we call a function to handle that. */
return linenoiseNoTTY();
} else if (isUnsupportedTerm()) {
} else if (getenv("FAKETTY_WITH_PROMPT") == NULL && isUnsupportedTerm()) {
size_t len;
printf("%s",prompt);
......@@ -1250,3 +1366,92 @@ int linenoiseHistoryLoad(const char *filename) {
fclose(fp);
return 0;
}
/* This function updates the search index based on the direction of the search.
* Returns 0 if the beginning or end of the history is reached, otherwise, returns 1. */
static int setNextSearchIndex(int *i) {
if (reverse_search_direction == 1) {
if (*i == history_len-1) return 0;
*i = *i + 1;
} else {
if (*i <= 0) return 0;
*i = *i - 1;
}
return 1;
}
linenoiseHistorySearchResult searchInHistory(char *search_term) {
linenoiseHistorySearchResult result = {0};
if (!history_len || !strlen(search_term)) return result;
int i = cycle_to_next_search ? search_result_history_index :
(reverse_search_direction == -1 ? history_len-1 : 0);
while (1) {
char *found = strstr(history[i], search_term);
/* check if we found the same string at another index when cycling, this would be annoying to cycle through
* as it might appear that cycling isn't working */
int strings_are_the_same = cycle_to_next_search && strcmp(history[i], history[search_result_history_index]) == 0;
if (found && !strings_are_the_same) {
int haystack_index = found - history[i];
result.result = history[i];
result.len = strlen(history[i]);
result.search_term_index = haystack_index;
result.search_term_len = strlen(search_term);
search_result_history_index = i;
break;
}
/* Exit if reached the end. */
if (!setNextSearchIndex(&i)) break;
}
return result;
}
static void refreshSearchResult(struct linenoiseState *ls) {
if (!reverse_search_mode_enabled) {
return;
}
linenoiseHistorySearchResult sr = searchInHistory(ls->buf);
int found = sr.result && sr.len;
/* If the search term has not changed and we are cycling to the next search result
* (using CTRL+R or CTRL+S), there is no need to reset the old search result. */
if (!cycle_to_next_search || found)
resetSearchResult();
cycle_to_next_search = 0;
if (found) {
char *bold = "\x1B[1m";
char *normal = "\x1B[0m";
int size_needed = sr.search_term_index + sr.search_term_len + sr.len -
(sr.search_term_index+sr.search_term_len) + sizeof(normal) + sizeof(bold) + sizeof(normal);
if (size_needed > sizeof(search_result_friendly) - 1) {
return;
}
/* Allocate memory for the prefix, match, and suffix strings, one extra byte for `\0`. */
char *prefix = calloc(sizeof(char), sr.search_term_index + 1);
char *match = calloc(sizeof(char), sr.search_term_len + 1);
char *suffix = calloc(sizeof(char), sr.len - (sr.search_term_index+sr.search_term_len) + 1);
memcpy(prefix, sr.result, sr.search_term_index);
memcpy(match, sr.result + sr.search_term_index, sr.search_term_len);
memcpy(suffix, sr.result + sr.search_term_index + sr.search_term_len,
sr.len - (sr.search_term_index+sr.search_term_len));
sprintf(search_result, "%s%s%s", prefix, match, suffix);
sprintf(search_result_friendly, "%s%s%s%s%s%s", normal, prefix, bold, match, normal, suffix);
free(prefix);
free(match);
free(suffix);
search_result_start_offset = sr.search_term_index;
}
}
......@@ -3357,7 +3357,7 @@ static void repl(void) {
linenoiseSetFreeHintsCallback(freeHintsCallback);
/* Only use history and load the rc file when stdin is a tty. */
if (isatty(fileno(stdin))) {
if (getenv("FAKETTY_WITH_PROMPT") != NULL || isatty(fileno(stdin))) {
historyfile = getDotfilePath(REDIS_CLI_HISTFILE_ENV,REDIS_CLI_HISTFILE_DEFAULT);
//keep in-memory history always regardless if history file can be determined
history = 1;
......
......@@ -68,6 +68,14 @@ start_server {tags {"cli"}} {
set _ [format_output [read_cli $fd]]
}
# Note: prompt may be affected by the local history, if failed, please
# try using `rm ~/.rediscli_history` to delete it and then retry.
proc test_interactive_cli_with_prompt {name code} {
set ::env(FAKETTY_WITH_PROMPT) 1
test_interactive_cli $name $code
unset ::env(FAKETTY_WITH_PROMPT)
}
proc test_interactive_cli {name code} {
set ::env(FAKETTY) 1
set fd [open_cli]
......@@ -145,6 +153,200 @@ start_server {tags {"cli"}} {
unset ::env(FAKETTY)
}
test_interactive_cli_with_prompt "should find first search result" {
run_command $fd "keys one\x0D"
run_command $fd "keys two\x0D"
puts $fd "\x12" ;# CTRL+R
read_cli $fd
puts -nonewline $fd "ey"
set result [read_cli $fd]
assert_equal 1 [regexp {\(reverse-i-search\): \x1B\[0mk\x1B\[1mey\x1B\[0ms two} $result]
}
test_interactive_cli_with_prompt "should find and use the first search result" {
set now [clock seconds]
run_command $fd "SET blah \"myvalue\"\x0D"
run_command $fd "GET blah\x0D"
puts $fd "\x12" ;# CTRL+R
read_cli $fd
puts -nonewline $fd "ET b"
set result [read_cli $fd]
assert_equal 1 [regexp {\(reverse-i-search\): \x1B\[0mG\x1B\[1mET b\x1B\[0mlah} $result]
puts $fd "\x0D" ;# ENTER
set result2 [read_cli $fd]
assert_equal 1 [regexp {.*"myvalue"\n} $result2]
}
test_interactive_cli_with_prompt "should be ok if there is no result" {
puts $fd "\x12" ;# CTRL+R
set now [clock seconds]
puts $fd "\x12" ;# CTRL+R
set result [read_cli $fd]
assert_equal 1 [regexp {\(reverse-i-search\):} $result]
set result2 [run_command $fd "keys \"$now\"\x0D"]
assert_equal 1 [regexp {.*(empty array).*} $result2]
}
test_interactive_cli_with_prompt "upon submitting search, (reverse-i-search) prompt should go away" {
puts $fd "\x12" ;# CTRL+R
set now [clock seconds]
set result [read_cli $fd]
assert_equal 1 [regexp {\(reverse-i-search\):} $result]
set result2 [run_command $fd "keys \"$now\"\x0D"]
assert_equal 1 [regexp {127\.0\.0\.1:[0-9]*(\[[0-9]])?>} $result2]
}
test_interactive_cli_with_prompt "should find second search result if user presses ctrl+r again" {
run_command $fd "keys one\x0D"
run_command $fd "keys two\x0D"
puts $fd "\x12" ;# CTRL+R
read_cli $fd
puts -nonewline $fd "ey"
set result [read_cli $fd]
assert_equal 1 [regexp {\(reverse-i-search\): \x1B\[0mk\x1B\[1mey\x1B\[0ms two} $result]
puts $fd "\x12" ;# CTRL+R
set result [read_cli $fd]
assert_equal 1 [regexp {\(reverse-i-search\): \x1B\[0mk\x1B\[1mey\x1B\[0ms one} $result]
}
test_interactive_cli_with_prompt "should find second search result if user presses ctrl+s" {
run_command $fd "keys one\x0D"
run_command $fd "keys two\x0D"
puts $fd "\x13" ;# CTRL+S
read_cli $fd
puts -nonewline $fd "ey"
set result [read_cli $fd]
assert_equal 1 [regexp {\(i-search\): \x1B\[0mk\x1B\[1mey\x1B\[0ms one} $result]
puts $fd "\x13" ;# CTRL+S
set result [read_cli $fd]
assert_equal 1 [regexp {\(i-search\): \x1B\[0mk\x1B\[1mey\x1B\[0ms two} $result]
}
test_interactive_cli_with_prompt "should exit reverse search if user presses ctrl+g" {
run_command $fd ""
puts $fd "\x12" ;# CTRL+R
set result [read_cli $fd]
assert_equal 1 [regexp {\(reverse-i-search\):} $result]
puts $fd "\x07" ;# CTRL+G
set result2 [read_cli $fd]
assert_equal 1 [regexp {127\.0\.0\.1:[0-9]*(\[[0-9]])?>} $result2]
}
test_interactive_cli_with_prompt "should exit reverse search if user presses up arrow" {
run_command $fd ""
puts $fd "\x12" ;# CTRL+R
set result [read_cli $fd]
assert_equal 1 [regexp {\(reverse-i-search\):} $result]
puts $fd "\x1B\x5B\x41" ;# up arrow
set result2 [read_cli $fd]
assert_equal 1 [regexp {127\.0\.0\.1:[0-9]*(\[[0-9]])?>} $result2]
}
test_interactive_cli_with_prompt "should exit reverse search if user presses right arrow" {
run_command $fd ""
puts $fd "\x12" ;# CTRL+R
set result [read_cli $fd]
assert_equal 1 [regexp {\(reverse-i-search\):} $result]
puts $fd "\x1B\x5B\x42" ;# right arrow
set result2 [read_cli $fd]
assert_equal 1 [regexp {127\.0\.0\.1:[0-9]*(\[[0-9]])?>} $result2]
}
test_interactive_cli_with_prompt "should exit reverse search if user presses down arrow" {
run_command $fd ""
puts $fd "\x12" ;# CTRL+R
set result [read_cli $fd]
assert_equal 1 [regexp {\(reverse-i-search\):} $result]
puts $fd "\x1B\x5B\x43" ;# down arrow
set result2 [read_cli $fd]
assert_equal 1 [regexp {127\.0\.0\.1:[0-9]*(\[[0-9]])?>} $result2]
}
test_interactive_cli_with_prompt "should exit reverse search if user presses left arrow" {
run_command $fd ""
puts $fd "\x12" ;# CTRL+R
set result [read_cli $fd]
assert_equal 1 [regexp {\(reverse-i-search\):} $result]
puts $fd "\x1B\x5B\x44" ;# left arrow
set result2 [read_cli $fd]
assert_equal 1 [regexp {127\.0\.0\.1:[0-9]*(\[[0-9]])?>} $result2]
}
test_interactive_cli_with_prompt "should disable and persist line if user presses tab" {
run_command $fd ""
puts $fd "\x12" ;# CTRL+R
set result [read_cli $fd]
assert_equal 1 [regexp {\(reverse-i-search\):} $result]
puts -nonewline $fd "GET blah"
read_cli $fd
puts -nonewline $fd "\x09" ;# TAB
set result2 [read_cli $fd]
assert_equal 1 [regexp {127\.0\.0\.1:[0-9]*(\[[0-9]])?> GET blah} $result2]
}
test_interactive_cli_with_prompt "should disable and persist search result if user presses tab" {
run_command $fd "GET one\x0D"
puts $fd "\x12" ;# CTRL+R
set result [read_cli $fd]
assert_equal 1 [regexp {\(reverse-i-search\):} $result]
puts -nonewline $fd "one"
read_cli $fd
puts -nonewline $fd "\x09" ;# TAB
set result2 [read_cli $fd]
assert_equal 1 [regexp {127\.0\.0\.1:[0-9]*(\[[0-9]])?> GET one} $result2]
}
test_interactive_cli_with_prompt "should disable and persist line and move the cursor if user presses tab" {
run_command $fd ""
puts $fd "\x12" ;# CTRL+R
set result [read_cli $fd]
assert_equal 1 [regexp {\(reverse-i-search\):} $result]
puts -nonewline $fd "GET blah"
read_cli $fd
puts -nonewline $fd "\x09" ;# TAB
set result2 [read_cli $fd]
assert_equal 1 [regexp {127\.0\.0\.1:[0-9]*(\[[0-9]])?> GET blah} $result2]
puts -nonewline $fd "suffix"
set result3 [read_cli $fd]
assert_equal 1 [regexp {127\.0\.0\.1:[0-9]*(\[[0-9]])?> GET blahsuffix} $result3]
}
test_interactive_cli "INFO response should be printed raw" {
set lines [split [run_command $fd info] "\n"]
foreach line $lines {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment