New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
History pager to fall back to subsequence search #9476
Conversation
I also wonder if it is reasonable to replace |
when a user expects the contains-check, subsequence semantics would give false positives |
tests/pexpects/history.py
Outdated
@@ -95,6 +95,10 @@ | |||
sendline("history search --prefix 'echo start*echo end' | cat") | |||
expect_prompt("echo start1; builtin history; echo end1\r\n") | |||
|
|||
# Verify subsequence searching works. | |||
sendline("history search --subsequence 'ec heag' | cat") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it is very useful to expose this.
I think we should instead document that the default search type (--contains
) uses glob matching, so we can use this today:
history search --contains 'e*c* *h*e*a*g'
or, more realistically
history search --contains 'ec*he*ag'
We can do
history-pager
in a followup PR, or add it to this one.
That sounds exciting
45dc371
to
3e09527
Compare
I removed the Unfortunately, I was unable to get either |
src/builtins/history.cpp
Outdated
@@ -260,6 +260,15 @@ maybe_t<int> builtin_history(parser_t &parser, io_streams_t &streams, const wcha | |||
parser.cancel_checker(), streams)) { | |||
status = STATUS_CMD_ERROR; | |||
} | |||
bool no_results = true; // TODO how can we find this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know how to check whether there were no results. history_t::search
seems to write directly to an IO stream, and returns false
only when the user supplies invalid arguments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's easier if we move this logic further down the call stack:
diff --git a/src/history.cpp b/src/history.cpp
index c07a7ebb9..e8a7d7913 100644
--- a/src/history.cpp
+++ b/src/history.cpp
@@ -1475,9 +1475,16 @@ static void do_1_history_search(history_t *hist, history_search_type_t search_ty
history_search_t searcher = history_search_t(hist, search_string, search_type,
case_sensitive ? 0 : history_search_ignore_case);
+ bool success = false;
while (!cancel_check() && searcher.go_to_next_match(history_search_direction_t::backward)) {
+ success = true;
if (!func(searcher.current_item())) {
break;
}
}
+ if (!success && (search_type == history_search_type_t::contains ||
+ search_type == history_search_type_t::contains_glob)) {
+ do_1_history_search(hist, history_search_type_t::contains_subsequence, search_string,
+ case_sensitive, func, cancel_check);
+ }
}
src/reader.cpp
Outdated
history_search_t search{history, search_string, history_search_type_t::contains_subsequence, | ||
smartcase_flags(search_string), history_index}; | ||
next_match_found = search.go_to_next_match(direction); | ||
// TODO next_match_found is 1 even if there are no matches |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
go_to_next_match
seems to return true
even when there are no matches. I don't know else to determine whether there were no matches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this small tweak, it seems to work fine for me.
The problem was that the fallback search goes out of scope, so it would always use the substring search.
diff --git a/src/reader.cpp b/src/reader.cpp
index a3b54126d..174b48cd5 100644
--- a/src/reader.cpp
+++ b/src/reader.cpp
@@ -1327,10 +1327,10 @@ static history_pager_result_t history_pager_search(const std::shared_ptr<history
bool next_match_found = search.go_to_next_match(direction);
if (!next_match_found) {
// If there were no matches, try again with subsequence search
- history_search_t search{history, search_string, history_search_type_t::contains_subsequence,
- smartcase_flags(search_string), history_index};
+ search =
+ history_search_t{history, search_string, history_search_type_t::contains_subsequence,
+ smartcase_flags(search_string), history_index};
next_match_found = search.go_to_next_match(direction);
- // TODO next_match_found is 1 even if there are no matches
}
while (completions.size() < page_size && next_match_found) {
const history_item_t &item = search.current_item();
src/history.h
Outdated
@@ -276,6 +278,9 @@ class history_search_t { | |||
/// Returns the current search result item. asserts if there is no current item. | |||
const history_item_t ¤t_item() const; | |||
|
|||
/// Returns whether the current item exists, which indicates whether a search had any results. | |||
bool current_item_exists() const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was another attempt to check for no matches, but it doesn't seem to be useful because current_item
is null before go_to_next_match()
is called, even if there are matches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
current_item is only set after the first go_to_next_match()
3e09527
to
ea80443
Compare
Thanks for the tips. I think this PR is now working as intended. However, I have been using this locally and I think I prefer it: master...elebow:fish-shell:history-contains-and-subsequence-search I believe it offers a few advantages:
If the approach in this PR is accepted as the default behavior, I would at least want a configuration variable or alternative input command ( [Edit: This comment previously suggested elebow/fish-shell@master...elebow:fish-shell:history-subsequence-search-alternative, but after using that for a few days, I no longer like it.] |
I've been mulling the idea of making Then a history pager could be accomplished with something like:
Or if a user wanted to start with the oldest entry:
Or any other arbitrary list could be passed in (file glob, git branches, ...) And with
I understand that there is some complication in combining input functions and normal commands, but it might be surmountable (with |
I like the change to the history pager a lot, I think it's a strict improvement. We should probably also add the fallback behavior to up-arrow search... though I'm not 100% sure, we can leave that for later. I don't think we should change the
preferrably
I see. I like the tiered behavior because it gives better results when typing an exact substring, which users are already used to doing.
This would lack history-specific stuff like syntax highlighting and future features like the ability to delete the selected history items or a timestamp in the description.
for generic menus, fzf should work well |
ea80443
to
0e699e2
Compare
If a `contains` search yields no results, try again with `contains_subsequence`.
0e699e2
to
997704b
Compare
Okay, I've removed the changes to the The tiered behavior is in this PR, and the "always include everything" behavior is in master...elebow:fish-shell:history-contains-and-subsequence-search. Maybe in a future PR we can add a second |
I like the new behavior, maybe we should add it to a patch release |
history
command
|
Description
This adds a subsequence search type to the
history
command. It was easier than I expected because wcstringutil already had asubsequence_in_string()
!#1874 discussed adding this behavior to
history-pager
, buthistory
seemed easier so I figured it was better to start with this. We can dohistory-pager
in a followup PR, or add it to this one.TODOs: