Skip to content
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

Highlight all search matches in document #5702

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

wes-adams
Copy link
Contributor

@wes-adams wes-adams commented Jan 27, 2023

demo

Altering document clears search_matches highlights

(PS: the extra whitespace being highlighted in the demo gif is a glitch in asciicinema capture)

Updated display of matching text to use ui.cursor.match. this is the result:
demo

References:
#4798

@wes-adams wes-adams marked this pull request as draft January 27, 2023 14:10
@wes-adams wes-adams force-pushed the search-highlight branch 2 times, most recently from ec8de2a to c4e8146 Compare January 27, 2023 14:29
@wes-adams wes-adams changed the title Tmp - highlight working, but still needs work Highlight all search matches in document Jan 27, 2023
@the-mikedavis
Copy link
Member

I haven't looked at this yet but also see #4635 which I think will have some overlap implementation-wise

@the-mikedavis the-mikedavis added A-helix-term Area: Helix term improvements S-waiting-on-review Status: Awaiting review from a maintainer. labels Jan 27, 2023
@wes-adams
Copy link
Contributor Author

I haven't looked at this yet but also see #4635 which I think will have some overlap implementation-wise

Thanks. I'll definitely look into that work.
This PR is 100% not ready. I'm still finding my way in this project and Rust in general, so any feedback and/or reviews I can get will be greatly appreciated!

@wes-adams wes-adams marked this pull request as ready for review January 30, 2023 00:43
@wes-adams
Copy link
Contributor Author

wes-adams commented Jan 30, 2023

i think the only optimization left is to search only the visible text for matches versus the entire document
i've given this a little effort, but would welcome any feedback i can get

EDIT: the above is fixed, but a bug is introduced:
if scrolling through matches moves current view, the search range is not updated, so highlights remain in the same place, but are wrong

@pascalkuthe
Copy link
Member

