mplement history search glob searches

Instead of treating the search term as a literal string to be matched
treat it as a glob. This allows the user to get a more useful set of
results by using the `*` glob character in the search term.

Partial fix for #3136
This commit is contained in:
Kurtis Rader 2017-09-15 13:43:45 -07:00
parent ee1d310651
commit 65dcd06ca1
6 changed files with 52 additions and 28 deletions

View file

@ -20,6 +20,7 @@ This section is for changes merged to the `major` branch that are not also merge
- Setting variables is much faster (#4200, #4341).
- Using a read-only variable in a for loop is now an error. Note that this never worked. It simply failed to set the for loop var and thus silently produced incorrect results (#4342).
- `math` is now a builtin rather than a wrapper around `bc` (#3157).
- `history search` supports globs for wildcard searching (#3136).
## Other significant changes
- Command substitution output is now limited to 10 MB by default (#3822).

View file

@ -140,12 +140,12 @@ static int parse_cmd_opts(history_cmd_opts_t &opts, int *optind, //!OCLINT(high
break;
}
case 'p': {
opts.search_type = HISTORY_SEARCH_TYPE_PREFIX;
opts.search_type = HISTORY_SEARCH_TYPE_PREFIX_GLOB;
opts.history_search_type_defined = true;
break;
}
case 'c': {
opts.search_type = HISTORY_SEARCH_TYPE_CONTAINS;
opts.search_type = HISTORY_SEARCH_TYPE_CONTAINS_GLOB;
opts.history_search_type_defined = true;
break;
}
@ -241,7 +241,7 @@ int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
// Establish appropriate defaults.
if (opts.hist_cmd == HIST_UNDEF) opts.hist_cmd = HIST_SEARCH;
if (!opts.history_search_type_defined) {
if (opts.hist_cmd == HIST_SEARCH) opts.search_type = HISTORY_SEARCH_TYPE_CONTAINS;
if (opts.hist_cmd == HIST_SEARCH) opts.search_type = HISTORY_SEARCH_TYPE_CONTAINS_GLOB;
if (opts.hist_cmd == HIST_DELETE) opts.search_type = HISTORY_SEARCH_TYPE_EXACT;
}

View file

@ -40,6 +40,7 @@
#include "parse_util.h"
#include "path.h"
#include "reader.h"
#include "wildcard.h" // IWYU pragma: keep
#include "wutil.h" // IWYU pragma: keep
// Our history format is intended to be valid YAML. Here it is:
@ -456,31 +457,36 @@ history_item_t::history_item_t(const wcstring &str, time_t when, history_identif
bool history_item_t::matches_search(const wcstring &term, enum history_search_type_t type,
bool case_sensitive) const {
// We don't use a switch below because there are only three cases and if the strings are the
// same length we can use the faster HISTORY_SEARCH_TYPE_EXACT for the other two cases.
//
// Too, we consider equal strings to match a prefix search, so that autosuggest will allow
// suggesting what you've typed.
if (case_sensitive) {
if (type == HISTORY_SEARCH_TYPE_EXACT || term.size() == contents.size()) {
return term == contents;
} else if (type == HISTORY_SEARCH_TYPE_CONTAINS) {
return contents.find(term) != wcstring::npos;
} else if (type == HISTORY_SEARCH_TYPE_PREFIX) {
return string_prefixes_string(term, contents);
}
} else {
wcstring lterm(L"");
for (wcstring::const_iterator it = term.begin(); it != term.end(); ++it) {
lterm.push_back(towlower(*it));
}
// Note that this->term has already been lowercased when constructing the
// search object if we're doing a case insensitive search.
const wcstring &content_to_match = case_sensitive ? contents : contents_lower;
if (type == HISTORY_SEARCH_TYPE_EXACT || lterm.size() == contents.size()) {
return lterm == contents_lower;
} else if (type == HISTORY_SEARCH_TYPE_CONTAINS) {
return contents_lower.find(lterm) != wcstring::npos;
} else if (type == HISTORY_SEARCH_TYPE_PREFIX) {
return string_prefixes_string(lterm, contents_lower);
switch (type) {
case HISTORY_SEARCH_TYPE_EXACT: {
return term == content_to_match;
}
case HISTORY_SEARCH_TYPE_CONTAINS: {
return content_to_match.find(term) != wcstring::npos;
}
case HISTORY_SEARCH_TYPE_PREFIX: {
return string_prefixes_string(term, content_to_match);
}
case HISTORY_SEARCH_TYPE_CONTAINS_GLOB: {
wcstring wcpattern1 = parse_util_unescape_wildcards(term);
if (wcpattern1.front() != ANY_STRING) wcpattern1.insert(0, 1, ANY_STRING);
if (wcpattern1.back() != ANY_STRING) wcpattern1.push_back(ANY_STRING);
return wildcard_match(content_to_match, wcpattern1);
}
case HISTORY_SEARCH_TYPE_PREFIX_GLOB: {
wcstring wcpattern2 = parse_util_unescape_wildcards(term);
if (wcpattern2.back() != ANY_STRING) wcpattern2.push_back(ANY_STRING);
return wildcard_match(content_to_match, wcpattern2);
}
case HISTORY_SEARCH_TYPE_CONTAINS_PCRE: {
abort();
}
case HISTORY_SEARCH_TYPE_PREFIX_PCRE: {
abort();
}
}
DIE("unexpected history_search_type_t value");

View file

@ -48,7 +48,15 @@ enum history_search_type_t {
// Search for commands containing the given string.
HISTORY_SEARCH_TYPE_CONTAINS,
// Search for commands starting with the given string.
HISTORY_SEARCH_TYPE_PREFIX
HISTORY_SEARCH_TYPE_PREFIX,
// Search for commands containing the given glob pattern.
HISTORY_SEARCH_TYPE_CONTAINS_GLOB,
// Search for commands starting with the given glob pattern.
HISTORY_SEARCH_TYPE_PREFIX_GLOB,
// Search for commands containing the given PCRE pattern.
HISTORY_SEARCH_TYPE_CONTAINS_PCRE,
// Search for commands starting with the given PCRE pattern.
HISTORY_SEARCH_TYPE_PREFIX_PCRE
};
typedef uint32_t history_identifier_t;

View file

@ -110,6 +110,14 @@ expect_prompt -re {history search --exact 'echo hell'\r\n} {
puts stderr "history function explicit exact search 'echo hell' failed"
}
# Verify that glob searching works.
send "history search --prefix 'echo start*echo end'\r"
expect_prompt -re {echo start1; builtin history; echo end1\r\n} {
puts "history function explicit glob search 'echo start*echo end' succeeded"
} timeout {
puts stderr "history function explicit glob search 'echo start*echo end' failed"
}
# ==========
# Delete a single command we recently ran.
send "history delete -e -C 'echo hello'\r"

View file

@ -6,6 +6,7 @@ history function implicit search with timestamps succeeded
history function explicit exact search 'echo goodbye' succeeded
history function explicit exact search 'echo hello' succeeded
history function explicit exact search 'echo hell' succeeded
history function explicit glob search 'echo start*echo end' succeeded
history function explicit exact delete 'echo hello' succeeded
history function explicit prefix delete 'echo hello AGAIN' succeeded
history function explicit exact search 'echo hello again' succeeded