Conversation
|
|
||
| if (_needle == needle && | ||
| if (_renderData == &renderData && | ||
| _needle == needle && |
There was a problem hiding this comment.
I made this change because I grew a little worried about the robustness of the _lastMutationId == lastMutationId check.
| { | ||
| self->OutputIdle.raise(*self, nullptr); | ||
| } | ||
| }); |
There was a problem hiding this comment.
I can imagine that this debounced event may be quite useful for other things in the future.
|
|
||
| _terminal->SetSearchHighlights(_searcher.Results()); | ||
| _terminal->SetSearchHighlightFocused(_searcher.CurrentMatch()); | ||
| _renderer->TriggerSearchHighlight(oldResults); |
There was a problem hiding this comment.
From what I can tell, we only need to call this function if the search got reset so I moved it inside this branch.
There was a problem hiding this comment.
This results in focused search highlight to be not invalidated even though it might change in the below else block.
I know it sounds inefficient to invalidate all highlights when just the focused one is changed, but to fix this inefficiency I guess you have to add TriggerSearchHighlightFocused() or something along those lines. And the Atlas engine also doesn't know which one got invalidated so it currently just re-draws both 🤕🥲
There was a problem hiding this comment.
Oh, I see. I think it's fine to have a bit of inefficiency here. I bet that the invalidation finishes in less than 1ms even if you had a million search hits - and we only refresh it 10 times per second at most.
If we get to the point where we have buffer snapshotting in the renderer, we can simply scribble the highlights into the snapshot directly. Then we simply check if the text or attributes of each row have changed compared to the last snapshot. (= We have 2 snapshots which we swap back and forth every other frame.) I'm pretty excited for that, as all those subtle issues like this one will just instantly vanish.
| _renderer->TriggerSearchHighlight(oldResults); | ||
| } | ||
| else | ||
| else if (!reset) |
There was a problem hiding this comment.
This allows us to ensure that we don't accidentally call FindNext() just because the OutputIdle event got triggered, which results in a Search() call.
| _terminal->SetSearchHighlights({}); | ||
| _terminal->SetSearchHighlightFocused({}); | ||
| _renderer->TriggerSearchHighlight(_searcher.Results()); | ||
| _searcher = {}; | ||
| _cachedSearchResultRows = {}; |
There was a problem hiding this comment.
We can't check _searcher.GetCurrent() here because otherwise we don't reset the _lastMutationId of a Searcher that found nothing. This is relevant in case you open the search box (the Ctrl+Shift+F one), enter something that doesn't exist, close it, and reopen it. If it gets closed and doesn't get reset here, then reopening it won't trigger a search otherwise.
| .newMinimum = scrollBar.Minimum(), | ||
| .newViewportSize = scrollBar.ViewportSize(), | ||
| }; | ||
| _updateScrollBar->Run(update); |
There was a problem hiding this comment.
Using the throttled func here is IMO just fine, and should ensure that we don't unnecessarily overwrite a pending scrollbar update.
| _searchBox->SetStatus(results.TotalMatches, results.CurrentMatch); | ||
|
|
||
| if (_searchBox) | ||
| if (results.SearchInvalidated) |
There was a problem hiding this comment.
This avoids doing a RaiseNotificationEvent even though the search mask hasn't changed.
| } | ||
|
|
||
| [[nodiscard]] HRESULT AtlasEngine::InvalidateHighlight(std::span<const til::point_span> highlights, const std::vector<LineRendition>& renditions) noexcept | ||
| [[nodiscard]] HRESULT AtlasEngine::InvalidateHighlight(std::span<const til::point_span> highlights, const TextBuffer& buffer) noexcept |
There was a problem hiding this comment.
Passing the TextBuffer directly is IMO fine, because long-term we'll remove Renderer and give each render engine direct access to the underlying TextBuffer anyway. (In all my tests so far I found that we can remove at least half of all rendering code by doing that. I'm excited to think about how much easier it'll make introducing new features!)
BTW, as mentioned in the PR comment, you may have noticed that a debug build felt kind of laggy while resizing the window - This change fixes it.
|
@tusharsnx I apologize for making this many changes so soon after we merged your PR. I hope these are fine. 😣 They do fix a couple more edge cases around highlights, however. If you have any concerns with my changes, or dislike anything, let me know and I'll try to fix it ASAP. BTW |
I'm ABSOLUTELY
Its idempotency is questionable 😅 OK, now on to this PR:
|
| .newViewportSize = scrollBar.ViewportSize(), | ||
| }; | ||
| _throttledUpdateScrollbar(update); | ||
| if (!_searchBox || _searchBox->Visibility() != Visibility::Visible) |
There was a problem hiding this comment.
Could this cause a problem when the search box is closed and the user uses Find previous/next match actions to navigate through the results?
There was a problem hiding this comment.
There was a problem hiding this comment.
Oh, I wasn't aware we had an action for that. If you use such an action in VS Code while its search box is closed, it opens it. I think we should do the same thing. Thanks for finding this!
There was a problem hiding this comment.
Both underlying issues should be fixed now:
- When using the action, we now open the search box. This triggers a search once it finished opening
- While searching we'll now always call
TriggerSearchHighlight
| { | ||
| co_await wil::resume_foreground(Dispatcher()); | ||
| if (auto automationPeer{ Automation::Peers::FrameworkElementAutomationPeer::FromElement(*this) }) | ||
| if (!_searchBox) |
There was a problem hiding this comment.
I guess this one isn't a problem because we're only checking for the existence of the search box and not its visibility.
|
FWIW this does seem to fix #17089 |
zadjii-msft
left a comment
There was a problem hiding this comment.
i'm 23/27 but i'll review those in post tomorrow, I just want my sweet sweet canary builds back
zadjii-msft
left a comment
There was a problem hiding this comment.
confirming: yea I'm cool with this
| if (!_storage.emplace(std::forward<MakeArgs>(args)...)) | ||
| const auto hadValue = _storage.emplace(std::forward<MakeArgs>(args)...); | ||
|
|
||
| if constexpr (Debounce) |
There was a problem hiding this comment.
oh oh okay I get it now. For a while I was thinking "a debounced trailing func is just a trailing func", but the difference is subtle:
- A trailing func will call the callback {one timeout} after the first call to the
throttled_func. If there are more calls to thethrottled_funcin that timeout, they'll just update the value which will be used at the end of the first timeout. - A debounced trailing func will call the callback {one timeout} after the last call to the
throttled_func. If there are more calls to thethrottled_funcin that timeout, they'll further delay the callback.
|
Sorry for the late report, but it seems like this is not yet fixed:
|
|
Ah fuck I forgot about that comment. Sorry about that! 😣 |
This addresses a review comment left by tusharsnx in #17092 which I forgot to fix before merging the PR. The fix itself is somewhat simple: `Terminal::SetSearchHighlightFocused` triggers a scroll if the target is outside of the current (scrolled) viewport and avoiding the call unless necessary fixes it. To do it properly though, I've split up `Search::ResetIfStale` into `IsStale` and `Reset`. Now we can properly detect staleness in advance and branch out the search reset cleanly. Additionally, I've taken the liberty to replace the `IVector` in `SearchResultRows` with a direct `const std::vector&` into `Searcher`. This removes a bunch of code and makes it faster to boot. ## Validation Steps Performed * Print lots of text * Search a common letter * Scroll up * Doesn't scroll back down ✅ * Hold enter to search more occurrences scrolls up as needed ✅ * `showMarksOnScrollbar` still works ✅

This PR extends
til::throttled_functo also support debouncing:Based on the latter the following series of changes were made:
OutputIdleevent was added toControlCorewhich israised once there hasn't been any incoming data in 100ms.
This also triggers an update of our regex patterns (URL detection).
TermControlwhich callsSearch().Search()in turn was modified to return its results by-valueas a struct, which avoids the need for a search-update event
and simplifies how we update the UI.
This architectural change, most importantly the removal of the
TextLayoutUpdatedevent, fixes a DoS bug in Windows Terminal:As the event leads to UI thread activity, printing lots of text
continuously results in the UI thread becoming unresponsive.
On top of these, a number of improvements were made:
IRenderEngine::InvalidateHighlightwas changed to take theTextBufferby-reference which avoids the need to accumulate theline renditions in a
std::vectorfirst. This improves Debug buildperformance during reflow by what I guess must be roughly
a magnitude faster. This difference is very noticeable.
ClearSearch()is called to removethe highlights. The search text is restored when it's reopened,
however the current search position isn't.
Closes #17073
Closes #17089
Validation Steps Performed
recalculates the highlights ✅