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

Show number of search results & positions of hits in scrollbar #14045

Merged
merged 80 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
fa06c6f
Introduce search status
Don-Vito Dec 14, 2020
ff1558c
Teach search box to update status when new output is written
Don-Vito Dec 16, 2020
5391f4e
Remove live-search feature flag
Don-Vito Dec 16, 2020
81a4a31
Merge remote-tracking branch 'upstream/main' into 6319-search-status
Don-Vito Dec 18, 2020
4ab4d41
Allow disabling navigation buttons in SearchBox
Don-Vito Dec 20, 2020
4a2de19
Add Searching status to translation
Don-Vito Dec 20, 2020
fe59504
Run search in the background + itnroduce grace for next search
Don-Vito Dec 20, 2020
1068f85
Merge branch 'main' into 6319-search-status
Don-Vito Jan 20, 2021
8e5efb7
Introduce computation of required statusbox width
Don-Vito Jan 20, 2021
160716d
Reduce the value :)
Don-Vito Jan 20, 2021
f87a988
Merge branch 'pull/8588' into dev/migrie/fhl/search-results
zadjii-msft Sep 20, 2022
85f1bb7
move the code around. It works again, bug... the buttons don't work a…
zadjii-msft Sep 20, 2022
bc5bf1e
That was silly of me
zadjii-msft Sep 20, 2022
a3c1d8a
for all your dead code removal needs, call 0118 999 881 999 119 725 3
zadjii-msft Sep 20, 2022
8ae9e39
pips in the scrollbar
zadjii-msft Sep 20, 2022
3f39b5b
KB focus works again
zadjii-msft Sep 20, 2022
ec0ae7d
throttledfunc, for good and for profit
zadjii-msft Sep 20, 2022
f61c080
cleanup
zadjii-msft Sep 20, 2022
5cf5ccd
this seems to work well enough
zadjii-msft Sep 20, 2022
d437420
making the scrollbar more sensible
zadjii-msft Sep 20, 2022
e4e603d
rename slightly for convention's sake
zadjii-msft Sep 20, 2022
57913e4
update the pips if the first hit is in the viewport
zadjii-msft Sep 20, 2022
bcbce34
Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/search…
zadjii-msft Nov 3, 2022
e49e13e
This is a test fix, because now we send notifications even for circling
zadjii-msft Nov 4, 2022
195fc67
Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/search…
zadjii-msft Nov 17, 2022
f396d36
POC: remove yielding for fun and for profit
zadjii-msft Nov 17, 2022
da1e89f
KISS, you're welcome leonard
zadjii-msft Nov 17, 2022
198b3cf
why _was_ that optional
zadjii-msft Nov 17, 2022
03f81b2
Migrate spelling-0.0.21 changes from main
DHowett Nov 17, 2022
953d87b
runformat
zadjii-msft Nov 17, 2022
9e6b97c
Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/search…
zadjii-msft Nov 30, 2022
28a71b1
this doesn't fail locally. Maybe it got fixed in the merge with main?
zadjii-msft Nov 30, 2022
2dc5683
-Wall
zadjii-msft Dec 1, 2022
49c729a
Merge branch 'dev/migrie/fhl/search-marks' of https://github.com/micr…
zadjii-msft Dec 1, 2022
5509a96
Merge branch 'main' into dev/migrie/fhl/search-marks
zadjii-msft Jan 29, 2023
b288f5d
oops missed this merge conflict
zadjii-msft Jan 29, 2023
ebdc887
Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/search…
zadjii-msft Jul 13, 2023
09eb0a9
some minor review nits
zadjii-msft Jul 13, 2023
5360d68
Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/search…
zadjii-msft Jul 18, 2023
e4056ca
PR nits
zadjii-msft Jul 18, 2023
28bce48
the codeformat check should just commit the fix itself >__<
zadjii-msft Jul 18, 2023
af77652
Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/search…
zadjii-msft Jul 19, 2023
5906372
Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/search…
zadjii-msft Jul 19, 2023
c2f36cc
this'll do
zadjii-msft Jul 19, 2023
aa47308
I think this is probably unreasonably cheecky
zadjii-msft Jul 19, 2023
876d4fa
audit mode more
zadjii-msft Jul 19, 2023
6b650d7
Merge branch 'main' into dev/migrie/fhl/search-marks
zadjii-msft Aug 9, 2023
d707096
format
zadjii-msft Aug 9, 2023
bc315b1
Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/search…
zadjii-msft Aug 14, 2023
fd77061
resave resw in VS
zadjii-msft Aug 14, 2023
c97721c
PR notes
zadjii-msft Aug 14, 2023
b1107de
this formatter man
zadjii-msft Aug 14, 2023
0a1568d
Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/search…
zadjii-msft Aug 15, 2023
462b8d2
Use ICU for text search
lhecker Aug 21, 2023
a346a2a
Improve documentation
lhecker Aug 21, 2023
dba1629
Spelling fix
lhecker Aug 21, 2023
8146ecd
Fix a bug and x86 woes
lhecker Aug 21, 2023
5ca26aa
More x86 woes, Improve UiaTextRangeBase
lhecker Aug 21, 2023
e0fb764
Fix unit tests
lhecker Aug 21, 2023
ecc735d
Implement backwards text iteration
lhecker Aug 21, 2023
56c7911
Address feedback
lhecker Aug 21, 2023
c731eae
Address feedback
lhecker Aug 22, 2023
ca4ec38
Address feedback, Use namespaces
lhecker Aug 22, 2023
d53d4bd
Spelling fix
lhecker Aug 22, 2023
1e527c4
Remove selection coloring from Search
lhecker Aug 23, 2023
1fc01d8
Address Dustin's feedback
lhecker Aug 23, 2023
7cccf56
Optimize GetReadableColumnCount
lhecker Aug 23, 2023
362693b
Fix AuditMode failures
lhecker Aug 23, 2023
1fb4331
Merge remote-tracking branch 'origin/main' into dev/migrie/search-v2-v3
zadjii-msft Aug 23, 2023
c1d1f63
Merge branch 'dev/migrie/fhl/search-marks' into dev/migrie/search-v2-v3
zadjii-msft Aug 23, 2023
d3e69f7
It's so much nicer
zadjii-msft Aug 23, 2023
574f30b
🚧 dead code construction crew
zadjii-msft Aug 23, 2023
198decc
🚧 dead code construction crew part 2
zadjii-msft Aug 23, 2023
444398d
Merge remote-tracking branch 'origin/main' into dev/migrie/search-v2-v3
zadjii-msft Aug 28, 2023
971e7c5
update scrollbar as we scroll the buffer
zadjii-msft Aug 28, 2023
5da6ceb
DRIVE-BY: Fix a potential crash in Search with 0 results
zadjii-msft Aug 29, 2023
4413534
notes
zadjii-msft Aug 31, 2023
3e42162
Merge remote-tracking branch 'origin/main' into dev/migrie/fhl/search…
zadjii-msft Sep 5, 2023
b22e5c1
don't prealloc the results vector just to toss it
zadjii-msft Sep 5, 2023
bd2f240
nits
zadjii-msft Sep 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 225 additions & 20 deletions src/cascadia/TerminalControl/ControlCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,13 @@ constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(100);
// The minimum delay between updating the locations of regex patterns
constexpr const auto UpdatePatternLocationsInterval = std::chrono::milliseconds(500);

