mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 04:43:10 +00:00
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:
parent
ee1d310651
commit
65dcd06ca1
6 changed files with 52 additions and 28 deletions
|
@ -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).
|
- 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).
|
- 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).
|
- `math` is now a builtin rather than a wrapper around `bc` (#3157).
|
||||||
|
- `history search` supports globs for wildcard searching (#3136).
|
||||||
|
|
||||||
## Other significant changes
|
## Other significant changes
|
||||||
- Command substitution output is now limited to 10 MB by default (#3822).
|
- Command substitution output is now limited to 10 MB by default (#3822).
|
||||||
|
|
|
@ -140,12 +140,12 @@ static int parse_cmd_opts(history_cmd_opts_t &opts, int *optind, //!OCLINT(high
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'p': {
|
case 'p': {
|
||||||
opts.search_type = HISTORY_SEARCH_TYPE_PREFIX;
|
opts.search_type = HISTORY_SEARCH_TYPE_PREFIX_GLOB;
|
||||||
opts.history_search_type_defined = true;
|
opts.history_search_type_defined = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'c': {
|
case 'c': {
|
||||||
opts.search_type = HISTORY_SEARCH_TYPE_CONTAINS;
|
opts.search_type = HISTORY_SEARCH_TYPE_CONTAINS_GLOB;
|
||||||
opts.history_search_type_defined = true;
|
opts.history_search_type_defined = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -241,7 +241,7 @@ int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
||||||
// Establish appropriate defaults.
|
// Establish appropriate defaults.
|
||||||
if (opts.hist_cmd == HIST_UNDEF) opts.hist_cmd = HIST_SEARCH;
|
if (opts.hist_cmd == HIST_UNDEF) opts.hist_cmd = HIST_SEARCH;
|
||||||
if (!opts.history_search_type_defined) {
|
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;
|
if (opts.hist_cmd == HIST_DELETE) opts.search_type = HISTORY_SEARCH_TYPE_EXACT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
#include "parse_util.h"
|
#include "parse_util.h"
|
||||||
#include "path.h"
|
#include "path.h"
|
||||||
#include "reader.h"
|
#include "reader.h"
|
||||||
|
#include "wildcard.h" // IWYU pragma: keep
|
||||||
#include "wutil.h" // IWYU pragma: keep
|
#include "wutil.h" // IWYU pragma: keep
|
||||||
|
|
||||||
// Our history format is intended to be valid YAML. Here it is:
|
// 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 history_item_t::matches_search(const wcstring &term, enum history_search_type_t type,
|
||||||
bool case_sensitive) const {
|
bool case_sensitive) const {
|
||||||
// We don't use a switch below because there are only three cases and if the strings are the
|
// Note that this->term has already been lowercased when constructing the
|
||||||
// same length we can use the faster HISTORY_SEARCH_TYPE_EXACT for the other two cases.
|
// search object if we're doing a case insensitive search.
|
||||||
//
|
const wcstring &content_to_match = case_sensitive ? contents : contents_lower;
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == HISTORY_SEARCH_TYPE_EXACT || lterm.size() == contents.size()) {
|
switch (type) {
|
||||||
return lterm == contents_lower;
|
case HISTORY_SEARCH_TYPE_EXACT: {
|
||||||
} else if (type == HISTORY_SEARCH_TYPE_CONTAINS) {
|
return term == content_to_match;
|
||||||
return contents_lower.find(lterm) != wcstring::npos;
|
}
|
||||||
} else if (type == HISTORY_SEARCH_TYPE_PREFIX) {
|
case HISTORY_SEARCH_TYPE_CONTAINS: {
|
||||||
return string_prefixes_string(lterm, contents_lower);
|
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");
|
DIE("unexpected history_search_type_t value");
|
||||||
|
|
|
@ -48,7 +48,15 @@ enum history_search_type_t {
|
||||||
// Search for commands containing the given string.
|
// Search for commands containing the given string.
|
||||||
HISTORY_SEARCH_TYPE_CONTAINS,
|
HISTORY_SEARCH_TYPE_CONTAINS,
|
||||||
// Search for commands starting with the given string.
|
// 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;
|
typedef uint32_t history_identifier_t;
|
||||||
|
|
|
@ -110,6 +110,14 @@ expect_prompt -re {history search --exact 'echo hell'\r\n} {
|
||||||
puts stderr "history function explicit exact search 'echo hell' failed"
|
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.
|
# Delete a single command we recently ran.
|
||||||
send "history delete -e -C 'echo hello'\r"
|
send "history delete -e -C 'echo hello'\r"
|
||||||
|
|
|
@ -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 goodbye' succeeded
|
||||||
history function explicit exact search 'echo hello' succeeded
|
history function explicit exact search 'echo hello' succeeded
|
||||||
history function explicit exact search 'echo hell' 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 exact delete 'echo hello' succeeded
|
||||||
history function explicit prefix delete 'echo hello AGAIN' succeeded
|
history function explicit prefix delete 'echo hello AGAIN' succeeded
|
||||||
history function explicit exact search 'echo hello again' succeeded
|
history function explicit exact search 'echo hello again' succeeded
|
||||||
|
|
Loading…
Reference in a new issue