Just a couple comments:

  • The matches should not be stored on the editor, they should be stored on the document or (even better) on the view
  • Right now the matches stick around forever which causes a host of problems if the text is edited. To fix this you either need to update the matches on every edit or hide them as soon as the document is edited. Regex matches has quite a bit of overhead because we need to convert Rope -> String (see Regex Automata #185) so that would not be ideal. I also dislike how long the highlights stick around in nvim (even tough I like the basic feature of search highlights) so I think it's actually a great solution to remove the search matches on edits.
  • Updating the matches while scrolling is too slow. I think you could recompute the matches on idle timeout if the view position changed.
  • This potentially should be rebased on rework positioning/rendering and enable softwrap/virtual text #5420 too as the way the "visible area" is calculated changes there (the view offset changes), this PR should also not be merged before that because I would have to update this command as part of rework positioning/rendering and enable softwrap/virtual text #5420 then (which is already very large)

@pascalkuthe pascalkuthe added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from a maintainer. labels Jan 30, 2023
@wes-adams
Copy link
Contributor Author

wes-adams commented Feb 8, 2023

Just a couple comments:

  • The matches should not be stored on the editor, they should be stored on the document or (even better) on the view
  • Right now the matches stick around forever which causes a host of problems if the text is edited. To fix this you either need to update the matches on every edit or hide them as soon as the document is edited. Regex matches has quite a bit of overhead because we need to convert Rope -> String (see Regex Automata #185) so that would not be ideal. I also dislike how long the highlights stick around in nvim (even tough I like the basic feature of search highlights) so I think it's actually a great solution to remove the search matches on edits.
  • Updating the matches while scrolling is too slow. I think you could recompute the matches on idle timeout if the view position changed.
  • This potentially should be rebased on rework positioning/rendering and enable softwrap/virtual text #5420 too as the way the "visible area" is calculated changes there (the view offset changes), this PR should also not be merged before that because I would have to update this command as part of rework positioning/rendering and enable softwrap/virtual text #5420 then (which is already very large)

i have this PR in the following state:

  • search_matches are now stored in View

  • search_matches highlights are updated with each new key pressed (in search_impl)

    • if n(or others) is used to traverse searches, search_matches are not updated
  • ; can be used to clear highlights, but highlights will resume if n is used to continue match traversal

  • highlights do not go away or update when doc updates

    • i tried to make this happen with Document::is_modified() but i had troubles
    • if is_modified() == true and the document is never saved, the search_matches highlights would never be rendered
    • i think i need help with this (either updating search_matches on edit, or clearing search_matches on edit)

    (also, FWIW, i tried all sorts of solutions using idle timeout, but it seems to be dependent on key presses. so i had some decent solutions in place, but they required a keystroke or two before highlights would update, etc)

    @the-mikedavis
    @pascalkuthe

@the-mikedavis the-mikedavis added S-waiting-on-review Status: Awaiting review from a maintainer. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Feb 8, 2023
@wes-adams wes-adams force-pushed the search-highlight branch 4 times, most recently from c46d038 to 5eb6e2f Compare February 8, 2023 18:09
helix-view/src/view.rs Outdated Show resolved Hide resolved
Comment on lines 112 to 120
impl fmt::Debug for SearchMatches {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SearchMatches")
.field("matches", &self.matches)
.field("new", &self.new)
.field("old", &self.old)
.finish()
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably not needed - can remove

Comment on lines +127 to +125
pub fn has_changed(&self, regex: &Regex) -> bool {
regex.as_str() != self.old.as_str()
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could not figure out a clean way to compare Regex, so this is what i came up with

Comment on lines +192 to +189
new: Regex::new("").unwrap(),
old: Regex::new("").unwrap(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i assume unwrap is safe to use in a case like this? but it is a big assumption...

helix-view/src/view.rs Outdated Show resolved Hide resolved
// but set search_matches.old to "" so that if 'n' is pressed
// to continue to traverse matches, highlights resume
view.search_matches.clear();
view.search_matches.old = Regex::new("").unwrap();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again... is unwrap safe to use here?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it is initialize code with empty string. I don't think unwrap will panic here in runtime. It's ok imo.

helix-term/src/ui/editor.rs Outdated Show resolved Hide resolved
@pathwave
Copy link

pathwave commented Feb 9, 2023

Is the color here the same as selections? Might need to difference between those.

A feature I would like is to highlight words that match the current primary selection, and that seems to overlap with this (since when you search the search term gets selected)

@wes-adams
Copy link
Contributor Author

wes-adams commented Feb 10, 2023

Is the color here the same as selections? Might need to difference between those.

A feature I would like is to highlight words that match the current primary selection, and that seems to overlap with this (since when you search the search term gets selected)

the search_matches highlight color is dictated by ui-selection:

    pub fn get_search_matches(
        &self,
        theme: &Theme,
    ) -> Option<Vec<(usize, std::ops::Range<usize>)>> {
        let selection_scope = theme
            .find_scope_index("ui.selection")
            .expect("could not find `ui.selection` scope in the theme!");
. . . 

an easy update or change to have a different highlight for selection and search_matches, but there would have to be a candidate definition in the theme configs.

Comment on lines +646 to +688
pub fn update_search_matches(&mut self, contents: &str, regex: &Regex) {
// this is expensive. only update search matches if search criteria has changed
if self.search_matches.has_changed(regex) {
self.search_matches.update(regex);
self.search_matches.clear();
for one_match in self.search_matches.new.find_iter(contents) {
self.search_matches
.matches
.push((one_match.start(), one_match.end()));
}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the first if checks if the Regex search criteria has changed. this prevents this block from executing if the user is traversing match results using n or similar. if the Regex search criteria has indeed changed, old is set equal to new, and existing matches are cleared. new matches are calculated via expensive Regex operation based on new search criteria

@wes-adams wes-adams force-pushed the search-highlight branch 3 times, most recently from 606e0c1 to 629acc9 Compare February 10, 2023 22:02
@wes-adams
Copy link
Contributor Author

this one is ready for a closer look guys

i think the behavior is close to what @pascalkuthe outlined in the comment above

@the-mikedavis
@pascalkuthe

@wes-adams
Copy link
Contributor Author

would either of you please take a look?
i'd be very grateful for some feedback on this one.
@the-mikedavis @pascalkuthe

@tmpm697
Copy link

tmpm697 commented Aug 29, 2023

What's status of this PR? this is really helpful to have all matched words highlighted.

@lpchaim
Copy link

lpchaim commented Oct 15, 2023

I've been looking into adding a search index/total matches statusline as I was used to back in nvim (e.g., indicating you're on match 2 out of 5 with something like [2/5] on the bottom) and it does look like I should wait until this gets merged in the interest of not duplicating the work made here so that the context holds search match information and whatnot, so I'll be keeping an eye on this.

@wes-adams
Copy link
Contributor Author

@the-mikedavis @archseer @pascalkuthe @sudormrfbin
sorry to blindly tag you guys again, but i'm still hoping to get some sort of review on this PR
would anyone kindly help me gain some traction on this?

@pascalkuthe
Copy link
Member

pascalkuthe commented Oct 16, 2023

my concern from #5702 (comment) still apply/are unadressed.

Running a regex over document contents synchronously on every keypress is unacceotable. Particularly of the rope -> string conversion is very non-ideal and should be done rarely (or rather not at all, the current implementation is a workaround). I am working on a streaming regex engine but that still takes some time and even with that solved (which would improve pref a lot) running a regex on every keypress is stijj not something I am ok with. Using the transactional word mapping in #6447 could allow you to update the ranges without actually running a regex and then you could run the actual regex with a 5s debounce or similar (with #8021). I don't think regularly covering to string would be ok tough so that has to wait until we have a streaming regex engine... Which will be a while.

You could keep all of this simpler by dismissing changes when transactions are applied just as I said in my first comment.
Store the highlights in a HashMap<ViewId, SerachMatches> and then clear the entire map in apply_impl. Then you just need some sort of view position changed hook (with #8021) to update the matches when scrolling (similar to inlay hints but not based on the idle timeout like the current implementation. I don't want to merge new stuff that uses the idle timeout).

@the-mikedavis the-mikedavis added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from a maintainer. labels Feb 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-helix-term Area: Helix term improvements S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants