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

Introduce search status #8588

Closed
wants to merge 10 commits into from
Closed

Conversation

Don-Vito
Copy link
Contributor

Summary of the Pull Request

This is a PoC for:

  • Search status in SearchBox (aka number of matches + index of the current match)
  • Live search (aka search upon typing)
  • Highlighting all matches (probably should try later on separately)

PR Checklist

Detailed Description of the Pull Request / Additional comments

  • Introduced this optionally (global setting to enable it)
  • The approach is following:
    • Every time the filter changes, enumerate all matches
    • Upon navigation just take the relevant match and select it

Validation Steps Performed

  • Manual testing, more tests required - this is a PoC only

@ghost ghost added Area-Accessibility Issues related to accessibility Area-User Interface Issues pertaining to the user interface of the Console or Terminal Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Product-Terminal The new Windows Terminal. labels Dec 14, 2020
@Don-Vito Don-Vito marked this pull request as ready for review December 16, 2020 01:12
@Don-Vito
Copy link
Contributor Author

@DHowett - this is just preliminary submission, but please review (especially the performance part) to know if the direction is OK

@DHowett
Copy link
Member

DHowett commented Dec 17, 2020

@Don-Vito I haven't forgotten, so I'm going to get to this tomorrow. Thanks for being patient 😄

@zadjii-msft
Copy link
Member

Okay so I'm just playing with this in debug - haven't looked at the code too carefully. It seems like leaving the find dialog open while running something like dir /s results in a pretty horrifying slowdown. It almost seems as though I've hung the terminal entirely.

I wonder, are we doing the search on the UI thread? maybe there's a way to dispatch the searches to a background thread, and then call back to the foregroud thread with the results?

@Don-Vito
Copy link
Contributor Author

@zadjii-msft - yeah, we definitely need to run it asynchronously. I mean - search few results immediately and the rest in background (to some limit). Can you share your test setup?

@Don-Vito
Copy link
Contributor Author

@zadjii-msft, @DHowett - I am converting this to a draft to do the first set of optimizations, as the current version seems to fail the basic performance test @zadjii-msft did 😄

@Don-Vito Don-Vito marked this pull request as draft December 18, 2020 09:01
@zadjii-msft
Copy link
Member

I mean, my test setup was literally just "checkout the branch, play with it". I wasn't doing any quantitative measuring, just qualitative.

8588-perf-000

Hopefully in that gif you can see a bit of what I was hitting. The scrolling absolutely hangs every so often as it processes the search. I typed "asd" pretty quickly after opening the dialog.

@Don-Vito
Copy link
Contributor Author

@zadjii-msft - yeah. I just tried to understand what was the search (aka how many matches). I started without background thread just to get a feedback on experience. I have a local branch with background thread, that does much better, but still not sufficiently performant IMHO. I plan a bunch of optimizations, including cancelling jobs, limiting the pre-search, etc. Hope to get to it next week.

@Don-Vito Don-Vito marked this pull request as ready for review December 20, 2020 02:11
@Don-Vito
Copy link
Contributor Author

@DHowett, @zadjii-msft - I did some optimizations to prevent terminal from hanging:

  1. Don't search immediately (aka grace period to allow additional typing by user / additional output to terminal)
  2. Search ID - cancel search if the new one was triggered (can be replaced with co-routine cancellation)
  3. Run the search itself in the background thread

I am not a great master of winrt co-routines, so I believe I will need your further advice (e.g., right now I am doing a lot of switches between background and foreground, but the alternative is synchronization, and I am not sure what is better).

@zadjii-msft
Copy link
Member

Okay so this definitely feels better, even in Debug.

I think the main weirdness I'm still seeing is what happens when more text is output to the buffer. While there's output, it just displays "Searching...", and it seems like the buttons are disabled. If you stop the output (like with ^C), then the search seems to run, and updates the find dialog to say something like ?/1309 - so it knows how many results there are, but the needle isn't on one of the matches, so it just displays ?.

Maybe in that case it should just not display anything there? Or something like 1309 matches to show how many matches.

I suppose the "searching doesn't seem to work while text is being output" issue is fine for now, since it's better than the current behavior. Trying to compare to a preview build - the terminal just crashed on me entirely while trying to search while scrolling 🤣

I'll take a spin though the code later, but that's my thoughts from a purely qualitative perspective

@Don-Vito
Copy link
Contributor Author

Don-Vito commented Jan 7, 2021

Okay so this definitely feels better, even in Debug.

I think the main weirdness I'm still seeing is what happens when more text is output to the buffer. While there's output, it just displays "Searching...", and it seems like the buttons are disabled. If you stop the output (like with ^C), then the search seems to run, and updates the find dialog to say something like ?/1309 - so it knows how many results there are, but the needle isn't on one of the matches, so it just displays ?.

Maybe in that case it should just not display anything there? Or something like 1309 matches to show how many matches.

I suppose the "searching doesn't seem to work while text is being output" issue is fine for now, since it's better than the current behavior. Trying to compare to a preview build - the terminal just crashed on me entirely while trying to search while scrolling 🤣

I'll take a spin though the code later, but that's my thoughts from a purely qualitative perspective

What you describe here - is what I exactly planned as expected product behavior 😆

  • I show searching when output is produced, to show that there should be re-calculation (I don't really recalculate, I wait for output to finish). During the re-calculation you should not be able to click next (thus the buttons are disabled).
  • When the recalculation is accomplished I don't want to select the first match, as it might result in a scrolling the screen. So I actually show the number of results, but don't show on which result we stand (that is why the question mark, idea of which I borrowed from VSCode)

@Don-Vito
Copy link
Contributor Author

It's been almost a week - so scheduled bump 😊

@Don-Vito
Copy link
Contributor Author

@DHowett, @zadjii-msft - Disclaimer before you read the bellow: I really love the team, the technology and the product (so please don't hate me 👼)

You are working on a huge stuff nowadays (RPC, buffer rewrite, settings UI, state storage, and so much more) and I really don't want to delay you, but I feel somewhat frustrated lately and want to share it. My major issue is that after a week of inactivity, PR goes off my L1 cache and restoring it there requires an effort.

I tried different approaches:

  • Several small contributions - the sum of all context switches is expensive compared to the actual work.
  • Medium contribution - getting back to the zone is difficult
  • Large contribution split into many - is endless 😄

Don't get me wrong - I know that you are putting a lot of effort in reviewing my commits and in majority of cases your feedback is very timely. I totally value that! And yet, even few PRs that are stuck / having no assignee / reviewer spoil the impression as it feels as irrational use of time.

I still continue contributing, as I really believe in you and the product I use every day, but this frustration persists and I haven't found a cure myself yet 😊

I absolutely understand the challenge you are facing as a small team under MSFT's quality requirements and I am not coming with a specific suggestion of how and whether to solve this, but just wanted to raise a flag that if a contributor needs to wait a week for a follow up / several weeks to close all required cycles, this can make an establishment of robust community challenging.

Another disclaimer: This above is probably my personal issue and other contributors don't feel so.

In addition, I know I asked this is in the past, but if there are specific areas that can make my contributions more impactful (increasing the ROI of more frequent reviews) please let me know. I tried to solve P1s or tickets with many up-votes - but empirically it hardly increases the odds 😊

Sorry if it was emotional - simply we are reaching another week-end where I don't know how to advance..

@zadjii-msft
Copy link
Member

Hey @Don-Vito. I'm really sorry that we've frustrated you like this. You put together some amazing PRs, and so consistently, and you deserve better than that. I could try and give excuses for taking so long (SUI sprint, baby not sleeping, attempted overthrow of democracy), but at the end of the day, this is on me. I dropped the ball by not prioritizing reivews fast enough, and I'm sorry for that.

Trying to clear out the PR backlog has been a personal quest of mine for a while. Since coming back from the holidays, I've been more distracted and just haven't given it the time it needs. I'll do better. I wanted to get the team to give me a second qualitative opinion on this particular PR during our team sync this week, but that got cancelled, (and now that I think on it, next week's needs to be moved too, @DHowett). I'll summon the rest of the team this morning to see if they can give some feedback (when they wake up). Broader though, I'll try and be better about tapping other members of the team in on community PRs, and faster, so in the future you're not blocked on just mine and Dustin's time.

As far as PR scope goes, just keep doing what you're doing honestly. Big ones like this, like the startup actions, like some of the cmdpal work, they're all really impressive and end up moving the needle in big jumps. The small fixes are still great too! The selection dismissing for example - that's a great example of something quick that just needed someone's eyes to find the right place to fix it.

In my (very specific) opinion, the best thing you can do to get more timely reviews is find someway to make my baby sleep 😆. Seriously though, there's a lot of work to in the search dialog, and this PR is a great example of making that experience a lot better. Now it's on us to make sure that your experience as a contributor is better.

Thanks for sharing. We'll do better 😊

@zadjii-msft zadjii-msft self-assigned this Jan 15, 2021
Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

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

Okay I'm not blocking on any of these things, but I'd maybe want answers before signing off.

Sorry again for all the delays 😕

src/cascadia/TerminalControl/SearchBoxControl.xaml Outdated Show resolved Hide resolved
std::vector<std::pair<COORD, COORD>> matches;
if (!_searchState.value().Text.empty())
{
const Search::Sensitivity sensitivity = _searchState.value().CaseSensitive ?
Copy link
Member

Choose a reason for hiding this comment

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

Should SearchState just store a Search::Sensitivity instead of a bool? </nit>

Search::Sensitivity::CaseInsensitive;

Search search(*GetUiaData(), _searchState.value().Text.c_str(), Search::Direction::Forward, sensitivity);
while (co_await _SearchOne(search))
Copy link
Member

Choose a reason for hiding this comment

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

/note this while loop collects up all the search matches, on a BG thread. If a new search is started, then it stops collecting matches

Copy link
Member

Choose a reason for hiding this comment

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

oh but _SearchOne returns to the FG thread. Hmm. Does it need to do that? Could we do the whole search on one pass on the BG thread, then hop back to finish the UI bits?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@zadjii-msft - yeah.. I switched to the foreground thread to avoid synchronization. An alternative is to do everything in BG and synchronize the state

co_return;
}
}
_searchState.value().Matches.emplace(matches);
Copy link
Member