// The delay before performing the search after change of search criteria
constexpr const auto SearchAfterChangeDelay = std::chrono::milliseconds(200);

namespace winrt::Microsoft::Terminal::Control::implementation
{
std::atomic<size_t> SearchState::_searchIdGenerator{ 0 };

static winrt::Microsoft::Terminal::Core::OptionalColor OptionalFromColor(const til::color& c)
{
Core::OptionalColor result;
Expand Down Expand Up @@ -212,6 +217,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
});

_updateSearchStatus = std::make_shared<ThrottledFuncTrailing<SearchState>>(
_dispatcher,
SearchAfterChangeDelay,
[weakThis = get_weak()](const auto& update) {
if (auto core{ weakThis.get() })
{
core->_searchState.emplace(update);
core->_SearchAsync(std::nullopt);
}
});

UpdateSettings(settings, unfocusedAppearance);
}

Expand Down Expand Up @@ -1259,6 +1275,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
nullptr;
}

til::color ControlCore::ForegroundColor() const
{
return _terminal->GetRenderSettings().GetColorAlias(ColorAlias::DefaultForeground);
}

til::color ControlCore::BackgroundColor() const
{
return _terminal->GetRenderSettings().GetColorAlias(ColorAlias::DefaultBackground);
Expand Down Expand Up @@ -1457,33 +1478,215 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return;
}

