mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 12:53:13 +00:00
Enable globbing in history-pager
The existing subsequence search commonly returns false positives. Support globs, to allow searching for disconnected substrings in a better way. Closes #10143 Closes #10131
This commit is contained in:
parent
31d157f117
commit
b44bdea230
4 changed files with 61 additions and 4 deletions
|
@ -31,6 +31,7 @@ Scripting improvements
|
|||
Interactive improvements
|
||||
------------------------
|
||||
- Command-specific tab completions may now offer results whose first character is a period. For example, it is now possible to tab-complete ``git add`` for files with leading periods. The default file completions hide these files, unless the token itself has a leading period (:issue:`3707`).
|
||||
- The :kbd:`Control-R` history search now uses glob syntax (:issue:`10131`).
|
||||
|
||||
New or improved bindings
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
@ -466,6 +466,48 @@ wcstring parse_util_unescape_wildcards(const wcstring &str) {
|
|||
return result;
|
||||
}
|
||||
|
||||
bool parse_util_contains_wildcards(const wcstring &str) {
|
||||
bool unesc_qmark = !feature_test(feature_flag_t::qmark_noglob);
|
||||
|
||||
const wchar_t *const cs = str.c_str();
|
||||
for (size_t i = 0; cs[i] != L'\0'; i++) {
|
||||
if (cs[i] == L'*') {
|
||||
return true;
|
||||
} else if (cs[i] == L'?' && unesc_qmark) {
|
||||
return true;
|
||||
} else if (cs[i] == L'\\' && cs[i + 1] == L'*') {
|
||||
i += 1;
|
||||
} else if (cs[i] == L'\\' && cs[i + 1] == L'?' && unesc_qmark) {
|
||||
i += 1;
|
||||
} else if (cs[i] == L'\\' && cs[i + 1] == L'\\') {
|
||||
// Not a wildcard, but ensure the next iteration doesn't see this escaped backslash.
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
wcstring parse_util_escape_wildcards(const wcstring &str) {
|
||||
wcstring result;
|
||||
result.reserve(str.size());
|
||||
bool unesc_qmark = !feature_test(feature_flag_t::qmark_noglob);
|
||||
|
||||
const wchar_t *const cs = str.c_str();
|
||||
for (size_t i = 0; cs[i] != L'\0'; i++) {
|
||||
if (cs[i] == L'*') {
|
||||
result.append(L"\\*");
|
||||
} else if (cs[i] == L'?' && unesc_qmark) {
|
||||
result.append(L"\\?");
|
||||
} else if (cs[i] == L'\\') {
|
||||
result.append(L"\\\\");
|
||||
} else {
|
||||
result.push_back(cs[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// Find the outermost quoting style of current token. Returns 0 if token is not quoted.
|
||||
static wchar_t get_quote(const wcstring &cmd_str, size_t len) {
|
||||
size_t i = 0;
|
||||
|
|
|
@ -96,6 +96,14 @@ size_t parse_util_get_offset(const wcstring &str, int line, long line_offset);
|
|||
/// transformation.
|
||||
wcstring parse_util_unescape_wildcards(const wcstring &str);
|
||||
|
||||
/// Return if the given string contains wildcard characters.
|
||||
bool parse_util_contains_wildcards(const wcstring &str);
|
||||
|
||||
/// Escape any wildcard characters in the given string. e.g. convert
|
||||
/// "a*b" to "a\*b".
|
||||
wcstring parse_util_escape_wildcards(const wcstring &str);
|
||||
|
||||
|
||||
/// Calculates information on the parameter at the specified index.
|
||||
///
|
||||
/// \param cmd The command to be analyzed
|
||||
|
|
|
@ -1135,17 +1135,21 @@ static history_pager_result_t history_pager_search(const HistorySharedPtr &histo
|
|||
size_t page_size = std::max(termsize_last().height / 2 - 2, (rust::isize)12);
|
||||
|
||||
rust::Box<completion_list_t> completions = new_completion_list();
|
||||
|
||||
rust::Box<HistorySearch> search =
|
||||
rust_history_search_new(history, search_string.c_str(), history_search_type_t::Contains,
|
||||
rust_history_search_new(history, search_string.c_str(), history_search_type_t::ContainsGlob,
|
||||
smartcase_flags(search_string), history_index);
|
||||
bool next_match_found = search->go_to_next_match(direction);
|
||||
if (!next_match_found) {
|
||||
// If there were no matches, try again with subsequence search
|
||||
|
||||
if (!next_match_found && !parse_util_contains_wildcards(search_string)) {
|
||||
// If there were no matches, and the user is not intending for
|
||||
// wildcard search, try again with subsequence search.
|
||||
search = rust_history_search_new(history, search_string.c_str(),
|
||||
history_search_type_t::ContainsSubsequence,
|
||||
smartcase_flags(search_string), history_index);
|
||||
next_match_found = search->go_to_next_match(direction);
|
||||
}
|
||||
|
||||
while (completions->size() < page_size && next_match_found) {
|
||||
const history_item_t &item = search->current_item();
|
||||
completions->push_back(*new_completion_with(
|
||||
|
@ -3671,7 +3675,9 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
|
|||
pager.set_prefix(MB_CUR_MAX > 1 ? L"► " : L"> ", false /* highlight */);
|
||||
// Update the search field, which triggers the actual history search.
|
||||
if (!history_search.active() || history_search.search_string().empty()) {
|
||||
insert_string(pager.search_field_line(), *command_line.text());
|
||||
// Escape any wildcards the user may have in their input.
|
||||
auto escaped_command_line = parse_util_escape_wildcards(*command_line.text());
|
||||
insert_string(pager.search_field_line(), escaped_command_line);
|
||||
} else {
|
||||
// If we have an actual history search already going, reuse that term
|
||||
// - this is if the user looks around a bit and decides to switch to the pager.
|
||||
|
|
Loading…
Reference in a new issue