Choose a reason for hiding this comment

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

do we need to like, std::move the matches? I never remember with these STL containers.

src/cascadia/TerminalControl/TermControl.cpp Outdated Show resolved Hide resolved
found = search.FindNext();
}

co_await winrt::resume_foreground(Dispatcher());
Copy link
Member

Choose a reason for hiding this comment

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

I'm mildly worried that this means for a buffer with 1000 matches, we'll need to hop back and forth between the UI thread and a BG thread 1000 times before we can display the results.

It seems fine qualitatively, but still...

Copy link
Member

Choose a reason for hiding this comment

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

Hmm. We should be able to return the match on whatever queue, and the caller can be in charge of moving to the appropriate queue when it's necessary.

@@ -269,6 +435,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& /*sender*/, RoutedEventArgs const& /*args*/)
{
_searchBox->Visibility(Visibility::Collapsed);
_searchState.reset();
Copy link
Member

Choose a reason for hiding this comment

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

This is absolutely a future brainstorm, so feel free to ignore me.

But it might be neat to be able to set up a search string, then close the dialog, then like, hit F3 to just perform a new search and hop to the search result. So you could search once, close the dialog, then do something in the terminal, and quickly repeat the search, if that makes sense.

That would involve a lot less "check if the search box is open" code. And here, we'd just keep the search state around. Though, that would mean we'd keep searching always on output, which is maybe not what's expected. We'd need to not update the search results until the action was triggered.

</brainstorm>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah.. I see your point. This can be a cool optimization to preserve the search results between dialog usages, but I guess we will still need to know if there is an active search or not to avoid being triggered on output.

Probably should still introduce as a follow-up

@Don-Vito
Copy link
Contributor Author

Hey @Don-Vito. I'm really sorry that we've frustrated you like this. You put together some amazing PRs, and so consistently, and you deserve better than that. I could try and give excuses for taking so long (SUI sprint, baby not sleeping, attempted overthrow of democracy), but at the end of the day, this is on me. I dropped the ball by not prioritizing reivews fast enough, and I'm sorry for that.

Trying to clear out the PR backlog has been a personal quest of mine for a while. Since coming back from the holidays, I've been more distracted and just haven't given it the time it needs. I'll do better. I wanted to get the team to give me a second qualitative opinion on this particular PR during our team sync this week, but that got cancelled, (and now that I think on it, next week's needs to be moved too, @DHowett). I'll summon the rest of the team this morning to see if they can give some feedback (when they wake up). Broader though, I'll try and be better about tapping other members of the team in on community PRs, and faster, so in the future you're not blocked on just mine and Dustin's time.

As far as PR scope goes, just keep doing what you're doing honestly. Big ones like this, like the startup actions, like some of the cmdpal work, they're all really impressive and end up moving the needle in big jumps. The small fixes are still great too! The selection dismissing for example - that's a great example of something quick that just needed someone's eyes to find the right place to fix it.