const auto direction = goForward ?
Search::Direction::Forward :
Search::Direction::Backward;
if (_bufferChangedSinceSearch)
{
const auto sensitivity = caseSensitive ? Search::Sensitivity::CaseSensitive : Search::Sensitivity::CaseInsensitive;
const SearchState searchState{ text, sensitivity, goForward };
_updateSearchStatus->Run(searchState);
}
else
{
_SelectSearchResult(goForward);
}
}

const auto sensitivity = caseSensitive ?
Search::Sensitivity::CaseSensitive :
Search::Sensitivity::CaseInsensitive;
// Method Description:
// - Search text in text buffer.
// This is triggered when the user starts typing, clicks on navigation or
// when the search is active and the terminal text is changing
// Arguments:
// - goForward: optional boolean that represents if the current search direction is forward
// - delay: time in milliseconds to wait before performing the search
// (grace time to allow next search to start)
// Return Value:
// - <none>
fire_and_forget ControlCore::_SearchAsync(std::optional<bool> goForward)
{
// Run only if the search state was initialized
if (_closing || !_searchState.has_value())
{
co_return;
}

const auto originalSearchId = _searchState->searchId;
auto weakThis{ get_weak() };

// If no matches were computed it means we need to perform the search
if (!_searchState->matches.has_value())
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
{
std::vector<std::pair<til::point, til::point>> matches;
if (!_searchState->text.empty())
{
// Collect up all the matches on a BG thread.
co_await winrt::resume_background();

// We perform explicit search forward, so the first result will also be the earliest buffer location
// We will use goForward later to decide if we need to select 1 of n or n of n.
::Search search(*GetRenderData(),
_searchState->text.c_str(),
Search::Direction::Forward,
_searchState->sensitivity);

while (_SearchOne(search))
{
// if search box was collapsed or the new one search was triggered - let's cancel this one
if (!_searchState.has_value() ||
_searchState->searchId != originalSearchId)
{
co_return;
}

matches.push_back(search.GetFoundLocation());
}

// if search box was collapsed or the new one search was triggered - let's cancel this one
if (!_searchState.has_value() ||
_searchState->searchId != originalSearchId)
{
co_return;
}
}
_searchState->matches.emplace(std::move(matches));
_bufferChangedSinceSearch = false;
}
if (auto core{ weakThis.get() })
{
_SelectSearchResult(_searchState->goForward);
}
}

void ControlCore::SearchChanged(const winrt::hstring& text,
const bool goForward,
const bool caseSensitive)
{
// Clear the selection reset the anchor
_terminal->ClearSelection();
_renderer->TriggerSelection();
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved

const auto sensitivity = caseSensitive ? Search::Sensitivity::CaseSensitive : Search::Sensitivity::CaseInsensitive;

const SearchState searchState{ text, sensitivity, goForward };
_updateSearchStatus->Run(searchState);
}

// Method Description:
// - Selects one of precomputed search results in the terminal (if exist).
// - Updates the search box control accordingly.
// - The selection might be preceded by going to next / previous result
// - goForward: if true, select next result; if false, select previous result;
// if not set, remain at the current result.
// Return Value:
// - <none>
void ControlCore::_SelectSearchResult(std::optional<bool> goForward)
{
if (_searchState.has_value() && _searchState->matches.has_value())
{
auto& state = _searchState.value();
auto& matches = state.matches.value();

::Search search(*GetRenderData(), text.c_str(), direction, sensitivity);
if (goForward.has_value())
{
state.UpdateIndex(goForward.value());

const auto currentMatch = state.GetCurrentMatch();
if (currentMatch.has_value())
{
auto lock = _terminal->LockForWriting();
_terminal->SetBlockSelection(false);
_terminal->SelectNewRegion(currentMatch->first, currentMatch->second);
_renderer->TriggerSelection();
}
}

// Let the control know we got one
auto foundResults = winrt::make_self<implementation::FoundResultsArgs>(true);
foundResults->TotalMatches(gsl::narrow<int32_t>(matches.size()));
foundResults->CurrentMatch(state.currentMatchIndex);
_FoundMatchHandlers(*this, *foundResults);
}
}

// Method Description:
// - Search for a single value. Takes the lock for the duration of the search.
// Arguments:
// - search: search object to use to find the next match.
// Return Value:
// - <none>
bool ControlCore::_SearchOne(::Search& search)
{
// 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();
const auto foundMatch{ search.FindNext() };
if (foundMatch)
return search.FindNext();
}

// Method Description:
// - Updates the index of the current match according to the direction.
// The index will remain unchanged (usually -1) if the number of matches is 0
// Arguments:
// - goForward: if true, move to the next match, else go to previous
// Return Value:
// - <none>
void SearchState::UpdateIndex(bool goForward)
{
if (matches.has_value())
{
const int numMatches = ::base::saturated_cast<int>(matches->size());
if (numMatches > 0)
{
if (currentMatchIndex == -1)
{
currentMatchIndex = goForward ? 0 : numMatches - 1;
}
else
{
currentMatchIndex = (numMatches + currentMatchIndex + (goForward ? 1 : -1)) % numMatches;
}
}
}
}

// Method Description:
// - Retrieves the current match
// Arguments:
// - <none>
// Return Value:
// - current match, null-opt if current match is invalid
// (e.g., when the index is -1 or there are no matches)
std::optional<std::pair<til::point, til::point>> SearchState::GetCurrentMatch()
{
if (matches.has_value() &&
currentMatchIndex > -1 &&
static_cast<size_t>(currentMatchIndex) < matches->size())
{
return til::at(matches.value(), currentMatchIndex);
}
else
{
_terminal->SetBlockSelection(false);
search.Select();
return std::nullopt;
}
}
void ControlCore::ExitSearch()
{
_searchState.reset();
}

// this is used for search,
// DO NOT call _updateSelectionUI() here.
// We don't want to show the markers so manually tell it to clear it.
_renderer->TriggerSelection();
_UpdateSelectionMarkersHandlers(*this, winrt::make<implementation::UpdateSelectionMarkersEventArgs>(true));
Windows::Foundation::Collections::IVector<int32_t> ControlCore::MatchRows()
{
auto results = winrt::single_threaded_vector<int32_t>();
if (_bufferChangedSinceSearch)
{
return results;
}

// Raise a FoundMatch event, which the control will use to notify
// narrator if there was any results in the buffer
auto foundResults = winrt::make_self<implementation::FoundResultsArgs>(foundMatch);
_FoundMatchHandlers(*this, *foundResults);
if (_searchState.has_value() && _searchState->matches.has_value())
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
{
for (auto&& [start, end] : *(_searchState->matches))
{
results.Append(start.y);
}
}
return results;
}

void ControlCore::Close()
Expand Down Expand Up @@ -1680,6 +1883,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation

// Start the throttled update of where our hyperlinks are.
(*_updatePatternLocations)();

zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
_bufferChangedSinceSearch = true;
}
catch (...)
{
Expand Down
37 changes: 37 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,31 @@ namespace winrt::Microsoft::Terminal::Control::implementation
WINRT_PROPERTY(bool, IsIndex16);
};

struct SearchState
{
public:
static std::atomic<size_t> _searchIdGenerator;

SearchState(const winrt::hstring& text, const Search::Sensitivity sensitivity, const bool forward) :
text(text),
sensitivity(sensitivity),
goForward(forward),
searchId(_searchIdGenerator.fetch_add(1))
{
}

std::optional<std::vector<std::pair<til::point, til::point>>> matches;
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved

void UpdateIndex(bool goForward);
std::optional<std::pair<til::point, til::point>> GetCurrentMatch();

winrt::hstring text;
Search::Sensitivity sensitivity;
bool goForward{ true };
size_t searchId;
int32_t currentMatchIndex{ -1 };
};

struct ControlCore : ControlCoreT<ControlCore>
{
public:
Expand Down Expand Up @@ -85,6 +110,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::hstring FontFaceName() const noexcept;
uint16_t FontWeight() const noexcept;

til::color ForegroundColor() const;
til::color BackgroundColor() const;

void SendInput(const winrt::hstring& wstr);
Expand Down Expand Up @@ -181,6 +207,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void Search(const winrt::hstring& text,
const bool goForward,
const bool caseSensitive);
void SearchChanged(const winrt::hstring& text, const bool goForward, const bool caseSensitive);
void ExitSearch();
Windows::Foundation::Collections::IVector<int32_t> MatchRows();

void LeftClickOnTerminal(const til::point terminalPosition,
const int numberOfClicks,
Expand Down Expand Up @@ -278,10 +307,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation

uint64_t _owningHwnd{ 0 };

std::optional<SearchState> _searchState;
bool _bufferChangedSinceSearch{ true };

winrt::Windows::System::DispatcherQueue _dispatcher{ nullptr };
std::shared_ptr<ThrottledFuncTrailing<>> _tsfTryRedrawCanvas;
std::unique_ptr<til::throttled_func_trailing<>> _updatePatternLocations;
std::shared_ptr<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>> _updateScrollBar;
std::shared_ptr<ThrottledFuncTrailing<SearchState>> _updateSearchStatus;

bool _setFontSizeUnderLock(float fontSize);
void _updateFont(const bool initialUpdate = false);
Expand Down Expand Up @@ -326,6 +359,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool _isBackgroundTransparent();
void _focusChanged(bool focused);

fire_and_forget _SearchAsync(std::optional<bool> goForward);
void _SelectSearchResult(std::optional<bool> goForward);
bool _SearchOne(::Search& search);

inline bool _IsClosing() const noexcept
{
#ifndef NDEBUG
Expand Down
6 changes: 6 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.idl
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,13 @@ namespace Microsoft.Terminal.Control
Microsoft.Terminal.Core.Point CursorPosition { get; };
void ResumeRendering();
void BlinkAttributeTick();

void Search(String text, Boolean goForward, Boolean caseSensitive);
void SearchChanged(String text, Boolean goForward, Boolean caseSensitive);
void ExitSearch();
IVector<Int32> MatchRows { get; };
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved

Microsoft.Terminal.Core.Color ForegroundColor { get; };
Microsoft.Terminal.Core.Color BackgroundColor { get; };

Boolean HasSelection { get; };
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalControl/EventArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}

WINRT_PROPERTY(bool, FoundMatch);
WINRT_PROPERTY(int32_t, TotalMatches);
WINRT_PROPERTY(int32_t, CurrentMatch);
};

struct ShowWindowArgs : public ShowWindowArgsT<ShowWindowArgs>
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalControl/EventArgs.idl
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ namespace Microsoft.Terminal.Control
runtimeclass FoundResultsArgs
{
Boolean FoundMatch { get; };
Int32 TotalMatches { get; };
Int32 CurrentMatch { get; };
}

runtimeclass ShowWindowArgs
Expand Down
Loading