In my (very specific) opinion, the best thing you can do to get more timely reviews is find someway to make my baby sleep 😆. Seriously though, there's a lot of work to in the search dialog, and this PR is a great example of making that experience a lot better. Now it's on us to make sure that your experience as a contributor is better.

Thanks for sharing. We'll do better 😊

@zadjii-msft - thanks for the kind words! And you absolutely should not be sorry - my feeling are subjective and I never shared them till now! 😊

Moreover, everything is a matter of expectations, this is my first open source project and I am still calibrating 😊

In any case, there's no urgency to cleanup the backlog right now - I was addressing the future work, and not just for me but probably for other contributors as well (which might feel similar).

The Terminal community is great with tons of ideas and reports, but I do see only several robust external code contributors (and j4james who is not a contributor but a code monster). I believe that the project deserves a bigger code contributing community: after all this is the product we use every day, the standards are great and there are enough low hanging fruits to start contributing (though I still don't know how to approach some of your "Easy Starters"). At least for me the major challenge is around interaction.

I know that you have SMEs, thus I am not sure if it is applicable, but making more devs engaged in PRs can help. In addition, even if there is no PR capacity, setting an assignee or giving some ack / keep-alive can improve the clarity... and I am already starting to suggest solutions, though I promised not to 😄

Finally, regarding the baby who refuses to sleep - my suggestion is to have a second baby. Usually this leads to two babies who do not sleep, but.. you stop caring about that anymore 😄

@carlos-zamora carlos-zamora removed their assignment Jan 20, 2021
@carlos-zamora carlos-zamora added the AutoMerge Marked for automatic merge by the bot when requirements are met label Jan 20, 2021
@ghost
Copy link

ghost commented Jan 20, 2021

Hello @carlos-zamora!

Because this pull request has the AutoMerge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@DHowett DHowett removed the AutoMerge Marked for automatic merge by the bot when requirements are met label Jan 20, 2021
Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

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

This is very clever, and I like it. It looks like it won't harm performance, but I do think that some caution is called-for around all the thread hopping.

@Don-Vito: thanks for always cutting through our layers of crap (both organizationally and code-wise) and getting good shit done 😄

// - the size in pixels
double SearchBoxControl::_TextWidth(winrt::hstring text, double fontSize)
{
auto t = winrt::Windows::UI::Xaml::Controls::TextBlock();
Copy link
Member

Choose a reason for hiding this comment

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

nit: whenever you find yourself writing auto f = type();, just prefer type f; -- it's shorter and has a better information locality!

Comment on lines +347 to +350
if (requiredWidth > StatusBox().Width())
{
StatusBox().Width(requiredWidth);
}
Copy link
Member

Choose a reason for hiding this comment

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

This seems like a complicated version of MaxWidth. Any opportunity for simplification?

@@ -181,7 +189,8 @@

<ToggleButton x:Name="CaseSensitivityButton"
x:Uid="SearchBox_CaseSensitivity"
Style="{StaticResource ToggleButtonStyle}">
Style="{StaticResource ToggleButtonStyle}"
Click="CaseSensitivityButtonClicked">
Copy link
Member

Choose a reason for hiding this comment

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

does this also trigger for keyboard activation? is there another event on ToggleButton that we can listen to for it being toggled?

Comment on lines 113 to 115
const winrt::hstring Text;
const bool CaseSensitive;
const size_t SearchId;
Copy link
Member

Choose a reason for hiding this comment

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

nit: we should enforce constness on members through constness on the SearchState itself. Class members who are themselves const are "special" (not copyable, not really moveable if I recall?)

Search::Sensitivity::CaseSensitive :
Search::Sensitivity::CaseInsensitive;
// If no matches were computed it means we need to perform the search
if (!_searchState.value().Matches.has_value())
Copy link
Member

Choose a reason for hiding this comment

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

nit: for conciseness, once you're sure that _searchState has a value, you can use _searchState-> instead of _searchState.value().

if (_searchBox)
{
_searchBox->SetStatus(gsl::narrow<int32_t>(matches.size()), state.CurrentMatchIndex);
_searchBox->SetNavigationEnabled(!_searchState.value().Matches.value().empty());
Copy link
Member

Choose a reason for hiding this comment

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

Reading this, I got the feeling "value value value value value"

{
// We don't lock the terminal for the duration of the entire search,
// since if the terminal was modified the search ID will be updated.
auto lock = _terminal->LockForWriting();
Copy link
Member

Choose a reason for hiding this comment

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

Does this need a write lock? or just a read lock

found = search.FindNext();
}

co_await winrt::resume_foreground(Dispatcher());
Copy link
Member

Choose a reason for hiding this comment

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

Hmm. We should be able to return the match on whatever queue, and the caller can be in charge of moving to the appropriate queue when it's necessary.


const SearchState searchState{ text, caseSensitive };
_searchState.emplace(searchState);
_SearchAsync(goForward, SearchAfterChangeDelay);
Copy link
Member

Choose a reason for hiding this comment

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

Careful- this is directly hooked up to the search textbox change handler, right? It will enqueue one search for every character typed, and then all of them will wait delay and then abort due to the IDs not matching. Ideally, we'd put this in a throttled function as well, so that it only gets invoked once per delay rather than invoking N times and noping out after delay.

@ghost ghost added the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jan 20, 2021
@Don-Vito
Copy link
Contributor Author

This is very clever, and I like it. It looks like it won't harm performance, but I do think that some caution is called-for around all the thread hopping.

@Don-Vito: thanks for always cutting through our layers of crap (both organizationally and code-wise) and getting good shit done 😄

Thanks! I want to be super cautious here - as the potential damage is... nice - and I am absolutely not in hurry to commit this one before we are fully confident! Just want to make sure we keep working on it - as it gets bigger and I am quite senile 😊

I any case I found another concerning case:

  • Assume we search backwards for some word, such that the matches are in lines 10, 20, 30:
  • In this case we find the match at line 30
  • Then assume the user scrolls up and selects a text in line 15. Then the user clicks "search previous"
  • We will select the match at line 20 (because our state suggests to move from 3/3 to 2/3), though probably the user expected to get the match in line 10 (at least VSCode in this case selects line 10 and puts the state at 1/3).

@ghost ghost removed the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jan 20, 2021
@Don-Vito Don-Vito marked this pull request as draft January 20, 2021 21:42
@zadjii-msft zadjii-msft assigned DHowett and Don-Vito and unassigned zadjii-msft Jan 25, 2021
@zadjii-msft zadjii-msft added this to the Terminal v1.7 milestone Feb 2, 2021
@DHowett
Copy link
Member

DHowett commented Feb 5, 2021

(Not sure where the next action item is here -- @Don-Vito, is it on me to review again, or were there additional concerns that warranted it staying in Draft status?)

@Don-Vito
Copy link
Contributor Author

Don-Vito commented Feb 5, 2021

@DHowett - it's on me, sorry. I took a break from it, hope to return to it soon!

@zadjii-msft
Copy link
Member

Hey so for the record, I'm about to blow up TermControl in #9820. #9222 looks like it'll be a lot easier to merge than this one, which might be impossible in its current form. Sorry about this 😢 (I'm still looking forward to this PR landing some day)

@zadjii-msft zadjii-msft removed this from the Terminal v1.9 milestone Apr 23, 2021
zadjii-msft added a commit that referenced this pull request Sep 5, 2023
This is a resurrection of #8588. That PR became painfully stale after
the `ControlCore` split. Original description:

> ## Summary of the Pull Request
> This is a PoC for:
> * Search status in SearchBox (aka number of matches + index of the
current match)
> * Live search (aka search upon typing)
> ## Detailed Description of the Pull Request / Additional comments
> * Introduced this optionally (global setting to enable it)
> * The approach is following:
>   * Every time the filter changes, enumerate all matches
>   * Upon navigation just take the relevant match and select it
> 

I cleaned it up a bit, and added support for also displaying the
positions of the matches in the scrollbar (if `showMarksOnScrollbar` is
also turned on).

It's also been made SUBSTANTIALLY easier after #15858 was merged.

Similar to before, searching while there's piles of output running isn't
_perfect_. But it's pretty awful currently, so that's not the end of the
world.

Gifs below.

* closes #8631 (which is a bullet point in #3920)
* closes #6319


Co-authored-by: Don-Vito <khvitaly@gmail.com>

---------

Co-authored-by: khvitaly <khvitaly@gmail.com>
@zadjii-msft
Copy link
Member

Oh hey, we resurrected this over to #14045 and merged it in like, 1.19. Thanks again for the contribution here!

@zadjii-msft zadjii-msft closed this Feb 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Accessibility Issues related to accessibility Area-User Interface Issues pertaining to the user interface of the Console or Terminal Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Product-Terminal The new Windows Terminal.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants