From fa06c6fa2f5334bd72bf3b9212c56e36485d5d95 Mon Sep 17 00:00:00 2001 From: khvitaly Date: Mon, 14 Dec 2020 23:53:28 +0200 Subject: [PATCH 01/61] Introduce search status --- .../SerializationTests.cpp | 2 + src/cascadia/TerminalApp/TerminalSettings.cpp | 1 + src/cascadia/TerminalApp/TerminalSettings.h | 2 + .../TerminalControl/IControlSettings.idl | 2 + .../Resources/en-US/Resources.resw | 4 + .../TerminalControl/SearchBoxControl.cpp | 49 ++++++++ .../TerminalControl/SearchBoxControl.h | 5 + .../TerminalControl/SearchBoxControl.idl | 5 + .../TerminalControl/SearchBoxControl.xaml | 16 ++- src/cascadia/TerminalControl/TermControl.cpp | 111 ++++++++++++++++-- src/cascadia/TerminalControl/TermControl.h | 6 + src/cascadia/TerminalControl/TermControl.xaml | 1 + .../TerminalSettingsEditor/Interaction.xaml | 7 ++ .../GlobalAppSettings.cpp | 5 + .../TerminalSettingsModel/GlobalAppSettings.h | 1 + .../GlobalAppSettings.idl | 4 + .../TerminalSettingsModel/defaults.json | 1 + .../UnitTests_TerminalCore/MockTermSettings.h | 3 + 18 files changed, 211 insertions(+), 14 deletions(-) diff --git a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp index 60491de9cbf..5a15e0386d6 100644 --- a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp @@ -104,6 +104,8 @@ namespace SettingsModelLocalTests "largePasteWarning": true, "multiLinePasteWarning": true, + "liveSearch": false, + "experimental.input.forceVT": false, "experimental.rendering.forceFullRepaint": false, "experimental.rendering.software": false diff --git a/src/cascadia/TerminalApp/TerminalSettings.cpp b/src/cascadia/TerminalApp/TerminalSettings.cpp index 681e99ecd18..c78578ca4aa 100644 --- a/src/cascadia/TerminalApp/TerminalSettings.cpp +++ b/src/cascadia/TerminalApp/TerminalSettings.cpp @@ -208,6 +208,7 @@ namespace winrt::TerminalApp::implementation _ForceFullRepaintRendering = globalSettings.ForceFullRepaintRendering(); _SoftwareRendering = globalSettings.SoftwareRendering(); _ForceVTInput = globalSettings.ForceVTInput(); + _LiveSearch = globalSettings.LiveSearch(); } // Method Description: diff --git a/src/cascadia/TerminalApp/TerminalSettings.h b/src/cascadia/TerminalApp/TerminalSettings.h index db4db524220..d7722c49e25 100644 --- a/src/cascadia/TerminalApp/TerminalSettings.h +++ b/src/cascadia/TerminalApp/TerminalSettings.h @@ -121,6 +121,8 @@ namespace winrt::TerminalApp::implementation GETSET_PROPERTY(bool, SoftwareRendering, false); GETSET_PROPERTY(bool, ForceVTInput, false); + GETSET_PROPERTY(bool, LiveSearch, false); + #pragma warning(pop) private: diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 2f140a0a3f8..2faa388ecf6 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -57,5 +57,7 @@ namespace Microsoft.Terminal.TerminalControl Boolean RetroTerminalEffect; Boolean ForceFullRepaintRendering; Boolean SoftwareRendering; + + Boolean LiveSearch; }; } diff --git a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw index 9497cf4037a..397f6aecc88 100644 --- a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw @@ -178,6 +178,10 @@ Ctrl+Click to follow link + + No results + Will be presented near the search box when Find operation returned no results. + Unable to find the selected font "{0}". diff --git a/src/cascadia/TerminalControl/SearchBoxControl.cpp b/src/cascadia/TerminalControl/SearchBoxControl.cpp index 57026c40321..de6fbc17a21 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.cpp +++ b/src/cascadia/TerminalControl/SearchBoxControl.cpp @@ -209,4 +209,53 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation { e.Handled(true); } + + // Method Description: + // - Handler for changing the text. Triggers SearchChanged event + // Arguments: + // - sender: not used + // - e: event data + // Return Value: + // - + void SearchBoxControl::TextBoxTextChanged(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/) + { + _SearchChangedHandlers(TextBox().Text(), _CaseSensitive()); + } + + // Method Description: + // - Handler for clicking the case sensitivity toggle. Triggers SearchChanged event + // Arguments: + // - sender: not used + // - e: not used + // Return Value: + // - + void SearchBoxControl::CaseSensitivityButtonClicked(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/) + { + _SearchChangedHandlers(TextBox().Text(), _CaseSensitive()); + } + + // Method Description: + // - Allows to set the value of the search status + // Arguments: + // - status: string value to populate in the StatusBox + // Return Value: + // - + void SearchBoxControl::SetStatus(winrt::hstring const& status) + { + if (StatusBox()) + { + StatusBox().Text(status); + } + } + + // Method Description: + // - Allows to set the visibility of the search status + // Arguments: + // - isVisible: if true, the status is visible + // Return Value: + // - + void SearchBoxControl::SetStatusVisible(bool isVisible) + { + StatusBox().Visibility(isVisible ? Visibility::Visible : Visibility::Collapsed); + } } diff --git a/src/cascadia/TerminalControl/SearchBoxControl.h b/src/cascadia/TerminalControl/SearchBoxControl.h index cdf4ee63830..06dceade261 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.h +++ b/src/cascadia/TerminalControl/SearchBoxControl.h @@ -31,12 +31,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void SetFocusOnTextbox(); void PopulateTextbox(winrt::hstring const& text); bool ContainsFocus(); + void SetStatus(winrt::hstring const& text); + void SetStatusVisible(bool isVisible); void GoBackwardClicked(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/); void GoForwardClicked(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/); void CloseClick(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + void TextBoxTextChanged(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + void CaseSensitivityButtonClicked(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); WINRT_CALLBACK(Search, SearchHandler); + WINRT_CALLBACK(SearchChanged, SearchChangedHandler); TYPED_EVENT(Closed, TerminalControl::SearchBoxControl, Windows::UI::Xaml::RoutedEventArgs); private: diff --git a/src/cascadia/TerminalControl/SearchBoxControl.idl b/src/cascadia/TerminalControl/SearchBoxControl.idl index 78011841c21..548199953d8 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.idl +++ b/src/cascadia/TerminalControl/SearchBoxControl.idl @@ -4,6 +4,7 @@ namespace Microsoft.Terminal.TerminalControl { delegate void SearchHandler(String query, Boolean goForward, Boolean isCaseSensitive); + delegate void SearchChangedHandler(String query, Boolean isCaseSensitive); [default_interface] runtimeclass SearchBoxControl : Windows.UI.Xaml.Controls.UserControl { @@ -12,7 +13,11 @@ namespace Microsoft.Terminal.TerminalControl void PopulateTextbox(String text); Boolean ContainsFocus(); + void SetStatus(String status); + void SetStatusVisible(Boolean isVisible); + event SearchHandler Search; + event SearchChangedHandler SearchChanged; event Windows.Foundation.TypedEventHandler Closed; } } diff --git a/src/cascadia/TerminalControl/SearchBoxControl.xaml b/src/cascadia/TerminalControl/SearchBoxControl.xaml index f64ae36ff66..d2befe2cc04 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.xaml +++ b/src/cascadia/TerminalControl/SearchBoxControl.xaml @@ -161,8 +161,19 @@ KeyDown="TextBoxKeyDown" Margin="5" HorizontalAlignment="Left" - VerticalAlignment="Center"> + VerticalAlignment="Center" + TextChanged="TextBoxTextChanged"> + + + + Style="{StaticResource ToggleButtonStyle}" + Click="CaseSensitivityButtonClicked"> diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 3a3a6d3ff31..284c352a880 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -188,6 +188,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _autoScrollTimer.Interval(AutoScrollUpdateInterval); _autoScrollTimer.Tick({ this, &TermControl::_UpdateAutoScroll }); + _SetLiveSearch(_settings.LiveSearch()); _ApplyUISettings(); } @@ -203,6 +204,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // get at its private implementation _searchBox.copy_from(winrt::get_self(searchBox)); _searchBox->Visibility(Visibility::Visible); + _searchBox->SetStatus(RS_(L"TermControl_NoMatch")); + _searchBox->SetStatusVisible(_isLiveSearchEnabled); // If a text is selected inside terminal, use it to populate the search box. // If the search box already contains a value, it will be overridden. @@ -235,26 +238,108 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - void TermControl::_Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive) { - if (text.size() == 0 || _closing) + if (_closing) { return; } - const Search::Direction direction = goForward ? - Search::Direction::Forward : - Search::Direction::Backward; + std::wstring searchStatus{ RS_(L"TermControl_NoMatch") }; - const Search::Sensitivity sensitivity = caseSensitive ? - Search::Sensitivity::CaseSensitive : - Search::Sensitivity::CaseInsensitive; + if (!text.empty()) + { + if (_isLiveSearchEnabled) + { + const int numMatches = ::base::saturated_cast(_liveSearchMatches.size()); + if (numMatches > 0) + { + _liveSearchIndex = (numMatches + _liveSearchIndex + (goForward ? 1 : -1)) % numMatches; + searchStatus = fmt::format(L"{}/{}", _liveSearchIndex + 1, numMatches); + _terminal->SetBlockSelection(false); - Search search(*GetUiaData(), text.c_str(), direction, sensitivity); - auto lock = _terminal->LockForWriting(); - if (search.FindNext()) + const auto& selectionCoords = til::at(_liveSearchMatches, _liveSearchIndex); + _terminal->SelectNewRegion(selectionCoords.first, selectionCoords.second); + _renderer->TriggerSelection(); + } + } + else + { + const Search::Direction direction = goForward ? + Search::Direction::Forward : + Search::Direction::Backward; + + const Search::Sensitivity sensitivity = caseSensitive ? + Search::Sensitivity::CaseSensitive : + Search::Sensitivity::CaseInsensitive; + + Search search(*GetUiaData(), text.c_str(), direction, sensitivity); + auto lock = _terminal->LockForWriting(); + if (search.FindNext()) + { + searchStatus = L""; + _terminal->SetBlockSelection(false); + search.Select(); + _renderer->TriggerSelection(); + } + } + } + + if (_searchBox) + { + _searchBox->SetStatus(searchStatus.data()); + } + } + + // Method Description: + // - If live search is enabled in settings, searches the buffer forward + // Arguments: + // - text: the text to search + // - caseSensitive: boolean that represents if the current search is case sensitive + // Return Value: + // - + void TermControl::_SearchChanged(const winrt::hstring& text, const bool caseSensitive) + { + if (_isLiveSearchEnabled) { - _terminal->SetBlockSelection(false); - search.Select(); + // Clear the selection reset the anchor + _terminal->ClearSelection(); _renderer->TriggerSelection(); + + const Search::Sensitivity sensitivity = caseSensitive ? + Search::Sensitivity::CaseSensitive : + Search::Sensitivity::CaseInsensitive; + + Search search(*GetUiaData(), text.c_str(), Search::Direction::Forward, sensitivity); + auto lock = _terminal->LockForWriting(); + + _liveSearchMatches.clear(); + _liveSearchIndex = -1; + while (search.FindNext()) + { + _liveSearchMatches.push_back(search.GetFoundLocation()); + } + + _Search(text, true, caseSensitive); + } + } + + // Method Description: + // - The goal of this method is to reset search in the case search mode is switched. + // We need this to avoid inconsistencies, when the mode is changed in the middle of + // navigation through results. + // Arguments: + // - isLiveSearchEnabled: true if live search mode is enabled + // Return Value: + // - + void TermControl::_SetLiveSearch(bool isLiveSearchEnabled) + { + if (_isLiveSearchEnabled != isLiveSearchEnabled) + { + _isLiveSearchEnabled = isLiveSearchEnabled; + if (_searchBox && _searchBox->Visibility() == Visibility::Visible) + { + _searchBox->SetStatusVisible(_isLiveSearchEnabled); + _searchBox->PopulateTextbox(L""); + } } } @@ -334,6 +419,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation { _RefreshSizeUnderLock(); } + + _SetLiveSearch(_settings.LiveSearch()); } } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 4e2ebba54ce..f38ded9908a 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -259,6 +259,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker; + bool _isLiveSearchEnabled{ false }; + std::vector> _liveSearchMatches; + int32_t _liveSearchIndex{ -1 }; + void _SetLiveSearchEnabled(bool isLiveSearchEnabled); + void _ApplyUISettings(); void _UpdateSystemParameterSettings() noexcept; void _InitializeBackgroundBrush(); @@ -325,6 +330,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation double _GetAutoScrollSpeed(double cursorDistanceFromBorder) const; void _Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive); + void _SearchChanged(const winrt::hstring& text, const bool caseSensitive); void _CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& sender, Windows::UI::Xaml::RoutedEventArgs const& args); // TSFInputControl Handlers diff --git a/src/cascadia/TerminalControl/TermControl.xaml b/src/cascadia/TerminalControl/TermControl.xaml index 8ef1d8f835a..51dde3521d6 100644 --- a/src/cascadia/TerminalControl/TermControl.xaml +++ b/src/cascadia/TerminalControl/TermControl.xaml @@ -76,6 +76,7 @@ HorizontalAlignment="Right" VerticalAlignment="Top" Search="_Search" + SearchChanged="_SearchChanged" Closed="_CloseSearchBoxControl" /> diff --git a/src/cascadia/TerminalSettingsEditor/Interaction.xaml b/src/cascadia/TerminalSettingsEditor/Interaction.xaml index eb6bd08e6bf..9786feacb91 100644 --- a/src/cascadia/TerminalSettingsEditor/Interaction.xaml +++ b/src/cascadia/TerminalSettingsEditor/Interaction.xaml @@ -38,6 +38,13 @@ the MIT License. See LICENSE in the project root for license information. --> IsChecked="{x:Bind State.Globals.SnapToGridOnResize, Mode=TwoWay}" Style="{StaticResource CheckBoxSettingStyle}"/> + + + + + diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 71c8336f6ed..c0dada89d87 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -40,6 +40,7 @@ static constexpr std::string_view AlwaysOnTopKey{ "alwaysOnTop" }; static constexpr std::string_view LegacyUseTabSwitcherModeKey{ "useTabSwitcher" }; static constexpr std::string_view TabSwitcherModeKey{ "tabSwitcherMode" }; static constexpr std::string_view DisableAnimationsKey{ "disableAnimations" }; +static constexpr std::string_view LiveSearchKey{ "liveSearch" }; static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" }; @@ -114,6 +115,7 @@ winrt::com_ptr GlobalAppSettings::Copy() const globals->_AlwaysOnTop = _AlwaysOnTop; globals->_TabSwitcherMode = _TabSwitcherMode; globals->_DisableAnimations = _DisableAnimations; + globals->_LiveSearch = _LiveSearch; globals->_UnparsedDefaultProfile = _UnparsedDefaultProfile; globals->_validDefaultProfile = _validDefaultProfile; @@ -300,6 +302,8 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, DisableAnimationsKey, _DisableAnimations); + JsonUtils::GetValueForKey(json, LiveSearchKey, _LiveSearch); + // This is a helper lambda to get the keybindings and commands out of both // and array of objects. We'll use this twice, once on the legacy // `keybindings` key, and again on the newer `bindings` key. @@ -389,6 +393,7 @@ Json::Value GlobalAppSettings::ToJson() const JsonUtils::SetValueForKey(json, AlwaysOnTopKey, _AlwaysOnTop); JsonUtils::SetValueForKey(json, TabSwitcherModeKey, _TabSwitcherMode); JsonUtils::SetValueForKey(json, DisableAnimationsKey, _DisableAnimations); + JsonUtils::SetValueForKey(json, LiveSearchKey, _LiveSearch); // clang-format on // TODO GH#8100: keymap needs to be serialized here diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 29c4fbda7e0..1ac4474e351 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -85,6 +85,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation GETSET_SETTING(bool, AlwaysOnTop, false); GETSET_SETTING(Model::TabSwitcherMode, TabSwitcherMode, Model::TabSwitcherMode::InOrder); GETSET_SETTING(bool, DisableAnimations, false); + GETSET_SETTING(bool, LiveSearch, false); private: guid _defaultProfile; diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index 6d427b4ec5b..437ff558b95 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -135,6 +135,10 @@ namespace Microsoft.Terminal.Settings.Model void ClearDisableAnimations(); Boolean DisableAnimations; + Boolean HasLiveSearch(); + void ClearLiveSearch(); + Boolean LiveSearch; + Windows.Foundation.Collections.IMapView ColorSchemes(); void AddColorScheme(ColorScheme scheme); diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index cb862728ed6..5ac0cf47bc4 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -26,6 +26,7 @@ "theme": "system", "snapToGridOnResize": true, "disableAnimations": false, + "liveSearch": false, "profiles": [ diff --git a/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h b/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h index e55e17b9612..3d11087c15b 100644 --- a/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h +++ b/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h @@ -39,6 +39,7 @@ namespace TerminalCoreUnitTests bool SuppressApplicationTitle() { return _suppressApplicationTitle; } uint32_t SelectionBackground() { return COLOR_WHITE; } bool ForceVTInput() { return false; } + bool LiveSearch() { return _liveSearch; } // other implemented methods uint32_t GetColorTableEntry(int32_t) const { return 123; } @@ -60,6 +61,7 @@ namespace TerminalCoreUnitTests void SuppressApplicationTitle(bool suppressApplicationTitle) { _suppressApplicationTitle = suppressApplicationTitle; } void SelectionBackground(uint32_t) {} void ForceVTInput(bool) {} + void LiveSearch(bool liveSearch) { _liveSearch = liveSearch; } GETSET_PROPERTY(winrt::Windows::Foundation::IReference, TabColor, nullptr); GETSET_PROPERTY(winrt::Windows::Foundation::IReference, StartingTabColor, nullptr); @@ -71,5 +73,6 @@ namespace TerminalCoreUnitTests bool _copyOnSelect{ false }; bool _suppressApplicationTitle{ false }; winrt::hstring _startingTitle; + bool _liveSearch{ false }; }; } From ff1558c2722c76b94b42baacd81b83ac4b035a46 Mon Sep 17 00:00:00 2001 From: khvitaly Date: Wed, 16 Dec 2020 02:24:52 +0200 Subject: [PATCH 02/61] Teach search box to update status when new output is written --- .../TerminalControl/SearchBoxControl.cpp | 16 +- .../TerminalControl/SearchBoxControl.h | 2 +- .../TerminalControl/SearchBoxControl.idl | 3 +- src/cascadia/TerminalControl/TermControl.cpp | 188 ++++++++++++++---- src/cascadia/TerminalControl/TermControl.h | 30 ++- 5 files changed, 188 insertions(+), 51 deletions(-) diff --git a/src/cascadia/TerminalControl/SearchBoxControl.cpp b/src/cascadia/TerminalControl/SearchBoxControl.cpp index de6fbc17a21..fa9dac3c88b 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.cpp +++ b/src/cascadia/TerminalControl/SearchBoxControl.cpp @@ -18,6 +18,18 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation this->CharacterReceived({ this, &SearchBoxControl::_CharacterHandler }); this->KeyDown({ this, &SearchBoxControl::_KeyDownHandler }); + this->RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) { + // Once the control is visible again we trigger SearchChanged event. + // We do this since probably we have a value from the previous search, + // and in such case logically the search changes from "nothing" to this value. + // A good example for SearchChanged event consumer is LiveSearch. + // Once the control is open we want LiveSearch to immediately perform the search + // with the value appearing in the box. + if (Visibility() == Visibility::Visible) + { + _SearchChangedHandlers(TextBox().Text(), _GoForward(), _CaseSensitive()); + } + }); _focusableElements.insert(TextBox()); _focusableElements.insert(CloseButton()); @@ -219,7 +231,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - void SearchBoxControl::TextBoxTextChanged(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/) { - _SearchChangedHandlers(TextBox().Text(), _CaseSensitive()); + _SearchChangedHandlers(TextBox().Text(), _GoForward(), _CaseSensitive()); } // Method Description: @@ -231,7 +243,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - void SearchBoxControl::CaseSensitivityButtonClicked(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/) { - _SearchChangedHandlers(TextBox().Text(), _CaseSensitive()); + _SearchChangedHandlers(TextBox().Text(), _GoForward(), _CaseSensitive()); } // Method Description: diff --git a/src/cascadia/TerminalControl/SearchBoxControl.h b/src/cascadia/TerminalControl/SearchBoxControl.h index 06dceade261..c46bea3cbc3 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.h +++ b/src/cascadia/TerminalControl/SearchBoxControl.h @@ -41,7 +41,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void CaseSensitivityButtonClicked(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); WINRT_CALLBACK(Search, SearchHandler); - WINRT_CALLBACK(SearchChanged, SearchChangedHandler); + WINRT_CALLBACK(SearchChanged, SearchHandler); TYPED_EVENT(Closed, TerminalControl::SearchBoxControl, Windows::UI::Xaml::RoutedEventArgs); private: diff --git a/src/cascadia/TerminalControl/SearchBoxControl.idl b/src/cascadia/TerminalControl/SearchBoxControl.idl index 548199953d8..62397598051 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.idl +++ b/src/cascadia/TerminalControl/SearchBoxControl.idl @@ -4,7 +4,6 @@ namespace Microsoft.Terminal.TerminalControl { delegate void SearchHandler(String query, Boolean goForward, Boolean isCaseSensitive); - delegate void SearchChangedHandler(String query, Boolean isCaseSensitive); [default_interface] runtimeclass SearchBoxControl : Windows.UI.Xaml.Controls.UserControl { @@ -17,7 +16,7 @@ namespace Microsoft.Terminal.TerminalControl void SetStatusVisible(Boolean isVisible); event SearchHandler Search; - event SearchChangedHandler SearchChanged; + event SearchHandler SearchChanged; event Windows.Foundation.TypedEventHandler Closed; } } diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 284c352a880..cd0d61af6a8 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -113,6 +113,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation auto onReceiveOutputFn = [this](const hstring str) { _terminal->Write(str); _updatePatternLocations->Run(); + _updateLiveSearch->Run(); }; _connectionOutputEventToken = _connection.TerminalOutput(onReceiveOutputFn); @@ -184,11 +185,26 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation ScrollBarUpdateInterval, Dispatcher()); + _updateLiveSearch = std::make_shared>( + [weakThis = get_weak()]() { + if (auto control{ weakThis.get() }) + { + // If in the middle of the live search, recompute the matches. + // We avoid navigation to the first result to prevent auto-scrolling. + if (control->_liveSearchState.has_value()) + { + control->_LiveSearchAll(control->_liveSearchState.value().Text, control->_liveSearchState.value().CaseSensitive); + } + } + }, + UpdatePatternLocationsInterval, + Dispatcher()); + static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast(1.0 / 30.0 * 1000000)); _autoScrollTimer.Interval(AutoScrollUpdateInterval); _autoScrollTimer.Tick({ this, &TermControl::_UpdateAutoScroll }); - _SetLiveSearch(_settings.LiveSearch()); + _SetLiveSearchEnabled(_settings.LiveSearch()); _ApplyUISettings(); } @@ -204,7 +220,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // get at its private implementation _searchBox.copy_from(winrt::get_self(searchBox)); _searchBox->Visibility(Visibility::Visible); - _searchBox->SetStatus(RS_(L"TermControl_NoMatch")); _searchBox->SetStatusVisible(_isLiveSearchEnabled); // If a text is selected inside terminal, use it to populate the search box. @@ -230,6 +245,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // Method Description: // - Search text in text buffer. This is triggered if the user click // search button or press enter. + // In the live search mode it will be also triggered once every time search criteria changes // Arguments: // - text: the text to search // - goForward: boolean that represents if the current search direction is forward @@ -238,54 +254,53 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - void TermControl::_Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive) { - if (_closing) + if (_closing || text.empty()) { return; } - std::wstring searchStatus{ RS_(L"TermControl_NoMatch") }; - - if (!text.empty()) + if (_isLiveSearchEnabled) { - if (_isLiveSearchEnabled) + // Live Search + // Run only if the live search state was initialized by LiveSearchAll + if (_liveSearchState.has_value()) { - const int numMatches = ::base::saturated_cast(_liveSearchMatches.size()); - if (numMatches > 0) + _liveSearchState.value().UpdateIndex(goForward); + + const auto currentMatch = _liveSearchState.value().GetCurrentMatch(); + if (currentMatch.has_value()) { - _liveSearchIndex = (numMatches + _liveSearchIndex + (goForward ? 1 : -1)) % numMatches; - searchStatus = fmt::format(L"{}/{}", _liveSearchIndex + 1, numMatches); + auto lock = _terminal->LockForWriting(); _terminal->SetBlockSelection(false); - - const auto& selectionCoords = til::at(_liveSearchMatches, _liveSearchIndex); - _terminal->SelectNewRegion(selectionCoords.first, selectionCoords.second); + _terminal->SelectNewRegion(currentMatch.value().first, currentMatch.value().second); _renderer->TriggerSelection(); } - } - else - { - const Search::Direction direction = goForward ? - Search::Direction::Forward : - Search::Direction::Backward; - - const Search::Sensitivity sensitivity = caseSensitive ? - Search::Sensitivity::CaseSensitive : - Search::Sensitivity::CaseInsensitive; - Search search(*GetUiaData(), text.c_str(), direction, sensitivity); - auto lock = _terminal->LockForWriting(); - if (search.FindNext()) + if (_searchBox) { - searchStatus = L""; - _terminal->SetBlockSelection(false); - search.Select(); - _renderer->TriggerSelection(); + _searchBox->SetStatus(_liveSearchState.value().Status()); } } } - - if (_searchBox) + else { - _searchBox->SetStatus(searchStatus.data()); + // Classic Search + const Search::Direction direction = goForward ? + Search::Direction::Forward : + Search::Direction::Backward; + + const Search::Sensitivity sensitivity = caseSensitive ? + Search::Sensitivity::CaseSensitive : + Search::Sensitivity::CaseInsensitive; + + Search search(*GetUiaData(), text.c_str(), direction, sensitivity); + auto lock = _terminal->LockForWriting(); + if (search.FindNext()) + { + _terminal->SetBlockSelection(false); + search.Select(); + _renderer->TriggerSelection(); + } } } @@ -296,29 +311,48 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - caseSensitive: boolean that represents if the current search is case sensitive // Return Value: // - - void TermControl::_SearchChanged(const winrt::hstring& text, const bool caseSensitive) + void TermControl::_SearchChanged(const winrt::hstring& text, const bool goForward, const bool caseSensitive) { - if (_isLiveSearchEnabled) + if (_isLiveSearchEnabled && _searchBox && _searchBox->Visibility() == Visibility::Visible) { // Clear the selection reset the anchor _terminal->ClearSelection(); _renderer->TriggerSelection(); + _LiveSearchAll(text, caseSensitive); + _Search(text, goForward, caseSensitive); + } + } + + // Method Description: + // - As a part of LiveSearch solution looks for all text matching the criteria + // Arguments: + // - text: the text to search + // - caseSensitive: boolean that represents if the current search is case sensitive + // Return Value: + // - + void TermControl::_LiveSearchAll(const winrt::hstring& text, const bool caseSensitive) + { + std::vector> matches; + if (!text.empty()) + { const Search::Sensitivity sensitivity = caseSensitive ? Search::Sensitivity::CaseSensitive : Search::Sensitivity::CaseInsensitive; Search search(*GetUiaData(), text.c_str(), Search::Direction::Forward, sensitivity); auto lock = _terminal->LockForWriting(); - - _liveSearchMatches.clear(); - _liveSearchIndex = -1; while (search.FindNext()) { - _liveSearchMatches.push_back(search.GetFoundLocation()); + matches.push_back(search.GetFoundLocation()); } + } - _Search(text, true, caseSensitive); + const LiveSearchState liveSearchState{ text, caseSensitive, matches }; + _liveSearchState.emplace(liveSearchState); + if (_searchBox) + { + _searchBox->SetStatus(liveSearchState.Status()); } } @@ -330,7 +364,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - isLiveSearchEnabled: true if live search mode is enabled // Return Value: // - - void TermControl::_SetLiveSearch(bool isLiveSearchEnabled) + void TermControl::_SetLiveSearchEnabled(bool isLiveSearchEnabled) { if (_isLiveSearchEnabled != isLiveSearchEnabled) { @@ -354,6 +388,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void TermControl::_CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& /*sender*/, RoutedEventArgs const& /*args*/) { _searchBox->Visibility(Visibility::Collapsed); + _liveSearchState.reset(); // Set focus back to terminal control this->Focus(FocusState::Programmatic); @@ -420,7 +455,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _RefreshSizeUnderLock(); } - _SetLiveSearch(_settings.LiveSearch()); + _SetLiveSearchEnabled(_settings.LiveSearch()); } } @@ -3251,6 +3286,73 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation return _terminal->GetTaskbarProgress(); } + // 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: + // - + void LiveSearchState::UpdateIndex(bool goForward) + { + const int numMatches = ::base::saturated_cast(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: + // - + // 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> LiveSearchState::GetCurrentMatch() + { + if (CurrentMatchIndex > -1 && CurrentMatchIndex < Matches.size()) + { + return til::at(Matches, CurrentMatchIndex); + } + else + { + return std::nullopt; + } + } + + // Method Description: + // - Builds a status message representing the search state: + // * "No results" - if matches are empty + // * "?/n" - if there are n matches and we didn't start the iteration over matches + // (usually we will get this after buffer update) + // * "m/n" - if we are currently at match m out of n. + // Arguments: + // - + // Return Value: + // - status message + winrt::hstring LiveSearchState::Status() const + { + if (Matches.empty()) + { + return RS_(L"TermControl_NoMatch"); + } + + if (CurrentMatchIndex < 0) + { + return fmt::format(L"?/{}", Matches.size()).data(); + } + + return fmt::format(L"{}/{}", CurrentMatchIndex + 1, Matches.size()).data(); + } + // -------------------------------- WinRT Events --------------------------------- // Winrt events need a method for adding a callback to the event and removing the callback. // These macros will define them both for you. diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index f38ded9908a..74daddecfa7 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -98,6 +98,26 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation const hstring _message; }; + struct LiveSearchState + { + public: + LiveSearchState(const winrt::hstring& text, const bool caseSensitive, std::vector> matches) : + Text(text), + CaseSensitive(caseSensitive), + Matches(matches) + { + } + + const winrt::hstring Text; + const bool CaseSensitive; + const std::vector> Matches; + int32_t CurrentMatchIndex{ -1 }; + + void UpdateIndex(bool goForward); + std::optional> GetCurrentMatch(); + winrt::hstring Status() const; + }; + struct TermControl : TermControlT { TermControl(IControlSettings settings, TerminalConnection::ITerminalConnection connection); @@ -207,6 +227,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation std::shared_ptr> _updatePatternLocations; + std::shared_ptr> _updateLiveSearch; + struct ScrollBarUpdate { std::optional newValue; @@ -259,10 +281,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker; + // LiveSearch bool _isLiveSearchEnabled{ false }; - std::vector> _liveSearchMatches; - int32_t _liveSearchIndex{ -1 }; + std::optional _liveSearchState; + void _SetLiveSearchEnabled(bool isLiveSearchEnabled); + void _LiveSearchAll(const winrt::hstring& text, const bool caseSensitive); void _ApplyUISettings(); void _UpdateSystemParameterSettings() noexcept; @@ -330,7 +354,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation double _GetAutoScrollSpeed(double cursorDistanceFromBorder) const; void _Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive); - void _SearchChanged(const winrt::hstring& text, const bool caseSensitive); + void _SearchChanged(const winrt::hstring& text, const bool goForward, const bool caseSensitive); void _CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& sender, Windows::UI::Xaml::RoutedEventArgs const& args); // TSFInputControl Handlers From 5391f4e64b1651f04e94a35b874f63e5ca5306ab Mon Sep 17 00:00:00 2001 From: khvitaly Date: Wed, 16 Dec 2020 02:56:08 +0200 Subject: [PATCH 03/61] Remove live-search feature flag --- .../SerializationTests.cpp | 2 - src/cascadia/TerminalApp/TerminalSettings.cpp | 1 - src/cascadia/TerminalApp/TerminalSettings.h | 2 - .../TerminalControl/IControlSettings.idl | 2 - .../TerminalControl/SearchBoxControl.cpp | 17 +-- .../TerminalControl/SearchBoxControl.h | 1 - .../TerminalControl/SearchBoxControl.idl | 2 - .../TerminalControl/SearchBoxControl.xaml | 3 +- src/cascadia/TerminalControl/TermControl.cpp | 113 +++++------------- src/cascadia/TerminalControl/TermControl.h | 14 +-- .../TerminalSettingsEditor/Interaction.xaml | 7 -- .../GlobalAppSettings.cpp | 5 - .../TerminalSettingsModel/GlobalAppSettings.h | 1 - .../GlobalAppSettings.idl | 4 - .../TerminalSettingsModel/defaults.json | 1 - .../UnitTests_TerminalCore/MockTermSettings.h | 3 - 16 files changed, 38 insertions(+), 140 deletions(-) diff --git a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp index 5a15e0386d6..60491de9cbf 100644 --- a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp @@ -104,8 +104,6 @@ namespace SettingsModelLocalTests "largePasteWarning": true, "multiLinePasteWarning": true, - "liveSearch": false, - "experimental.input.forceVT": false, "experimental.rendering.forceFullRepaint": false, "experimental.rendering.software": false diff --git a/src/cascadia/TerminalApp/TerminalSettings.cpp b/src/cascadia/TerminalApp/TerminalSettings.cpp index c78578ca4aa..681e99ecd18 100644 --- a/src/cascadia/TerminalApp/TerminalSettings.cpp +++ b/src/cascadia/TerminalApp/TerminalSettings.cpp @@ -208,7 +208,6 @@ namespace winrt::TerminalApp::implementation _ForceFullRepaintRendering = globalSettings.ForceFullRepaintRendering(); _SoftwareRendering = globalSettings.SoftwareRendering(); _ForceVTInput = globalSettings.ForceVTInput(); - _LiveSearch = globalSettings.LiveSearch(); } // Method Description: diff --git a/src/cascadia/TerminalApp/TerminalSettings.h b/src/cascadia/TerminalApp/TerminalSettings.h index d7722c49e25..db4db524220 100644 --- a/src/cascadia/TerminalApp/TerminalSettings.h +++ b/src/cascadia/TerminalApp/TerminalSettings.h @@ -121,8 +121,6 @@ namespace winrt::TerminalApp::implementation GETSET_PROPERTY(bool, SoftwareRendering, false); GETSET_PROPERTY(bool, ForceVTInput, false); - GETSET_PROPERTY(bool, LiveSearch, false); - #pragma warning(pop) private: diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 2faa388ecf6..2f140a0a3f8 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -57,7 +57,5 @@ namespace Microsoft.Terminal.TerminalControl Boolean RetroTerminalEffect; Boolean ForceFullRepaintRendering; Boolean SoftwareRendering; - - Boolean LiveSearch; }; } diff --git a/src/cascadia/TerminalControl/SearchBoxControl.cpp b/src/cascadia/TerminalControl/SearchBoxControl.cpp index fa9dac3c88b..c3c04ed2ed3 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.cpp +++ b/src/cascadia/TerminalControl/SearchBoxControl.cpp @@ -22,9 +22,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // Once the control is visible again we trigger SearchChanged event. // We do this since probably we have a value from the previous search, // and in such case logically the search changes from "nothing" to this value. - // A good example for SearchChanged event consumer is LiveSearch. - // Once the control is open we want LiveSearch to immediately perform the search - // with the value appearing in the box. + // A good example for SearchChanged event consumer is Terminal Control. + // Once the Search Box is open we want the Terminal Control + // to immediately perform the search with the value appearing in the box. if (Visibility() == Visibility::Visible) { _SearchChangedHandlers(TextBox().Text(), _GoForward(), _CaseSensitive()); @@ -259,15 +259,4 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation StatusBox().Text(status); } } - - // Method Description: - // - Allows to set the visibility of the search status - // Arguments: - // - isVisible: if true, the status is visible - // Return Value: - // - - void SearchBoxControl::SetStatusVisible(bool isVisible) - { - StatusBox().Visibility(isVisible ? Visibility::Visible : Visibility::Collapsed); - } } diff --git a/src/cascadia/TerminalControl/SearchBoxControl.h b/src/cascadia/TerminalControl/SearchBoxControl.h index c46bea3cbc3..7e04a6a9c38 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.h +++ b/src/cascadia/TerminalControl/SearchBoxControl.h @@ -32,7 +32,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void PopulateTextbox(winrt::hstring const& text); bool ContainsFocus(); void SetStatus(winrt::hstring const& text); - void SetStatusVisible(bool isVisible); void GoBackwardClicked(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/); void GoForwardClicked(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/); diff --git a/src/cascadia/TerminalControl/SearchBoxControl.idl b/src/cascadia/TerminalControl/SearchBoxControl.idl index 62397598051..cc28a89a588 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.idl +++ b/src/cascadia/TerminalControl/SearchBoxControl.idl @@ -11,9 +11,7 @@ namespace Microsoft.Terminal.TerminalControl void SetFocusOnTextbox(); void PopulateTextbox(String text); Boolean ContainsFocus(); - void SetStatus(String status); - void SetStatusVisible(Boolean isVisible); event SearchHandler Search; event SearchHandler SearchChanged; diff --git a/src/cascadia/TerminalControl/SearchBoxControl.xaml b/src/cascadia/TerminalControl/SearchBoxControl.xaml index d2befe2cc04..9caeed46513 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.xaml +++ b/src/cascadia/TerminalControl/SearchBoxControl.xaml @@ -171,8 +171,7 @@ FontSize="15" Margin="5" HorizontalAlignment="Left" - VerticalAlignment="Center" - Visibility="Collapsed"> + VerticalAlignment="Center"> Write(str); _updatePatternLocations->Run(); - _updateLiveSearch->Run(); + _updateSearchStatus->Run(); }; _connectionOutputEventToken = _connection.TerminalOutput(onReceiveOutputFn); @@ -185,15 +185,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation ScrollBarUpdateInterval, Dispatcher()); - _updateLiveSearch = std::make_shared>( + _updateSearchStatus = std::make_shared>( [weakThis = get_weak()]() { if (auto control{ weakThis.get() }) { // If in the middle of the live search, recompute the matches. // We avoid navigation to the first result to prevent auto-scrolling. - if (control->_liveSearchState.has_value()) + if (control->_searchState.has_value()) { - control->_LiveSearchAll(control->_liveSearchState.value().Text, control->_liveSearchState.value().CaseSensitive); + control->_SearchAll(control->_searchState.value().Text, control->_searchState.value().CaseSensitive); } } }, @@ -203,8 +203,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast(1.0 / 30.0 * 1000000)); _autoScrollTimer.Interval(AutoScrollUpdateInterval); _autoScrollTimer.Tick({ this, &TermControl::_UpdateAutoScroll }); - - _SetLiveSearchEnabled(_settings.LiveSearch()); _ApplyUISettings(); } @@ -220,7 +218,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // get at its private implementation _searchBox.copy_from(winrt::get_self(searchBox)); _searchBox->Visibility(Visibility::Visible); - _searchBox->SetStatusVisible(_isLiveSearchEnabled); // If a text is selected inside terminal, use it to populate the search box. // If the search box already contains a value, it will be overridden. @@ -247,59 +244,30 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // search button or press enter. // In the live search mode it will be also triggered once every time search criteria changes // Arguments: - // - text: the text to search + // - text: not used // - goForward: boolean that represents if the current search direction is forward - // - caseSensitive: boolean that represents if the current search is case sensitive + // - caseSensitive: not used // Return Value: // - - void TermControl::_Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive) + void TermControl::_Search(const winrt::hstring& /*text*/, const bool goForward, const bool /*caseSensitive*/) { - if (_closing || text.empty()) + // Run only if the live search state was initialized by _SearchAll + if (!_closing && _searchState.has_value()) { - return; - } + _searchState.value().UpdateIndex(goForward); - if (_isLiveSearchEnabled) - { - // Live Search - // Run only if the live search state was initialized by LiveSearchAll - if (_liveSearchState.has_value()) + const auto currentMatch = _searchState.value().GetCurrentMatch(); + if (currentMatch.has_value()) { - _liveSearchState.value().UpdateIndex(goForward); - - const auto currentMatch = _liveSearchState.value().GetCurrentMatch(); - if (currentMatch.has_value()) - { - auto lock = _terminal->LockForWriting(); - _terminal->SetBlockSelection(false); - _terminal->SelectNewRegion(currentMatch.value().first, currentMatch.value().second); - _renderer->TriggerSelection(); - } - - if (_searchBox) - { - _searchBox->SetStatus(_liveSearchState.value().Status()); - } + auto lock = _terminal->LockForWriting(); + _terminal->SetBlockSelection(false); + _terminal->SelectNewRegion(currentMatch.value().first, currentMatch.value().second); + _renderer->TriggerSelection(); } - } - else - { - // Classic Search - const Search::Direction direction = goForward ? - Search::Direction::Forward : - Search::Direction::Backward; - const Search::Sensitivity sensitivity = caseSensitive ? - Search::Sensitivity::CaseSensitive : - Search::Sensitivity::CaseInsensitive; - - Search search(*GetUiaData(), text.c_str(), direction, sensitivity); - auto lock = _terminal->LockForWriting(); - if (search.FindNext()) + if (_searchBox) { - _terminal->SetBlockSelection(false); - search.Select(); - _renderer->TriggerSelection(); + _searchBox->SetStatus(_searchState.value().Status()); } } } @@ -313,25 +281,25 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - void TermControl::_SearchChanged(const winrt::hstring& text, const bool goForward, const bool caseSensitive) { - if (_isLiveSearchEnabled && _searchBox && _searchBox->Visibility() == Visibility::Visible) + if (_searchBox && _searchBox->Visibility() == Visibility::Visible) { // Clear the selection reset the anchor _terminal->ClearSelection(); _renderer->TriggerSelection(); - _LiveSearchAll(text, caseSensitive); + _SearchAll(text, caseSensitive); _Search(text, goForward, caseSensitive); } } // Method Description: - // - As a part of LiveSearch solution looks for all text matching the criteria + // - Performs search given a criteria and collects all matches // Arguments: // - text: the text to search // - caseSensitive: boolean that represents if the current search is case sensitive // Return Value: // - - void TermControl::_LiveSearchAll(const winrt::hstring& text, const bool caseSensitive) + void TermControl::_SearchAll(const winrt::hstring& text, const bool caseSensitive) { std::vector> matches; if (!text.empty()) @@ -348,32 +316,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } } - const LiveSearchState liveSearchState{ text, caseSensitive, matches }; - _liveSearchState.emplace(liveSearchState); + const SearchState searchState{ text, caseSensitive, matches }; + _searchState.emplace(searchState); if (_searchBox) { - _searchBox->SetStatus(liveSearchState.Status()); - } - } - - // Method Description: - // - The goal of this method is to reset search in the case search mode is switched. - // We need this to avoid inconsistencies, when the mode is changed in the middle of - // navigation through results. - // Arguments: - // - isLiveSearchEnabled: true if live search mode is enabled - // Return Value: - // - - void TermControl::_SetLiveSearchEnabled(bool isLiveSearchEnabled) - { - if (_isLiveSearchEnabled != isLiveSearchEnabled) - { - _isLiveSearchEnabled = isLiveSearchEnabled; - if (_searchBox && _searchBox->Visibility() == Visibility::Visible) - { - _searchBox->SetStatusVisible(_isLiveSearchEnabled); - _searchBox->PopulateTextbox(L""); - } + _searchBox->SetStatus(searchState.Status()); } } @@ -388,7 +335,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void TermControl::_CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& /*sender*/, RoutedEventArgs const& /*args*/) { _searchBox->Visibility(Visibility::Collapsed); - _liveSearchState.reset(); + _searchState.reset(); // Set focus back to terminal control this->Focus(FocusState::Programmatic); @@ -454,8 +401,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation { _RefreshSizeUnderLock(); } - - _SetLiveSearchEnabled(_settings.LiveSearch()); } } @@ -3293,7 +3238,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - goForward: if true, move to the next match, else go to previous // Return Value: // - - void LiveSearchState::UpdateIndex(bool goForward) + void SearchState::UpdateIndex(bool goForward) { const int numMatches = ::base::saturated_cast(Matches.size()); if (numMatches > 0) @@ -3316,7 +3261,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // 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> LiveSearchState::GetCurrentMatch() + std::optional> SearchState::GetCurrentMatch() { if (CurrentMatchIndex > -1 && CurrentMatchIndex < Matches.size()) { @@ -3338,7 +3283,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - // Return Value: // - status message - winrt::hstring LiveSearchState::Status() const + winrt::hstring SearchState::Status() const { if (Matches.empty()) { diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 74daddecfa7..6ab4dd753ee 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -98,10 +98,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation const hstring _message; }; - struct LiveSearchState + struct SearchState { public: - LiveSearchState(const winrt::hstring& text, const bool caseSensitive, std::vector> matches) : + SearchState(const winrt::hstring& text, const bool caseSensitive, std::vector> matches) : Text(text), CaseSensitive(caseSensitive), Matches(matches) @@ -227,7 +227,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation std::shared_ptr> _updatePatternLocations; - std::shared_ptr> _updateLiveSearch; + std::shared_ptr> _updateSearchStatus; struct ScrollBarUpdate { @@ -281,12 +281,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker; - // LiveSearch - bool _isLiveSearchEnabled{ false }; - std::optional _liveSearchState; - - void _SetLiveSearchEnabled(bool isLiveSearchEnabled); - void _LiveSearchAll(const winrt::hstring& text, const bool caseSensitive); + std::optional _searchState; + void _SearchAll(const winrt::hstring& text, const bool caseSensitive); void _ApplyUISettings(); void _UpdateSystemParameterSettings() noexcept; diff --git a/src/cascadia/TerminalSettingsEditor/Interaction.xaml b/src/cascadia/TerminalSettingsEditor/Interaction.xaml index 9786feacb91..eb6bd08e6bf 100644 --- a/src/cascadia/TerminalSettingsEditor/Interaction.xaml +++ b/src/cascadia/TerminalSettingsEditor/Interaction.xaml @@ -38,13 +38,6 @@ the MIT License. See LICENSE in the project root for license information. --> IsChecked="{x:Bind State.Globals.SnapToGridOnResize, Mode=TwoWay}" Style="{StaticResource CheckBoxSettingStyle}"/> - - - - - diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index c0dada89d87..71c8336f6ed 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -40,7 +40,6 @@ static constexpr std::string_view AlwaysOnTopKey{ "alwaysOnTop" }; static constexpr std::string_view LegacyUseTabSwitcherModeKey{ "useTabSwitcher" }; static constexpr std::string_view TabSwitcherModeKey{ "tabSwitcherMode" }; static constexpr std::string_view DisableAnimationsKey{ "disableAnimations" }; -static constexpr std::string_view LiveSearchKey{ "liveSearch" }; static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" }; @@ -115,7 +114,6 @@ winrt::com_ptr GlobalAppSettings::Copy() const globals->_AlwaysOnTop = _AlwaysOnTop; globals->_TabSwitcherMode = _TabSwitcherMode; globals->_DisableAnimations = _DisableAnimations; - globals->_LiveSearch = _LiveSearch; globals->_UnparsedDefaultProfile = _UnparsedDefaultProfile; globals->_validDefaultProfile = _validDefaultProfile; @@ -302,8 +300,6 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, DisableAnimationsKey, _DisableAnimations); - JsonUtils::GetValueForKey(json, LiveSearchKey, _LiveSearch); - // This is a helper lambda to get the keybindings and commands out of both // and array of objects. We'll use this twice, once on the legacy // `keybindings` key, and again on the newer `bindings` key. @@ -393,7 +389,6 @@ Json::Value GlobalAppSettings::ToJson() const JsonUtils::SetValueForKey(json, AlwaysOnTopKey, _AlwaysOnTop); JsonUtils::SetValueForKey(json, TabSwitcherModeKey, _TabSwitcherMode); JsonUtils::SetValueForKey(json, DisableAnimationsKey, _DisableAnimations); - JsonUtils::SetValueForKey(json, LiveSearchKey, _LiveSearch); // clang-format on // TODO GH#8100: keymap needs to be serialized here diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 1ac4474e351..29c4fbda7e0 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -85,7 +85,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation GETSET_SETTING(bool, AlwaysOnTop, false); GETSET_SETTING(Model::TabSwitcherMode, TabSwitcherMode, Model::TabSwitcherMode::InOrder); GETSET_SETTING(bool, DisableAnimations, false); - GETSET_SETTING(bool, LiveSearch, false); private: guid _defaultProfile; diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index 437ff558b95..6d427b4ec5b 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -135,10 +135,6 @@ namespace Microsoft.Terminal.Settings.Model void ClearDisableAnimations(); Boolean DisableAnimations; - Boolean HasLiveSearch(); - void ClearLiveSearch(); - Boolean LiveSearch; - Windows.Foundation.Collections.IMapView ColorSchemes(); void AddColorScheme(ColorScheme scheme); diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index 5ac0cf47bc4..cb862728ed6 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -26,7 +26,6 @@ "theme": "system", "snapToGridOnResize": true, "disableAnimations": false, - "liveSearch": false, "profiles": [ diff --git a/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h b/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h index 3d11087c15b..e55e17b9612 100644 --- a/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h +++ b/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h @@ -39,7 +39,6 @@ namespace TerminalCoreUnitTests bool SuppressApplicationTitle() { return _suppressApplicationTitle; } uint32_t SelectionBackground() { return COLOR_WHITE; } bool ForceVTInput() { return false; } - bool LiveSearch() { return _liveSearch; } // other implemented methods uint32_t GetColorTableEntry(int32_t) const { return 123; } @@ -61,7 +60,6 @@ namespace TerminalCoreUnitTests void SuppressApplicationTitle(bool suppressApplicationTitle) { _suppressApplicationTitle = suppressApplicationTitle; } void SelectionBackground(uint32_t) {} void ForceVTInput(bool) {} - void LiveSearch(bool liveSearch) { _liveSearch = liveSearch; } GETSET_PROPERTY(winrt::Windows::Foundation::IReference, TabColor, nullptr); GETSET_PROPERTY(winrt::Windows::Foundation::IReference, StartingTabColor, nullptr); @@ -73,6 +71,5 @@ namespace TerminalCoreUnitTests bool _copyOnSelect{ false }; bool _suppressApplicationTitle{ false }; winrt::hstring _startingTitle; - bool _liveSearch{ false }; }; } From 4ab4d416f05c24a4707ce614646a497b89880775 Mon Sep 17 00:00:00 2001 From: khvitaly Date: Sun, 20 Dec 2020 04:07:37 +0200 Subject: [PATCH 04/61] Allow disabling navigation buttons in SearchBox --- src/cascadia/TerminalControl/SearchBoxControl.cpp | 12 ++++++++++++ src/cascadia/TerminalControl/SearchBoxControl.h | 1 + src/cascadia/TerminalControl/SearchBoxControl.idl | 1 + 3 files changed, 14 insertions(+) diff --git a/src/cascadia/TerminalControl/SearchBoxControl.cpp b/src/cascadia/TerminalControl/SearchBoxControl.cpp index c3c04ed2ed3..79258093904 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.cpp +++ b/src/cascadia/TerminalControl/SearchBoxControl.cpp @@ -259,4 +259,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation StatusBox().Text(status); } } + + // Method Description: + // - Enables / disables results navigation buttons + // Arguments: + // - enable: if true, the buttons should be enabled + // Return Value: + // - + void SearchBoxControl::SetNavigationEnabled(bool enabled) + { + GoBackwardButton().IsEnabled(enabled); + GoForwardButton().IsEnabled(enabled); + } } diff --git a/src/cascadia/TerminalControl/SearchBoxControl.h b/src/cascadia/TerminalControl/SearchBoxControl.h index 7e04a6a9c38..04c00087d3c 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.h +++ b/src/cascadia/TerminalControl/SearchBoxControl.h @@ -32,6 +32,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void PopulateTextbox(winrt::hstring const& text); bool ContainsFocus(); void SetStatus(winrt::hstring const& text); + void SetNavigationEnabled(bool enabled); void GoBackwardClicked(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/); void GoForwardClicked(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/); diff --git a/src/cascadia/TerminalControl/SearchBoxControl.idl b/src/cascadia/TerminalControl/SearchBoxControl.idl index cc28a89a588..b0d228a99fb 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.idl +++ b/src/cascadia/TerminalControl/SearchBoxControl.idl @@ -12,6 +12,7 @@ namespace Microsoft.Terminal.TerminalControl void PopulateTextbox(String text); Boolean ContainsFocus(); void SetStatus(String status); + void SetNavigationEnabled(Boolean enabled); event SearchHandler Search; event SearchHandler SearchChanged; From 4a2de19375175d6a484e5219cb1294757478015f Mon Sep 17 00:00:00 2001 From: khvitaly Date: Sun, 20 Dec 2020 04:08:09 +0200 Subject: [PATCH 05/61] Add Searching status to translation --- src/cascadia/TerminalControl/Resources/en-US/Resources.resw | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw index ad395701ee9..434edb36e9b 100644 --- a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw @@ -182,6 +182,10 @@ No results Will be presented near the search box when Find operation returned no results. + + Searching... + Will be presented near the search box when Find operation is running. + Unable to find the selected font "{0}". From fe59504d20e916a9fd085537dac144a89d2e0baf Mon Sep 17 00:00:00 2001 From: khvitaly Date: Sun, 20 Dec 2020 04:08:51 +0200 Subject: [PATCH 06/61] Run search in the background + itnroduce grace for next search --- src/cascadia/TerminalControl/TermControl.cpp | 221 ++++++++++++++----- src/cascadia/TerminalControl/TermControl.h | 13 +- 2 files changed, 175 insertions(+), 59 deletions(-) diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index dc746f2a11b..d0df56c9466 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -38,6 +38,15 @@ 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 minimum delay between triggering search upon output to terminal +constexpr const auto SearchUponOutputInterval = std::chrono::milliseconds(500); + +// The delay before performing the search after output to terminal +constexpr const auto SearchAfterOutputDelay = std::chrono::milliseconds(800); + +// The delay before performing the search after change of search criteria +constexpr const auto SearchAfterChangeDelay = std::chrono::milliseconds(200); + DEFINE_ENUM_FLAG_OPERATORS(winrt::Microsoft::Terminal::TerminalControl::CopyFormat); namespace winrt::Microsoft::Terminal::TerminalControl::implementation @@ -193,11 +202,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // We avoid navigation to the first result to prevent auto-scrolling. if (control->_searchState.has_value()) { - control->_SearchAll(control->_searchState.value().Text, control->_searchState.value().CaseSensitive); + const SearchState searchState{ control->_searchState.value().Text, control->_searchState.value().CaseSensitive }; + control->_searchState.emplace(searchState); + control->_SearchAsync(std::nullopt, SearchAfterOutputDelay); } } }, - UpdatePatternLocationsInterval, + SearchUponOutputInterval, Dispatcher()); static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast(1.0 / 30.0 * 1000000)); @@ -251,76 +262,165 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - void TermControl::_Search(const winrt::hstring& /*text*/, const bool goForward, const bool /*caseSensitive*/) { - // Run only if the live search state was initialized by _SearchAll - if (!_closing && _searchState.has_value()) + _SelectSearchResult(goForward); + } + + // 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: + // - + fire_and_forget TermControl::_SearchAsync(std::optional goForward, Windows::Foundation::TimeSpan const& delay) + { + // Run only if the live search state was initialized + if (_closing || !_searchState.has_value()) { - _searchState.value().UpdateIndex(goForward); + return; + } + + const auto originalSearchId = _searchState.value().SearchId; + auto weakThis{ this->get_weak() }; + + // If no matches were computed it means we need to perform the search + if (!_searchState.value().Matches.has_value()) + { + // Before we search, let's wait a bit: + // probably the search criteria or the data are still modified. + co_await winrt::resume_after(delay); - const auto currentMatch = _searchState.value().GetCurrentMatch(); - if (currentMatch.has_value()) + // Switch back to Dispatcher so we can set the Searching status + co_await winrt::resume_foreground(Dispatcher()); + if (auto control{ weakThis.get() }) { - auto lock = _terminal->LockForWriting(); - _terminal->SetBlockSelection(false); - _terminal->SelectNewRegion(currentMatch.value().first, currentMatch.value().second); - _renderer->TriggerSelection(); + // If search box was collapsed or the new one search was triggered - let's cancel this one + if (!_searchState.has_value() || _searchState.value().SearchId != originalSearchId) + { + co_return; + } + + // Let's mark the start of searching + if (_searchBox) + { + _searchBox->SetStatus(_searchState.value().Status()); + _searchBox->SetNavigationEnabled(false); + } + + std::vector> matches; + if (!_searchState.value().Text.empty()) + { + const Search::Sensitivity sensitivity = _searchState.value().CaseSensitive ? + Search::Sensitivity::CaseSensitive : + Search::Sensitivity::CaseInsensitive; + + Search search(*GetUiaData(), _searchState.value().Text.c_str(), Search::Direction::Forward, sensitivity); + while (co_await _SearchOne(search)) + { + // if search box was collapsed or the new one search was triggered - let's cancel this one + if (!_searchState.has_value() || _searchState.value().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.value().SearchId != originalSearchId) + { + co_return; + } + } + _searchState.value().Matches.emplace(matches); + } + } + + if (auto control{ weakThis.get() }) + { + _SelectSearchResult(goForward); + } + } + + // 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: + // - + void TermControl::_SelectSearchResult(std::optional goForward) + { + if (_searchState.has_value() && _searchState.value().Matches.has_value()) + { + if (goForward.has_value()) + { + _searchState.value().UpdateIndex(goForward.value()); + + const auto currentMatch = _searchState.value().GetCurrentMatch(); + if (currentMatch.has_value()) + { + auto lock = _terminal->LockForWriting(); + _terminal->SetBlockSelection(false); + _terminal->SelectNewRegion(currentMatch.value().first, currentMatch.value().second); + _renderer->TriggerSelection(); + } } if (_searchBox) { _searchBox->SetStatus(_searchState.value().Status()); + _searchBox->SetNavigationEnabled(!_searchState.value().Matches.value().empty()); } } } // Method Description: - // - If live search is enabled in settings, searches the buffer forward + // - Search for a single value in a background // Arguments: - // - text: the text to search - // - caseSensitive: boolean that represents if the current search is case sensitive + // - search: search object to use to find the next match // Return Value: // - - void TermControl::_SearchChanged(const winrt::hstring& text, const bool goForward, const bool caseSensitive) + winrt::Windows::Foundation::IAsyncOperation TermControl::_SearchOne(Search& search) { - if (_searchBox && _searchBox->Visibility() == Visibility::Visible) - { - // Clear the selection reset the anchor - _terminal->ClearSelection(); - _renderer->TriggerSelection(); + bool found{ false }; + auto weakThis{ this->get_weak() }; - _SearchAll(text, caseSensitive); - _Search(text, goForward, caseSensitive); + co_await winrt::resume_background(); + if (auto control{ weakThis.get() }) + { + // 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(); + found = search.FindNext(); } + + co_await winrt::resume_foreground(Dispatcher()); + co_return found; } // Method Description: - // - Performs search given a criteria and collects all matches + // - If live search is enabled in settings, searches the buffer forward // Arguments: // - text: the text to search // - caseSensitive: boolean that represents if the current search is case sensitive // Return Value: // - - void TermControl::_SearchAll(const winrt::hstring& text, const bool caseSensitive) + void TermControl::_SearchChanged(const winrt::hstring& text, const bool goForward, const bool caseSensitive) { - std::vector> matches; - if (!text.empty()) + if (_searchBox && _searchBox->Visibility() == Visibility::Visible) { - const Search::Sensitivity sensitivity = caseSensitive ? - Search::Sensitivity::CaseSensitive : - Search::Sensitivity::CaseInsensitive; - - Search search(*GetUiaData(), text.c_str(), Search::Direction::Forward, sensitivity); - auto lock = _terminal->LockForWriting(); - while (search.FindNext()) - { - matches.push_back(search.GetFoundLocation()); - } - } + // Clear the selection reset the anchor + _terminal->ClearSelection(); + _renderer->TriggerSelection(); - const SearchState searchState{ text, caseSensitive, matches }; - _searchState.emplace(searchState); - if (_searchBox) - { - _searchBox->SetStatus(searchState.Status()); + const SearchState searchState{ text, caseSensitive }; + _searchState.emplace(searchState); + _SearchAsync(goForward, SearchAfterChangeDelay); } } @@ -3295,6 +3395,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation return _terminal->GetTaskbarProgress(); } + std::atomic SearchState::_searchIdGenerator{ 0 }; + // 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 @@ -3304,16 +3406,19 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - void SearchState::UpdateIndex(bool goForward) { - const int numMatches = ::base::saturated_cast(Matches.size()); - if (numMatches > 0) + if (Matches.has_value()) { - if (CurrentMatchIndex == -1) + const int numMatches = ::base::saturated_cast(Matches.value().size()); + if (numMatches > 0) { - CurrentMatchIndex = goForward ? 0 : numMatches - 1; - } - else - { - CurrentMatchIndex = (numMatches + CurrentMatchIndex + (goForward ? 1 : -1)) % numMatches; + if (CurrentMatchIndex == -1) + { + CurrentMatchIndex = goForward ? 0 : numMatches - 1; + } + else + { + CurrentMatchIndex = (numMatches + CurrentMatchIndex + (goForward ? 1 : -1)) % numMatches; + } } } } @@ -3327,9 +3432,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // (e.g., when the index is -1 or there are no matches) std::optional> SearchState::GetCurrentMatch() { - if (CurrentMatchIndex > -1 && CurrentMatchIndex < Matches.size()) + if (Matches.has_value() && CurrentMatchIndex > -1 && CurrentMatchIndex < Matches.value().size()) { - return til::at(Matches, CurrentMatchIndex); + return til::at(Matches.value(), CurrentMatchIndex); } else { @@ -3349,17 +3454,23 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - status message winrt::hstring SearchState::Status() const { - if (Matches.empty()) + if (!Matches.has_value()) + { + return RS_(L"TermControl_Searching"); + } + + const auto& searchResults = Matches.value(); + if (searchResults.empty()) { return RS_(L"TermControl_NoMatch"); } if (CurrentMatchIndex < 0) { - return fmt::format(L"?/{}", Matches.size()).data(); + return fmt::format(L"?/{}", searchResults.size()).data(); } - return fmt::format(L"{}/{}", CurrentMatchIndex + 1, Matches.size()).data(); + return fmt::format(L"{}/{}", CurrentMatchIndex + 1, searchResults.size()).data(); } // -------------------------------- WinRT Events --------------------------------- diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index f77f26f66bc..721b603cc6b 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -101,16 +101,19 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation struct SearchState { public: - SearchState(const winrt::hstring& text, const bool caseSensitive, std::vector> matches) : + static std::atomic _searchIdGenerator; + + SearchState(const winrt::hstring& text, const bool caseSensitive) : Text(text), CaseSensitive(caseSensitive), - Matches(matches) + SearchId(_searchIdGenerator.fetch_add(1)) { } const winrt::hstring Text; const bool CaseSensitive; - const std::vector> Matches; + const size_t SearchId; + std::optional>> Matches; int32_t CurrentMatchIndex{ -1 }; void UpdateIndex(bool goForward); @@ -283,7 +286,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker; std::optional _searchState; - void _SearchAll(const winrt::hstring& text, const bool caseSensitive); void _ApplyUISettings(); void _UpdateSystemParameterSettings() noexcept; @@ -350,9 +352,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation const unsigned int _NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime); double _GetAutoScrollSpeed(double cursorDistanceFromBorder) const; + winrt::Windows::Foundation::IAsyncOperation _SearchOne(Search& search); 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 _CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + fire_and_forget _SearchAsync(std::optional goForward, Windows::Foundation::TimeSpan const& delay); + void _SelectSearchResult(std::optional goForward); // TSFInputControl Handlers void _CompositionCompleted(winrt::hstring text); From 8e5efb71bb9ab83478f364c24f417306ead55404 Mon Sep 17 00:00:00 2001 From: khvitaly Date: Wed, 20 Jan 2021 19:01:06 +0200 Subject: [PATCH 07/61] Introduce computation of required statusbox width --- .../TerminalControl/SearchBoxControl.cpp | 104 +++++++++++++++++- .../TerminalControl/SearchBoxControl.h | 12 +- .../TerminalControl/SearchBoxControl.idl | 2 +- .../TerminalControl/SearchBoxControl.xaml | 4 +- src/cascadia/TerminalControl/TermControl.cpp | 51 +++------ src/cascadia/TerminalControl/TermControl.h | 1 - 6 files changed, 123 insertions(+), 51 deletions(-) diff --git a/src/cascadia/TerminalControl/SearchBoxControl.cpp b/src/cascadia/TerminalControl/SearchBoxControl.cpp index 79258093904..b0decb15164 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.cpp +++ b/src/cascadia/TerminalControl/SearchBoxControl.cpp @@ -4,6 +4,7 @@ #include "pch.h" #include "SearchBoxControl.h" #include "SearchBoxControl.g.cpp" +#include using namespace winrt; using namespace winrt::Windows::UI::Xaml; @@ -20,7 +21,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation this->KeyDown({ this, &SearchBoxControl::_KeyDownHandler }); this->RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) { // Once the control is visible again we trigger SearchChanged event. - // We do this since probably we have a value from the previous search, + // We do this since we probably have a value from the previous search, // and in such case logically the search changes from "nothing" to this value. // A good example for SearchChanged event consumer is Terminal Control. // Once the Search Box is open we want the Terminal Control @@ -36,6 +37,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _focusableElements.insert(CaseSensitivityButton()); _focusableElements.insert(GoForwardButton()); _focusableElements.insert(GoBackwardButton()); + + StatusBox().Width(_GetStatusMaxWidth()); } // Method Description: @@ -247,17 +250,106 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } // Method Description: - // - Allows to set the value of the search status + // - Formats a status message representing the search state: + // * "Searching" - if totalMatches is negative + // * "No results" - if totalMatches is 0 + // * "?/n" - if totalMatches=n matches and we didn't start the iteration over matches + // (usually we will get this after buffer update) + // * "m/n" - if we are currently at match m out of n. + // * "m/max+" - if n > max results to show + // * "?/max+" - if m > max results to show // Arguments: - // - status: string value to populate in the StatusBox + // - totalMatches - total number of matches (search results) + // - currentMatch - the index of the current match (0-based) + // Return Value: + // - status message + winrt::hstring SearchBoxControl::_FormatStatus(int32_t totalMatches, int32_t currentMatch) + { + if (totalMatches < 0) + { + return RS_(L"TermControl_Searching"); + } + + if (totalMatches == 0) + { + return RS_(L"TermControl_NoMatch"); + } + std::wstringstream ss; + + if (currentMatch < 0 || currentMatch > (MaximumTotalResultsToShowInStatus - 1)) + { + ss << CurrentIndexTooHighStatus; + } + else + { + ss << currentMatch + 1; + } + + ss << StatusDelimiter; + + if (totalMatches > MaximumTotalResultsToShowInStatus) + { + ss << TotalResultsTooHighStatus; + } + else + { + ss << totalMatches; + } + + return ss.str().data(); + } + + // Method Description: + // - Helper method to measure the width of the text block given the text and the font size + // Arguments: + // - text: the text to measure + // - fontSize: the size of the font to measure + // Return Value: + // - the size in pixels + double SearchBoxControl::_TextWidth(winrt::hstring text, double fontSize) + { + auto t = winrt::Windows::UI::Xaml::Controls::TextBlock(); + t.FontSize(fontSize); + t.Text(text); + t.Measure({ FLT_MAX, FLT_MAX }); + return t.ActualWidth(); + } + + // Method Description: + // - This method tries to predict the maximal size of the status box + // by measuring different possible statuses + // Return Value: + // - the size in pixels + double SearchBoxControl::_GetStatusMaxWidth() + { + const auto fontSize = StatusBox().FontSize(); + const auto maxLength = std::max({ _TextWidth(_FormatStatus(-1, -1), fontSize), + _TextWidth(_FormatStatus(0, -1), fontSize), + _TextWidth(_FormatStatus(MaximumTotalResultsToShowInStatus, MaximumTotalResultsToShowInStatus - 1), fontSize), + _TextWidth(_FormatStatus(MaximumTotalResultsToShowInStatus + 1, MaximumTotalResultsToShowInStatus - 1), fontSize), + _TextWidth(_FormatStatus(MaximumTotalResultsToShowInStatus + 1, MaximumTotalResultsToShowInStatus), fontSize) }); + + return maxLength; + } + + // Method Description: + // - Formats and sets the status message in the status box. + // Increases the size of the box if required. + // Arguments: + // - totalMatches - total number of matches (search results) + // - currentMatch - the index of the current match (0-based) // Return Value: // - - void SearchBoxControl::SetStatus(winrt::hstring const& status) + void SearchBoxControl::SetStatus(int32_t totalMatches, int32_t currentMatch) { - if (StatusBox()) + const auto status = _FormatStatus(totalMatches, currentMatch); + const auto requiredWidth = _TextWidth(status, StatusBox().FontSize()); + if (requiredWidth > StatusBox().Width()) { - StatusBox().Text(status); + StatusBox().Width(requiredWidth); } + + StatusBox().Text(status); } // Method Description: diff --git a/src/cascadia/TerminalControl/SearchBoxControl.h b/src/cascadia/TerminalControl/SearchBoxControl.h index 04c00087d3c..ab78166d63f 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.h +++ b/src/cascadia/TerminalControl/SearchBoxControl.h @@ -17,13 +17,17 @@ Author(s): #include "winrt/Windows.UI.Xaml.h" #include "winrt/Windows.UI.Xaml.Controls.h" #include "../../cascadia/inc/cppwinrt_utils.h" - #include "SearchBoxControl.g.h" namespace winrt::Microsoft::Terminal::TerminalControl::implementation { struct SearchBoxControl : SearchBoxControlT { + static constexpr int32_t MaximumTotalResultsToShowInStatus = 999; + static constexpr std::wstring_view TotalResultsTooHighStatus = L"999+"; + static constexpr std::wstring_view CurrentIndexTooHighStatus = L"?"; + static constexpr std::wstring_view StatusDelimiter = L"/"; + SearchBoxControl(); void TextBoxKeyDown(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e); @@ -31,7 +35,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void SetFocusOnTextbox(); void PopulateTextbox(winrt::hstring const& text); bool ContainsFocus(); - void SetStatus(winrt::hstring const& text); + void SetStatus(int32_t totalMatches, int32_t currentMatch); void SetNavigationEnabled(bool enabled); void GoBackwardClicked(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/); @@ -47,6 +51,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation private: std::unordered_set _focusableElements; + static winrt::hstring _FormatStatus(int32_t totalMatches, int32_t currentMatch); + static double _TextWidth(winrt::hstring text, double fontSize); + double _GetStatusMaxWidth(); + bool _GoForward(); bool _CaseSensitive(); void _KeyDownHandler(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e); diff --git a/src/cascadia/TerminalControl/SearchBoxControl.idl b/src/cascadia/TerminalControl/SearchBoxControl.idl index b0d228a99fb..0de5f825ee7 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.idl +++ b/src/cascadia/TerminalControl/SearchBoxControl.idl @@ -11,7 +11,7 @@ namespace Microsoft.Terminal.TerminalControl void SetFocusOnTextbox(); void PopulateTextbox(String text); Boolean ContainsFocus(); - void SetStatus(String status); + void SetStatus(Int32 totalMatches, Int32 currentMatch); void SetNavigationEnabled(Boolean enabled); event SearchHandler Search; diff --git a/src/cascadia/TerminalControl/SearchBoxControl.xaml b/src/cascadia/TerminalControl/SearchBoxControl.xaml index 9caeed46513..bb87ab6efb3 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.xaml +++ b/src/cascadia/TerminalControl/SearchBoxControl.xaml @@ -53,7 +53,7 @@ - + @@ -164,10 +164,8 @@ VerticalAlignment="Center" TextChanged="TextBoxTextChanged"> - _searchState.has_value()) { @@ -277,7 +277,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - fire_and_forget TermControl::_SearchAsync(std::optional goForward, Windows::Foundation::TimeSpan const& delay) { - // Run only if the live search state was initialized + // Run only if the search state was initialized if (_closing || !_searchState.has_value()) { return; @@ -306,7 +306,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // Let's mark the start of searching if (_searchBox) { - _searchBox->SetStatus(_searchState.value().Status()); + _searchBox->SetStatus(-1, -1); _searchBox->SetNavigationEnabled(false); } @@ -317,6 +317,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation Search::Sensitivity::CaseSensitive : Search::Sensitivity::CaseInsensitive; + // 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(*GetUiaData(), _searchState.value().Text.c_str(), Search::Direction::Forward, sensitivity); while (co_await _SearchOne(search)) { @@ -357,11 +359,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation { if (_searchState.has_value() && _searchState.value().Matches.has_value()) { + auto& state = _searchState.value(); + auto& matches = state.Matches.value(); + if (goForward.has_value()) { - _searchState.value().UpdateIndex(goForward.value()); + state.UpdateIndex(goForward.value()); - const auto currentMatch = _searchState.value().GetCurrentMatch(); + const auto currentMatch = state.GetCurrentMatch(); if (currentMatch.has_value()) { auto lock = _terminal->LockForWriting(); @@ -373,7 +378,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation if (_searchBox) { - _searchBox->SetStatus(_searchState.value().Status()); + _searchBox->SetStatus(gsl::narrow(matches.size()), state.CurrentMatchIndex); _searchBox->SetNavigationEnabled(!_searchState.value().Matches.value().empty()); } } @@ -404,9 +409,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } // Method Description: - // - If live search is enabled in settings, searches the buffer forward + // - The handler for the "search criteria changed" event. Clears selection and initiates a new search. // Arguments: // - text: the text to search + // - goForward: indicates whether the search should be performed forward (if set to true) or backward // - caseSensitive: boolean that represents if the current search is case sensitive // Return Value: // - @@ -3481,37 +3487,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } } - // Method Description: - // - Builds a status message representing the search state: - // * "No results" - if matches are empty - // * "?/n" - if there are n matches and we didn't start the iteration over matches - // (usually we will get this after buffer update) - // * "m/n" - if we are currently at match m out of n. - // Arguments: - // - - // Return Value: - // - status message - winrt::hstring SearchState::Status() const - { - if (!Matches.has_value()) - { - return RS_(L"TermControl_Searching"); - } - - const auto& searchResults = Matches.value(); - if (searchResults.empty()) - { - return RS_(L"TermControl_NoMatch"); - } - - if (CurrentMatchIndex < 0) - { - return fmt::format(L"?/{}", searchResults.size()).data(); - } - - return fmt::format(L"{}/{}", CurrentMatchIndex + 1, searchResults.size()).data(); - } - // -------------------------------- WinRT Events --------------------------------- // Winrt events need a method for adding a callback to the event and removing the callback. // These macros will define them both for you. diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 554cc08032e..3c81d64ccee 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -118,7 +118,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void UpdateIndex(bool goForward); std::optional> GetCurrentMatch(); - winrt::hstring Status() const; }; struct TermControl : TermControlT From 160716d47b720c7ebc2aca935d5a822c9c227a9b Mon Sep 17 00:00:00 2001 From: khvitaly Date: Wed, 20 Jan 2021 23:43:35 +0200 Subject: [PATCH 08/61] Reduce the value :) --- .../TerminalControl/SearchBoxControl.cpp | 2 +- src/cascadia/TerminalControl/TermControl.cpp | 35 +++++++++---------- src/cascadia/TerminalControl/TermControl.h | 6 ++-- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/cascadia/TerminalControl/SearchBoxControl.cpp b/src/cascadia/TerminalControl/SearchBoxControl.cpp index b0decb15164..432da162c5e 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.cpp +++ b/src/cascadia/TerminalControl/SearchBoxControl.cpp @@ -308,7 +308,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - the size in pixels double SearchBoxControl::_TextWidth(winrt::hstring text, double fontSize) { - auto t = winrt::Windows::UI::Xaml::Controls::TextBlock(); + winrt::Windows::UI::Xaml::Controls::TextBlock t; t.FontSize(fontSize); t.Text(text); t.Measure({ FLT_MAX, FLT_MAX }); diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index a5e6f7e2442..1cd6b99971c 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -202,7 +202,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // We avoid navigation to the first result to prevent auto-scrolling. if (control->_searchState.has_value()) { - const SearchState searchState{ control->_searchState.value().Text, control->_searchState.value().CaseSensitive }; + const SearchState searchState{ control->_searchState->Text, control->_searchState->Sensitivity }; control->_searchState.emplace(searchState); control->_SearchAsync(std::nullopt, SearchAfterOutputDelay); } @@ -283,11 +283,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation return; } - const auto originalSearchId = _searchState.value().SearchId; + const auto originalSearchId = _searchState->SearchId; auto weakThis{ this->get_weak() }; // If no matches were computed it means we need to perform the search - if (!_searchState.value().Matches.has_value()) + if (!_searchState->Matches.has_value()) { // Before we search, let's wait a bit: // probably the search criteria or the data are still modified. @@ -298,7 +298,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation if (auto control{ weakThis.get() }) { // If search box was collapsed or the new one search was triggered - let's cancel this one - if (!_searchState.has_value() || _searchState.value().SearchId != originalSearchId) + if (!_searchState.has_value() || _searchState->SearchId != originalSearchId) { co_return; } @@ -311,19 +311,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } std::vector> matches; - if (!_searchState.value().Text.empty()) + if (!_searchState->Text.empty()) { - const Search::Sensitivity sensitivity = _searchState.value().CaseSensitive ? - Search::Sensitivity::CaseSensitive : - Search::Sensitivity::CaseInsensitive; - // 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(*GetUiaData(), _searchState.value().Text.c_str(), Search::Direction::Forward, sensitivity); + Search search(*GetUiaData(), _searchState->Text.c_str(), Search::Direction::Forward, _searchState->Sensitivity); while (co_await _SearchOne(search)) { // if search box was collapsed or the new one search was triggered - let's cancel this one - if (!_searchState.has_value() || _searchState.value().SearchId != originalSearchId) + if (!_searchState.has_value() || _searchState->SearchId != originalSearchId) { co_return; } @@ -332,12 +328,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } // if search box was collapsed or the new one search was triggered - let's cancel this one - if (!_searchState.has_value() || _searchState.value().SearchId != originalSearchId) + if (!_searchState.has_value() || _searchState->SearchId != originalSearchId) { co_return; } } - _searchState.value().Matches.emplace(matches); + _searchState->Matches.emplace(std::move(matches)); } } @@ -357,7 +353,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - void TermControl::_SelectSearchResult(std::optional goForward) { - if (_searchState.has_value() && _searchState.value().Matches.has_value()) + if (_searchState.has_value() && _searchState->Matches.has_value()) { auto& state = _searchState.value(); auto& matches = state.Matches.value(); @@ -371,7 +367,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation { auto lock = _terminal->LockForWriting(); _terminal->SetBlockSelection(false); - _terminal->SelectNewRegion(currentMatch.value().first, currentMatch.value().second); + _terminal->SelectNewRegion(currentMatch->first, currentMatch->second); _renderer->TriggerSelection(); } } @@ -379,7 +375,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation if (_searchBox) { _searchBox->SetStatus(gsl::narrow(matches.size()), state.CurrentMatchIndex); - _searchBox->SetNavigationEnabled(!_searchState.value().Matches.value().empty()); + _searchBox->SetNavigationEnabled(!_searchState->Matches->empty()); } } } @@ -424,7 +420,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _terminal->ClearSelection(); _renderer->TriggerSelection(); - const SearchState searchState{ text, caseSensitive }; + const auto sensitivity = caseSensitive ? Search::Sensitivity::CaseSensitive : Search::Sensitivity::CaseInsensitive; + const SearchState searchState{ text, sensitivity }; _searchState.emplace(searchState); _SearchAsync(goForward, SearchAfterChangeDelay); } @@ -3453,7 +3450,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation { if (Matches.has_value()) { - const int numMatches = ::base::saturated_cast(Matches.value().size()); + const int numMatches = ::base::saturated_cast(Matches->size()); if (numMatches > 0) { if (CurrentMatchIndex == -1) @@ -3477,7 +3474,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // (e.g., when the index is -1 or there are no matches) std::optional> SearchState::GetCurrentMatch() { - if (Matches.has_value() && CurrentMatchIndex > -1 && CurrentMatchIndex < Matches.value().size()) + if (Matches.has_value() && CurrentMatchIndex > -1 && CurrentMatchIndex < Matches->size()) { return til::at(Matches.value(), CurrentMatchIndex); } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 3c81d64ccee..3f83ddbc2ce 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -103,15 +103,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation public: static std::atomic _searchIdGenerator; - SearchState(const winrt::hstring& text, const bool caseSensitive) : + SearchState(const winrt::hstring& text, const Search::Sensitivity sensitivity) : Text(text), - CaseSensitive(caseSensitive), + Sensitivity(sensitivity), SearchId(_searchIdGenerator.fetch_add(1)) { } const winrt::hstring Text; - const bool CaseSensitive; + const Search::Sensitivity Sensitivity; const size_t SearchId; std::optional>> Matches; int32_t CurrentMatchIndex{ -1 }; From 85f1bb7cdeb54f1d16c4e1727064d2d08f6c801a Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 20 Sep 2022 06:53:51 -0500 Subject: [PATCH 09/61] move the code around. It works again, bug... the buttons don't work at all?? --- src/cascadia/TerminalControl/ControlCore.cpp | 282 ++++++++++++- src/cascadia/TerminalControl/ControlCore.h | 11 +- src/cascadia/TerminalControl/ControlCore.idl | 4 + src/cascadia/TerminalControl/EventArgs.h | 2 + src/cascadia/TerminalControl/EventArgs.idl | 2 + .../TerminalControl/SearchBoxControl.h | 7 - src/cascadia/TerminalControl/TermControl.cpp | 395 ++++++++---------- src/cascadia/TerminalControl/TermControl.h | 6 +- 8 files changed, 446 insertions(+), 263 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 7fa53304299..e24fb9b7936 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -36,8 +36,19 @@ 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 minimum delay between triggering search upon output to terminal +constexpr const auto SearchUponOutputInterval = std::chrono::milliseconds(500); + +// The delay before performing the search after output to terminal +constexpr const auto SearchAfterOutputDelay = std::chrono::milliseconds(800); + +// 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 SearchState::_searchIdGenerator{ 0 }; + static winrt::Microsoft::Terminal::Core::OptionalColor OptionalFromColor(const til::color& c) { Core::OptionalColor result; @@ -230,6 +241,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation } }); + // TODO! is this in the right place + _updateSearchStatus = std::make_shared>( + _dispatcher, + SearchUponOutputInterval, + [weakThis = get_weak()]() { + if (auto core{ weakThis.get() }) + { + // If in the middle of the search, recompute the matches. + // We avoid navigation to the first result to prevent auto-scrolling. + if (core->_searchState.has_value()) + { + const SearchState searchState{ core->_searchState->Text, core->_searchState->Sensitivity }; + core->_searchState.emplace(searchState); + core->_SearchAsync(std::nullopt, SearchAfterOutputDelay); + } + } + }); + UpdateSettings(settings, unfocusedAppearance); } @@ -1484,42 +1513,251 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - void ControlCore::Search(const winrt::hstring& text, const bool goForward, - const bool caseSensitive) + const bool /*caseSensitive*/) { if (text.size() == 0) { return; } - const auto direction = goForward ? - Search::Direction::Forward : - Search::Direction::Backward; + // const auto direction = goForward ? + // Search::Direction::Forward : + // Search::Direction::Backward; - const auto sensitivity = caseSensitive ? - Search::Sensitivity::CaseSensitive : - Search::Sensitivity::CaseInsensitive; + // const auto sensitivity = caseSensitive ? + // Search::Sensitivity::CaseSensitive : + // Search::Sensitivity::CaseInsensitive; - ::Search search(*GetUiaData(), text.c_str(), direction, sensitivity); - auto lock = _terminal->LockForWriting(); - const auto foundMatch{ search.FindNext() }; - if (foundMatch) + // ::Search search(*GetUiaData(), text.c_str(), direction, sensitivity); + // auto lock = _terminal->LockForWriting(); + // const auto foundMatch{ search.FindNext() }; + // if (foundMatch) + // { + // _terminal->SetBlockSelection(false); + // search.Select(); + + // // 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(true)); + // } + + // // 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(foundMatch); + // _FoundMatchHandlers(*this, *foundResults); + + _SelectSearchResult(goForward); + } + + // 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: + // - + fire_and_forget ControlCore::_SearchAsync(std::optional goForward, + Windows::Foundation::TimeSpan const& delay) + { + // Run only if the search state was initialized + if (_closing || !_searchState.has_value()) { - _terminal->SetBlockSelection(false); - search.Select(); + co_return; + } - // 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(true)); + const auto originalSearchId = _searchState->SearchId; + auto weakThis{ this->get_weak() }; + + // If no matches were computed it means we need to perform the search + if (!_searchState->Matches.has_value()) + { + // Before we search, let's wait a bit: + // probably the search criteria or the data are still modified. + co_await winrt::resume_after(delay); + + // Switch back to Dispatcher so we can set the Searching status + co_await winrt::resume_foreground(_dispatcher); + if (auto control{ weakThis.get() }) + { + // 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; + } + + // // Let's mark the start of searching + // if (_searchBox) + // { + // _searchBox->SetStatus(-1, -1); + // _searchBox->SetNavigationEnabled(false); + // } + + std::vector> matches; + if (!_searchState->Text.empty()) + { + // 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(*GetUiaData(), _searchState->Text.c_str(), Search::Direction::Forward, _searchState->Sensitivity); + while (co_await _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)); + } } - // 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(foundMatch); - _FoundMatchHandlers(*this, *foundResults); + if (auto control{ weakThis.get() }) + { + _SelectSearchResult(goForward); + } } + void ControlCore::SearchChanged(const winrt::hstring& text, + const bool goForward, + const bool caseSensitive) + { + // Clear the selection reset the anchor + _terminal->ClearSelection(); + _renderer->TriggerSelection(); + + const auto sensitivity = caseSensitive ? Search::Sensitivity::CaseSensitive : Search::Sensitivity::CaseInsensitive; + const SearchState searchState{ text, sensitivity }; + _searchState.emplace(searchState); + _SearchAsync(goForward, SearchAfterChangeDelay); + } + + // 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: + // - + void ControlCore::_SelectSearchResult(std::optional goForward) + { + if (_searchState.has_value() && _searchState->Matches.has_value()) + { + auto& state = _searchState.value(); + auto& matches = state.Matches.value(); + + 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(); + } + } + + // if (_searchBox) + // { + // _searchBox->SetStatus(gsl::narrow(matches.size()), state.CurrentMatchIndex); + // _searchBox->SetNavigationEnabled(!_searchState->Matches->empty()); + // } + + auto foundResults = winrt::make_self(true); + foundResults->TotalMatches(gsl::narrow(matches.size())); + foundResults->CurrentMatch(state.CurrentMatchIndex); + _FoundMatchHandlers(*this, *foundResults); + } + } + + // Method Description: + // - Search for a single value in a background + // Arguments: + // - search: search object to use to find the next match + // Return Value: + // - + winrt::Windows::Foundation::IAsyncOperation ControlCore::_SearchOne(::Search& search) + { + bool found{ false }; + auto weakThis{ get_weak() }; + + co_await winrt::resume_background(); + if (auto control{ weakThis.get() }) + { + // 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(); + found = search.FindNext(); + } + + co_await winrt::resume_foreground(_dispatcher); + co_return found; + } + + // 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: + // - + void SearchState::UpdateIndex(bool goForward) + { + if (Matches.has_value()) + { + const int numMatches = ::base::saturated_cast(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: + // - + // 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> SearchState::GetCurrentMatch() + { + if (Matches.has_value() && CurrentMatchIndex > -1 && CurrentMatchIndex < Matches->size()) + { + return til::at(Matches.value(), CurrentMatchIndex); + } + else + { + return std::nullopt; + } + } + void ControlCore::ExitSearch() + { + _searchState.reset(); + } void ControlCore::Close() { if (!_IsClosing()) diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index a21c28f2ab1..ba20fd6086c 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -65,11 +65,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation const winrt::hstring Text; const Search::Sensitivity Sensitivity; const size_t SearchId; - std::optional>> Matches; + std::optional>> Matches; int32_t CurrentMatchIndex{ -1 }; void UpdateIndex(bool goForward); - std::optional> GetCurrentMatch(); + std::optional> GetCurrentMatch(); }; struct ControlCore : ControlCoreT @@ -203,6 +203,8 @@ 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(); void LeftClickOnTerminal(const til::point terminalPosition, const int numberOfClicks, @@ -305,6 +307,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation std::shared_ptr> _tsfTryRedrawCanvas; std::unique_ptr> _updatePatternLocations; std::shared_ptr> _updateScrollBar; + std::shared_ptr> _updateSearchStatus; bool _setFontSizeUnderLock(float fontSize); void _updateFont(const bool initialUpdate = false); @@ -350,6 +353,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool _isBackgroundTransparent(); void _focusChanged(bool focused); + fire_and_forget _SearchAsync(std::optional goForward, Windows::Foundation::TimeSpan const& delay); + void _SelectSearchResult(std::optional goForward); + winrt::Windows::Foundation::IAsyncOperation _SearchOne(::Search& search); + inline bool _IsClosing() const noexcept { #ifndef NDEBUG diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index a65384e2bc2..07bf211c4af 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -115,7 +115,11 @@ 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(); + Microsoft.Terminal.Core.Color BackgroundColor { get; }; Boolean HasSelection { get; }; diff --git a/src/cascadia/TerminalControl/EventArgs.h b/src/cascadia/TerminalControl/EventArgs.h index a542e391f63..e332567e9bf 100644 --- a/src/cascadia/TerminalControl/EventArgs.h +++ b/src/cascadia/TerminalControl/EventArgs.h @@ -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 diff --git a/src/cascadia/TerminalControl/EventArgs.idl b/src/cascadia/TerminalControl/EventArgs.idl index 941384c5b05..69d592c8273 100644 --- a/src/cascadia/TerminalControl/EventArgs.idl +++ b/src/cascadia/TerminalControl/EventArgs.idl @@ -72,6 +72,8 @@ namespace Microsoft.Terminal.Control runtimeclass FoundResultsArgs { Boolean FoundMatch { get; }; + Int32 TotalMatches { get; }; + Int32 CurrentMatch { get; }; } runtimeclass ShowWindowArgs diff --git a/src/cascadia/TerminalControl/SearchBoxControl.h b/src/cascadia/TerminalControl/SearchBoxControl.h index 96ea94cb32c..c7394e89565 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.h +++ b/src/cascadia/TerminalControl/SearchBoxControl.h @@ -14,13 +14,6 @@ Author(s): --*/ #pragma once -#include "winrt/Windows.UI.Xaml.h" -#include "winrt/Windows.UI.Xaml.Controls.h" -<<<<<<< HEAD - -======= -#include "../../cascadia/inc/cppwinrt_utils.h" ->>>>>>> pull/8588 #include "SearchBoxControl.g.h" namespace winrt::Microsoft::Terminal::Control::implementation diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index f1c7de75824..8dcb7d2e560 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -41,23 +41,12 @@ constexpr const auto UpdatePatternLocationsInterval = std::chrono::milliseconds( // The minimum delay between emitting warning bells constexpr const auto TerminalWarningBellInterval = std::chrono::milliseconds(1000); -// The minimum delay between triggering search upon output to terminal -constexpr const auto SearchUponOutputInterval = std::chrono::milliseconds(500); - -// The delay before performing the search after output to terminal -constexpr const auto SearchAfterOutputDelay = std::chrono::milliseconds(800); - -// The delay before performing the search after change of search criteria -constexpr const auto SearchAfterChangeDelay = std::chrono::milliseconds(200); - DEFINE_ENUM_FLAG_OPERATORS(winrt::Microsoft::Terminal::Control::CopyFormat); DEFINE_ENUM_FLAG_OPERATORS(winrt::Microsoft::Terminal::Control::MouseButtonState); namespace winrt::Microsoft::Terminal::Control::implementation { - std::atomic SearchState::_searchIdGenerator{ 0 }; - TermControl::TermControl(IControlSettings settings, Control::IControlAppearance unfocusedAppearance, TerminalConnection::ITerminalConnection connection) : @@ -141,24 +130,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation } }); - // TODO! is this in the right place - _updateSearchStatus = std::make_shared>( - [weakThis = get_weak()]() { - if (auto control{ weakThis.get() }) - { - // If in the middle of the search, recompute the matches. - // We avoid navigation to the first result to prevent auto-scrolling. - if (control->_searchState.has_value()) - { - const SearchState searchState{ control->_searchState->Text, control->_searchState->Sensitivity }; - control->_searchState.emplace(searchState); - control->_SearchAsync(std::nullopt, SearchAfterOutputDelay); - } - } - }, - SearchUponOutputInterval, - Dispatcher()); - static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast(1.0 / 30.0 * 1000000)); _autoScrollTimer.Interval(AutoScrollUpdateInterval); _autoScrollTimer.Tick({ this, &TermControl::_UpdateAutoScroll }); @@ -289,154 +260,161 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - caseSensitive: not used // Return Value: // - - void TermControl::_Search(const winrt::hstring& /*text*/, + void TermControl::_Search(const winrt::hstring& text, const bool goForward, - const bool /*caseSensitive*/) + const bool caseSensitive) { // TODO! Most of this probably needs to be in core now. - // _core.Search(text, goForward, caseSensitive); - _SelectSearchResult(goForward); - } - - // 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: - // - - fire_and_forget TermControl::_SearchAsync(std::optional goForward, Windows::Foundation::TimeSpan const& delay) - { - // Run only if the search state was initialized - if (_closing || !_searchState.has_value()) - { - return; - } - - const auto originalSearchId = _searchState->SearchId; - auto weakThis{ this->get_weak() }; - - // If no matches were computed it means we need to perform the search - if (!_searchState->Matches.has_value()) - { - // Before we search, let's wait a bit: - // probably the search criteria or the data are still modified. - co_await winrt::resume_after(delay); - - // Switch back to Dispatcher so we can set the Searching status - co_await winrt::resume_foreground(Dispatcher()); - if (auto control{ weakThis.get() }) - { - // 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; - } - - // Let's mark the start of searching - if (_searchBox) - { - _searchBox->SetStatus(-1, -1); - _searchBox->SetNavigationEnabled(false); - } - - std::vector> matches; - if (!_searchState->Text.empty()) - { - // 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(*GetUiaData(), _searchState->Text.c_str(), Search::Direction::Forward, _searchState->Sensitivity); - while (co_await _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)); - } - } - - if (auto control{ weakThis.get() }) - { - _SelectSearchResult(goForward); - } - } - - // 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: - // - - void TermControl::_SelectSearchResult(std::optional goForward) - { - if (_searchState.has_value() && _searchState->Matches.has_value()) - { - auto& state = _searchState.value(); - auto& matches = state.Matches.value(); - - 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(); - } - } - - if (_searchBox) - { - _searchBox->SetStatus(gsl::narrow(matches.size()), state.CurrentMatchIndex); - _searchBox->SetNavigationEnabled(!_searchState->Matches->empty()); - } - } - } - - // Method Description: - // - Search for a single value in a background - // Arguments: - // - search: search object to use to find the next match - // Return Value: - // - - winrt::Windows::Foundation::IAsyncOperation TermControl::_SearchOne(Search& search) - { - bool found{ false }; - auto weakThis{ this->get_weak() }; - - co_await winrt::resume_background(); - if (auto control{ weakThis.get() }) - { - // 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(); - found = search.FindNext(); - } - - co_await winrt::resume_foreground(Dispatcher()); - co_return found; - } + _core.Search(text, goForward, caseSensitive); + // _SelectSearchResult(goForward); + + // Let's mark the start of searching + if (_searchBox) + { + _searchBox->SetStatus(-1, -1); + _searchBox->SetNavigationEnabled(false); + } + } + + // // 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: + // // - + // fire_and_forget TermControl::_SearchAsync(std::optional goForward, Windows::Foundation::TimeSpan const& delay) + // { + // // Run only if the search state was initialized + // if (_closing || !_searchState.has_value()) + // { + // return; + // } + + // const auto originalSearchId = _searchState->SearchId; + // auto weakThis{ this->get_weak() }; + + // // If no matches were computed it means we need to perform the search + // if (!_searchState->Matches.has_value()) + // { + // // Before we search, let's wait a bit: + // // probably the search criteria or the data are still modified. + // co_await winrt::resume_after(delay); + + // // Switch back to Dispatcher so we can set the Searching status + // co_await winrt::resume_foreground(Dispatcher()); + // if (auto control{ weakThis.get() }) + // { + // // 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; + // } + + // // Let's mark the start of searching + // if (_searchBox) + // { + // _searchBox->SetStatus(-1, -1); + // _searchBox->SetNavigationEnabled(false); + // } + + // std::vector> matches; + // if (!_searchState->Text.empty()) + // { + // // 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(*GetUiaData(), _searchState->Text.c_str(), Search::Direction::Forward, _searchState->Sensitivity); + // while (co_await _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)); + // } + // } + + // if (auto control{ weakThis.get() }) + // { + // _SelectSearchResult(goForward); + // } + // } + + // // 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: + // // - + // void TermControl::_SelectSearchResult(std::optional goForward) + // { + // if (_searchState.has_value() && _searchState->Matches.has_value()) + // { + // auto& state = _searchState.value(); + // auto& matches = state.Matches.value(); + + // 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(); + // } + // } + + // if (_searchBox) + // { + // _searchBox->SetStatus(gsl::narrow(matches.size()), state.CurrentMatchIndex); + // _searchBox->SetNavigationEnabled(!_searchState->Matches->empty()); + // } + // } + // } + + // // Method Description: + // // - Search for a single value in a background + // // Arguments: + // // - search: search object to use to find the next match + // // Return Value: + // // - + // winrt::Windows::Foundation::IAsyncOperation TermControl::_SearchOne(Search& search) + // { + // bool found{ false }; + // auto weakThis{ this->get_weak() }; + + // co_await winrt::resume_background(); + // if (auto control{ weakThis.get() }) + // { + // // 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(); + // found = search.FindNext(); + // } + + // co_await winrt::resume_foreground(Dispatcher()); + // co_return found; + // } // Method Description: // - The handler for the "search criteria changed" event. Clears selection and initiates a new search. @@ -452,59 +430,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation { if (_searchBox && _searchBox->Visibility() == Visibility::Visible) { - // Clear the selection reset the anchor - _terminal->ClearSelection(); - _renderer->TriggerSelection(); - - const auto sensitivity = caseSensitive ? Search::Sensitivity::CaseSensitive : Search::Sensitivity::CaseInsensitive; - const SearchState searchState{ text, sensitivity }; - _searchState.emplace(searchState); - _SearchAsync(goForward, SearchAfterChangeDelay); - } - } - - // 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: - // - - void SearchState::UpdateIndex(bool goForward) - { - if (Matches.has_value()) - { - const int numMatches = ::base::saturated_cast(Matches->size()); - if (numMatches > 0) - { - if (CurrentMatchIndex == -1) - { - CurrentMatchIndex = goForward ? 0 : numMatches - 1; - } - else - { - CurrentMatchIndex = (numMatches + CurrentMatchIndex + (goForward ? 1 : -1)) % numMatches; - } - } - } - } + _core.SearchChanged(text, goForward, caseSensitive); + // // Clear the selection reset the anchor + // _terminal->ClearSelection(); + // _renderer->TriggerSelection(); - // Method Description: - // - Retrieves the current match - // Arguments: - // - - // 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> SearchState::GetCurrentMatch() - { - if (Matches.has_value() && CurrentMatchIndex > -1 && CurrentMatchIndex < Matches->size()) - { - return til::at(Matches.value(), CurrentMatchIndex); - } - else - { - return std::nullopt; + // const auto sensitivity = caseSensitive ? Search::Sensitivity::CaseSensitive : Search::Sensitivity::CaseInsensitive; + // const SearchState searchState{ text, sensitivity }; + // _searchState.emplace(searchState); + // _SearchAsync(goForward, SearchAfterChangeDelay); } } @@ -520,7 +454,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const RoutedEventArgs& /*args*/) { _searchBox->Visibility(Visibility::Collapsed); - _searchState.reset(); + _core.ExitSearch(); // Set focus back to terminal control this->Focus(FocusState::Programmatic); @@ -3312,7 +3246,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - args: contains information about the results that were or were not found. // Return Value: // - - void TermControl::_coreFoundMatch(const IInspectable& /*sender*/, const Control::FoundResultsArgs& args) + winrt::fire_and_forget TermControl::_coreFoundMatch(const IInspectable& /*sender*/, Control::FoundResultsArgs args) { if (auto automationPeer{ Automation::Peers::FrameworkElementAutomationPeer::FromElement(*this) }) { @@ -3322,6 +3256,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation args.FoundMatch() ? RS_(L"SearchBox_MatchesAvailable") : RS_(L"SearchBox_NoMatches"), // what to announce if results were found L"SearchBoxResultAnnouncement" /* unique name for this group of notifications */); } + + if (_searchBox) + { + co_await wil::resume_foreground(Dispatcher()); + _searchBox->SetStatus(args.TotalMatches(), args.CurrentMatch()); + _searchBox->SetNavigationEnabled(false); + } } void TermControl::OwningHwnd(uint64_t owner) diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index c3a3c78a99b..ce1e37ccf5c 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -183,8 +183,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation std::shared_ptr _playWarningBell; - std::shared_ptr> _updateSearchStatus; - struct ScrollBarUpdate { std::optional newValue; @@ -298,8 +296,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _SearchChanged(const winrt::hstring& text, const bool goForward, const bool caseSensitive); void _CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); - fire_and_forget _SearchAsync(std::optional goForward, Windows::Foundation::TimeSpan const& delay); - void _SelectSearchResult(std::optional goForward); // TSFInputControl Handlers void _CompositionCompleted(winrt::hstring text); @@ -315,7 +311,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation winrt::fire_and_forget _coreTransparencyChanged(IInspectable sender, Control::TransparencyChangedEventArgs args); void _coreRaisedNotice(const IInspectable& s, const Control::NoticeEventArgs& args); void _coreWarningBell(const IInspectable& sender, const IInspectable& args); - void _coreFoundMatch(const IInspectable& sender, const Control::FoundResultsArgs& args); + winrt::fire_and_forget _coreFoundMatch(const IInspectable& sender, Control::FoundResultsArgs args); til::point _toPosInDips(const Core::Point terminalCellPos); void _throttledUpdateScrollbar(const ScrollBarUpdate& update); From bc5bf1e130f3da37bf649a717802802bc1cc2c4c Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 20 Sep 2022 07:01:14 -0500 Subject: [PATCH 10/61] That was silly of me --- src/cascadia/TerminalControl/TermControl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 8dcb7d2e560..810fafaffb1 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -3261,7 +3261,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { co_await wil::resume_foreground(Dispatcher()); _searchBox->SetStatus(args.TotalMatches(), args.CurrentMatch()); - _searchBox->SetNavigationEnabled(false); + _searchBox->SetNavigationEnabled(true); } } From a3c1d8a254c233fe970348edf312f64408ae26c8 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 20 Sep 2022 07:03:16 -0500 Subject: [PATCH 11/61] for all your dead code removal needs, call 0118 999 881 999 119 725 3 --- src/cascadia/TerminalControl/ControlCore.cpp | 35 +---- src/cascadia/TerminalControl/TermControl.cpp | 150 ------------------- 2 files changed, 1 insertion(+), 184 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index e24fb9b7936..94a6a42981a 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1520,34 +1520,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation return; } - // const auto direction = goForward ? - // Search::Direction::Forward : - // Search::Direction::Backward; - - // const auto sensitivity = caseSensitive ? - // Search::Sensitivity::CaseSensitive : - // Search::Sensitivity::CaseInsensitive; - - // ::Search search(*GetUiaData(), text.c_str(), direction, sensitivity); - // auto lock = _terminal->LockForWriting(); - // const auto foundMatch{ search.FindNext() }; - // if (foundMatch) - // { - // _terminal->SetBlockSelection(false); - // search.Select(); - - // // 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(true)); - // } - - // // 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(foundMatch); - // _FoundMatchHandlers(*this, *foundResults); - _SelectSearchResult(goForward); } @@ -1673,12 +1645,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } - // if (_searchBox) - // { - // _searchBox->SetStatus(gsl::narrow(matches.size()), state.CurrentMatchIndex); - // _searchBox->SetNavigationEnabled(!_searchState->Matches->empty()); - // } - + // Let the control know we got one auto foundResults = winrt::make_self(true); foundResults->TotalMatches(gsl::narrow(matches.size())); foundResults->CurrentMatch(state.CurrentMatchIndex); diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 810fafaffb1..3df955ed633 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -264,10 +264,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const bool goForward, const bool caseSensitive) { - // TODO! Most of this probably needs to be in core now. - _core.Search(text, goForward, caseSensitive); - // _SelectSearchResult(goForward); // Let's mark the start of searching if (_searchBox) @@ -277,145 +274,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } - // // 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: - // // - - // fire_and_forget TermControl::_SearchAsync(std::optional goForward, Windows::Foundation::TimeSpan const& delay) - // { - // // Run only if the search state was initialized - // if (_closing || !_searchState.has_value()) - // { - // return; - // } - - // const auto originalSearchId = _searchState->SearchId; - // auto weakThis{ this->get_weak() }; - - // // If no matches were computed it means we need to perform the search - // if (!_searchState->Matches.has_value()) - // { - // // Before we search, let's wait a bit: - // // probably the search criteria or the data are still modified. - // co_await winrt::resume_after(delay); - - // // Switch back to Dispatcher so we can set the Searching status - // co_await winrt::resume_foreground(Dispatcher()); - // if (auto control{ weakThis.get() }) - // { - // // 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; - // } - - // // Let's mark the start of searching - // if (_searchBox) - // { - // _searchBox->SetStatus(-1, -1); - // _searchBox->SetNavigationEnabled(false); - // } - - // std::vector> matches; - // if (!_searchState->Text.empty()) - // { - // // 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(*GetUiaData(), _searchState->Text.c_str(), Search::Direction::Forward, _searchState->Sensitivity); - // while (co_await _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)); - // } - // } - - // if (auto control{ weakThis.get() }) - // { - // _SelectSearchResult(goForward); - // } - // } - - // // 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: - // // - - // void TermControl::_SelectSearchResult(std::optional goForward) - // { - // if (_searchState.has_value() && _searchState->Matches.has_value()) - // { - // auto& state = _searchState.value(); - // auto& matches = state.Matches.value(); - - // 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(); - // } - // } - - // if (_searchBox) - // { - // _searchBox->SetStatus(gsl::narrow(matches.size()), state.CurrentMatchIndex); - // _searchBox->SetNavigationEnabled(!_searchState->Matches->empty()); - // } - // } - // } - - // // Method Description: - // // - Search for a single value in a background - // // Arguments: - // // - search: search object to use to find the next match - // // Return Value: - // // - - // winrt::Windows::Foundation::IAsyncOperation TermControl::_SearchOne(Search& search) - // { - // bool found{ false }; - // auto weakThis{ this->get_weak() }; - - // co_await winrt::resume_background(); - // if (auto control{ weakThis.get() }) - // { - // // 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(); - // found = search.FindNext(); - // } - - // co_await winrt::resume_foreground(Dispatcher()); - // co_return found; - // } - // Method Description: // - The handler for the "search criteria changed" event. Clears selection and initiates a new search. // Arguments: @@ -431,14 +289,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (_searchBox && _searchBox->Visibility() == Visibility::Visible) { _core.SearchChanged(text, goForward, caseSensitive); - // // Clear the selection reset the anchor - // _terminal->ClearSelection(); - // _renderer->TriggerSelection(); - - // const auto sensitivity = caseSensitive ? Search::Sensitivity::CaseSensitive : Search::Sensitivity::CaseInsensitive; - // const SearchState searchState{ text, sensitivity }; - // _searchState.emplace(searchState); - // _SearchAsync(goForward, SearchAfterChangeDelay); } } From 8ae9e39d67d17fb897aa2bdb1ee3fb2076d4b3ed Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 20 Sep 2022 10:17:15 -0500 Subject: [PATCH 12/61] pips in the scrollbar --- src/cascadia/TerminalControl/ControlCore.cpp | 21 +++++++++++ src/cascadia/TerminalControl/ControlCore.h | 2 ++ src/cascadia/TerminalControl/ControlCore.idl | 2 ++ src/cascadia/TerminalControl/TermControl.cpp | 38 +++++++++++++++----- 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 94a6a42981a..81847b3c1ab 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1277,6 +1277,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); @@ -1650,6 +1655,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation foundResults->TotalMatches(gsl::narrow(matches.size())); foundResults->CurrentMatch(state.CurrentMatchIndex); _FoundMatchHandlers(*this, *foundResults); + + // _terminalScrollPositionChanged(BufferHeight(), ViewHeight(), BufferHeight()); } } @@ -1725,6 +1732,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation { _searchState.reset(); } + + Windows::Foundation::Collections::IVector ControlCore::MatchRows() + { + auto results = winrt::single_threaded_vector(); + if (_searchState.has_value() && (*_searchState).Matches.has_value()) + { + for (auto&& [start, end] : *((*_searchState).Matches)) + { + results.Append(start.Y); + } + } + return results; + } + void ControlCore::Close() { if (!_IsClosing()) diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index ba20fd6086c..a9bf838551a 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -107,6 +107,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); @@ -205,6 +206,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const bool caseSensitive); void SearchChanged(const winrt::hstring& text, const bool goForward, const bool caseSensitive); void ExitSearch(); + Windows::Foundation::Collections::IVector MatchRows(); void LeftClickOnTerminal(const til::point terminalPosition, const int numberOfClicks, diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index 07bf211c4af..93b889d3a39 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -119,7 +119,9 @@ namespace Microsoft.Terminal.Control void Search(String text, Boolean goForward, Boolean caseSensitive); void SearchChanged(String text, Boolean goForward, Boolean caseSensitive); void ExitSearch(); + IVector MatchRows { get; }; + Microsoft.Terminal.Core.Color ForegroundColor { get; }; Microsoft.Terminal.Core.Color BackgroundColor { get; }; Boolean HasSelection { get; }; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 3df955ed633..c9230533d8e 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -165,6 +165,21 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto fullHeight{ ScrollBarCanvas().ActualHeight() }; const auto totalBufferRows{ update.newMaximum + update.newViewportSize }; + auto drawPip = [&](const auto row, const auto rightAlign, const auto& brush) { + Windows::UI::Xaml::Shapes::Rectangle r; + r.Fill(brush); + r.Width(16.0f / 3.0f); // pip width - 1/3rd of the scrollbar width. + r.Height(2); + const auto fractionalHeight = row / totalBufferRows; + const auto relativePos = fractionalHeight * fullHeight; + ScrollBarCanvas().Children().Append(r); + Windows::UI::Xaml::Controls::Canvas::SetTop(r, relativePos); + if (rightAlign) + { + Windows::UI::Xaml::Controls::Canvas::SetLeft(r, 16.0f * .66f); + } + }; + for (const auto m : marks) { Windows::UI::Xaml::Shapes::Rectangle r; @@ -175,14 +190,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation // pre-evaluate that for us, and shove the real value into the // Color member, regardless if the mark has a literal value set. brush.Color(static_cast(m.Color.Color)); - r.Fill(brush); - r.Width(16.0f / 3.0f); // pip width - 1/3rd of the scrollbar width. - r.Height(2); - const auto markRow = m.Start.Y; - const auto fractionalHeight = markRow / totalBufferRows; - const auto relativePos = fractionalHeight * fullHeight; - ScrollBarCanvas().Children().Append(r); - Windows::UI::Xaml::Controls::Canvas::SetTop(r, relativePos); + drawPip(m.Start.Y, false, brush); + } + + auto searchMatches{ _core.MatchRows() }; + if (searchMatches.Size() > 0) + { + auto fgColor{ _core.ForegroundColor() }; + Media::SolidColorBrush searchMarkBrush{}; + searchMarkBrush.Color(static_cast(fgColor)); + for (const auto m : searchMatches) + { + drawPip(m, true, searchMarkBrush); + } } } } @@ -3107,6 +3127,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation L"SearchBoxResultAnnouncement" /* unique name for this group of notifications */); } + // ControlCore will manually trigger a _terminalScrollPositionChanged to update the scrollbat marks + if (_searchBox) { co_await wil::resume_foreground(Dispatcher()); From 3f39b5b10a402f9447781129bb460e2afea6d1ca Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 20 Sep 2022 10:36:28 -0500 Subject: [PATCH 13/61] KB focus works again --- src/cascadia/TerminalControl/ControlCore.cpp | 6 +++--- src/cascadia/TerminalControl/TermControl.cpp | 7 ------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 81847b3c1ab..998fab95216 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1548,7 +1548,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } const auto originalSearchId = _searchState->SearchId; - auto weakThis{ this->get_weak() }; + auto weakThis{ get_weak() }; // If no matches were computed it means we need to perform the search if (!_searchState->Matches.has_value()) @@ -1559,7 +1559,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Switch back to Dispatcher so we can set the Searching status co_await winrt::resume_foreground(_dispatcher); - if (auto control{ weakThis.get() }) + if (auto core{ weakThis.get() }) { // If search box was collapsed or the new one search was triggered - let's cancel this one if (!_searchState.has_value() || _searchState->SearchId != originalSearchId) @@ -1601,7 +1601,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } - if (auto control{ weakThis.get() }) + if (auto core{ weakThis.get() }) { _SelectSearchResult(goForward); } diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index c9230533d8e..74e399ab176 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -285,13 +285,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation const bool caseSensitive) { _core.Search(text, goForward, caseSensitive); - - // Let's mark the start of searching - if (_searchBox) - { - _searchBox->SetStatus(-1, -1); - _searchBox->SetNavigationEnabled(false); - } } // Method Description: From ec0ae7d34e36c21d7503a2c546d2f1b103861bcb Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 20 Sep 2022 11:15:55 -0500 Subject: [PATCH 14/61] throttledfunc, for good and for profit --- src/cascadia/TerminalControl/ControlCore.cpp | 105 ++++++++++--------- src/cascadia/TerminalControl/ControlCore.h | 12 ++- 2 files changed, 60 insertions(+), 57 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 998fab95216..ba544869c0d 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -242,20 +242,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation }); // TODO! is this in the right place - _updateSearchStatus = std::make_shared>( + _updateSearchStatus = std::make_shared>( _dispatcher, SearchUponOutputInterval, - [weakThis = get_weak()]() { + [weakThis = get_weak()](const auto& update) { if (auto core{ weakThis.get() }) { - // If in the middle of the search, recompute the matches. - // We avoid navigation to the first result to prevent auto-scrolling. - if (core->_searchState.has_value()) - { - const SearchState searchState{ core->_searchState->Text, core->_searchState->Sensitivity }; - core->_searchState.emplace(searchState); - core->_SearchAsync(std::nullopt, SearchAfterOutputDelay); - } + // // If in the middle of the search, recompute the matches. + // // We avoid navigation to the first result to prevent auto-scrolling. + // if (core->_searchState.has_value()) + // { + // const SearchState searchState{ core->_searchState->Text, core->_searchState->Sensitivity }; + core->_searchState.emplace(update); + core->_SearchAsync(std::nullopt, SearchAfterOutputDelay); + // } } }); @@ -1553,57 +1553,56 @@ namespace winrt::Microsoft::Terminal::Control::implementation // If no matches were computed it means we need to perform the search if (!_searchState->Matches.has_value()) { - // Before we search, let's wait a bit: - // probably the search criteria or the data are still modified. - co_await winrt::resume_after(delay); - - // Switch back to Dispatcher so we can set the Searching status - co_await winrt::resume_foreground(_dispatcher); - if (auto core{ weakThis.get() }) + // // Before we search, let's wait a bit: + // // probably the search criteria or the data are still modified. + // co_await winrt::resume_after(delay); + + // // Switch back to Dispatcher so we can set the Searching status + // co_await winrt::resume_foreground(_dispatcher); + // if (auto core{ weakThis.get() }) + // { + // // 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; + // } + + std::vector> matches; + if (!_searchState->Text.empty()) { - // 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; - } - - // // Let's mark the start of searching - // if (_searchBox) - // { - // _searchBox->SetStatus(-1, -1); - // _searchBox->SetNavigationEnabled(false); - // } - - std::vector> matches; - if (!_searchState->Text.empty()) + // 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(*GetUiaData(), + _searchState->Text.c_str(), + Search::Direction::Forward, + _searchState->Sensitivity); + + while (co_await _SearchOne(search)) { - // 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(*GetUiaData(), _searchState->Text.c_str(), Search::Direction::Forward, _searchState->Sensitivity); - while (co_await _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) + 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)); } + _searchState->Matches.emplace(std::move(matches)); + // } } if (auto core{ weakThis.get() }) { - _SelectSearchResult(goForward); + _SelectSearchResult(_searchState->goForward); } } @@ -1616,9 +1615,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation _renderer->TriggerSelection(); const auto sensitivity = caseSensitive ? Search::Sensitivity::CaseSensitive : Search::Sensitivity::CaseInsensitive; - const SearchState searchState{ text, sensitivity }; - _searchState.emplace(searchState); - _SearchAsync(goForward, SearchAfterChangeDelay); + const SearchState searchState{ text, sensitivity, goForward }; + // _searchState.emplace(searchState); + + _updateSearchStatus->Run(searchState); + // _SearchAsync(goForward, SearchAfterChangeDelay); } // Method Description: diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index a9bf838551a..9619313d707 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -55,16 +55,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation public: static std::atomic _searchIdGenerator; - SearchState(const winrt::hstring& text, const Search::Sensitivity sensitivity) : + SearchState(const winrt::hstring& text, const Search::Sensitivity sensitivity, const std::optional forward) : Text(text), Sensitivity(sensitivity), + goForward(forward), SearchId(_searchIdGenerator.fetch_add(1)) { } - const winrt::hstring Text; - const Search::Sensitivity Sensitivity; - const size_t SearchId; + winrt::hstring Text; + Search::Sensitivity Sensitivity; + std::optional goForward{ std::nullopt }; + size_t SearchId; std::optional>> Matches; int32_t CurrentMatchIndex{ -1 }; @@ -309,7 +311,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation std::shared_ptr> _tsfTryRedrawCanvas; std::unique_ptr> _updatePatternLocations; std::shared_ptr> _updateScrollBar; - std::shared_ptr> _updateSearchStatus; + std::shared_ptr> _updateSearchStatus; bool _setFontSizeUnderLock(float fontSize); void _updateFont(const bool initialUpdate = false); From f61c080a26c1f2c3ffa8304e5348f3759867bf3b Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 20 Sep 2022 11:28:47 -0500 Subject: [PATCH 15/61] cleanup --- src/cascadia/TerminalControl/ControlCore.cpp | 27 +------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index ba544869c0d..7f6d811f0d1 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -248,14 +248,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation [weakThis = get_weak()](const auto& update) { if (auto core{ weakThis.get() }) { - // // If in the middle of the search, recompute the matches. - // // We avoid navigation to the first result to prevent auto-scrolling. - // if (core->_searchState.has_value()) - // { - // const SearchState searchState{ core->_searchState->Text, core->_searchState->Sensitivity }; core->_searchState.emplace(update); core->_SearchAsync(std::nullopt, SearchAfterOutputDelay); - // } } }); @@ -1553,20 +1547,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation // If no matches were computed it means we need to perform the search if (!_searchState->Matches.has_value()) { - // // Before we search, let's wait a bit: - // // probably the search criteria or the data are still modified. - // co_await winrt::resume_after(delay); - - // // Switch back to Dispatcher so we can set the Searching status - // co_await winrt::resume_foreground(_dispatcher); - // if (auto core{ weakThis.get() }) - // { - // // 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; - // } - std::vector> matches; if (!_searchState->Text.empty()) { @@ -1597,7 +1577,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } _searchState->Matches.emplace(std::move(matches)); - // } } if (auto core{ weakThis.get() }) @@ -1615,11 +1594,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation _renderer->TriggerSelection(); const auto sensitivity = caseSensitive ? Search::Sensitivity::CaseSensitive : Search::Sensitivity::CaseInsensitive; - const SearchState searchState{ text, sensitivity, goForward }; - // _searchState.emplace(searchState); + const SearchState searchState{ text, sensitivity, goForward }; _updateSearchStatus->Run(searchState); - // _SearchAsync(goForward, SearchAfterChangeDelay); } // Method Description: @@ -1656,8 +1633,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation foundResults->TotalMatches(gsl::narrow(matches.size())); foundResults->CurrentMatch(state.CurrentMatchIndex); _FoundMatchHandlers(*this, *foundResults); - - // _terminalScrollPositionChanged(BufferHeight(), ViewHeight(), BufferHeight()); } } From 5cf5ccdd5118c0897dfbc5db54e2f790344528e7 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 20 Sep 2022 11:39:15 -0500 Subject: [PATCH 16/61] this seems to work well enough --- src/cascadia/TerminalControl/ControlCore.cpp | 16 ++++++++++++++-- src/cascadia/TerminalControl/ControlCore.h | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 7f6d811f0d1..0e25e50ff2b 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1512,14 +1512,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - void ControlCore::Search(const winrt::hstring& text, const bool goForward, - const bool /*caseSensitive*/) + const bool caseSensitive) { if (text.size() == 0) { return; } - _SelectSearchResult(goForward); + if (_bufferChangedSinceSearch) + { + const auto sensitivity = caseSensitive ? Search::Sensitivity::CaseSensitive : Search::Sensitivity::CaseInsensitive; + const SearchState searchState{ text, sensitivity, goForward }; + _updateSearchStatus->Run(searchState); + } + else + { + _SelectSearchResult(goForward); + } } // Method Description: @@ -1577,6 +1586,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } _searchState->Matches.emplace(std::move(matches)); + _bufferChangedSinceSearch = false; } if (auto core{ weakThis.get() }) @@ -1913,6 +1923,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Start the throttled update of where our hyperlinks are. (*_updatePatternLocations)(); + + _bufferChangedSinceSearch = true; } catch (...) { diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 9619313d707..70bbc7d1ab9 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -306,6 +306,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation uint64_t _owningHwnd{ 0 }; std::optional _searchState; + bool _bufferChangedSinceSearch{ true }; winrt::Windows::System::DispatcherQueue _dispatcher{ nullptr }; std::shared_ptr> _tsfTryRedrawCanvas; From d437420eed42fefcdf3ae690b2749a9f5846f9fc Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 20 Sep 2022 11:54:04 -0500 Subject: [PATCH 17/61] making the scrollbar more sensible --- src/cascadia/TerminalControl/ControlCore.cpp | 9 +++++++-- src/cascadia/TerminalControl/TermControl.cpp | 2 +- src/cascadia/TerminalCore/Terminal.cpp | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 0e25e50ff2b..a7140bdd312 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1722,9 +1722,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation Windows::Foundation::Collections::IVector ControlCore::MatchRows() { auto results = winrt::single_threaded_vector(); - if (_searchState.has_value() && (*_searchState).Matches.has_value()) + if (_bufferChangedSinceSearch) + { + return results; + } + + if (_searchState.has_value() && _searchState->Matches.has_value()) { - for (auto&& [start, end] : *((*_searchState).Matches)) + for (auto&& [start, end] : *(_searchState->Matches)) { results.Append(start.Y); } diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 74e399ab176..abd03eae6a4 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -194,7 +194,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } auto searchMatches{ _core.MatchRows() }; - if (searchMatches.Size() > 0) + if (searchMatches.Size() > 0 && _searchBox->Visibility() == Visibility::Visible) { auto fgColor{ _core.ForegroundColor() }; Media::SolidColorBrush searchMarkBrush{}; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 30e20985296..0dd180ed2ce 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -1268,7 +1268,7 @@ void Terminal::_AdjustCursorPosition(const til::point proposedPosition) } // If the viewport moved, then send a scrolling notification. - if (updatedViewport) + if (updatedViewport || rowsPushedOffTopOfBuffer != 0) { _NotifyScrollEvent(); } From e4e603d75729d34de9ff3764e28c7417841671f1 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 20 Sep 2022 12:11:55 -0500 Subject: [PATCH 18/61] rename slightly for convention's sake --- src/cascadia/TerminalControl/ControlCore.cpp | 40 ++++++++++---------- src/cascadia/TerminalControl/ControlCore.h | 19 +++++----- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index a7140bdd312..aec57f6459a 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1550,27 +1550,27 @@ namespace winrt::Microsoft::Terminal::Control::implementation co_return; } - const auto originalSearchId = _searchState->SearchId; + 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()) + if (!_searchState->matches.has_value()) { std::vector> matches; - if (!_searchState->Text.empty()) + if (!_searchState->text.empty()) { // 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(*GetUiaData(), - _searchState->Text.c_str(), + _searchState->text.c_str(), Search::Direction::Forward, - _searchState->Sensitivity); + _searchState->sensitivity); while (co_await _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) + _searchState->searchId != originalSearchId) { co_return; } @@ -1580,12 +1580,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation // if search box was collapsed or the new one search was triggered - let's cancel this one if (!_searchState.has_value() || - _searchState->SearchId != originalSearchId) + _searchState->searchId != originalSearchId) { co_return; } } - _searchState->Matches.emplace(std::move(matches)); + _searchState->matches.emplace(std::move(matches)); _bufferChangedSinceSearch = false; } @@ -1619,10 +1619,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - void ControlCore::_SelectSearchResult(std::optional goForward) { - if (_searchState.has_value() && _searchState->Matches.has_value()) + if (_searchState.has_value() && _searchState->matches.has_value()) { auto& state = _searchState.value(); - auto& matches = state.Matches.value(); + auto& matches = state.matches.value(); if (goForward.has_value()) { @@ -1641,7 +1641,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Let the control know we got one auto foundResults = winrt::make_self(true); foundResults->TotalMatches(gsl::narrow(matches.size())); - foundResults->CurrentMatch(state.CurrentMatchIndex); + foundResults->CurrentMatch(state.currentMatchIndex); _FoundMatchHandlers(*this, *foundResults); } } @@ -1679,18 +1679,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - void SearchState::UpdateIndex(bool goForward) { - if (Matches.has_value()) + if (matches.has_value()) { - const int numMatches = ::base::saturated_cast(Matches->size()); + const int numMatches = ::base::saturated_cast(matches->size()); if (numMatches > 0) { - if (CurrentMatchIndex == -1) + if (currentMatchIndex == -1) { - CurrentMatchIndex = goForward ? 0 : numMatches - 1; + currentMatchIndex = goForward ? 0 : numMatches - 1; } else { - CurrentMatchIndex = (numMatches + CurrentMatchIndex + (goForward ? 1 : -1)) % numMatches; + currentMatchIndex = (numMatches + currentMatchIndex + (goForward ? 1 : -1)) % numMatches; } } } @@ -1705,9 +1705,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation // (e.g., when the index is -1 or there are no matches) std::optional> SearchState::GetCurrentMatch() { - if (Matches.has_value() && CurrentMatchIndex > -1 && CurrentMatchIndex < Matches->size()) + if (matches.has_value() && currentMatchIndex > -1 && currentMatchIndex < matches->size()) { - return til::at(Matches.value(), CurrentMatchIndex); + return til::at(matches.value(), currentMatchIndex); } else { @@ -1727,9 +1727,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation return results; } - if (_searchState.has_value() && _searchState->Matches.has_value()) + if (_searchState.has_value() && _searchState->matches.has_value()) { - for (auto&& [start, end] : *(_searchState->Matches)) + for (auto&& [start, end] : *(_searchState->matches)) { results.Append(start.Y); } diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 70bbc7d1ab9..1fb52f03a92 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -56,22 +56,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation static std::atomic _searchIdGenerator; SearchState(const winrt::hstring& text, const Search::Sensitivity sensitivity, const std::optional forward) : - Text(text), - Sensitivity(sensitivity), + text(text), + sensitivity(sensitivity), goForward(forward), - SearchId(_searchIdGenerator.fetch_add(1)) + searchId(_searchIdGenerator.fetch_add(1)) { } - winrt::hstring Text; - Search::Sensitivity Sensitivity; - std::optional goForward{ std::nullopt }; - size_t SearchId; - std::optional>> Matches; - int32_t CurrentMatchIndex{ -1 }; + std::optional>> matches; void UpdateIndex(bool goForward); std::optional> GetCurrentMatch(); + + winrt::hstring text; + Search::Sensitivity sensitivity; + std::optional goForward{ std::nullopt }; + size_t searchId; + int32_t currentMatchIndex{ -1 }; }; struct ControlCore : ControlCoreT From 57913e426b4a6193bc0f445d765d292da9e0771d Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 20 Sep 2022 12:57:00 -0500 Subject: [PATCH 19/61] update the pips if the first hit is in the viewport --- src/cascadia/TerminalControl/ControlCore.cpp | 14 +++----------- src/cascadia/TerminalControl/ControlCore.h | 2 +- .../TerminalControl/SearchBoxControl.h | 2 +- src/cascadia/TerminalControl/TermControl.cpp | 18 ++++++++++++++++-- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index aec57f6459a..fc98d76bf08 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -36,12 +36,6 @@ 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 minimum delay between triggering search upon output to terminal -constexpr const auto SearchUponOutputInterval = std::chrono::milliseconds(500); - -// The delay before performing the search after output to terminal -constexpr const auto SearchAfterOutputDelay = std::chrono::milliseconds(800); - // The delay before performing the search after change of search criteria constexpr const auto SearchAfterChangeDelay = std::chrono::milliseconds(200); @@ -241,15 +235,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation } }); - // TODO! is this in the right place _updateSearchStatus = std::make_shared>( _dispatcher, - SearchUponOutputInterval, + SearchAfterChangeDelay, [weakThis = get_weak()](const auto& update) { if (auto core{ weakThis.get() }) { core->_searchState.emplace(update); - core->_SearchAsync(std::nullopt, SearchAfterOutputDelay); + core->_SearchAsync(std::nullopt); } }); @@ -1541,8 +1534,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // (grace time to allow next search to start) // Return Value: // - - fire_and_forget ControlCore::_SearchAsync(std::optional goForward, - Windows::Foundation::TimeSpan const& delay) + fire_and_forget ControlCore::_SearchAsync(std::optional goForward) { // Run only if the search state was initialized if (_closing || !_searchState.has_value()) diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 1fb52f03a92..2fb4c0f1652 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -359,7 +359,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool _isBackgroundTransparent(); void _focusChanged(bool focused); - fire_and_forget _SearchAsync(std::optional goForward, Windows::Foundation::TimeSpan const& delay); + fire_and_forget _SearchAsync(std::optional goForward); void _SelectSearchResult(std::optional goForward); winrt::Windows::Foundation::IAsyncOperation _SearchOne(::Search& search); diff --git a/src/cascadia/TerminalControl/SearchBoxControl.h b/src/cascadia/TerminalControl/SearchBoxControl.h index c7394e89565..038661cffc2 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.h +++ b/src/cascadia/TerminalControl/SearchBoxControl.h @@ -16,7 +16,7 @@ Author(s): #pragma once #include "SearchBoxControl.g.h" - namespace winrt::Microsoft::Terminal::Control::implementation +namespace winrt::Microsoft::Terminal::Control::implementation { struct SearchBoxControl : SearchBoxControlT { diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index abd03eae6a4..4c69d1dd96a 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -3120,11 +3120,25 @@ namespace winrt::Microsoft::Terminal::Control::implementation L"SearchBoxResultAnnouncement" /* unique name for this group of notifications */); } - // ControlCore will manually trigger a _terminalScrollPositionChanged to update the scrollbat marks + co_await wil::resume_foreground(Dispatcher()); + + // Manually send a scrollbar update, now, on the UI thread. We're + // already UI-driven, so that's okay. We're not really changing the + // scrollbar, but we do want to update the position of any marks. The + // Core might send a scrollbar updated event too, but if the first + // search hit is in the visible viewport, then the pips won't display + // until the user first scrolls. + auto scrollBar = ScrollBar(); + ScrollBarUpdate update{ + .newValue = scrollBar.Value(), + .newMaximum = scrollBar.Maximum(), + .newMinimum = scrollBar.Minimum(), + .newViewportSize = scrollBar.ViewportSize(), + }; + _throttledUpdateScrollbar(update); if (_searchBox) { - co_await wil::resume_foreground(Dispatcher()); _searchBox->SetStatus(args.TotalMatches(), args.CurrentMatch()); _searchBox->SetNavigationEnabled(true); } From e49e13e3c6e181920a89ecb18ca7c719983472e3 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 4 Nov 2022 07:36:08 -0500 Subject: [PATCH 20/61] This is a test fix, because now we send notifications even for circling --- src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp index f3f5db07e47..77fdb0cca74 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp @@ -180,10 +180,11 @@ void ScrollTest::TestNotifyScrolling() // causes the first scroll event auto scrolled = currentRow >= TerminalViewHeight - 1; - // When we circle the buffer, the scroll bar's position does not - // change. + // When we circle the buffer, the scroll bar's position does not change. + // However, as of GH#14045, we still want to send notifications, so the + // control layer can update the position of marks on the scrollbar. auto circledBuffer = currentRow >= totalBufferSize - 1; - auto expectScrollBarNotification = scrolled && !circledBuffer; + auto expectScrollBarNotification = scrolled || circledBuffer; if (expectScrollBarNotification) { From f396d3685c625797726181fbb8184faacb5b427e Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 17 Nov 2022 13:50:42 -0600 Subject: [PATCH 21/61] POC: remove yielding for fun and for profit --- src/cascadia/TerminalControl/ControlCore.cpp | 31 ++++++++++++-------- src/cascadia/TerminalControl/ControlCore.h | 3 +- src/cascadia/TerminalControl/TermControl.h | 2 +- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index b979132f3d5..01b5760dadc 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1527,6 +1527,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation std::vector> matches; if (!_searchState->text.empty()) { + 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(*GetUiaData(), @@ -1534,12 +1536,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation Search::Direction::Forward, _searchState->sensitivity); - while (co_await _SearchOne(search)) + while (/*co_await*/ _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_await winrt::resume_foreground(_dispatcher); co_return; } @@ -1550,13 +1553,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (!_searchState.has_value() || _searchState->searchId != originalSearchId) { + co_await winrt::resume_foreground(_dispatcher); co_return; } } _searchState->matches.emplace(std::move(matches)); _bufferChangedSinceSearch = false; } - + co_await winrt::resume_foreground(_dispatcher); if (auto core{ weakThis.get() }) { _SelectSearchResult(_searchState->goForward); @@ -1620,22 +1624,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - search: search object to use to find the next match // Return Value: // - - winrt::Windows::Foundation::IAsyncOperation ControlCore::_SearchOne(::Search& search) + // winrt::Windows::Foundation::IAsyncOperation ControlCore::_SearchOne(::Search& search) + bool ControlCore::_SearchOne(::Search& search) { bool found{ false }; auto weakThis{ get_weak() }; - co_await winrt::resume_background(); - if (auto control{ weakThis.get() }) - { - // 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(); - found = search.FindNext(); - } + // co_await winrt::resume_background(); + // if (auto control{ weakThis.get() }) + // { + // 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(); + found = search.FindNext(); + // } - co_await winrt::resume_foreground(_dispatcher); - co_return found; + // co_await winrt::resume_foreground(_dispatcher); + return found; } // Method Description: diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 09c7e239b18..a91a5237bd9 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -360,7 +360,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation fire_and_forget _SearchAsync(std::optional goForward); void _SelectSearchResult(std::optional goForward); - winrt::Windows::Foundation::IAsyncOperation _SearchOne(::Search& search); + // winrt::Windows::Foundation::IAsyncOperation _SearchOne(::Search& search); + bool _SearchOne(::Search& search); inline bool _IsClosing() const noexcept { diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 1d52c3e6ff6..0a370347959 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -294,7 +294,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const til::point _toTerminalOrigin(winrt::Windows::Foundation::Point cursorPosition); double _GetAutoScrollSpeed(double cursorDistanceFromBorder) const; - winrt::Windows::Foundation::IAsyncOperation _SearchOne(Search& search); + // winrt::Windows::Foundation::IAsyncOperation _SearchOne(Search& search); void _Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive); void _SearchChanged(const winrt::hstring& text, const bool goForward, const bool caseSensitive); From da1e89f3ee02f8b6365b925900a4c742d5fe4387 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 17 Nov 2022 14:03:32 -0600 Subject: [PATCH 22/61] KISS, you're welcome leonard --- src/cascadia/TerminalControl/ControlCore.cpp | 23 +++++--------------- src/cascadia/TerminalControl/ControlCore.h | 1 - src/cascadia/TerminalControl/TermControl.cpp | 4 ++-- 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 01b5760dadc..b7e4be0264d 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1527,6 +1527,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation std::vector> 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 @@ -1536,13 +1537,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation Search::Direction::Forward, _searchState->sensitivity); - while (/*co_await*/ _SearchOne(search)) + 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_await winrt::resume_foreground(_dispatcher); co_return; } @@ -1553,14 +1553,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (!_searchState.has_value() || _searchState->searchId != originalSearchId) { - co_await winrt::resume_foreground(_dispatcher); co_return; } } _searchState->matches.emplace(std::move(matches)); _bufferChangedSinceSearch = false; } - co_await winrt::resume_foreground(_dispatcher); if (auto core{ weakThis.get() }) { _SelectSearchResult(_searchState->goForward); @@ -1619,28 +1617,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation } // Method Description: - // - Search for a single value in a background + // - 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 + // - search: search object to use to find the next match. // Return Value: // - - // winrt::Windows::Foundation::IAsyncOperation ControlCore::_SearchOne(::Search& search) bool ControlCore::_SearchOne(::Search& search) { - bool found{ false }; - auto weakThis{ get_weak() }; - - // co_await winrt::resume_background(); - // if (auto control{ weakThis.get() }) - // { // 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(); - found = search.FindNext(); - // } - - // co_await winrt::resume_foreground(_dispatcher); - return found; + return search.FindNext(); } // Method Description: diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index a91a5237bd9..5e7d646c024 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -360,7 +360,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation fire_and_forget _SearchAsync(std::optional goForward); void _SelectSearchResult(std::optional goForward); - // winrt::Windows::Foundation::IAsyncOperation _SearchOne(::Search& search); bool _SearchOne(::Search& search); inline bool _IsClosing() const noexcept diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 5f1e9f3093d..805525163e5 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -3122,6 +3122,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - winrt::fire_and_forget TermControl::_coreFoundMatch(const IInspectable& /*sender*/, Control::FoundResultsArgs args) { + + co_await wil::resume_foreground(Dispatcher()); if (auto automationPeer{ Automation::Peers::FrameworkElementAutomationPeer::FromElement(*this) }) { automationPeer.RaiseNotificationEvent( @@ -3131,8 +3133,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation L"SearchBoxResultAnnouncement" /* unique name for this group of notifications */); } - co_await wil::resume_foreground(Dispatcher()); - // Manually send a scrollbar update, now, on the UI thread. We're // already UI-driven, so that's okay. We're not really changing the // scrollbar, but we do want to update the position of any marks. The From 198b3cff4da17fad8ede769e98d4b13a310dbef6 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 17 Nov 2022 14:13:31 -0600 Subject: [PATCH 23/61] why _was_ that optional --- src/cascadia/TerminalControl/ControlCore.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 5e7d646c024..a495807f765 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -55,7 +55,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation public: static std::atomic _searchIdGenerator; - SearchState(const winrt::hstring& text, const Search::Sensitivity sensitivity, const std::optional forward) : + SearchState(const winrt::hstring& text, const Search::Sensitivity sensitivity, const bool forward) : text(text), sensitivity(sensitivity), goForward(forward), @@ -70,7 +70,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation winrt::hstring text; Search::Sensitivity sensitivity; - std::optional goForward{ std::nullopt }; + bool goForward{ true }; size_t searchId; int32_t currentMatchIndex{ -1 }; }; From 03f81b2b7ea65470994a8a732cdc787a79f0cc17 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Thu, 17 Nov 2022 14:14:18 -0600 Subject: [PATCH 24/61] Migrate spelling-0.0.21 changes from main --- .github/actions/spelling/README.md | 1 + .github/actions/spelling/advice.md | 2 +- .github/actions/spelling/allow/allow.txt | 8 +- .github/actions/spelling/candidate.patterns | 523 +++++++++++++ .github/actions/spelling/excludes.txt | 38 +- .github/actions/spelling/expect/alphabet.txt | 7 - .github/actions/spelling/expect/expect.txt | 695 +++--------------- .github/actions/spelling/expect/web.txt | 2 - .../actions/spelling/line_forbidden.patterns | 30 +- .../actions/spelling/patterns/patterns.txt | 57 +- .github/actions/spelling/reject.txt | 27 +- .github/workflows/spelling2.yml | 104 ++- 12 files changed, 829 insertions(+), 665 deletions(-) create mode 100644 .github/actions/spelling/candidate.patterns diff --git a/.github/actions/spelling/README.md b/.github/actions/spelling/README.md index 2b1b5828597..4c40f7f02ac 100644 --- a/.github/actions/spelling/README.md +++ b/.github/actions/spelling/README.md @@ -6,6 +6,7 @@ File | Purpose | Format | Info [reject.txt](reject.txt) | Remove words from the dictionary (after allow) | grep pattern matching whole dictionary words | [reject](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-reject) [excludes.txt](excludes.txt) | Files to ignore entirely | perl regular expression | [excludes](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-excludes) [patterns/*.txt](patterns/) | Patterns to ignore from checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns) +[candidate.patterns](candidate.patterns) | Patterns that might be worth adding to [patterns.txt](patterns.txt) | perl regular expression with optional comment block introductions (all matches will be suggested) | [candidates](https://github.com/check-spelling/check-spelling/wiki/Feature:-Suggest-patterns) [line_forbidden.patterns](line_forbidden.patterns) | Patterns to flag in checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns) [expect/*.txt](expect.txt) | Expected words that aren't in the dictionary | one word per line (sorted, alphabetically) | [expect](https://github.com/check-spelling/check-spelling/wiki/Configuration#expect) [advice.md](advice.md) | Supplement for GitHub comment when unrecognized words are found | GitHub Markdown | [advice](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice) diff --git a/.github/actions/spelling/advice.md b/.github/actions/spelling/advice.md index 2c4c5443f8f..d82df49ee22 100644 --- a/.github/actions/spelling/advice.md +++ b/.github/actions/spelling/advice.md @@ -21,7 +21,7 @@ See the `README.md` in each directory for more information. :microscope: You can test your commits **without** *appending* to a PR by creating a new branch with that extra change and pushing it to your fork. The [check-spelling](https://github.com/marketplace/actions/check-spelling) action will run in response to your **push** -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. :wink: -
:clamp: If the flagged items are false positives +
If the flagged items are :exploding_head: false positives If items relate to a ... * binary file (or some other file you wouldn't want to check at all). diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index d26e2a56e33..eaa0a471190 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -1,7 +1,7 @@ admins allcolors -apc Apc +apc breadcrumb breadcrumbs bsd @@ -14,8 +14,8 @@ CMMI copyable cybersecurity dalet -dcs Dcs +dcs dialytika dje downside @@ -34,10 +34,12 @@ gantt gcc geeksforgeeks ghe +github gje godbolt hostname hostnames +https hyperlink hyperlinking hyperlinks @@ -82,6 +84,7 @@ runtimes shcha slnt Sos +ssh timeline timelines timestamped @@ -90,6 +93,7 @@ tokenizes tonos toolset tshe +ubuntu uiatextrange UIs und diff --git a/.github/actions/spelling/candidate.patterns b/.github/actions/spelling/candidate.patterns new file mode 100644 index 00000000000..4b40e728ee3 --- /dev/null +++ b/.github/actions/spelling/candidate.patterns @@ -0,0 +1,523 @@ +# marker to ignore all code on line +^.*/\* #no-spell-check-line \*/.*$ +# marker for ignoring a comment to the end of the line +// #no-spell-check.*$ + +# patch hunk comments +^\@\@ -\d+(?:,\d+|) \+\d+(?:,\d+|) \@\@ .* +# git index header +index [0-9a-z]{7,40}\.\.[0-9a-z]{7,40} + +# cid urls +(['"])cid:.*?\g{-1} + +# data url in parens +\(data:[^)]*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\) +# data url in quotes +([`'"])data:.*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1} +# data url +data:[-a-zA-Z=;:/0-9+]*,\S* + +# mailto urls +mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} + +# magnet urls +magnet:[?=:\w]+ + +# magnet urls +"magnet:[^"]+" + +# obs: +"obs:[^"]*" + +# The `\b` here means a break, it's the fancy way to handle urls, but it makes things harder to read +# In this examples content, I'm using a number of different ways to match things to show various approaches +# asciinema +\basciinema\.org/a/[0-9a-zA-Z]+ + +# apple +\bdeveloper\.apple\.com/[-\w?=/]+ +# Apple music +\bembed\.music\.apple\.com/fr/playlist/usr-share/[-\w.]+ + +# appveyor api +\bci\.appveyor\.com/api/projects/status/[0-9a-z]+ +# appveyor project +\bci\.appveyor\.com/project/(?:[^/\s"]*/){2}builds?/\d+/job/[0-9a-z]+ + +# Amazon + +# Amazon +\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|) +# AWS S3 +\b\w*\.s3[^.]*\.amazonaws\.com/[-\w/&#%_?:=]* +# AWS execute-api +\b[0-9a-z]{10}\.execute-api\.[-0-9a-z]+\.amazonaws\.com\b +# AWS ELB +\b\w+\.[-0-9a-z]+\.elb\.amazonaws\.com\b +# AWS SNS +\bsns\.[-0-9a-z]+.amazonaws\.com/[-\w/&#%_?:=]* +# AWS VPC +vpc-\w+ + +# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there +# YouTube url +\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]* +# YouTube music +\bmusic\.youtube\.com/youtubei/v1/browse(?:[?&]\w+=[-a-zA-Z0-9?&=_]*) +# YouTube tag +<\s*youtube\s+id=['"][-a-zA-Z0-9?_]*['"] +# YouTube image +\bimg\.youtube\.com/vi/[-a-zA-Z0-9?&=_]* +# Google Accounts +\baccounts.google.com/[-_/?=.:;+%&0-9a-zA-Z]* +# Google Analytics +\bgoogle-analytics\.com/collect.[-0-9a-zA-Z?%=&_.~]* +# Google APIs +\bgoogleapis\.(?:com|dev)/[a-z]+/(?:v\d+/|)[a-z]+/[-@:./?=\w+|&]+ +# Google Storage +\b[-a-zA-Z0-9.]*\bstorage\d*\.googleapis\.com(?:/\S*|) +# Google Calendar +\bcalendar\.google\.com/calendar(?:/u/\d+|)/embed\?src=[@./?=\w&%]+ +\w+\@group\.calendar\.google\.com\b +# Google DataStudio +\bdatastudio\.google\.com/(?:(?:c/|)u/\d+/|)(?:embed/|)(?:open|reporting|datasources|s)/[-0-9a-zA-Z]+(?:/page/[-0-9a-zA-Z]+|) +# The leading `/` here is as opposed to the `\b` above +# ... a short way to match `https://` or `http://` since most urls have one of those prefixes +# Google Docs +/docs\.google\.com/[a-z]+/(?:ccc\?key=\w+|(?:u/\d+|d/(?:e/|)[0-9a-zA-Z_-]+/)?(?:edit\?[-\w=#.]*|/\?[\w=&]*|)) +# Google Drive +\bdrive\.google\.com/(?:file/d/|open)[-0-9a-zA-Z_?=]* +# Google Groups +\bgroups\.google\.com/(?:(?:forum/#!|d/)(?:msg|topics?|searchin)|a)/[^/\s"]+/[-a-zA-Z0-9$]+(?:/[-a-zA-Z0-9]+)* +# Google Maps +\bmaps\.google\.com/maps\?[\w&;=]* +# Google themes +themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+. +# Google CDN +\bclients2\.google(?:usercontent|)\.com[-0-9a-zA-Z/.]* +# Goo.gl +/goo\.gl/[a-zA-Z0-9]+ +# Google Chrome Store +\bchrome\.google\.com/webstore/detail/[-\w]*(?:/\w*|) +# Google Books +\bgoogle\.(?:\w{2,4})/books(?:/\w+)*\?[-\w\d=&#.]* +# Google Fonts +\bfonts\.(?:googleapis|gstatic)\.com/[-/?=:;+&0-9a-zA-Z]* +# Google Forms +\bforms\.gle/\w+ +# Google Scholar +\bscholar\.google\.com/citations\?user=[A-Za-z0-9_]+ +# Google Colab Research Drive +\bcolab\.research\.google\.com/drive/[-0-9a-zA-Z_?=]* + +# GitHub SHAs (api) +\bapi.github\.com/repos(?:/[^/\s"]+){3}/[0-9a-f]+\b +# GitHub SHAs (markdown) +(?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|) +# GitHub SHAs +\bgithub\.com(?:/[^/\s"]+){2}[@#][0-9a-f]+\b +# GitHub wiki +\bgithub\.com/(?:[^/]+/){2}wiki/(?:(?:[^/]+/|)_history|[^/]+(?:/_compare|)/[0-9a-f.]{40,})\b +# githubusercontent +/[-a-z0-9]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]* +# githubassets +\bgithubassets.com/[0-9a-f]+(?:[-/\w.]+) +# gist github +\bgist\.github\.com/[^/\s"]+/[0-9a-f]+ +# git.io +\bgit\.io/[0-9a-zA-Z]+ +# GitHub JSON +"node_id": "[-a-zA-Z=;:/0-9+]*" +# Contributor +\[[^\]]+\]\(https://github\.com/[^/\s"]+\) +# GHSA +GHSA(?:-[0-9a-z]{4}){3} + +# GitLab commit +\bgitlab\.[^/\s"]*/\S+/\S+/commit/[0-9a-f]{7,16}#[0-9a-f]{40}\b +# GitLab merge requests +\bgitlab\.[^/\s"]*/\S+/\S+/-/merge_requests/\d+/diffs#[0-9a-f]{40}\b +# GitLab uploads +\bgitlab\.[^/\s"]*/uploads/[-a-zA-Z=;:/0-9+]* +# GitLab commits +\bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b + +# binanace +accounts.binance.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]* + +# bitbucket diff +\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}diff(?:stat|)(?:/[^/\s"]+){2}:[0-9a-f]+ +# bitbucket repositories commits +\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}commits?/[0-9a-f]+ +# bitbucket commits +\bbitbucket\.org/(?:[^/\s"]+/){2}commits?/[0-9a-f]+ + +# bit.ly +\bbit\.ly/\w+ + +# bitrise +\bapp\.bitrise\.io/app/[0-9a-f]*/[\w.?=&]* + +# bootstrapcdn.com +\bbootstrapcdn\.com/[-./\w]+ + +# cdn.cloudflare.com +\bcdnjs\.cloudflare\.com/[./\w]+ + +# circleci +\bcircleci\.com/gh(?:/[^/\s"]+){1,5}.[a-z]+\?[-0-9a-zA-Z=&]+ + +# gitter +\bgitter\.im(?:/[^/\s"]+){2}\?at=[0-9a-f]+ + +# gravatar +\bgravatar\.com/avatar/[0-9a-f]+ + +# ibm +[a-z.]*ibm\.com/[-_#=:%!?~.\\/\d\w]* + +# imgur +\bimgur\.com/[^.]+ + +# Internet Archive +\barchive\.org/web/\d+/(?:[-\w.?,'/\\+&%$#_:]*) + +# discord +/discord(?:app\.com|\.gg)/(?:invite/)?[a-zA-Z0-9]{7,} + +# Disqus +\bdisqus\.com/[-\w/%.()!?&=_]* + +# medium link +\blink\.medium\.com/[a-zA-Z0-9]+ +# medium +\bmedium\.com/\@?[^/\s"]+/[-\w]+ + +# microsoft +\b(?:https?://|)(?:(?:download\.visualstudio|docs|msdn2?|research)\.microsoft|blogs\.msdn)\.com/[-_a-zA-Z0-9()=./%]* +# powerbi +\bapp\.powerbi\.com/reportEmbed/[^"' ]* +# vs devops +\bvisualstudio.com(?::443|)/[-\w/?=%&.]* +# microsoft store +\bmicrosoft\.com/store/apps/\w+ + +# mvnrepository.com +\bmvnrepository\.com/[-0-9a-z./]+ + +# now.sh +/[0-9a-z-.]+\.now\.sh\b + +# oracle +\bdocs\.oracle\.com/[-0-9a-zA-Z./_?#&=]* + +# chromatic.com +/\S+.chromatic.com\S*[")] + +# codacy +\bapi\.codacy\.com/project/badge/Grade/[0-9a-f]+ + +# compai +\bcompai\.pub/v1/png/[0-9a-f]+ + +# mailgun api +\.api\.mailgun\.net/v3/domains/[0-9a-z]+\.mailgun.org/messages/[0-9a-zA-Z=@]* +# mailgun +\b[0-9a-z]+.mailgun.org + +# /message-id/ +/message-id/[-\w@./%]+ + +# Reddit +\breddit\.com/r/[/\w_]* + +# requestb.in +\brequestb\.in/[0-9a-z]+ + +# sched +\b[a-z0-9]+\.sched\.com\b + +# Slack url +slack://[a-zA-Z0-9?&=]+ +# Slack +\bslack\.com/[-0-9a-zA-Z/_~?&=.]* +# Slack edge +\bslack-edge\.com/[-a-zA-Z0-9?&=%./]+ +# Slack images +\bslack-imgs\.com/[-a-zA-Z0-9?&=%.]+ + +# shields.io +\bshields\.io/[-\w/%?=&.:+;,]* + +# stackexchange -- https://stackexchange.com/feeds/sites +\b(?:askubuntu|serverfault|stack(?:exchange|overflow)|superuser).com/(?:questions/\w+/[-\w]+|a/) + +# Sentry +[0-9a-f]{32}\@o\d+\.ingest\.sentry\.io\b + +# Twitter markdown +\[\@[^[/\]:]*?\]\(https://twitter.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)\) +# Twitter hashtag +\btwitter\.com/hashtag/[\w?_=&]* +# Twitter status +\btwitter\.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|) +# Twitter profile images +\btwimg\.com/profile_images/[_\w./]* +# Twitter media +\btwimg\.com/media/[-_\w./?=]* +# Twitter link shortened +\bt\.co/\w+ + +# facebook +\bfburl\.com/[0-9a-z_]+ +# facebook CDN +\bfbcdn\.net/[\w/.,]* +# facebook watch +\bfb\.watch/[0-9A-Za-z]+ + +# dropbox +\bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+ + +# ipfs protocol +ipfs://[0-9a-z]* +# ipfs url +/ipfs/[0-9a-z]* + +# w3 +\bw3\.org/[-0-9a-zA-Z/#.]+ + +# loom +\bloom\.com/embed/[0-9a-f]+ + +# regex101 +\bregex101\.com/r/[^/\s"]+/\d+ + +# figma +\bfigma\.com/file(?:/[0-9a-zA-Z]+/)+ + +# freecodecamp.org +\bfreecodecamp\.org/[-\w/.]+ + +# image.tmdb.org +\bimage\.tmdb\.org/[/\w.]+ + +# mermaid +\bmermaid\.ink/img/[-\w]+|\bmermaid-js\.github\.io/mermaid-live-editor/#/edit/[-\w]+ + +# Wikipedia +\ben\.wikipedia\.org/wiki/[-\w%.#]+ + +# gitweb +[^"\s]+/gitweb/\S+;h=[0-9a-f]+ + +# HyperKitty lists +/archives/list/[^@/]+\@[^/\s"]*/message/[^/\s"]*/ + +# lists +/thread\.html/[^"\s]+ + +# list-management +\blist-manage\.com/subscribe(?:[?&](?:u|id)=[0-9a-f]+)+ + +# kubectl.kubernetes.io/last-applied-configuration +"kubectl.kubernetes.io/last-applied-configuration": ".*" + +# pgp +\bgnupg\.net/pks/lookup[?&=0-9a-zA-Z]* + +# Spotify +\bopen\.spotify\.com/embed/playlist/\w+ + +# Mastodon +\bmastodon\.[-a-z.]*/(?:media/|\@)[?&=0-9a-zA-Z_]* + +# scastie +\bscastie\.scala-lang\.org/[^/]+/\w+ + +# images.unsplash.com +\bimages\.unsplash\.com/(?:(?:flagged|reserve)/|)[-\w./%?=%&.;]+ + +# pastebin +\bpastebin\.com/[\w/]+ + +# heroku +\b\w+\.heroku\.com/source/archive/\w+ + +# quip +\b\w+\.quip\.com/\w+(?:(?:#|/issues/)\w+)? + +# badgen.net +\bbadgen\.net/badge/[^")\]'\s]+ + +# statuspage.io +\w+\.statuspage\.io\b + +# media.giphy.com +\bmedia\.giphy\.com/media/[^/]+/[\w.?&=]+ + +# tinyurl +\btinyurl\.com/\w+ + +# getopts +\bgetopts\s+(?:"[^"]+"|'[^']+') + +# ANSI color codes +(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m + +# URL escaped characters +\%[0-9A-F][A-F] +# IPv6 +\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b +# c99 hex digits (not the full format, just one I've seen) +0x[0-9a-fA-F](?:\.[0-9a-fA-F]*|)[pP] +# Punycode +\bxn--[-0-9a-z]+ +# sha +sha\d+:[0-9]*[a-f]{3,}[0-9a-f]* +# sha-... -- uses a fancy capture +(['"]|")[0-9a-f]{40,}\g{-1} +# hex runs +\b[0-9a-fA-F]{16,}\b +# hex in url queries +=[0-9a-fA-F]*?(?:[A-F]{3,}|[a-f]{3,})[0-9a-fA-F]*?& +# ssh +(?:ssh-\S+|-nistp256) [-a-zA-Z=;:/0-9+]{12,} + +# PGP +\b(?:[0-9A-F]{4} ){9}[0-9A-F]{4}\b +# GPG keys +\b(?:[0-9A-F]{4} ){5}(?: [0-9A-F]{4}){5}\b +# Well known gpg keys +.well-known/openpgpkey/[\w./]+ + +# uuid: +\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b +# hex digits including css/html color classes: +(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b +# integrity +integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}" + +# https://www.gnu.org/software/groff/manual/groff.html +# man troff content +\\f[BCIPR] +# ' +\\\(aq + +# .desktop mime types +^MimeTypes?=.*$ +# .desktop localized entries +^[A-Z][a-z]+\[[a-z]+\]=.*$ +# Localized .desktop content +Name\[[^\]]+\]=.* + +# IServiceProvider +\bI(?=(?:[A-Z][a-z]{2,})+\b) + +# crypt +"\$2[ayb]\$.{56}" + +# scrypt / argon +\$(?:scrypt|argon\d+[di]*)\$\S+ + +# Input to GitHub JSON +content: "[-a-zA-Z=;:/0-9+]*=" + +# Python stringprefix / binaryprefix +# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings +(?v# +(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_])) +# Compiler flags (Scala) +(?:^|[\t ,>"'`=(])-J-[DPWXY](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) +# Compiler flags +#(?:^|[\t ,"'`=(])-[DPWXYLlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) + +# Compiler flags (linker) +,-B +# curl arguments +\b(?:\\n|)curl(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)* +# set arguments +\bset(?:\s+-[abefimouxE]{1,2})*\s+-[abefimouxE]{3,}(?:\s+-[abefimouxE]+)* +# tar arguments +\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+ +# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long... +\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b +# macOS temp folders +/var/folders/\w\w/[+\w]+/(?:T|-Caches-)/ diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index b09b9a65f3b..bc509a5669e 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -2,14 +2,14 @@ (?:(?i)\.png$) (?:^|/)(?i)COPYRIGHT (?:^|/)(?i)LICEN[CS]E +(?:^|/)3rdparty/ (?:^|/)dirs$ (?:^|/)go\.mod$ (?:^|/)go\.sum$ (?:^|/)package(?:-lock|)\.json$ (?:^|/)sources(?:|\.dep)$ (?:^|/)vendor/ -ignore$ -SUMS$ +\.a$ \.ai$ \.avi$ \.bmp$ @@ -20,6 +20,8 @@ SUMS$ \.crt$ \.csr$ \.dll$ +\.docx?$ +\.drawio$ \.DS_Store$ \.eot$ \.eps$ @@ -31,6 +33,7 @@ SUMS$ \.icns$ \.ico$ \.jar$ +\.jks$ \.jpeg$ \.jpg$ \.key$ @@ -41,6 +44,7 @@ SUMS$ \.mod$ \.mp3$ \.mp4$ +\.o$ \.ocf$ \.otf$ \.pbxproj$ @@ -48,22 +52,41 @@ SUMS$ \.pem$ \.png$ \.psd$ +\.pyc$ \.runsettings$ +\.s$ \.sig$ \.so$ \.svg$ \.svgz$ +\.svgz?$ \.tar$ \.tgz$ +\.tiff?$ \.ttf$ \.vsdx$ \.wav$ +\.webm$ +\.webp$ \.woff +\.woff2?$ \.xcf$ \.xls +\.xlsx?$ \.xpm$ \.yml$ \.zip$ +^\.github/actions/spelling/ +^\.github/fabricbot.json$ +^\.gitignore$ +^\Q.git-blame-ignore-revs\E$ +^\Q.github/workflows/spelling.yml\E$ +^\Qdoc/reference/windows-terminal-logo.ans\E$ +^\Qsamples/ConPTY/EchoCon/EchoCon/EchoCon.vcxproj.filters\E$ +^\Qsrc/host/exe/Host.EXE.vcxproj.filters\E$ +^\Qsrc/host/ft_host/chafa.txt\E$ +^\Qsrc/tools/closetest/CloseTest.vcxproj.filters\E$ +^\XamlStyler.json$ ^build/config/ ^consolegit2gitfilters\.json$ ^dep/ @@ -90,12 +113,5 @@ SUMS$ ^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$ ^src/types/ut_types/UtilsTests.cpp$ ^tools/ReleaseEngineering/ServicingPipeline.ps1$ -^\.github/actions/spelling/ -^\.github/fabricbot.json$ -^\.gitignore$ -^\Q.github/workflows/spelling.yml\E$ -^\Qsamples/ConPTY/EchoCon/EchoCon/EchoCon.vcxproj.filters\E$ -^\Qsrc/host/exe/Host.EXE.vcxproj.filters\E$ -^\Qsrc/host/ft_host/chafa.txt\E$ -^\Qsrc/tools/closetest/CloseTest.vcxproj.filters\E$ -^\XamlStyler.json$ +ignore$ +SUMS$ diff --git a/.github/actions/spelling/expect/alphabet.txt b/.github/actions/spelling/expect/alphabet.txt index 7275b200cba..23933713a40 100644 --- a/.github/actions/spelling/expect/alphabet.txt +++ b/.github/actions/spelling/expect/alphabet.txt @@ -5,26 +5,19 @@ AAAAAABBBBBBCCC AAAAABBBBBBCCC abcd abcd -abcde -abcdef -ABCDEFG -ABCDEFGH ABCDEFGHIJ abcdefghijk ABCDEFGHIJKLMNO abcdefghijklmnop ABCDEFGHIJKLMNOPQRST -abcdefghijklmnopqrstuvwxyz ABCG ABE abf BBBBB BBBBBBBB -BBBBBBBBBBBBBBDDDD BBBBBCCC BBBBCCCCC BBGGRR -CCE EFG EFGh QQQQQQQQQQABCDEFGHIJ diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 74a5b24bc94..8f8f4828d0a 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1,21 +1,19 @@ +aabbcc ABANDONFONT +abbcc ABCDEFGHIJKLMNOPQRSTUVWXY abgr abi +ABORTIFHUNG ACCESSTOKEN -acec -acf acidev ACIOSS ACover actctx ACTCTXW activatable -ACTIVEBORDER -ACTIVECAPTION ADDALIAS ADDREF -addressof ADDSTRING ADDTOOL AEnd @@ -27,8 +25,8 @@ ahz AImpl AInplace ALIGNRIGHT -alloc allocing +allocs alpc ALTERNATENAME ALTF @@ -39,35 +37,27 @@ ansicpg ANSISYS ANSISYSRC ANSISYSSC -antialias antialiasing ANull anycpu APARTMENTTHREADED APCs -api APIENTRY apiset APPBARDATA appcontainer -APPICON appium -applet appletname +applets applicationmodel APPLMODAL appmodel -apps APPWINDOW APrep apsect APSTUDIO archeologists -architected argb -argc -args -argv ARRAYSIZE ARROWKEYS asan @@ -77,19 +67,15 @@ ASDF asdfghjkl ASetting ASingle -asm -asmv asmx ASYNCWINDOWPOS atch ATest -attr ATTRCOLOR aumid Authenticode AUTOBUDDY AUTOCHECKBOX -autogenerated autohide AUTOHSCROLL automagically @@ -98,34 +84,29 @@ AUTORADIOBUTTON autoscrolling Autowrap AVerify -AVI AVX awch azzle -backend backgrounded Backgrounder backgrounding -backport +backported backstory barbaz Batang -baz Bazz BBDM bbwe bcount -bcrypt bcx bcz BEFOREPARENT beginthread -bgcolor bgfx bgidx Bgk BGR -BGRA +bgra BHID bigobj binplace @@ -135,14 +116,12 @@ bitcrazed bitflag bitmask BITOPERATION -bitset +bitsets BKCOLOR BKGND Bksp -blog Blt BLUESCROLL -bmp BODGY BOLDFONT BOOLIFY @@ -158,44 +137,34 @@ bpp BPPF branchconfig brandings -BRK Browsable -bsearch Bspace bstr BTNFACE -buf bufferout buffersize buflen -bugfix buildtransitive BUILDURI burriter BValue -byref -bytearray bytebuffer cac cacafire -callee capslock CARETBLINKINGENABLED CARRIAGERETURN cascadia -cassert castsi catid cazamor CBash -cbegin cbiex CBN CBoolean cbt cbuffer CCCBB -ccf cch CCHAR cci @@ -206,17 +175,11 @@ CComp CConsole CConversion CCRT -cctype -CDATA cdd -cdecl CDeclaration CEdit CELLSIZE -cend -cerr cfae -Cfg cfie cfiex cfte @@ -226,43 +189,31 @@ chafa changelist chaof charinfo -charset CHARSETINFO -chcp -checkbox -checkboxes chh chk -chrono CHT Cic -cjk CLA Clcompile CLE cleartype CLICKACTIVE clickdown -climits clipbrd CLIPCHILDREN CLIPSIBLINGS -clocale closetest cloudconsole cls CLSCTX -clsid +clsids CLUSTERMAP -cmath cmatrix cmder CMDEXT -Cmdlet -cmdline cmh CMOUSEBUTTONS -cmp cmpeq cmt cmw @@ -270,17 +221,16 @@ cmyk CNL cnt CNTRL -codebase Codeflow codepage codepath -codepoint +codepoints coinit COLLECTIONURI colorizing COLORMATRIX -colorref -colorscheme +COLORREFs +colorschemes colorspaces colorspec colortable @@ -289,33 +239,27 @@ colortest colortool COLR combaseapi -combobox comctl COMDAT commandline commctrl commdlg COMMITID -compat componentization conapi conareainfo conattrs conbufferout -concat concfg conclnt conddkrefs condrv conechokey conemu -config configurability conhost -conhostv conime conimeinfo -conint conintegrity conintegrityuwp coninteractivitybase @@ -343,34 +287,28 @@ consoleinternal Consoleroot CONSOLESETFOREGROUND consoletaeftemplates -CONSOLEV +consoleuwp Consolewait CONSOLEWINDOWOWNER consrv -constexpr constexprable constness contentfiles conterm -CONTEXTMENU contsf contypes convarea conwinuserrefs -coord coordnew COPYCOLOR -copymode CORESYSTEM cotaskmem countof -cout CPG cpinfo CPINFOEX CPLINFO cplusplus -cpp CPPCORECHECK cppcorecheckrules cpprest @@ -378,67 +316,45 @@ cpprestsdk cppwinrt CProc cpx -crbegin CREATESCREENBUFFER CREATESTRUCT CREATESTRUCTW cred -cref -crend -Crisman +crisman CRLFs crloew Crt CRTLIBS csbi csbiex -csharp CSHORT -CSIDL Cspace -csproj -Csr csrmsg CSRSS csrutil -cstdarg -cstddef -cstdio -cstdlib -cstr -cstring cstyle -csv CSwitch CTerminal CText -ctime ctl ctlseqs -Ctlv -ctor CTRLEVENT CTRLFREQUENCY CTRLKEYSHORTCUTS CTRLVOLUME -Ctx Ctxt -ctype CUF cupxy -curated CURRENTFONT currentmode CURRENTPAGE CURSORCOLOR CURSORSIZE CURSORTYPE +CUsers CUU Cwa cwch -cwchar -cwctype -cwd CXFRAME CXFULLSCREEN CXHSCROLL @@ -448,14 +364,11 @@ CXSIZE CXSMICON CXVIRTUALSCREEN CXVSCROLL -cxx CYFRAME CYFULLSCREEN -cygwin CYHSCROLL CYMIN CYPADDEDBORDER -CYRL CYSIZE CYSIZEFRAME CYSMICON @@ -463,8 +376,6 @@ CYVIRTUALSCREEN CYVSCROLL dai DATABLOCK -DATAVIEW -DATAW DBatch dbcs DBCSCHAR @@ -477,7 +388,6 @@ DBGOUTPUT dbh dblclk DBlob -DBUILD DColor DCOLORVALUE dcommon @@ -494,9 +404,11 @@ debugtype DECAC DECALN DECANM +DECARM DECAUPSS DECAWM DECBKM +DECCARA DECCKM DECCOLM DECCRA @@ -505,20 +417,19 @@ DECDHL decdld DECDWL DECEKBD +DECERA +DECFRA DECID DECKPAM DECKPM DECKPNM DECLRMM -decls -declspec -decltype -declval DECNKM DECNRCM DECOM DECPCTERM DECPS +DECRARA DECRC DECREQTPARM DECRLM @@ -526,6 +437,7 @@ DECRQM DECRQSS DECRQTSR decrst +DECSACE DECSASD DECSC DECSCA @@ -534,6 +446,7 @@ DECSCPP DECSCUSR DECSED DECSEL +DECSERA DECSET DECSLPP DECSLRM @@ -543,9 +456,7 @@ DECSTBM DECSTR DECSWL DECTCEM -Dedupe -deduplicate -deduplicated +DECXCPR DEFAPP DEFAULTBACKGROUND DEFAULTFOREGROUND @@ -560,38 +471,23 @@ DEFFACE defing DEFPUSHBUTTON defterm -deiconify DELAYLOAD -deletable DELETEONRELEASE -delims Delt demoable depersist deprioritized -deps -deque -deref -deserialization -deserialize -deserialized -deserializer -deserializing +deserializers desktopwindowxamlsource -dest DESTINATIONNAME devicecode -Devops Dext DFactory DFF -DFMT dhandler dialogbox -DINLINE directio DIRECTX -Dirs DISABLEDELAYEDEXPANSION DISABLENOSCROLL DISPLAYATTRIBUTE @@ -600,27 +496,21 @@ DISPLAYCHANGE distro dlg DLGC -dll -dllexport DLLGETVERSIONPROC -dllimport dllinit dllmain DLLVERSIONINFO DLOAD DLOOK dmp -DOCTYPE DONTCARE doskey dotnet -doubleclick -downlevel DPG -dpi DPIAPI DPICHANGE DPICHANGED +DPIs dpix dpiy dpnx @@ -628,8 +518,6 @@ DRAWFRAME DRAWITEM DRAWITEMSTRUCT drcs -dropdown -DROPDOWNLIST DROPFILES drv DSBCAPS @@ -640,21 +528,17 @@ DSBVOLUME dsm dsound DSSCL -dst DSwap DTest -dtor DTTERM DUMMYUNIONNAME -DUNICODE -DUNIT dup'ed dvi dwl DWLP dwm dwmapi -dword +DWORDs dwrite dxgi dxgidwm @@ -663,7 +547,6 @@ dxinterop dxsm dxttbmp Dyreen -eaf EASTEUROPE ECH echokey @@ -681,54 +564,37 @@ EINS EJO ELEMENTNOTAVAILABLE elems -elif -elseif emacs EMPTYBOX enabledelayedexpansion -endian -endif -endl -endlocal endptr endregion -ENQ -enqueuing ENTIREBUFFER -entrypoint +entrypoints ENU -enum ENUMLOGFONT ENUMLOGFONTEX enumranges -envvar -eol eplace EPres EQU ERASEBKGND -errorlevel -ETB etcoreapp ETW -ETX EUDC EVENTID eventing everytime evflags evt -ewdelete -exe execd -executables executionengine exemain EXETYPE +exeuwp exewin exitwin expectedinput -expr EXPUNGECOMMANDHISTORY EXSTYLE EXTENDEDEDITKEY @@ -737,43 +603,29 @@ EXTTEXTOUT facename FACENODE FACESIZE -failfast FAILIFTHERE -fallthrough fastlink fcharset -fclose -fcntl -fdc -FDD fdw fesb FFDE FFrom fgbg FGCOLOR -fgetc -fgetwc FGHIJ fgidx FGs FILEDESCRIPTION -fileno -filepath FILESUBTYPE FILESYSPATH -filesystem -FILETYPE fileurl FILEW FILLATTR FILLCONSOLEOUTPUT FILTERONPASTE -finalizer FINDCASE FINDDLG FINDDOWN -FINDSTR FINDSTRINGEXACT FINDUP FIter @@ -784,7 +636,6 @@ flyout fmodern fmtarg fmtid -FNV FOLDERID FONTCHANGE fontdlg @@ -793,8 +644,7 @@ FONTENUMPROC FONTFACE FONTFAMILY FONTHEIGHT -FONTINFO -fontlist +fontinfo FONTOK FONTSIZE FONTSTRING @@ -804,27 +654,20 @@ FONTWEIGHT FONTWIDTH FONTWINDOW fooo -forceinline FORCEOFFFEEDBACK FORCEONFEEDBACK -FORCEV -foreach -fprintf framebuffer FRAMECHANGED fre -freopen -frontend +frontends fsanitize Fscreen FSCTL FSINFOCLASS -fstream fte Ftm -fullscreen +Fullscreens fullwidth -func FUNCTIONCALL fuzzer fuzzmain @@ -836,10 +679,10 @@ fwlink GAUSSIAN gci gcx -gcy gdi gdip gdirenderer +Geddy geopol GETALIAS GETALIASES @@ -849,7 +692,6 @@ GETALIASEXESLENGTH GETAUTOHIDEBAREX GETCARETWIDTH getch -getchar GETCLIENTAREAANIMATION GETCOMMANDHISTORY GETCOMMANDHISTORYLENGTH @@ -871,9 +713,9 @@ GETHUNGAPPTIMEOUT GETICON GETITEMDATA GETKEYBOARDLAYOUTNAME +GETKEYSTATE GETLARGESTWINDOWSIZE GETLBTEXT -getline GETMINMAXINFO GETMOUSEINFO GETMOUSEVANISH @@ -883,8 +725,6 @@ GETOBJECT GETPOS GETSELECTIONINFO getset -GETSTATE -GETTEXT GETTEXTLEN GETTITLE GETWAITTOKILLSERVICETIMEOUT @@ -892,7 +732,6 @@ GETWAITTOKILLTIMEOUT GETWHEELSCROLLCHARACTERS GETWHEELSCROLLCHARS GETWHEELSCROLLLINES -getwriter GFEh Gfun gfx @@ -901,24 +740,17 @@ GHIJK GHIJKL GHIJKLM gitfilters -github -gitlab +gitmodules gle GLOBALFOCUS -globals GLYPHENTRY -gmail GMEM GNUC Goldmine gonce -Google goutput -GPUs -grayscale GREENSCROLL Grehan -grep Greyscale gridline groupbox @@ -927,7 +759,6 @@ gsl GTP GTR guc -gui guidatom GValue GWL @@ -937,8 +768,6 @@ HABCDEF Hackathon HALTCOND HANGEUL -hardcoded -hardcodes hashalg HASSTRINGS hbitmap @@ -953,7 +782,6 @@ hdr HDROP hdrstop HEIGHTSCROLL -hfile hfont hfontresource hglobal @@ -961,11 +789,9 @@ hhh hhook hhx HIBYTE -HICON +hicon HIDEWINDOW -HIGHLIGHTTEXT hinst -HINSTANCE Hirots HISTORYBUFS HISTORYNODUP @@ -977,33 +803,25 @@ hkey hkl HKLM hlocal -HLS hlsl -HMENU hmod hmodule hmon -HMONITOR -horiz HORZ hostable hostlib -HOTFIX HPA HPCON hpj -hpp HPR HProvider HREDRAW hresult hrottled -HRSRC hscroll hsl hstr hstring -hsv HTBOTTOMLEFT HTBOTTOMRIGHT HTCAPTION @@ -1011,7 +829,6 @@ HTCLIENT HTLEFT HTMAXBUTTON HTMINBUTTON -html HTMLTo HTRIGHT HTTOP @@ -1022,37 +839,17 @@ HVP hwheel hwnd HWNDPARENT -hxx -IAccessibility -IAction -IApi -IApplication -IBase -ICache -icacls iccex -IChar icket -ico -IComponent ICONERROR Iconified ICONINFORMATION IConsole ICONSTOP -IControl ICONWARNING -ICore -IData IDCANCEL IDD -IDefault -IDesktop -IDevice -IDictionary IDISHWND -IDispatch -IDisposable idl idllib IDOK @@ -1060,35 +857,18 @@ IDR idth idx IDXGI -IDynamic IEnd IEnum -IEnumerable IFACEMETHODIMP -ifdef ification -ifndef -IFont -ifstream IGNOREEND IGNORELANGUAGE -IHigh IHosted iid -IInitialize -IInput -IInspectable -IInteract -IInteractivity IIo -IList -imagemagick -Imatch ime Imm -IMouse IMPEXP -impl inbox inclusivity INCONTEXT @@ -1096,97 +876,59 @@ INFOEX inheritcursor inheritdoc inheritfrom -ini INITCOMMONCONTROLSEX INITDIALOG initguid INITMENU inkscape -inl INLINEPREFIX inlines -INotify -inout -inplace inproc Inputkeyinfo INPUTPROCESSORPROFILE inputrc Inputreadhandledata INSERTMODE -intellisense INTERACTIVITYBASE INTERCEPTCOPYPASTE INTERNALNAME -interop -interoperability inthread intsafe INVALIDARG INVALIDATERECT -IObservable ioctl -iomanip -iostream -iot ipch -ipconfig -IPersist ipp IProperty IPSINK ipsp -IRaw -IRead -IReference -IRender -IScheme -ISelection IShell -IState -IStoryboard -isupper ISwap -iswdigit -iswspace -ISystem iterm itermcolors ITerminal -IText itf Ith itoa IUI -IUia IUnknown ivalid -IValue -IVector -IWait -IWeb -IWin -IWindow -IXaml +IWIC IXMP IXP jconcpp JOBOBJECT JOBOBJECTINFOCLASS jpe -jpeg -jpg JPN -json -jsonc jsoncpp +Jsons jsprovider jumplist KAttrs kawa Kazu kazum -kbd kcub kcud kcuf @@ -1194,13 +936,11 @@ kcuu kernelbase kernelbasestaging KEYBDINPUT -keybinding keychord keydown keyevent KEYFIRST KEYLAST -keymap Keymapping keyscan keystate @@ -1220,22 +960,19 @@ langid LANGUAGELIST lasterror lastexitcode -LATN LAYOUTRTL +lbl LBN -LBound LBUTTON LBUTTONDBLCLK LBUTTONDOWN LBUTTONUP lcb +lci LCONTROL LCTRL lcx LEFTALIGN -LEFTSHIFT -len -lhs libpopcnt libsancov libtickit @@ -1245,11 +982,7 @@ LINESELECTION LINEWRAP LINKERRCAP LINKERROR -linkpath linputfile -Linq -linux -listbox listproperties listptr listptrsize @@ -1265,36 +998,28 @@ LOADONCALL loadu LOBYTE localappdata -localhost locsrc Loewen LOGFONT LOGFONTA LOGFONTW logissue -lowercased loword lparam -LPBYTE LPCCH lpch -LPCOLORREF LPCPLINFO LPCREATESTRUCT lpcs -LPCSTR LPCTSTR -LPCWSTR lpdata LPDBLIST lpdis LPDRAWITEMSTRUCT lpdw -LPDWORD lpelfe lpfn LPFNADDPROPSHEETPAGE -LPINT lpl LPMEASUREITEMSTRUCT LPMINMAXINFO @@ -1304,18 +1029,15 @@ LPNEWCPLINFOA LPNEWCPLINFOW LPNMHDR lpntme -LPPOINT LPPROC LPPROPSHEETPAGE LPPSHNOTIFY lprc -LPRECT lpstr lpsz LPTSTR LPTTFONTLIST lpv -LPVOID LPW LPWCH lpwfx @@ -1323,7 +1045,6 @@ LPWINDOWPOS lpwpos lpwstr LRESULT -lru lsb lsconfig lss @@ -1332,8 +1053,6 @@ lstrcmp lstrcmpi LTEXT LTLTLTLTL -ltrim -ltype LUID luma lval @@ -1342,7 +1061,6 @@ LVERTICAL LWA LWIN lwkmvj -mailto majorly makeappx MAKEINTRESOURCE @@ -1351,12 +1069,11 @@ MAKELANGID MAKELONG MAKELPARAM MAKELRESULT -malloc MAPBITMAP +MAPVIRTUALKEY MAPVK MAXDIMENSTRING maxing -MAXLENGTH MAXSHORT maxval maxversiontested @@ -1372,35 +1089,25 @@ MDs MEASUREITEM megamix memallocator -memcmp -memcpy -memmove -memset MENUCHAR MENUCONTROL MENUDROPALIGNMENT -MENUITEM MENUITEMINFO MENUSELECT -Mersenne messageext -metadata metaproj midl mii MIIM milli -mimetype mincore mindbogglingly -mingw minimizeall minkernel MINMAXINFO minwin minwindef Mip -mkdir MMBB mmcc MMCPL @@ -1413,46 +1120,34 @@ MODERNCORE MONITORINFO MONITORINFOEXW MONITORINFOF -monospaced -monostate MOUSEACTIVATE MOUSEFIRST MOUSEHWHEEL MOUSEMOVE -mousewheel movemask MOVESTART msb -msbuild msctf msctls msdata -msdn msft MSGCMDLINEF MSGF MSGFILTER MSGFLG MSGMARKMODE -MSGS MSGSCROLLMODE MSGSELECTMODE msiexec MSIL msix msrc -msvcrt MSVCRTD msys MTSM -mui -Mul -multiline munged munges murmurhash -mutex -mutexes muxes myapplet mydir @@ -1461,12 +1156,8 @@ Mypair Myval NAMELENGTH nameof -namespace -namespaced namestream -nano natvis -nbsp NCCALCSIZE NCCREATE NCLBUTTONDOWN @@ -1478,9 +1169,7 @@ NCRBUTTONDOWN NCRBUTTONUP NCXBUTTONDOWN NCXBUTTONUP -NDEBUG NEL -NEQ netcoreapp netstandard NEWCPLINFO @@ -1495,21 +1184,16 @@ NEWTEXTMETRICEX Newtonsoft NEXTLINE nfe -Nls NLSMODE nnn NOACTIVATE NOAPPLYNOW NOCLIP -NOCOLOR NOCOMM NOCONTEXTHELP NOCOPYBITS -nodiscard NODUP -noexcept -NOHELP -noinline +noexcepts NOINTEGRALHEIGHT NOINTERFACE NOLINKINFO @@ -1525,13 +1209,13 @@ NONINFRINGEMENT NONPREROTATED nonspace NOOWNERZORDER +Nop NOPAINT NOPQRST noprofile NOREDRAW NOREMOVE NOREPOSITION -noreturn NORMALDISPLAY NOSCRATCH NOSEARCH @@ -1540,23 +1224,17 @@ NOSENDCHANGING NOSIZE NOSNAPSHOT NOTHOUSANDS -nothrow NOTICKS +NOTIMEOUTIFNOTHUNG NOTIMPL -notin -NOTNULL NOTOPMOST NOTRACK NOTSUPPORTED nouicompat nounihan NOUPDATE -novtable -nowait NOYIELD NOZORDER -NPM -npos nrcs NSTATUS ntapi @@ -1568,6 +1246,7 @@ ntdll ntifs ntlpcapi ntm +nto ntrtl ntstatus ntsubauth @@ -1578,30 +1257,25 @@ ntuser NTVDM ntverp NTWIN -nuget nugetversions nullability nullness nullonfailure -nullopt -nullptr +nullopts NULs numlock numpad NUMSCROLL nupkg -nuspec NVIDIA -NVR OACR -oauth objbase ocolor odl -oem oemcp OEMFONT OEMFORMAT +OEMs offboarded OLEAUT OLECHAR @@ -1639,15 +1313,11 @@ OSG OSGENG osign oss -ostream -ostringstream +otepad ouicompat OUnter outdir -outfile -Outof OUTOFCONTEXT -OUTOFMEMORY Outptr outstr OVERLAPPEDWINDOW @@ -1663,15 +1333,12 @@ PAINTPARAMS PAINTSTRUCT PALPC pankaj -params parentable parms passthrough PATCOPY pathcch PATTERNID -PBOOL -PBYTE pcat pcb pcch @@ -1680,7 +1347,6 @@ PCCONSOLE PCD pcg pch -PCHAR PCIDLIST PCIS PCLIENT @@ -1702,12 +1368,10 @@ PCWCH PCWCHAR PCWSTR pda -pdb -pdbonly +Pdbs pdbstr pdtobj pdw -PDWORD pdx peb PEMAGIC @@ -1726,12 +1390,11 @@ pgdn PGONu pguid pgup -PHANDLE phhook phwnd -pid pidl PIDLIST +pids pii pinvoke pipename @@ -1740,20 +1403,12 @@ pixelheight PIXELSLIST PJOBOBJECT pkey -placeholders platforming playsound -plist ploc -PLOC ploca -PLOCA plocm -PLOCM PLOGICAL -plugin -PMv -png pnm PNMLINK pntm @@ -1762,14 +1417,11 @@ POBJECT Podcast POINTSLIST POLYTEXTW -popd -POPF poppack -popup POPUPATTR +popups PORFLG positionals -posix POSTCHARBREAKS POSX POSXSCROLL @@ -1789,18 +1441,12 @@ ppsz ppv ppwch PQRST -pragma prc prealigned -prebuilt -precomp prect prefast -prefilled prefs preinstalled -PRELOAD -PREMULTIPLIED prepopulated presorted PREVENTPINNING @@ -1809,14 +1455,11 @@ PREVIEWWINDOW PREVLINE prg pri -printf prioritization processenv processhost PROCESSINFOCLASS procs -Progman -proj PROPERTYID PROPERTYKEY PROPERTYVAL @@ -1830,7 +1473,6 @@ propvar propvariant propvarutil psa -psd PSECURITY pseudocode pseudoconsole @@ -1838,12 +1480,10 @@ pseudoterminal psh pshn PSHNOTIFY -PSHORT pshpack PSINGLE psl psldl -psm PSNRET PSobject psp @@ -1852,48 +1492,36 @@ psr PSTR psz ptch -ptr -ptrdiff +ptrs ptsz PTYIn PUCHAR -PULONG PUNICODE -pushd -putchar -putwchar -PVOID pwch -PWCHAR PWDDMCONSOLECONTEXT -PWORD pws -pwsh pwstr pwsz pythonw +Qaabbcc qos QRSTU -qsort -queryable QUERYOPEN QUESTIONMARK quickedit QUZ QWER -Qxxxxxx +Qxxxxxxxxxxxxxxx qzmp RAII RALT rasterbar rasterfont rasterization -rawinput RAWPATH raytracers razzlerc rbar -rbegin RBUTTON RBUTTONDBLCLK RBUTTONDOWN @@ -1909,33 +1537,23 @@ RCOCW RCONTROL RCOW rcv -rdbuf readback READCONSOLE READCONSOLEOUTPUT READCONSOLEOUTPUTSTRING -Readline -readme READMODE -readonly -READWRITE -realloc +reallocs reamapping rects redef redefinable Redir -redirector redist REDSCROLL -refactor -refactoring REFCLSID -refcount REFGUID REFIID REFPROPERTYKEY -regex REGISTEROS REGISTERVDM regkey @@ -1956,18 +1574,15 @@ rescap Resequence RESETCONTENT resheader -resizable resmimetype resw resx -retval rfa rfid rftp -rgb -rgba RGBCOLOR rgbi +rgbs rgci rgfae rgfte @@ -1980,29 +1595,24 @@ rgs rgui rgw rgwch -rhs RIGHTALIGN RIGHTBUTTON riid Rike RIPMSG RIS -RMENU -rng roadmap robomac -roundtrip +rosetta +roundtrips RRF RRRGGGBB rsas rtcore RTEXT -rtf RTFTo -Rtl RTLREADING Rtn -rtrim RTTI ruleset runas @@ -2010,19 +1620,18 @@ RUNDLL runformat runft RUNFULLSCREEN +runfuzz runsettings -runtests +runtest runtimeclass runuia runut runxamlformat -rvalue RVERTICAL rvpa RWIN rxvt safearray -SAFECAST safemath sapi sba @@ -2040,12 +1649,10 @@ SCRBUFSIZE screenbuffer SCREENBUFFERINFO screeninfo -screenshot +screenshots scriptload -Scrollable scrollback -scrollbar -Scroller +scrollbars SCROLLFORWARD SCROLLINFO scrolllock @@ -2055,18 +1662,14 @@ SCROLLSCREENBUFFER scursor sddl sdeleted -sdk SDKDDK -searchbox securityappcontainer segfault SELCHANGE SELECTALL -selectany SELECTEDFONT SELECTSTRING Selfhosters -SERIALIZERS SERVERDLL SETACTIVE SETBUDDYINT @@ -2077,7 +1680,6 @@ SETCURSOR SETCURSORINFO SETCURSORPOSITION SETDISPLAYMODE -setfill SETFOCUS SETFONT SETFOREGROUND @@ -2088,30 +1690,24 @@ setintegritylevel SETITEMDATA SETITEMHEIGHT SETKEYSHORTCUTS -setlocal -setlocale SETMENUCLOSE -setmode SETNUMBEROFCOMMANDS SETOS SETPALETTE -SETPOS SETRANGE SETSCREENBUFFERSIZE SETSEL SETTEXTATTRIBUTE SETTINGCHANGE -SETTITLE -setw Setwindow SETWINDOWINFO SFGAO SFGAOF sfi SFINAE +SFolder SFUI sgr -SHANDLE SHCo shcore shellapi @@ -2119,7 +1715,6 @@ shellex shellscalingapi SHFILEINFO SHGFI -SHGFP SHIFTJIS Shl shlguid @@ -2134,7 +1729,6 @@ SHOWNA SHOWNOACTIVATE SHOWNORMAL SHOWWINDOW -SHRT sidebyside SIF SIGDN @@ -2143,7 +1737,6 @@ SINGLETHREADED siup sixel SIZEBOX -sizeof SIZESCROLL SKIPFONT SKIPOWNPROCESS @@ -2166,24 +1759,17 @@ SOURCEBRANCH sourced spammy spand -sprintf -srand -src SRCCODEPAGE SRCCOPY SRCINVERT srcsrv SRCSRVTRG srctool -sre srect srv srvinit srvpipe ssa -ssh -sstream -standalone STARTF STARTUPINFO STARTUPINFOEX @@ -2196,57 +1782,36 @@ Statusline stdafx STDAPI stdc -stdcall stdcpp -stderr -stdexcept -stdin -STDIO STDMETHODCALLTYPE STDMETHODIMP -stdout STGM stl -stoi -stol -stoul stoutapot Stri -strikethrough -stringstream +Stringable STRINGTABLE -strlen strrev strsafe -strtok -structs STUBHEAD STUVWX -STX stylecop SUA subcompartment -subfolder +subfolders subkey SUBLANG -submenu subresource -subspan -substr subsystemconsole subsystemwindows suiteless -svg swapchain swapchainpanel swappable SWMR SWP -swprintf SYMED -symlink SYNCPAINT -sys syscalls SYSCHAR SYSCOMMAND @@ -2266,8 +1831,6 @@ TARG targetentrypoint TARGETLIBS TARGETNAME -targetnametoken -targetsize targetver taskbar tbar @@ -2282,21 +1845,20 @@ TCI tcome tcommandline tcommands +Tdd TDelegated TDP TEAMPROJECT tearoff Teb tellp -telnet -telnetd -templated teraflop terminalcore +terminalinput +terminalrenderdata TERMINALSCROLLING terminfo TEs -testapp testbuildplatform testcon testd @@ -2319,7 +1881,6 @@ texel TExpected textattribute TEXTATTRIBUTEID -textbox textboxes textbuffer TEXTINCLUDE @@ -2327,15 +1888,14 @@ textinfo TEXTMETRIC TEXTMETRICW textmode +texttests TFCAT tfoo TFunction tga -threadpool THUMBPOSITION THUMBTRACK TIcon -tif tilunittests titlebar TITLEISLINKNAME @@ -2348,35 +1908,28 @@ TMAE TMPF TMult tmultiple -tmux -todo +TODOs tofrom tokenhelpers -tokenized -tokenizing -toolbar +toolbars TOOLINFO -tooltip TOOLWINDOW TOPDOWNDIB TOPLEFT -toplevel TOPRIGHT TOpt tosign touchpad -towlower -towupper Tpp Tpqrst tprivapi tracelog tracelogging traceloggingprovider +traceviewpp trackbar TRACKCOMPOSITION trackpad -transcoder transitioning Trd TREX @@ -2385,42 +1938,32 @@ triaging TRIANGLESTRIP Tribool TRIMZEROHEADINGS -truetype trx tsattrs tsf +tsgr TStr TSTRFORMAT TSub TTBITMAP -ttf TTFONT TTFONTLIST tthe tthis TTM TTo -TVPP +tvpp Txtev typechecked -typechecking -typedef -typeid -typeinfo typelib -typename -typeof typeparam TYUI UAC uap uapadmin UAX -ubuntu ucd uch -UCHAR -ucs udk UDM uer @@ -2429,42 +1972,30 @@ uia UIACCESS uiacore uiautomationcore -Uid uielem UIELEMENTENABLEDONLY -uint -uintptr +UINTs ulcch -ulong umul umulh Unadvise unattend -uncomment UNCPRIORITY -undef -Unescape unexpand -Unfocus unhighlighting unhosted -unicode -UNICODESTRING UNICODETEXT UNICRT -uninit uninitialize -uninstall Unintense Uniscribe -unittest unittesting +unittests unk unknwn unmark UNORM unparseable -unpause unregistering untests untextured @@ -2473,11 +2004,6 @@ UPDATEDISPLAY UPDOWN UPKEY UPSS -upvote -uri -url -urlencoded -USASCII usebackq USECALLBACK USECOLOR @@ -2491,41 +2017,29 @@ USEPOSITION userbase USERDATA userdpiapi -username Userp userprivapi -userprofile USERSRV USESHOWWINDOW USESIZE USESTDHANDLES -ushort usp USRDLL -utf -utils utr -uuid -uuidof -uuidv UVWX UVWXY uwa uwp uxtheme -vals Vanara vararg vclib -Vcount vcpkg vcprintf vcxitems -vcxproj vec vectorized VERCTRL -versioning VERTBAR VFT vga @@ -2534,24 +2048,22 @@ viewkind viewports Virt VIRTTERM -Virtualizing vkey +VKKEYSCAN VMs VPA -VPATH VPR VProc VRaw VREDRAW vsc +vsconfig vscprintf VSCROLL vsdevshell vsinfo -vsnprintf vso vspath -vsprintf VSTAMP vstest VSTS @@ -2578,51 +2090,39 @@ WANSUNG WANTARROWS WANTTAB wapproj -wav WAVEFORMATEX wbuilder wch -wchar +wchars WCIA WCIW -WClass -wcout -wcschr -wcscmp -wcscpy WCSHELPER wcsicmp -wcslen wcsnicmp -wcsrchr wcsrev -wcstod -wcstoul wddm wddmcon WDDMCONSOLECONTEXT wdm webpage -website -websocket +websites +websockets wekyb -WEOF wex wextest wextestclass WFill wfopen WHelper -whitelisting +wic WIDTHSCROLL Widthx -Wiki -Wikipedia wil WImpl WINAPI winbase winbasep +wincodec wincon winconp winconpty @@ -2632,14 +2132,12 @@ wincontypes WINCORE windbg WINDEF -WINDIR windll WINDOWALPHA Windowbuffer windowdpiapi WINDOWEDGE windowext -WINDOWFRAME windowime WINDOWINFO windowio @@ -2653,9 +2151,9 @@ windowrect windowsapp windowsinternalstring WINDOWSIZE +windowsshell windowsterminal windowsx -WINDOWTEXT windowtheme WINDOWTITLE winevent @@ -2692,9 +2190,6 @@ WNull wnwb workarea workaround -workflow -WORKITEM -wostream WOutside WOWARM WOWx @@ -2704,7 +2199,6 @@ wpf WPR WPrep WPresent -wprintf wprp wprpi wregex @@ -2721,11 +2215,8 @@ WRunoff WScript wsl WSLENV -wsmatch -wss wstr -wstring -wstringstream +wstrings wsz wtd WTest @@ -2746,9 +2237,8 @@ wyhash wymix wyr xact -xaml Xamlmeta -xargs +xamls xaz xbf xbutton @@ -2772,19 +2262,12 @@ xinxinchaof XManifest XMath XMFLOAT -xml -xmlns -xor xorg -XPosition XResource -xsd xsi -xsize xstyler XSubstantial xtended -xterm XTest XTPOPSGR XTPUSHSGR @@ -2792,26 +2275,22 @@ xtr XTWINOPS xunit xutr -xvalue XVIRTUALSCREEN XWalk xwwyzz xxyyzz yact -YAML YCast YCENTER YCount YDPI -yml YOffset -YPosition -YSize YSubstantial YVIRTUALSCREEN YWalk Zabcdefghijklmnopqrstuvwxyz ZCmd ZCtrl -zsh zxcvbnm +ZYXWVU +ZYXWVUTd diff --git a/.github/actions/spelling/expect/web.txt b/.github/actions/spelling/expect/web.txt index 47ce4058d24..52c1cfd1f0f 100644 --- a/.github/actions/spelling/expect/web.txt +++ b/.github/actions/spelling/expect/web.txt @@ -1,5 +1,3 @@ -http -www WCAG winui appshellintegration diff --git a/.github/actions/spelling/line_forbidden.patterns b/.github/actions/spelling/line_forbidden.patterns index 0120c775a60..31ad2ddcd26 100644 --- a/.github/actions/spelling/line_forbidden.patterns +++ b/.github/actions/spelling/line_forbidden.patterns @@ -1,3 +1,11 @@ +# reject `m_data` as there's a certain OS which has evil defines that break things if it's used elsewhere +# \bm_data\b + +# If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test, +# you might not want to check in code where you were debugging w/ `fit()`, in which case, you might want +# to use this: +#\bfit\( + # s.b. GitHub \bGithub\b @@ -16,6 +24,12 @@ # s.b. greater than \bgreater then\b +# s.b. into +#\sin to\s + +# s.b. opt-in +\sopt in\s + # s.b. less than \bless then\b @@ -27,10 +41,22 @@ \b[Nn]o[nt][- ]existent\b # s.b. preexisting -[Pp]re-existing +[Pp]re[- ]existing + +# s.b. preempt +[Pp]re[- ]empt\b # s.b. preemptively -[Pp]re-emptively +[Pp]re[- ]emptively + +# s.b. reentrancy +[Rr]e[- ]entrancy + +# s.b. reentrant +[Rr]e[- ]entrant + +# s.b. workaround(s) +#\bwork[- ]arounds?\b # Reject duplicate words \s([A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})\s\g{-1}\s diff --git a/.github/actions/spelling/patterns/patterns.txt b/.github/actions/spelling/patterns/patterns.txt index 7360e920f7f..a0e1931f36f 100644 --- a/.github/actions/spelling/patterns/patterns.txt +++ b/.github/actions/spelling/patterns/patterns.txt @@ -27,13 +27,68 @@ ROY\sG\.\sBIV # Python stringprefix / binaryprefix \b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)' +# Automatically suggested patterns +# hit-count: 3831 file-count: 582 +# IServiceProvider +\bI(?=(?:[A-Z][a-z]{2,})+\b) + +# hit-count: 71 file-count: 35 +# Compiler flags +(?:^|[\t ,"'`=(])-[D](?=[A-Z]{2,}|[A-Z][a-z]) +(?:^|[\t ,"'`=(])-[X](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) + +# hit-count: 41 file-count: 28 +# version suffix v# +(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_])) + +# hit-count: 20 file-count: 9 +# hex runs +\b[0-9a-fA-F]{16,}\b + +# hit-count: 10 file-count: 7 +# uuid: +\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b + +# hit-count: 4 file-count: 4 +# mailto urls +mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} + +# hit-count: 4 file-count: 1 +# ANSI color codes +(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m + +# hit-count: 2 file-count: 1 +# latex +\\(?:n(?:ew|ormal|osub)|r(?:enew)|t(?:able(?:of|)|he|itle))(?=[a-z]+) + +# hit-count: 1 file-count: 1 +# hex digits including css/html color classes: +(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b + +# hit-count: 1 file-count: 1 +# Non-English +[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]* + +# hit-count: 1 file-count: 1 +# French +# This corpus only had capital letters, but you probably want lowercase ones as well. +\b[LN]'+[a-z]{2,}\b + # acceptable duplicates # ls directory listings [-bcdlpsw](?:[-r][-w][-sx]){3}\s+\d+\s+(\S+)\s+\g{-1}\s+\d+\s+ # C/idl types + English ... \s(Guid|long|LONG|that) \g{-1}\s + # javadoc / .net -(?:\@(?:groupname|param)|(?:public|private)(?:\s+static|\s+readonly)*)\s+(\w+)\s+\g{-1}\s +(?:[\\@](?:groupname|param)|(?:public|private)(?:\s+static|\s+readonly)*)\s+(\w+)\s+\g{-1}\s + +# Commit message -- Signed-off-by and friends +^\s*(?:(?:Based-on-patch|Co-authored|Helped|Mentored|Reported|Reviewed|Signed-off)-by|Thanks-to): (?:[^<]*<[^>]*>|[^<]*)\s*$ + +# Autogenerated revert commit message +^This reverts commit [0-9a-f]{40}\.$ + # vtmode --vtmode\s+(\w+)\s+\g{-1}\s diff --git a/.github/actions/spelling/reject.txt b/.github/actions/spelling/reject.txt index 4d43bc53eee..301719de47e 100644 --- a/.github/actions/spelling/reject.txt +++ b/.github/actions/spelling/reject.txt @@ -1,31 +1,12 @@ -benefitting -occurences? -Sorce ^attache$ ^attacher$ ^attachers$ +benefitting +occurences? ^dependan.* ^oer$ -^spae$ -^spae-man$ -^spaebook$ -^spaecraft$ -^spaed$ -^spaedom$ -^spaeing$ -^spaeings$ -^spaeman$ -^spaer$ -^Spaerobee$ -^spaes$ -^spaewife$ -^spaewoman$ -^spaework$ -^spaewright$ +Sorce +^[Ss]pae.* ^untill$ ^untilling$ -^wether$ ^wether.* -^wethers$ -^wetherteg$ -^[Ss]pae.* diff --git a/.github/workflows/spelling2.yml b/.github/workflows/spelling2.yml index d8369ca0cd3..446b24343ed 100644 --- a/.github/workflows/spelling2.yml +++ b/.github/workflows/spelling2.yml @@ -1,10 +1,57 @@ # spelling.yml is blocked per https://github.com/check-spelling/check-spelling/security/advisories/GHSA-g86g-chm8-7r2p name: Spell checking + +# Comment management is handled through a secondary job, for details see: +# https://github.com/check-spelling/check-spelling/wiki/Feature%3A-Restricted-Permissions +# +# `jobs.comment-push` runs when a push is made to a repository and the `jobs.spelling` job needs to make a comment +# (in odd cases, it might actually run just to collapse a commment, but that's fairly rare) +# it needs `contents: write` in order to add a comment. +# +# `jobs.comment-pr` runs when a pull_request is made to a repository and the `jobs.spelling` job needs to make a comment +# or collapse a comment (in the case where it had previously made a comment and now no longer needs to show a comment) +# it needs `pull-requests: write` in order to manipulate those comments. + +# Updating pull request branches is managed via comment handling. +# For details, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-expect-list +# +# These elements work together to make it happen: +# +# `on.issue_comment` +# This event listens to comments by users asking to update the metadata. +# +# `jobs.update` +# This job runs in response to an issue_comment and will push a new commit +# to update the spelling metadata. +# +# `with.experimental_apply_changes_via_bot` +# Tells the action to support and generate messages that enable it +# to make a commit to update the spelling metadata. +# +# `with.ssh_key` +# In order to trigger workflows when the commit is made, you can provide a +# secret (typically, a write-enabled github deploy key). +# +# For background, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-with-deploy-key + on: - pull_request_target: push: - branches: ["**"] - tags-ignore: ["**"] + branches: + - "**" + tags-ignore: + - "**" + pull_request_target: + branches: + - "**" + tags-ignore: + - "**" + types: + - 'opened' + - 'reopened' + - 'synchronize' + issue_comment: + types: + - 'created' jobs: spelling: @@ -24,23 +71,64 @@ jobs: steps: - name: check-spelling id: spelling - uses: check-spelling/check-spelling@v0.0.20 + uses: check-spelling/check-spelling@v0.0.21 with: suppress_push_for_open_pull_request: 1 checkout: true + check_file_names: 1 + spell_check_this: check-spelling/spell-check-this@prerelease post_comment: 0 + use_magic_file: 1 + extra_dictionary_limit: 10 + extra_dictionaries: + cspell:software-terms/src/software-terms.txt + cspell:python/src/python/python-lib.txt + cspell:node/node.txt + cspell:cpp/src/stdlib-c.txt + cspell:cpp/src/stdlib-cpp.txt + cspell:fullstack/fullstack.txt + cspell:filetypes/filetypes.txt + cspell:html/html.txt + cspell:cpp/src/compiler-msvc.txt + cspell:python/src/common/extra.txt + cspell:powershell/powershell.txt + cspell:aws/aws.txt + cspell:cpp/src/lang-keywords.txt + cspell:npm/npm.txt + cspell:dotnet/dotnet.txt + cspell:python/src/python/python.txt + cspell:css/css.txt + cspell:cpp/src/stdlib-cmath.txt + check_extra_dictionaries: '' - comment: - name: Report + comment-push: + name: Report (Push) + # If your workflow isn't running on push, you can remove this job runs-on: ubuntu-latest needs: spelling permissions: contents: write + if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push' + steps: + - name: comment + uses: check-spelling/check-spelling@v0.0.21 + with: + checkout: true + spell_check_this: check-spelling/spell-check-this@prerelease + task: ${{ needs.spelling.outputs.followup }} + + comment-pr: + name: Report (PR) + # If you workflow isn't running on pull_request*, you can remove this job + runs-on: ubuntu-latest + needs: spelling + permissions: pull-requests: write - if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name != 'push' + if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request') steps: - name: comment - uses: check-spelling/check-spelling@v0.0.20 + uses: check-spelling/check-spelling@v0.0.21 with: checkout: true + spell_check_this: check-spelling/spell-check-this@prerelease task: ${{ needs.spelling.outputs.followup }} From 953d87b1adef4c4dcbc513ffd457029cc48a9768 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 17 Nov 2022 14:14:18 -0600 Subject: [PATCH 25/61] runformat --- src/cascadia/TerminalControl/TermControl.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 805525163e5..208d490bc9a 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -3122,7 +3122,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - winrt::fire_and_forget TermControl::_coreFoundMatch(const IInspectable& /*sender*/, Control::FoundResultsArgs args) { - co_await wil::resume_foreground(Dispatcher()); if (auto automationPeer{ Automation::Peers::FrameworkElementAutomationPeer::FromElement(*this) }) { From 28a71b10ac2bb2877f64788d8538f7c37ff5902c Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 30 Nov 2022 13:36:42 -0600 Subject: [PATCH 26/61] this doesn't fail locally. Maybe it got fixed in the merge with main? --- src/cascadia/TerminalControl/ControlCore.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 290b0c85094..64768adf88f 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1664,7 +1664,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation // (e.g., when the index is -1 or there are no matches) std::optional> SearchState::GetCurrentMatch() { - if (matches.has_value() && currentMatchIndex > -1 && currentMatchIndex < matches->size()) + if (matches.has_value() && + currentMatchIndex > -1 && + currentMatchIndex < matches->size()) { return til::at(matches.value(), currentMatchIndex); } From 2dc568391d0cf58657d0ff0a1c86158c938129f5 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 1 Dec 2022 16:22:22 -0600 Subject: [PATCH 27/61] -Wall --- src/cascadia/TerminalControl/ControlCore.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index b7e4be0264d..f9e0465f09f 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1665,7 +1665,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation // (e.g., when the index is -1 or there are no matches) std::optional> SearchState::GetCurrentMatch() { - if (matches.has_value() && currentMatchIndex > -1 && currentMatchIndex < matches->size()) + if (matches.has_value() && + currentMatchIndex > -1 && + static_cast(currentMatchIndex) < matches->size()) { return til::at(matches.value(), currentMatchIndex); } From b288f5dec97a9170d5e8db05efb97863b75c537a Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Sun, 29 Jan 2023 09:54:52 -0600 Subject: [PATCH 28/61] oops missed this merge conflict --- src/cascadia/TerminalControl/ControlCore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 7eebab62cf1..357573136fa 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1683,7 +1683,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { for (auto&& [start, end] : *(_searchState->matches)) { - results.Append(start.Y); + results.Append(start.y); } } return results; From 09eb0a91a6d30b84fc7effd612a199e1330ee9e4 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 13 Jul 2023 13:34:08 -0500 Subject: [PATCH 29/61] some minor review nits --- src/cascadia/TerminalControl/TermControl.cpp | 6 +++--- src/cascadia/TerminalControl/TermControl.h | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 6c1239551d2..5e83e85d749 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -329,12 +329,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation drawPip(m.Start.Y, false, brush); } - auto searchMatches{ _core.MatchRows() }; + const auto searchMatches{ _core.MatchRows() }; if (searchMatches.Size() > 0 && _searchBox->Visibility() == Visibility::Visible) { - auto fgColor{ _core.ForegroundColor() }; + const til::color fgColor{ _core.ForegroundColor() }; Media::SolidColorBrush searchMarkBrush{}; - searchMarkBrush.Color(static_cast(fgColor)); + searchMarkBrush.Color(fgColor); for (const auto m : searchMatches) { drawPip(m, true, searchMarkBrush); diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 9518b6dd12e..77964e63526 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -334,7 +334,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation double _GetAutoScrollSpeed(double cursorDistanceFromBorder) const; - // winrt::Windows::Foundation::IAsyncOperation _SearchOne(Search& search); void _Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive); void _SearchChanged(const winrt::hstring& text, const bool goForward, const bool caseSensitive); From e4056caaeb1ceea1b0958f26757aa48cff1b6929 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 18 Jul 2023 15:10:57 -0500 Subject: [PATCH 30/61] PR nits --- src/cascadia/TerminalControl/ControlCore.cpp | 2 +- src/cascadia/TerminalControl/ControlCore.h | 2 +- src/cascadia/TerminalControl/ControlCore.idl | 2 +- .../Resources/en-US/Resources.resw | 58 ++++++++++--------- .../TerminalControl/SearchBoxControl.cpp | 16 ++--- src/cascadia/TerminalControl/TermControl.cpp | 2 +- 6 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index b25ee10302a..cb14f34a650 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1772,7 +1772,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _searchState.reset(); } - Windows::Foundation::Collections::IVector ControlCore::MatchRows() + Windows::Foundation::Collections::IVector ControlCore::SearchResultRows() { auto results = winrt::single_threaded_vector(); if (_bufferChangedSinceSearch) diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 712f3d562fd..c99acd7d858 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -225,7 +225,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const bool caseSensitive); void SearchChanged(const winrt::hstring& text, const bool goForward, const bool caseSensitive); void ExitSearch(); - Windows::Foundation::Collections::IVector MatchRows(); + Windows::Foundation::Collections::IVector SearchResultRows(); void LeftClickOnTerminal(const til::point terminalPosition, const int numberOfClicks, diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index f6c0afc30e4..2418b0de898 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -125,7 +125,7 @@ namespace Microsoft.Terminal.Control void Search(String text, Boolean goForward, Boolean caseSensitive); void SearchChanged(String text, Boolean goForward, Boolean caseSensitive); void ExitSearch(); - IVector MatchRows { get; }; + IVector SearchResultRows { get; }; Microsoft.Terminal.Core.Color ForegroundColor { get; }; Microsoft.Terminal.Core.Color BackgroundColor { get; }; diff --git a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw index c47e4d90a29..dad5d1c8aaf 100644 --- a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw @@ -1,17 +1,17 @@ - @@ -186,6 +186,10 @@ Searching... Will be presented near the search box when Find operation is running. + + {0}/{1} + Will be displayed to indicate what result the user has selected, of how many total results. {0} will be replaced with the index of the current result. {1} will be replaced with the total number of results. + Invalid URI Whenever we encounter an invalid URI or URL we show this string as a warning. diff --git a/src/cascadia/TerminalControl/SearchBoxControl.cpp b/src/cascadia/TerminalControl/SearchBoxControl.cpp index e6c3cdd9322..4c2233debae 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.cpp +++ b/src/cascadia/TerminalControl/SearchBoxControl.cpp @@ -274,29 +274,29 @@ namespace winrt::Microsoft::Terminal::Control::implementation { return RS_(L"TermControl_NoMatch"); } - std::wstringstream ss; + + std::wstring currentString; + std::wstring totalString; if (currentMatch < 0 || currentMatch > (MaximumTotalResultsToShowInStatus - 1)) { - ss << CurrentIndexTooHighStatus; + currentString = CurrentIndexTooHighStatus; } else { - ss << currentMatch + 1; + currentString = fmt::format(L"{}", currentMatch + 1); } - ss << StatusDelimiter; - if (totalMatches > MaximumTotalResultsToShowInStatus) { - ss << TotalResultsTooHighStatus; + totalString = TotalResultsTooHighStatus; } else { - ss << totalMatches; + totalString = fmt::format(L"{}", totalMatches); } - return ss.str().data(); + return winrt::hstring{ fmt::format(RS_(L"TermControl_NumResults").c_str(), currentString, totalString) }; } // Method Description: diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 6f4dc876867..b7737a56499 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -329,7 +329,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation drawPip(m.Start.Y, false, brush); } - const auto searchMatches{ _core.MatchRows() }; + const auto searchMatches{ _core.SearchResultRows() }; if (searchMatches.Size() > 0 && _searchBox->Visibility() == Visibility::Visible) { const til::color fgColor{ _core.ForegroundColor() }; From 28bce48b298b2ed461e5e8e434eca9b3a1ed028a Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 18 Jul 2023 15:28:16 -0500 Subject: [PATCH 31/61] the codeformat check should just commit the fix itself >__< --- src/cascadia/TerminalControl/ControlCore.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index cb14f34a650..83187bdf093 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1470,9 +1470,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (_inUnitTests) [[unlikely]] - { - _ScrollPositionChangedHandlers(*this, update); - } + { + _ScrollPositionChangedHandlers(*this, update); + } else { const auto shared = _shared.lock_shared(); From c2f36cc44ed649d6a7d4cfc7e3d70347d71982ac Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 19 Jul 2023 14:17:58 -0500 Subject: [PATCH 32/61] this'll do --- src/cascadia/TerminalControl/ControlCore.cpp | 9 ++++++--- src/cascadia/TerminalCore/Terminal.hpp | 3 +++ src/cascadia/TerminalCore/TerminalApi.cpp | 2 +- src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp | 14 +++++++++++--- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 64cdd119a40..046cac4ea4f 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -217,6 +217,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation [weakThis = get_weak()](const auto& update) { if (auto core{ weakThis.get() }) { + // Begin a search + core->_terminal->AlwaysNotifyOnBufferRotation(true); core->_searchState.emplace(update); core->_SearchAsync(std::nullopt); } @@ -1470,9 +1472,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (_inUnitTests) [[unlikely]] - { - _ScrollPositionChangedHandlers(*this, update); - } + { + _ScrollPositionChangedHandlers(*this, update); + } else { const auto shared = _shared.lock_shared(); @@ -1769,6 +1771,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } void ControlCore::ExitSearch() { + _terminal->AlwaysNotifyOnBufferRotation(false); _searchState.reset(); } diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index fc392bc7e0c..4e3b0348876 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -16,6 +16,7 @@ #include "../../cascadia/terminalcore/ITerminalInput.hpp" #include +#include inline constexpr std::wstring_view linkPattern{ LR"(\b(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|$!:,.;]*[A-Za-z0-9+&@#/%=~_|$])" }; inline constexpr size_t TaskbarMinProgress{ 10 }; @@ -103,6 +104,8 @@ class Microsoft::Terminal::Core::Terminal final : const til::point& end, const bool fromUi); + til::property AlwaysNotifyOnBufferRotation; + #pragma region ITerminalApi // These methods are defined in TerminalApi.cpp void ReturnResponse(const std::wstring_view response) override; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index a32a1ddb496..a144f489387 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -466,7 +466,7 @@ void Terminal::NotifyBufferRotation(const int delta) const auto oldScrollOffset = _scrollOffset; _PreserveUserScrollOffset(delta); - if (_scrollOffset != oldScrollOffset || hasScrollMarks) + if (_scrollOffset != oldScrollOffset || hasScrollMarks || AlwaysNotifyOnBufferRotation()) { _NotifyScrollEvent(); } diff --git a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp index 42d2ceebdc3..12223e94a63 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp @@ -154,6 +154,13 @@ void ScrollTest::TestNotifyScrolling() // SHRT_MAX // - Have a selection + BEGIN_TEST_METHOD_PROPERTIES() + TEST_METHOD_PROPERTY(L"Data:notifyOnCircling", L"{false, true}") + END_TEST_METHOD_PROPERTIES(); + INIT_TEST_PROPERTY(bool, notifyOnCircling, L"Controls whether we should always request scroll notifications"); + + _term->AlwaysNotifyOnBufferRotation(notifyOnCircling); + Log::Comment(L"Watch out - this test takes a while to run, and won't " L"output anything unless in encounters an error. This is expected."); @@ -181,10 +188,11 @@ void ScrollTest::TestNotifyScrolling() auto scrolled = currentRow >= TerminalViewHeight - 1; // When we circle the buffer, the scroll bar's position does not change. - // However, as of GH#14045, we still want to send notifications, so the - // control layer can update the position of marks on the scrollbar. + // However, as of GH#14045, we will send a notification IF the control + // requested on (by setting AlwaysNotifyOnBufferRotation) auto circledBuffer = currentRow >= totalBufferSize - 1; - auto expectScrollBarNotification = scrolled || circledBuffer; + auto expectScrollBarNotification = (scrolled && !circledBuffer) || // If we scrolled, but didn't circle the buffer OR + (circledBuffer && notifyOnCircling); // we circled AND we asked for notifications. if (expectScrollBarNotification) { From aa4730812c2a17bfaad882efaeb988ba9029dba5 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 19 Jul 2023 15:04:10 -0500 Subject: [PATCH 33/61] I think this is probably unreasonably cheecky --- src/inc/til/winrt.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/inc/til/winrt.h b/src/inc/til/winrt.h index 28bcefdf354..0badc08b65a 100644 --- a/src/inc/til/winrt.h +++ b/src/inc/til/winrt.h @@ -23,11 +23,13 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" } operator bool() const noexcept { +#ifdef WINRT_Windows_Foundation_H if constexpr (std::is_same_v) { return !_value.empty(); } else +#endif { return _value; } From 876d4faf53713d1a0c4a3d4034905f3d0c3ce006 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 19 Jul 2023 16:19:32 -0500 Subject: [PATCH 34/61] audit mode more --- src/cascadia/TerminalControl/ControlCore.cpp | 6 +++--- src/inc/til/winrt.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 046cac4ea4f..d40f1bc5344 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1472,9 +1472,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (_inUnitTests) [[unlikely]] - { - _ScrollPositionChangedHandlers(*this, update); - } + { + _ScrollPositionChangedHandlers(*this, update); + } else { const auto shared = _shared.lock_shared(); diff --git a/src/inc/til/winrt.h b/src/inc/til/winrt.h index 0badc08b65a..af479319d64 100644 --- a/src/inc/til/winrt.h +++ b/src/inc/til/winrt.h @@ -13,7 +13,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" property& operator=(const property& other) = default; - T operator()() const + T operator()() const noexcept { return _value; } From d707096ba6122cd6967d8b631055295991ce32bd Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 9 Aug 2023 08:23:39 -0500 Subject: [PATCH 35/61] format --- src/cascadia/TerminalControl/ControlCore.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index d40f1bc5344..5cecd472774 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1470,8 +1470,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation viewHeight, bufferSize) }; - if (_inUnitTests) - [[unlikely]] + if (_inUnitTests) [[unlikely]] { _ScrollPositionChangedHandlers(*this, update); } From fd77061c42f48e1ee7e57f992074ace9aae6e669 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 14 Aug 2023 06:14:35 -0500 Subject: [PATCH 36/61] resave resw in VS --- .../Resources/en-US/Resources.resw | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw index dad5d1c8aaf..a604fc912e4 100644 --- a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw @@ -1,17 +1,17 @@ - @@ -288,4 +288,4 @@ Please either install the missing font or choose another one. Select output The tooltip for a button for selecting all of a command's output - + \ No newline at end of file From c97721c352e478c60fe3c381f4b00630ce2777dc Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 14 Aug 2023 07:15:27 -0500 Subject: [PATCH 37/61] PR notes --- src/cascadia/TerminalControl/ControlCore.cpp | 46 ++++++++++++++----- src/cascadia/TerminalControl/ControlCore.h | 4 ++ .../TerminalControl/SearchBoxControl.cpp | 12 ++--- src/cascadia/TerminalControl/TermControl.cpp | 4 +- 4 files changed, 47 insertions(+), 19 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index bb163b4612a..7996811399a 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1473,10 +1473,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation viewHeight, bufferSize) }; - if (_inUnitTests) [[unlikely]] - { - _ScrollPositionChangedHandlers(*this, update); - } + if (_inUnitTests) + [[unlikely]] + { + _ScrollPositionChangedHandlers(*this, update); + } else { const auto shared = _shared.lock_shared(); @@ -1618,6 +1619,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Collect up all the matches on a BG thread. co_await winrt::resume_background(); + auto core = weakThis.get(); + if (!core) + { + // We've been destroyed since we started this coroutine. + // Just bail + co_return; + } + // here, `this` is safe to use for the rest of the method. `core` will keep us alive. + // 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(), @@ -1644,6 +1654,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation co_return; } } + _searchState->matches.emplace(std::move(matches)); _bufferChangedSinceSearch = false; } @@ -1657,9 +1668,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation const bool goForward, const bool caseSensitive) { - // Clear the selection reset the anchor - _terminal->ClearSelection(); - _renderer->TriggerSelection(); + { + auto lock = _terminal->LockForWriting(); + // Clear the selection reset the anchor + _terminal->ClearSelection(); + _renderer->TriggerSelection(); + } const auto sensitivity = caseSensitive ? Search::Sensitivity::CaseSensitive : Search::Sensitivity::CaseInsensitive; @@ -1779,20 +1793,30 @@ namespace winrt::Microsoft::Terminal::Control::implementation Windows::Foundation::Collections::IVector ControlCore::SearchResultRows() { - auto results = winrt::single_threaded_vector(); + auto results = std::vector(); if (_bufferChangedSinceSearch) { - return results; + return winrt::single_threaded_vector(); } + // use a map to remove duplicates + std::map rows; + if (_searchState.has_value() && _searchState->matches.has_value()) { for (auto&& [start, end] : *(_searchState->matches)) { - results.Append(start.y); + const auto row = start.y; + + // First check if it's in the map + if (rows.find(row) == rows.end()) + { + rows[row] = true; + results.push_back(row); + } } } - return results; + return winrt::single_threaded_vector(std::move(results)); } void ControlCore::Close() diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 58cb9255806..e38646e6d06 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -67,6 +67,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation { } + // Why is this an optional vector, instead of just checking if it's empty? + // * No _searchState? Then we have no search started. + // * _searchState, no _matches? We haven't run the search yet. + // * _searchState, _matches has 0 results? We didn't find anything std::optional>> matches; void UpdateIndex(bool goForward); diff --git a/src/cascadia/TerminalControl/SearchBoxControl.cpp b/src/cascadia/TerminalControl/SearchBoxControl.cpp index 4c2233debae..ad7518e553a 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.cpp +++ b/src/cascadia/TerminalControl/SearchBoxControl.cpp @@ -77,6 +77,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation { if (e.OriginalKey() == winrt::Windows::System::VirtualKey::Enter) { + // If the buttons are disabled, then don't allow enter to search either. + if (!GoForwardButton().IsEnabled() || !GoBackwardButton().IsEnabled()) + { + return; + } + const auto state = CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift); if (WI_IsFlagSet(state, CoreVirtualKeyStates::Down)) { @@ -343,12 +349,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation void SearchBoxControl::SetStatus(int32_t totalMatches, int32_t currentMatch) { const auto status = _FormatStatus(totalMatches, currentMatch); - const auto requiredWidth = _TextWidth(status, StatusBox().FontSize()); - if (requiredWidth > StatusBox().Width()) - { - StatusBox().Width(requiredWidth); - } - StatusBox().Text(status); } diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index cf2eea53b21..83c8bc8b9b6 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -412,9 +412,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation // search button or press enter. // In the live search mode it will be also triggered once every time search criteria changes // Arguments: - // - text: not used + // - text: the text to search // - goForward: boolean that represents if the current search direction is forward - // - caseSensitive: not used + // - caseSensitive: boolean that represents if the current search is case sensitive // Return Value: // - void TermControl::_Search(const winrt::hstring& text, From b1107de808a12d30c23cb8b0d26691adfe02fea7 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 14 Aug 2023 12:41:37 -0500 Subject: [PATCH 38/61] this formatter man --- src/cascadia/TerminalControl/ControlCore.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 7996811399a..e53ac8128d0 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1473,11 +1473,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation viewHeight, bufferSize) }; - if (_inUnitTests) - [[unlikely]] - { - _ScrollPositionChangedHandlers(*this, update); - } + if (_inUnitTests) [[unlikely]] + { + _ScrollPositionChangedHandlers(*this, update); + } else { const auto shared = _shared.lock_shared(); From 462b8d228d1f8add0467b7c80c1a88802abdb669 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 21 Aug 2023 14:21:02 +0200 Subject: [PATCH 39/61] Use ICU for text search --- .github/actions/spelling/allow/apis.txt | 1 + .github/actions/spelling/expect/expect.txt | 52 +-- src/buffer/out/Row.cpp | 207 +++++++++--- src/buffer/out/Row.hpp | 44 ++- src/buffer/out/UTextAdapter.cpp | 262 +++++++++++++++ src/buffer/out/UTextAdapter.h | 13 + src/buffer/out/lib/bufferout.vcxproj | 2 + src/buffer/out/search.cpp | 332 +++--------------- src/buffer/out/search.h | 62 +--- src/buffer/out/textBuffer.cpp | 336 ++++++++----------- src/buffer/out/textBuffer.hpp | 26 +- src/cascadia/TerminalControl/ControlCore.cpp | 37 +- src/cascadia/TerminalControl/ControlCore.h | 10 +- src/cascadia/TerminalControl/ControlCore.idl | 1 + src/cascadia/TerminalControl/TermControl.cpp | 1 + src/cascadia/TerminalCore/Terminal.cpp | 12 +- src/cascadia/TerminalCore/Terminal.hpp | 1 - src/cascadia/TerminalCore/TerminalApi.cpp | 2 - src/host/_stream.cpp | 4 +- src/host/selectionInput.cpp | 7 +- src/host/telemetry.cpp | 38 --- src/host/telemetry.hpp | 6 - src/inc/til/at.h | 1 + src/interactivity/win32/find.cpp | 68 ++-- src/terminal/adapter/adaptDispatch.cpp | 8 +- src/types/UiaTextRangeBase.cpp | 70 ++-- src/types/UiaTracing.cpp | 20 +- src/types/UiaTracing.h | 16 +- 28 files changed, 823 insertions(+), 816 deletions(-) create mode 100644 src/buffer/out/UTextAdapter.cpp create mode 100644 src/buffer/out/UTextAdapter.h diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 08b1c6bcadb..1f85a1afab2 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -52,6 +52,7 @@ futex GETDESKWALLPAPER GETHIGHCONTRAST GETMOUSEHOVERTIME +GETTEXTLENGTH Hashtable HIGHCONTRASTON HIGHCONTRASTW diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 626777f6bdb..3a69241e2fd 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -7,7 +7,6 @@ ABCF abgr abi ABORTIFHUNG -ACCESSTOKEN acidev ACIOSS ACover @@ -117,10 +116,8 @@ binplace binplaced bitcoin bitcrazed -bitflag bitmask BITOPERATION -bitsets BKCOLOR BKGND Bksp @@ -149,12 +146,12 @@ bufferout buffersize buflen buildtransitive -BUILDURI burriter BValue bytebuffer cac cacafire +CALLCONV capslock CARETBLINKINGENABLED CARRIAGERETURN @@ -198,7 +195,6 @@ CHT Cic cielab Cielab -Clcompile CLE cleartype CLICKACTIVE @@ -229,7 +225,6 @@ codepage codepath codepoints coinit -COLLECTIONURI colorizing COLORMATRIX COLORREFs @@ -307,7 +302,6 @@ coordnew COPYCOLOR CORESYSTEM cotaskmem -countof CPG cpinfo CPINFOEX @@ -315,7 +309,6 @@ CPLINFO cplusplus CPPCORECHECK cppcorecheckrules -cpprest cpprestsdk cppwinrt CProc @@ -382,7 +375,6 @@ dai DATABLOCK DBatch dbcs -DBCSCHAR DBCSFONT dbg DBGALL @@ -504,7 +496,6 @@ devicecode Dext DFactory DFF -dhandler dialogbox directio DIRECTX @@ -522,7 +513,6 @@ dllmain DLLVERSIONINFO DLOAD DLOOK -dmp DONTCARE doskey dotnet @@ -600,7 +590,6 @@ eplace EPres EQU ERASEBKGND -etcoreapp ETW EUDC EVENTID @@ -642,7 +631,6 @@ FGs FILEDESCRIPTION FILESUBTYPE FILESYSPATH -fileurl FILEW FILLATTR FILLCONSOLEOUTPUT @@ -824,7 +812,6 @@ HIWORD HKCU hkey hkl -HKLM hlocal hlsl HMB @@ -832,7 +819,6 @@ HMK hmod hmodule hmon -homeglyphs homoglyph HORZ hostable @@ -941,7 +927,6 @@ IUI IUnknown ivalid IWIC -IXMP IXP jconcpp JOBOBJECT @@ -965,7 +950,6 @@ kernelbasestaging KEYBDINPUT keychord keydown -keyevent KEYFIRST KEYLAST Keymapping @@ -1012,7 +996,6 @@ LINEWRAP LINKERRCAP LINKERROR linputfile -listproperties listptr listptrsize lld @@ -1131,7 +1114,6 @@ MIIM milli mincore mindbogglingly -minimizeall minkernel MINMAXINFO minwin @@ -1318,7 +1300,6 @@ onecoreuuid ONECOREWINDOWS onehalf oneseq -ONLCR openbash opencode opencon @@ -1328,13 +1309,6 @@ openps openvt ORIGINALFILENAME osc -OSCBG -OSCCT -OSCFG -OSCRCC -OSCSCB -OSCSCC -OSCWT OSDEPENDSROOT OSG OSGENG @@ -1453,7 +1427,6 @@ PPEB ppf ppguid ppidl -pplx PPROC ppropvar ppsi @@ -1468,7 +1441,6 @@ prealigned prect prefast prefs -preinstalled prepopulated presorted PREVENTPINNING @@ -1481,7 +1453,6 @@ prioritization processenv processhost PROCESSINFOCLASS -procs PROPERTYID PROPERTYKEY PROPERTYVAL @@ -1496,7 +1467,6 @@ propvariant propvarutil psa PSECURITY -pseudocode pseudoconsole pseudoterminal psh @@ -1776,7 +1746,6 @@ SND SOLIDBOX Solutiondir somefile -SOURCEBRANCH sourced spammy SRCCODEPAGE @@ -1828,7 +1797,6 @@ SUBLANG subresource subsystemconsole subsystemwindows -suiteless swapchain swapchainpanel swappable @@ -1873,7 +1841,6 @@ tcommands Tdd TDelegated TDP -TEAMPROJECT tearoff Teb Techo @@ -1885,23 +1852,18 @@ terminalrenderdata TERMINALSCROLLING terminfo TEs -testbuildplatform testcon testd -testdlls testenv testlab testlist testmd -testmode testname -testnameprefix TESTNULL testpass testpasses testtestabc testtesttesttesttest -testtimeout TEXCOORD texel TExpected @@ -1929,7 +1891,6 @@ TJson TLambda TLDP TLEN -Tlgdata TMAE TMPF TMult @@ -1989,11 +1950,13 @@ UAC uap uapadmin UAX +UBool ucd uch udk UDM uer +UError uget uia UIACCESS @@ -2023,13 +1986,14 @@ unknwn UNORM unparseable unregistering -untests untextured untimes UPDATEDISPLAY UPDOWN UPKEY UPSS +uregex +URegular usebackq USECALLBACK USECOLOR @@ -2051,6 +2015,9 @@ USESIZE USESTDHANDLES usp USRDLL +utext +UText +UTEXT utr UVWX UVWXY @@ -2134,7 +2101,6 @@ WDDMCONSOLECONTEXT wdm webpage websites -websockets wekyb wex wextest @@ -2162,7 +2128,6 @@ windbg WINDEF windll WINDOWALPHA -Windowbuffer windowdpiapi WINDOWEDGE windowext @@ -2306,7 +2271,6 @@ xunit xutr XVIRTUALSCREEN XWalk -xxyyzz yact YCast YCENTER diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 760f8cf501f..c933de6950e 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -10,8 +10,19 @@ #include "textBuffer.hpp" #include "../../types/inc/GlyphWidth.hpp" +// It would be nice to add checked array access in the future, but it's a little annoying to do so without imparting +// performance (including Debug performance). Other languages are a little bit more ergonomic there than C++. +#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).) +#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4). +#pragma warning(disable : 26472) // Don't use a static_cast for arithmetic conversions. Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1). + extern "C" int __isa_available; +constexpr auto clamp(auto value, auto lo, auto hi) +{ + return value < lo ? lo : (value > hi ? hi : value); +} + // The STL is missing a std::iota_n analogue for std::iota, so I made my own. template constexpr OutIt iota_n(OutIt dest, Diff count, T val) @@ -71,6 +82,82 @@ constexpr OutIt copy_n_small(InIt first, Diff count, OutIt dest) return dest; } +CharToColumnMapper::CharToColumnMapper(const wchar_t* chars, const uint16_t* charOffsets, ptrdiff_t lastCharOffset, til::CoordType currentColumn) noexcept : + _chars{ chars }, + _charOffsets{ charOffsets }, + _lastCharOffset{ lastCharOffset }, + _currentColumn{ currentColumn } +{ +} + +til::CoordType CharToColumnMapper::GetLeadingColumnAt(ptrdiff_t offset) noexcept +{ + offset = offset < 0 ? 0 : (offset > _lastCharOffset ? _lastCharOffset : offset); + + auto col = _currentColumn; + const auto currentOffset = _charOffsets[col]; + + // Goal: Move the _currentColumn cursor to a cell which contains the given target offset. + // Depending on where the target offset is we have to either search forward or backward. + if (offset < currentOffset) + { + // Backward search. + // Goal: Find the first preceding column where the offset is <= the target offset. This results in the first + // cell that contains our target offset, even if that offset is in the middle of a long grapheme. + // + // We abuse the fact that the trailing half of wide glyphs is marked with CharOffsetsTrailer to our advantage. + // Since they're >0x8000, the `offset < _charOffsets[col]` check will always be true and ensure we iterate over them. + // + // Since _charOffsets cannot contain negative values and because offset has been + // clamped to be positive we naturally exit when reaching the first column. + for (; offset < _charOffsets[col - 1]; --col) + { + } + } + else if (offset > currentOffset) + { + // Forward search. + // Goal: Find the first subsequent column where the offset is > the target offset. + // We stop 1 column before that however so that the next loop works correctly. + // It's the inverse of the loop above. + // + // Since offset has been clamped to be at most 1 less than the maximum + // _charOffsets value the loop naturally exits before hitting the end. + for (; offset >= (_charOffsets[col + 1] & CharOffsetsMask); ++col) + { + } + // Now that we found the cell that definitely includes this char offset, + // we have to iterate back to the cell's starting column. + for (; WI_IsFlagSet(_charOffsets[col], CharOffsetsTrailer); --col) + { + } + } + + _currentColumn = col; + return col; +} + +til::CoordType CharToColumnMapper::GetTrailingColumnAt(ptrdiff_t offset) noexcept +{ + auto col = GetLeadingColumnAt(offset); + // This loop is a little redundant with the forward search loop in GetLeadingColumnAt() + // but it's realistically not worth caring about this. This code is not a bottleneck. + for (; WI_IsFlagSet(_charOffsets[col + 1], CharOffsetsTrailer); ++col) + { + } + return col; +} + +til::CoordType CharToColumnMapper::GetLeadingColumnAt(const wchar_t* str) noexcept +{ + return GetLeadingColumnAt(str - _chars); +} + +til::CoordType CharToColumnMapper::GetTrailingColumnAt(const wchar_t* str) noexcept +{ + return GetTrailingColumnAt(str - _chars); +} + // Routine Description: // - constructor // Arguments: @@ -118,10 +205,16 @@ LineRendition ROW::GetLineRendition() const noexcept return _lineRendition; } -uint16_t ROW::GetLineWidth() const noexcept +// Returns the index 1 past the last (technically) valid column in the row. +// The interplay between the old console and newer VT APIs which support line renditions is +// still unclear so it might be necessary to add two kinds of this function in the future. +// Console APIs treat the buffer as a large NxM matrix after all. +til::CoordType ROW::GetReadableColumnCount() const noexcept { - const auto scale = _lineRendition != LineRendition::SingleWidth ? 1 : 0; - return _columnCount >> scale; + const til::CoordType columnCount = _columnCount; + const til::CoordType scale = _lineRendition != LineRendition::SingleWidth; + const til::CoordType padding = _doubleBytePadded; + return (columnCount - padding) >> scale; } // Routine Description: @@ -287,26 +380,6 @@ til::CoordType ROW::NavigateToNext(til::CoordType column) const noexcept return _adjustForward(_clampedColumn(column + 1)); } -uint16_t ROW::_adjustBackward(uint16_t column) const noexcept -{ - // Safety: This is a little bit more dangerous. The first column is supposed - // to never be a trailer and so this loop should exit if column == 0. - for (; _uncheckedIsTrailer(column); --column) - { - } - return column; -} - -uint16_t ROW::_adjustForward(uint16_t column) const noexcept -{ - // Safety: This is a little bit more dangerous. The last column is supposed - // to never be a trailer and so this loop should exit if column == _columnCount. - for (; _uncheckedIsTrailer(column); ++column) - { - } - return column; -} - // Routine Description: // - clears char data in column in row // Arguments: @@ -841,12 +914,6 @@ uint16_t ROW::size() const noexcept return _columnCount; } -til::CoordType ROW::LineRenditionColumns() const noexcept -{ - const auto scale = _lineRendition != LineRendition::SingleWidth ? 1 : 0; - return _columnCount >> scale; -} - til::CoordType ROW::MeasureLeft() const noexcept { const auto text = GetText(); @@ -945,20 +1012,33 @@ DbcsAttribute ROW::DbcsAttrAt(til::CoordType column) const noexcept std::wstring_view ROW::GetText() const noexcept { - return { _chars.data(), _charSize() }; + const auto width = size_t{ til::at(_charOffsets, GetReadableColumnCount()) } & CharOffsetsMask; + return { _chars.data(), width }; } std::wstring_view ROW::GetText(til::CoordType columnBegin, til::CoordType columnEnd) const noexcept { const til::CoordType columns = _columnCount; - const auto colBeg = std::max(0, std::min(columns, columnBegin)); - const auto colEnd = std::max(colBeg, std::min(columns, columnEnd)); + const auto colBeg = clamp(columnBegin, 0, columns); + const auto colEnd = clamp(columnEnd, colBeg, columns); const size_t chBeg = _uncheckedCharOffset(gsl::narrow_cast(colBeg)); const size_t chEnd = _uncheckedCharOffset(gsl::narrow_cast(colEnd)); #pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1). return { _chars.data() + chBeg, chEnd - chBeg }; } +til::CoordType ROW::GetLeftAlignedColumnAtChar(const ptrdiff_t offset) const noexcept +{ + auto mapper = _createCharToColumnMapper(offset); + return mapper.GetLeadingColumnAt(offset); +} + +til::CoordType ROW::GetRightAlignedColumnAtChar(const ptrdiff_t offset) const noexcept +{ + auto mapper = _createCharToColumnMapper(offset); + return mapper.GetTrailingColumnAt(offset); +} + DelimiterClass ROW::DelimiterClassAt(til::CoordType column, const std::wstring_view& wordDelimiters) const noexcept { const auto col = _clampedColumn(column); @@ -982,43 +1062,80 @@ DelimiterClass ROW::DelimiterClassAt(til::CoordType column, const std::wstring_v template constexpr uint16_t ROW::_clampedUint16(T v) noexcept { - return static_cast(std::max(T{ 0 }, std::min(T{ 65535 }, v))); + return static_cast(clamp(v, 0, 65535)); } template constexpr uint16_t ROW::_clampedColumn(T v) const noexcept { - return static_cast(std::max(T{ 0 }, std::min(_columnCount - 1u, v))); + return static_cast(clamp(v, 0, _columnCount - 1)); } template constexpr uint16_t ROW::_clampedColumnInclusive(T v) const noexcept { - return static_cast(std::max(T{ 0 }, std::min(_columnCount, v))); + return static_cast(clamp(v, 0, _columnCount)); } -// Safety: off must be [0, _charSize()]. -wchar_t ROW::_uncheckedChar(size_t off) const noexcept +uint16_t ROW::_charSize() const noexcept { - return til::at(_chars, off); + // Safety: _charOffsets is an array of `_columnCount + 1` entries. + return _charOffsets[_columnCount]; } -uint16_t ROW::_charSize() const noexcept +// Safety: off must be [0, _charSize()]. +template +wchar_t ROW::_uncheckedChar(T off) const noexcept { - // Safety: _charOffsets is an array of `_columnCount + 1` entries. - return til::at(_charOffsets, _columnCount); + return _chars[off]; } // Safety: col must be [0, _columnCount]. -uint16_t ROW::_uncheckedCharOffset(size_t col) const noexcept +template +uint16_t ROW::_uncheckedCharOffset(T col) const noexcept { assert(col < _charOffsets.size()); - return til::at(_charOffsets, col) & CharOffsetsMask; + return _charOffsets[col] & CharOffsetsMask; } // Safety: col must be [0, _columnCount]. -bool ROW::_uncheckedIsTrailer(size_t col) const noexcept +template +bool ROW::_uncheckedIsTrailer(T col) const noexcept { assert(col < _charOffsets.size()); - return WI_IsFlagSet(til::at(_charOffsets, col), CharOffsetsTrailer); + return WI_IsFlagSet(_charOffsets[col], CharOffsetsTrailer); +} + +template +T ROW::_adjustBackward(T column) const noexcept +{ + // Safety: This is a little bit more dangerous. The first column is supposed + // to never be a trailer and so this loop should exit if column == 0. + for (; _uncheckedIsTrailer(column); --column) + { + } + return column; +} + +template +T ROW::_adjustForward(T column) const noexcept +{ + // Safety: This is a little bit more dangerous. The last column is supposed + // to never be a trailer and so this loop should exit if column == _columnCount. + for (; _uncheckedIsTrailer(column); ++column) + { + } + return column; +} + +// Creates a CharToColumnMapper given an offset into _chars.data(). +// In other words, for a 120 column ROW with just ASCII text, the offset should be [0,120). +CharToColumnMapper ROW::_createCharToColumnMapper(ptrdiff_t offset) const noexcept +{ + const auto charsSize = _charSize(); + const auto lastChar = gsl::narrow_cast(charsSize - 1); + // We can sort of guess what column belongs to what offset because BMP glyphs are very common and + // UTF-16 stores them in 1 char. In other words, usually a ROW will have N chars for N columns. + const auto guessedColumn = gsl::narrow_cast(clamp(offset, 0, _columnCount)); + return CharToColumnMapper{ _chars.data(), _charOffsets.data(), lastChar, guessedColumn }; } diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index c19f343f235..379b1856bcc 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -65,6 +65,28 @@ struct RowCopyTextFromState til::CoordType sourceColumnEnd = 0; // OUT }; +// This structure is basically an inverse of ROW::_charOffsets. If you have a pointer +// into a ROW's text this class can tell you what cell that pointer belongs to. +struct CharToColumnMapper +{ + CharToColumnMapper(const wchar_t* chars, const uint16_t* charOffsets, ptrdiff_t lastCharOffset, til::CoordType currentColumn) noexcept; + + til::CoordType GetLeadingColumnAt(ptrdiff_t offset) noexcept; + til::CoordType GetTrailingColumnAt(ptrdiff_t offset) noexcept; + til::CoordType GetLeadingColumnAt(const wchar_t* str) noexcept; + til::CoordType GetTrailingColumnAt(const wchar_t* str) noexcept; + +private: + // See ROW and its members with identical name. + static constexpr uint16_t CharOffsetsTrailer = 0x8000; + static constexpr uint16_t CharOffsetsMask = 0x7fff; + + const wchar_t* _chars; + const uint16_t* _charOffsets; + ptrdiff_t _lastCharOffset; + til::CoordType _currentColumn; +}; + class ROW final { public: @@ -106,7 +128,7 @@ class ROW final bool WasDoubleBytePadded() const noexcept; void SetLineRendition(const LineRendition lineRendition) noexcept; LineRendition GetLineRendition() const noexcept; - uint16_t GetLineWidth() const noexcept; + til::CoordType GetReadableColumnCount() const noexcept; void Reset(const TextAttribute& attr) noexcept; void TransferAttributes(const til::small_rle& attr, til::CoordType newWidth); @@ -128,7 +150,6 @@ class ROW final TextAttribute GetAttrByColumn(til::CoordType column) const; std::vector GetHyperlinks() const; uint16_t size() const noexcept; - til::CoordType LineRenditionColumns() const noexcept; til::CoordType MeasureLeft() const noexcept; til::CoordType MeasureRight() const noexcept; bool ContainsText() const noexcept; @@ -136,6 +157,8 @@ class ROW final DbcsAttribute DbcsAttrAt(til::CoordType column) const noexcept; std::wstring_view GetText() const noexcept; std::wstring_view GetText(til::CoordType columnBegin, til::CoordType columnEnd) const noexcept; + til::CoordType GetLeftAlignedColumnAtChar(ptrdiff_t offset) const noexcept; + til::CoordType GetRightAlignedColumnAtChar(ptrdiff_t offset) const noexcept; DelimiterClass DelimiterClassAt(til::CoordType column, const std::wstring_view& wordDelimiters) const noexcept; auto AttrBegin() const noexcept { return _attr.begin(); } @@ -206,16 +229,21 @@ class ROW final template constexpr uint16_t _clampedColumnInclusive(T v) const noexcept; - uint16_t _adjustBackward(uint16_t column) const noexcept; - uint16_t _adjustForward(uint16_t column) const noexcept; - - wchar_t _uncheckedChar(size_t off) const noexcept; uint16_t _charSize() const noexcept; - uint16_t _uncheckedCharOffset(size_t col) const noexcept; - bool _uncheckedIsTrailer(size_t col) const noexcept; + template + wchar_t _uncheckedChar(T off) const noexcept; + template + uint16_t _uncheckedCharOffset(T col) const noexcept; + template + bool _uncheckedIsTrailer(T col) const noexcept; + template + T _adjustBackward(T column) const noexcept; + template + T _adjustForward(T column) const noexcept; void _init() noexcept; void _resizeChars(uint16_t colEndDirty, uint16_t chBegDirty, size_t chEndDirty, uint16_t chEndDirtyOld); + CharToColumnMapper _createCharToColumnMapper(ptrdiff_t offset) const noexcept; // These fields are a bit "wasteful", but it makes all this a bit more robust against // programming errors during initial development (which is when this comment was written). diff --git a/src/buffer/out/UTextAdapter.cpp b/src/buffer/out/UTextAdapter.cpp new file mode 100644 index 00000000000..d23d883b336 --- /dev/null +++ b/src/buffer/out/UTextAdapter.cpp @@ -0,0 +1,262 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" +#include "UTextAdapter.h" + +#include "textBuffer.hpp" + +struct RowRange +{ + til::CoordType begin; + til::CoordType end; +}; + +constexpr size_t& accessLength(UText* ut) noexcept +{ + return *std::bit_cast(&ut->p); +} + +constexpr RowRange& accessRowRange(UText* ut) noexcept +{ + return *std::bit_cast(&ut->a); +} + +constexpr til::CoordType& accessCurrentRow(UText* ut) noexcept +{ + return ut->b; +} + +static UText* U_CALLCONV utextClone(UText* dest, const UText* src, UBool deep, UErrorCode* status) noexcept +{ + __assume(status != nullptr); + + if (deep) + { + *status = U_UNSUPPORTED_ERROR; + return dest; + } + + dest = utext_setup(dest, 0, status); + if (*status <= U_ZERO_ERROR) + { + memcpy(dest, src, sizeof(UText)); + } + + return dest; +} + +static int64_t U_CALLCONV utextLength(UText* ut) noexcept +try +{ + auto length = accessLength(ut); + + if (!length) + { + const auto& textBuffer = *static_cast(ut->context); + const auto range = accessRowRange(ut); + + for (til::CoordType y = range.begin; y < range.end; ++y) + { + length += textBuffer.GetRowByOffset(y).GetText().size(); + } + + accessLength(ut) = length; + } + + return gsl::narrow_cast(length); +} +catch (...) +{ + return 0; +} + +static UBool U_CALLCONV utextAccess(UText* ut, int64_t nativeIndex, UBool forward) noexcept +try +{ + if (!forward) + { + // Even after reading the ICU documentation I'm a little unclear on how to handle the forward flag. + // I _think_ it's basically nothing but "nativeIndex--" for us, but I didn't want to verify it + // because right now we never use any ICU functions that require backwards text access anyways. + return false; + } + + const auto& textBuffer = *static_cast(ut->context); + const auto range = accessRowRange(ut); + auto start = ut->chunkNativeStart; + auto limit = ut->chunkNativeLimit; + auto y = accessCurrentRow(ut); + std::wstring_view text; + + if (nativeIndex >= start && nativeIndex < limit) + { + return true; + } + + if (nativeIndex < start) + { + for (;;) + { + --y; + if (y < range.begin) + { + return false; + } + + text = textBuffer.GetRowByOffset(y).GetText(); + limit = start; + start -= text.size(); + + if (nativeIndex >= start) + { + break; + } + } + } + else + { + for (;;) + { + ++y; + if (y >= range.end) + { + return false; + } + + text = textBuffer.GetRowByOffset(y).GetText(); + start = limit; + limit += text.size(); + + if (nativeIndex < limit) + { + break; + } + } + } + + accessCurrentRow(ut) = y; + ut->chunkNativeStart = start; + ut->chunkNativeLimit = limit; + ut->chunkOffset = gsl::narrow_cast(nativeIndex - start); + ut->chunkLength = gsl::narrow_cast(text.size()); +#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). + ut->chunkContents = reinterpret_cast(text.data()); + ut->nativeIndexingLimit = ut->chunkLength; + return true; +} +catch (...) +{ + return false; +} + +static int32_t U_CALLCONV utextExtract(UText* ut, int64_t nativeStart, int64_t nativeLimit, char16_t* dest, int32_t destCapacity, UErrorCode* status) noexcept +try +{ + __assume(status != nullptr); + + if (*status > U_ZERO_ERROR) + { + return 0; + } + if (destCapacity < 0 || (dest == nullptr && destCapacity > 0) || nativeStart > nativeLimit) + { + *status = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } + + if (!utextAccess(ut, nativeStart, true)) + { + return 0; + } + + nativeLimit = std::min(ut->chunkNativeLimit, nativeLimit); + + if (destCapacity <= 0) + { + return gsl::narrow_cast(nativeLimit - nativeStart); + } + + const auto& textBuffer = *static_cast(ut->context); + const auto y = accessCurrentRow(ut); + const auto offset = ut->chunkNativeStart - nativeStart; + const auto text = textBuffer.GetRowByOffset(y).GetText().substr(offset); + const auto length = std::min(gsl::narrow_cast(destCapacity), text.size()); + + memcpy(dest, text.data(), length * sizeof(char16_t)); + + if (length < destCapacity) + { +#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1). + dest[length] = 0; + } + + return gsl::narrow_cast(length); +} +catch (...) +{ + // The only thing that can fail is GetRowByOffset() which in turn can only fail when VirtualAlloc() fails. + *status = U_MEMORY_ALLOCATION_ERROR; + return 0; +} + +static constexpr UTextFuncs utextFuncs{ + .tableSize = sizeof(UTextFuncs), + .clone = utextClone, + .nativeLength = utextLength, + .access = utextAccess, + .extract = utextExtract, +}; + +UText* UTextFromTextBuffer(UText* ut, const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd, UErrorCode* status) noexcept +{ + __assume(status != nullptr); + + ut = utext_setup(ut, 0, status); + if (*status > U_ZERO_ERROR) + { + return nullptr; + } + + ut->providerProperties = (1 << UTEXT_PROVIDER_LENGTH_IS_EXPENSIVE) | (1 << UTEXT_PROVIDER_STABLE_CHUNKS); + ut->pFuncs = &utextFuncs; + ut->context = &textBuffer; + accessCurrentRow(ut) = rowBeg - 1; // the utextAccess() below will advance this by 1. + accessRowRange(ut) = { rowBeg, rowEnd }; + + utextAccess(ut, 0, true); + return ut; +} + +til::point_span BufferRangeFromUText(UText* ut, int64_t nativeIndexBeg, int64_t nativeIndexEnd) +{ + // The parameters are given as a half-open [beg,end) range, but the point_span we return in closed [beg,end]. + nativeIndexEnd--; + + const auto& textBuffer = *static_cast(ut->context); + til::point_span ret; + + if (utextAccess(ut, nativeIndexBeg, true)) + { + const auto y = accessCurrentRow(ut); + ret.start.x = textBuffer.GetRowByOffset(y).GetLeftAlignedColumnAtChar(nativeIndexBeg - ut->chunkNativeStart); + ret.start.y = y; + } + else + { + ret.start.y = accessRowRange(ut).begin; + } + + if (utextAccess(ut, nativeIndexEnd, true)) + { + const auto y = accessCurrentRow(ut); + ret.end.x = textBuffer.GetRowByOffset(y).GetLeftAlignedColumnAtChar(nativeIndexEnd - ut->chunkNativeStart); + ret.end.y = y; + } + else + { + ret.end = ret.start; + } + + return ret; +} diff --git a/src/buffer/out/UTextAdapter.h b/src/buffer/out/UTextAdapter.h new file mode 100644 index 00000000000..06d413902a4 --- /dev/null +++ b/src/buffer/out/UTextAdapter.h @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +// Can't forward declare the UErrorCode enum. Thanks C++. +#include + +class TextBuffer; +struct UText; + +UText* UTextFromTextBuffer(UText* ut, const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd, UErrorCode* status) noexcept; +til::point_span BufferRangeFromUText(UText* ut, int64_t nativeIndexBeg, int64_t nativeIndexEnd); diff --git a/src/buffer/out/lib/bufferout.vcxproj b/src/buffer/out/lib/bufferout.vcxproj index f7962921c2d..20385eff096 100644 --- a/src/buffer/out/lib/bufferout.vcxproj +++ b/src/buffer/out/lib/bufferout.vcxproj @@ -26,6 +26,7 @@ Create + @@ -44,6 +45,7 @@ + diff --git a/src/buffer/out/search.cpp b/src/buffer/out/search.cpp index d060f55b7de..1735872ae10 100644 --- a/src/buffer/out/search.cpp +++ b/src/buffer/out/search.cpp @@ -2,11 +2,8 @@ // Licensed under the MIT license. #include "precomp.h" - #include "search.h" -#include - #include "textBuffer.hpp" #include "../types/inc/GlyphWidth.hpp" @@ -20,324 +17,83 @@ using namespace Microsoft::Console::Types; // - textBuffer - The screen text buffer to search through (the "haystack") // - renderData - The IRenderData type reference, it is for providing selection methods // - str - The search term you want to find (the "needle") -// - direction - The direction to search (upward or downward) -// - sensitivity - Whether or not you care about case -Search::Search(Microsoft::Console::Render::IRenderData& renderData, - const std::wstring_view str, - const Direction direction, - const Sensitivity sensitivity) : - _direction(direction), - _sensitivity(sensitivity), - _needle(s_CreateNeedleFromString(str)), - _renderData(renderData), - _coordAnchor(s_GetInitialAnchor(renderData, direction)) +// - reverse - True when searching backward/upwards in the buffer +// - caseInsensitive - As the name indicates: case insensitivity +Search::Search(Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view str, bool reverse, bool caseInsensitive) : + _renderData{ &renderData }, + _step{ reverse ? -1 : 1 } { - _coordNext = _coordAnchor; -} + const auto& textBuffer = _renderData->GetTextBuffer(); -// Routine Description: -// - Constructs a Search object. -// - Make a Search object then call .FindNext() to locate items. -// - Once you've found something, you can perform actions like .Select() or .Color() -// Arguments: -// - textBuffer - The screen text buffer to search through (the "haystack") -// - renderData - The IRenderData type reference, it is for providing selection methods -// - str - The search term you want to find (the "needle") -// - direction - The direction to search (upward or downward) -// - sensitivity - Whether or not you care about case -// - anchor - starting search location in screenInfo -Search::Search(Microsoft::Console::Render::IRenderData& renderData, - const std::wstring_view str, - const Direction direction, - const Sensitivity sensitivity, - const til::point anchor) : - _direction(direction), - _sensitivity(sensitivity), - _needle(s_CreateNeedleFromString(str)), - _coordAnchor(anchor), - _renderData(renderData) -{ - _coordNext = _coordAnchor; -} + _results = textBuffer.SearchText(str, caseInsensitive); + _mutationCount = textBuffer.GetMutationCount(); -// Routine Description -// - Locates the next instance of the search term within the screen buffer. -// Arguments: -// - - Uses internal state from constructor -// Return Value: -// - True if we found another item. False if we've reached the end of the buffer. -// - NOTE: You can FindNext() again after False to go around the buffer again. -bool Search::FindNext() -{ - if (_reachedEnd) + if (_results.empty()) { - _reachedEnd = false; - return false; + return; } - do - { - if (_FindNeedleInHaystackAt(_coordNext, _coordSelStart, _coordSelEnd)) - { - _UpdateNextPosition(); - _reachedEnd = _coordNext == _coordAnchor; - return true; - } - else - { - _UpdateNextPosition(); - } - - } while (_coordNext != _coordAnchor); + const auto highestIndex = gsl::narrow_cast(_results.size()) - 1; + auto firstIndex = reverse ? highestIndex : 0; - return false; -} - -// Routine Description: -// - Takes the found word and selects it in the screen buffer -void Search::Select() const -{ - // Convert buffer selection offsets into the equivalent screen coordinates - // required by SelectNewRegion, taking line renditions into account. - const auto& textBuffer = _renderData.GetTextBuffer(); - const auto selStart = textBuffer.BufferToScreenPosition(_coordSelStart); - const auto selEnd = textBuffer.BufferToScreenPosition(_coordSelEnd); - _renderData.SelectNewRegion(selStart, selEnd); -} - -// Routine Description: -// - Applies the supplied TextAttribute to the current search result. -// Arguments: -// - attr - The attribute to apply to the result -void Search::Color(const TextAttribute attr) const -{ - // Only select if we've found something. - if (_coordSelEnd >= _coordSelStart) + if (_renderData->IsSelectionActive()) { - _renderData.ColorSelection(_coordSelStart, _coordSelEnd, attr); - } -} - -// Routine Description: -// - gets start and end position of text sound by search. only guaranteed to have valid data if FindNext has -// been called and returned true. -// Return Value: -// - pair containing [start, end] coord positions of text found by search -std::pair Search::GetFoundLocation() const noexcept -{ - return { _coordSelStart, _coordSelEnd }; -} - -// Routine Description: -// - Finds the anchor position where we will start searches from. -// - This position will represent the "wrap around" point in the buffer or where -// we reach the end of our search. -// - If the screen buffer given already has a selection in it, it will be used to determine the anchor. -// - Otherwise, we will choose one of the ends of the screen buffer depending on direction. -// Arguments: -// - renderData - The reference to the IRenderData interface type object -// - direction - The intended direction of the search -// Return Value: -// - Coordinate to start the search from. -til::point Search::s_GetInitialAnchor(const Microsoft::Console::Render::IRenderData& renderData, const Direction direction) -{ - const auto& textBuffer = renderData.GetTextBuffer(); - const auto textBufferEndPosition = renderData.GetTextBufferEndPosition(); - if (renderData.IsSelectionActive()) - { - // Convert the screen position of the selection anchor into an equivalent - // buffer position to start searching, taking line rendition into account. - auto anchor = textBuffer.ScreenToBufferPosition(renderData.GetSelectionAnchor()); - - if (direction == Direction::Forward) - { - textBuffer.GetSize().IncrementInBoundsCircular(anchor); - } - else - { - textBuffer.GetSize().DecrementInBoundsCircular(anchor); - // If the selection starts at (0, 0), we need to make sure - // it does not exceed the text buffer end position - anchor.x = std::min(textBufferEndPosition.x, anchor.x); - anchor.y = std::min(textBufferEndPosition.y, anchor.y); - } - return anchor; - } - else - { - if (direction == Direction::Forward) + const auto anchor = textBuffer.ScreenToBufferPosition(_renderData->GetSelectionAnchor()); + if (reverse) { - return { 0, 0 }; + for (; firstIndex >= 0 && til::at(_results, firstIndex).start >= anchor; --firstIndex) + { + } } else { - return textBufferEndPosition; + for (; firstIndex <= highestIndex && til::at(_results, firstIndex).start <= anchor; ++firstIndex) + { + } } } -} -// Routine Description: -// - Attempts to compare the search term (the needle) to the screen buffer (the haystack) -// at the given coordinate position of the screen buffer. -// - Performs one comparison. Call again with new positions to check other spots. -// Arguments: -// - pos - The position in the haystack (screen buffer) to compare -// - start - If we found it, this is filled with the coordinate of the first character of the needle. -// - end - If we found it, this is filled with the coordinate of the last character of the needle. -// Return Value: -// - True if we found it. False if not. -bool Search::_FindNeedleInHaystackAt(const til::point pos, til::point& start, til::point& end) const -{ - start = {}; - end = {}; - - auto bufferPos = pos; - - for (const auto& needleChars : _needle) - { - // Haystack is the buffer. Needle is the string we were given. - const auto hayIter = _renderData.GetTextBuffer().GetTextDataAt(bufferPos); - const auto hayChars = *hayIter; - - // If we didn't match at any point of the needle, return false. - if (!_CompareChars(hayChars, needleChars)) - { - return false; - } - - _IncrementCoord(bufferPos); - } - - _DecrementCoord(bufferPos); - - // If we made it the whole way through the needle, then it was in the haystack. - // Fill out the span that we found the result at and return true. - start = pos; - end = bufferPos; - - return true; + // We reverse the _index by 1 so that the first call to FindNext() moves it to the firstIndex we found. + _index = firstIndex - _step; } -// Routine Description: -// - Provides an abstraction for comparing two spans of text. -// - Internally handles case sensitivity based on object construction. -// Arguments: -// - one - String view representing the first string of text -// - two - String view representing the second string of text -// Return Value: -// - True if they are the same. False otherwise. -bool Search::_CompareChars(const std::wstring_view one, const std::wstring_view two) const noexcept +bool Search::IsStale() const noexcept { - if (one.size() != two.size()) - { - return false; - } - - for (size_t i = 0; i < one.size(); i++) - { - if (_ApplySensitivity(one.at(i)) != _ApplySensitivity(two.at(i))) - { - return false; - } - } - - return true; + return !_renderData || _renderData->GetTextBuffer().GetMutationCount() != _mutationCount; } // Routine Description: -// - Provides an abstraction for conditionally applying case sensitivity -// based on object construction -// Arguments: -// - wch - Character to adjust if necessary -// Return Value: -// - Adjusted value (or not). -wchar_t Search::_ApplySensitivity(const wchar_t wch) const noexcept +// - Takes the found word and selects it in the screen buffer +bool Search::SelectNext() { - if (_sensitivity == Sensitivity::CaseInsensitive) + if (_results.empty()) { - return ::towlower(wch); - } - else - { - return wch; + return false; } -} -// Routine Description: -// - Helper to increment a coordinate in respect to the associated screen buffer -// Arguments -// - coord - Updated by function to increment one position (will wrap X and Y direction) -void Search::_IncrementCoord(til::point& coord) const noexcept -{ - _renderData.GetTextBuffer().GetSize().IncrementInBoundsCircular(coord); -} + const auto count = gsl::narrow_cast(_results.size()); + const auto& textBuffer = _renderData->GetTextBuffer(); -// Routine Description: -// - Helper to decrement a coordinate in respect to the associated screen buffer -// Arguments -// - coord - Updated by function to decrement one position (will wrap X and Y direction) -void Search::_DecrementCoord(til::point& coord) const noexcept -{ - _renderData.GetTextBuffer().GetSize().DecrementInBoundsCircular(coord); -} + _index = (_index + _step + count) % count; -// Routine Description: -// - Helper to update the coordinate position to the next point to be searched -// Return Value: -// - True if we haven't reached the end of the buffer. False otherwise. -void Search::_UpdateNextPosition() -{ - if (_direction == Direction::Forward) - { - _IncrementCoord(_coordNext); - } - else if (_direction == Direction::Backward) - { - _DecrementCoord(_coordNext); - } - else - { - THROW_HR(E_NOTIMPL); - } - - // To reduce wrap-around time, if the next position is larger than - // the end position of the written text - // We put the next position to: - // Forward: (0, 0) - // Backward: the position of the end of the text buffer - const auto bufferEndPosition = _renderData.GetTextBufferEndPosition(); + const auto& s = til::at(_results, _index); + // Convert buffer selection offsets into the equivalent screen coordinates + // required by SelectNewRegion, taking line renditions into account. + const auto selStart = textBuffer.BufferToScreenPosition(s.start); + const auto selEnd = textBuffer.BufferToScreenPosition(s.end); - if (_coordNext.y > bufferEndPosition.y || - (_coordNext.y == bufferEndPosition.y && _coordNext.x > bufferEndPosition.x)) - { - if (_direction == Direction::Forward) - { - _coordNext = {}; - } - else - { - _coordNext = bufferEndPosition; - } - } + _renderData->SelectNewRegion(selStart, selEnd); + return true; } // Routine Description: -// - Creates a "needle" of the correct format for comparison to the screen buffer text data -// that we can use for our search +// - Applies the supplied TextAttribute to all search results. // Arguments: -// - wstr - String that will be our search term -// Return Value: -// - Structured text data for comparison to screen buffer text data. -std::vector Search::s_CreateNeedleFromString(const std::wstring_view wstr) +// - attr - The attribute to apply to the result +void Search::ColorAll(const TextAttribute& attr) const { - std::vector cells; - for (const auto& chars : til::utf16_iterator{ wstr }) + for (const auto& s : _results) { - if (IsGlyphFullWidth(chars)) - { - cells.emplace_back(chars); - } - cells.emplace_back(chars); + _renderData->ColorSelection(s.start, s.end, attr); } - return cells; } diff --git a/src/buffer/out/search.h b/src/buffer/out/search.h index 68f9f82bfd9..b8361188b16 100644 --- a/src/buffer/out/search.h +++ b/src/buffer/out/search.h @@ -21,64 +21,24 @@ Revision History: #include "textBuffer.hpp" #include "../renderer/inc/IRenderData.hpp" -// This used to be in find.h. -#define SEARCH_STRING_LENGTH (80) - class Search final { public: - enum class Direction - { - Forward, - Backward - }; - - enum class Sensitivity - { - CaseInsensitive, - CaseSensitive - }; - - Search(Microsoft::Console::Render::IRenderData& renderData, - const std::wstring_view str, - const Direction dir, - const Sensitivity sensitivity); + Search() = default; + Search(Microsoft::Console::Render::IRenderData& renderData, std::wstring_view str, bool reverse, bool caseInsensitive); - Search(Microsoft::Console::Render::IRenderData& renderData, - const std::wstring_view str, - const Direction dir, - const Sensitivity sensitivity, - const til::point anchor); + bool IsStale() const noexcept; + bool SelectNext(); - bool FindNext(); - void Select() const; - void Color(const TextAttribute attr) const; - - std::pair GetFoundLocation() const noexcept; + void ColorAll(const TextAttribute& attr) const; private: - wchar_t _ApplySensitivity(const wchar_t wch) const noexcept; - bool _FindNeedleInHaystackAt(const til::point pos, til::point& start, til::point& end) const; - bool _CompareChars(const std::wstring_view one, const std::wstring_view two) const noexcept; - void _UpdateNextPosition(); - - void _IncrementCoord(til::point& coord) const noexcept; - void _DecrementCoord(til::point& coord) const noexcept; - - static til::point s_GetInitialAnchor(const Microsoft::Console::Render::IRenderData& renderData, const Direction dir); - - static std::vector s_CreateNeedleFromString(const std::wstring_view wstr); - - bool _reachedEnd = false; - til::point _coordNext; - til::point _coordSelStart; - til::point _coordSelEnd; - - const til::point _coordAnchor; - const std::vector _needle; - const Direction _direction; - const Sensitivity _sensitivity; - Microsoft::Console::Render::IRenderData& _renderData; + // _renderData is a pointer so that Search() is constexpr default constructable. + Microsoft::Console::Render::IRenderData* _renderData = nullptr; + std::vector _results; + ptrdiff_t _index = 0; + ptrdiff_t _step = 0; + uint64_t _mutationCount = 0; #ifdef UNIT_TESTING friend class SearchTests; diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index d120b2e4a98..5219a1a2f51 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -8,15 +8,42 @@ #include #include +#include "UTextAdapter.h" +#include "../../types/inc/GlyphWidth.hpp" #include "../renderer/base/renderer.hpp" -#include "../types/inc/utils.hpp" #include "../types/inc/convert.hpp" -#include "../../types/inc/GlyphWidth.hpp" +#include "../types/inc/utils.hpp" using namespace Microsoft::Console; using namespace Microsoft::Console::Types; using PointTree = interval_tree::IntervalTree; +using unique_URegularExpression = wistd::unique_ptr>; + +constexpr bool allWhitespace(const std::wstring_view& text) noexcept +{ + for (const auto ch : text) + { + if (ch != L' ') + { + return false; + } + } + return true; +} + +static URegularExpression* createURegularExpression(const std::wstring_view& pattern, uint32_t flags, UErrorCode* error) noexcept +{ +#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). + const auto re = uregex_open(reinterpret_cast(pattern.data()), gsl::narrow_cast(pattern.size()), flags, nullptr, error); + // ICU describes the time unit as being dependent on CPU performance and "typically [in] the order of milliseconds", + // but this claim seems highly outdated already. On my CPU from 2021, a limit of 4096 equals roughly 600ms. + uregex_setTimeLimit(re, 4096, error); + uregex_setStackLimit(re, 4 * 1024 * 1024, error); + return re; +} + +static std::atomic s_mutationCountInitialValue; // Routine Description: // - Creates a new instance of TextBuffer @@ -36,6 +63,9 @@ TextBuffer::TextBuffer(til::size screenBufferSize, Microsoft::Console::Render::Renderer& renderer) : _renderer{ renderer }, _currentAttributes{ defaultAttributes }, + // This way every TextBuffer will start with a ""unique"" _mutationCount + // and so it'll compare unequal with the counter of other TextBuffers. + _mutationCount{ s_mutationCountInitialValue.fetch_add(0x100000000) }, _cursor{ cursorSize, *this }, _isActiveBuffer{ isActiveBuffer } { @@ -51,6 +81,10 @@ TextBuffer::~TextBuffer() { _destroy(); } + if (_urlRegex) + { + uregex_close(_urlRegex); + } } // I put these functions in a block at the start of the class, because they're the most @@ -166,6 +200,23 @@ ROW& TextBuffer::_getRowByOffsetDirect(size_t offset) return *reinterpret_cast(row); } +ROW& TextBuffer::_getRow(til::CoordType y) const +{ + // Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows. + auto offset = (_firstRow + y) % _height; + + // Support negative wrap around. This way an index of -1 will + // wrap to _rowCount-1 and make implementing scrolling easier. + if (offset < 0) + { + offset += _height; + } + + // We add 1 to the row offset, because row "0" is the one returned by GetScratchpadRow(). +#pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3). + return const_cast(this)->_getRowByOffsetDirect(gsl::narrow_cast(offset) + 1); +} + // Returns the "user-visible" index of the last committed row, which can be used // to short-circuit some algorithms that try to scan the entire buffer. // Returns 0 if no rows are committed in. @@ -183,27 +234,15 @@ til::CoordType TextBuffer::_estimateOffsetOfLastCommittedRow() const noexcept // (what corresponds to the top row of the screen buffer). const ROW& TextBuffer::GetRowByOffset(const til::CoordType index) const { - // The const_cast is safe because "const" never had any meaning in C++ in the first place. -#pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3). - return const_cast(this)->GetRowByOffset(index); + return _getRow(index); } // Retrieves a row from the buffer by its offset from the first row of the text buffer // (what corresponds to the top row of the screen buffer). -ROW& TextBuffer::GetRowByOffset(const til::CoordType index) +ROW& TextBuffer::GetMutableRowByOffset(const til::CoordType index) { - // Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows. - auto offset = (_firstRow + index) % _height; - - // Support negative wrap around. This way an index of -1 will - // wrap to _rowCount-1 and make implementing scrolling easier. - if (offset < 0) - { - offset += _height; - } - - // We add 1 to the row offset, because row "0" is the one returned by GetScratchpadRow(). - return _getRowByOffsetDirect(gsl::narrow_cast(offset) + 1); + _mutationCount++; + return _getRow(index); } // Returns a row filled with whitespace and the current attributes, for you to freely use. @@ -345,91 +384,6 @@ TextBufferCellIterator TextBuffer::GetCellDataAt(const til::point at, const View return TextBufferCellIterator(*this, at, limit); } -//Routine Description: -// - Corrects and enforces consistent double byte character state (KAttrs line) within a row of the text buffer. -// - This will take the given double byte information and check that it will be consistent when inserted into the buffer -// at the current cursor position. -// - It will correct the buffer (by erasing the character prior to the cursor) if necessary to make a consistent state. -//Arguments: -// - dbcsAttribute - Double byte information associated with the character about to be inserted into the buffer -//Return Value: -// - True if it is valid to insert a character with the given double byte attributes. False otherwise. -bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute) -{ - // To figure out if the sequence is valid, we have to look at the character that comes before the current one - const auto coordPrevPosition = _GetPreviousFromCursor(); - auto& prevRow = GetRowByOffset(coordPrevPosition.y); - DbcsAttribute prevDbcsAttr = DbcsAttribute::Single; - try - { - prevDbcsAttr = prevRow.DbcsAttrAt(coordPrevPosition.x); - } - catch (...) - { - LOG_HR(wil::ResultFromCaughtException()); - return false; - } - - auto fValidSequence = true; // Valid until proven otherwise - auto fCorrectableByErase = false; // Can't be corrected until proven otherwise - - // Here's the matrix of valid items: - // N = None (single byte) - // L = Lead (leading byte of double byte sequence - // T = Trail (trailing byte of double byte sequence - // Prev Curr Result - // N N OK. - // N L OK. - // N T Fail, uncorrectable. Trailing byte must have had leading before it. - // L N Fail, OK with erase. Lead needs trailing pair. Can erase lead to correct. - // L L Fail, OK with erase. Lead needs trailing pair. Can erase prev lead to correct. - // L T OK. - // T N OK. - // T L OK. - // T T Fail, uncorrectable. New trailing byte must have had leading before it. - - // Check for only failing portions of the matrix: - if (prevDbcsAttr == DbcsAttribute::Single && dbcsAttribute == DbcsAttribute::Trailing) - { - // N, T failing case (uncorrectable) - fValidSequence = false; - } - else if (prevDbcsAttr == DbcsAttribute::Leading) - { - if (dbcsAttribute == DbcsAttribute::Single || dbcsAttribute == DbcsAttribute::Leading) - { - // L, N and L, L failing cases (correctable) - fValidSequence = false; - fCorrectableByErase = true; - } - } - else if (prevDbcsAttr == DbcsAttribute::Trailing && dbcsAttribute == DbcsAttribute::Trailing) - { - // T, T failing case (uncorrectable) - fValidSequence = false; - } - - // If it's correctable by erase, erase the previous character - if (fCorrectableByErase) - { - // Erase previous character into an N type. - try - { - prevRow.ClearCell(coordPrevPosition.x); - } - catch (...) - { - LOG_HR(wil::ResultFromCaughtException()); - return false; - } - - // Sequence is now N N or N L, which are both okay. Set sequence back to valid. - fValidSequence = true; - } - - return fValidSequence; -} - //Routine Description: // - Call before inserting a character into the buffer. // - This will ensure a consistent double byte state (KAttrs line) within the text buffer @@ -453,7 +407,7 @@ void TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute if (cursorPosition.x == lineWidth - 1) { // set that we're wrapping for double byte reasons - auto& row = GetRowByOffset(cursorPosition.y); + auto& row = GetMutableRowByOffset(cursorPosition.y); row.SetDoubleBytePadded(true); // then move the cursor forward and onto the next row @@ -482,7 +436,7 @@ size_t TextBuffer::GraphemePrev(const std::wstring_view& chars, size_t position) // You can continue calling the function on the same row as long as state.columnEnd < state.columnLimit. void TextBuffer::Write(til::CoordType row, const TextAttribute& attributes, RowWriteState& state) { - auto& r = GetRowByOffset(row); + auto& r = GetMutableRowByOffset(row); r.ReplaceText(state); r.ReplaceAttributes(state.columnBegin, state.columnEnd, attributes); TriggerRedraw(Viewport::FromExclusive({ state.columnBeginDirty, row, state.columnEndDirty, row + 1 })); @@ -535,7 +489,7 @@ void TextBuffer::FillRect(const til::rect& rect, const std::wstring_view& fill, for (auto y = rect.top; y < rect.bottom; ++y) { - auto& r = GetRowByOffset(y); + auto& r = GetMutableRowByOffset(y); r.CopyTextFrom(state); r.ReplaceAttributes(rect.left, rect.right, attributes); TriggerRedraw(Viewport::FromExclusive({ state.columnBeginDirty, y, state.columnEndDirty, y + 1 })); @@ -616,7 +570,7 @@ OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt, } // Get the row and write the cells - auto& row = GetRowByOffset(target.y); + auto& row = GetMutableRowByOffset(target.y); const auto newIt = row.WriteCells(givenIt, target.x, wrap, limitRight); // Take the cell distance written and notify that it needs to be repainted. @@ -648,7 +602,7 @@ void TextBuffer::InsertCharacter(const std::wstring_view chars, const auto iCol = GetCursor().GetPosition().x; // column logical and array positions are equal. // Get the row associated with the given logical position - auto& Row = GetRowByOffset(iRow); + auto& Row = GetMutableRowByOffset(iRow); // Store character and double byte data switch (dbcsAttribute) @@ -708,7 +662,7 @@ void TextBuffer::_AdjustWrapOnCurrentRow(const bool fSet) const auto uiCurrentRowOffset = GetCursor().GetPosition().y; // Set the wrap status as appropriate - GetRowByOffset(uiCurrentRowOffset).SetWrapForced(fSet); + GetMutableRowByOffset(uiCurrentRowOffset).SetWrapForced(fSet); } //Routine Description: @@ -784,7 +738,7 @@ void TextBuffer::IncrementCircularBuffer(const TextAttribute& fillAttributes) _PruneHyperlinks(); // Second, clean out the old "first row" as it will become the "last row" of the buffer after the circle is performed. - GetRowByOffset(0).Reset(fillAttributes); + GetMutableRowByOffset(0).Reset(fillAttributes); { // Now proceed to increment. // Incrementing it will cause the next line down to become the new "top" of the window (the new "0" in logical coordinates) @@ -955,7 +909,7 @@ void TextBuffer::ScrollRows(const til::CoordType firstRow, til::CoordType size, for (; y != end; y += step) { - GetRowByOffset(y + delta).CopyFrom(GetRowByOffset(y)); + GetMutableRowByOffset(y + delta).CopyFrom(GetRowByOffset(y)); } } @@ -969,6 +923,11 @@ const Cursor& TextBuffer::GetCursor() const noexcept return _cursor; } +uint64_t TextBuffer::GetMutationCount() const noexcept +{ + return _mutationCount; +} + const TextAttribute& TextBuffer::GetCurrentAttributes() const noexcept { return _currentAttributes; @@ -981,14 +940,14 @@ void TextBuffer::SetCurrentAttributes(const TextAttribute& currentAttributes) no void TextBuffer::SetWrapForced(const til::CoordType y, bool wrap) { - GetRowByOffset(y).SetWrapForced(wrap); + GetMutableRowByOffset(y).SetWrapForced(wrap); } void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition, const TextAttribute& fillAttributes) { const auto cursorPosition = GetCursor().GetPosition(); const auto rowIndex = cursorPosition.y; - auto& row = GetRowByOffset(rowIndex); + auto& row = GetMutableRowByOffset(rowIndex); if (row.GetLineRendition() != lineRendition) { row.SetLineRendition(lineRendition); @@ -1013,7 +972,7 @@ void TextBuffer::ResetLineRenditionRange(const til::CoordType startRow, const ti { for (auto row = startRow; row < endRow; row++) { - GetRowByOffset(row).SetLineRendition(LineRendition::SingleWidth); + GetMutableRowByOffset(row).SetLineRendition(LineRendition::SingleWidth); } } @@ -1090,7 +1049,7 @@ void TextBuffer::Reset() noexcept for (; dstRow < copyableRows; ++dstRow, ++srcRow) { - newBuffer.GetRowByOffset(dstRow).CopyFrom(GetRowByOffset(srcRow)); + newBuffer.GetMutableRowByOffset(dstRow).CopyFrom(GetRowByOffset(srcRow)); } // NOTE: Keep this in sync with _reserve(). @@ -2446,7 +2405,7 @@ try const auto newBufferPos = newCursor.GetPosition(); if (newBufferPos.x == 0) { - auto& newRow = newBuffer.GetRowByOffset(newBufferPos.y); + auto& newRow = newBuffer.GetMutableRowByOffset(newBufferPos.y); newRow.SetLineRendition(row.GetLineRendition()); } @@ -2516,7 +2475,7 @@ try // copy attributes from the old row till the end of the new row, and // move on. const auto newRowY = newCursor.GetPosition().y; - auto& newRow = newBuffer.GetRowByOffset(newRowY); + auto& newRow = newBuffer.GetMutableRowByOffset(newRowY); auto newAttrColumn = newCursor.GetPosition().x; const auto newWidth = newBuffer.GetLineWidth(newRowY); // Stop when we get to the end of the buffer width, or the new position @@ -2631,7 +2590,7 @@ try // into the new one, and resize the row to match. We'll rely on the // behavior of ATTR_ROW::Resize to trim down when narrower, or extend // the last attr when wider. - auto& newRow = newBuffer.GetRowByOffset(newRowY); + auto& newRow = newBuffer.GetMutableRowByOffset(newRowY); const auto newWidth = newBuffer.GetLineWidth(newRowY); newRow.TransferAttributes(row.Attributes(), newWidth); @@ -2641,7 +2600,6 @@ try // Finish copying remaining parameters from the old text buffer to the new one newBuffer.CopyProperties(oldBuffer); newBuffer.CopyHyperlinkMaps(oldBuffer); - newBuffer.CopyPatterns(oldBuffer); // If we found where to put the cursor while placing characters into the buffer, // just put the cursor there. Otherwise we have to advance manually. @@ -2804,103 +2762,87 @@ void TextBuffer::CopyHyperlinkMaps(const TextBuffer& other) } // Method Description: -// - Adds a regex pattern we should search for -// - The searching does not happen here, we only search when asked to by TerminalCore +// - Finds patterns within the requested region of the text buffer // Arguments: -// - The regex pattern +// - The firstRow to start searching from +// - The lastRow to search // Return value: -// - An ID that the caller should associate with the given pattern -const size_t TextBuffer::AddPatternRecognizer(const std::wstring_view regexString) +// - An interval tree containing the patterns found +PointTree TextBuffer::GetPatterns(const til::CoordType firstRow, const til::CoordType lastRow) { - ++_currentPatternId; - _idsAndPatterns.emplace(std::make_pair(_currentPatternId, regexString)); - return _currentPatternId; -} + PointTree::interval_vector intervals; -// Method Description: -// - Clears the patterns we know of and resets the pattern ID counter -void TextBuffer::ClearPatternRecognizers() noexcept -{ - _idsAndPatterns.clear(); - _currentPatternId = 0; + UErrorCode status = U_ZERO_ERROR; +#pragma warning(suppress : 26477) // Use 'nullptr' rather than 0 or NULL (es.47). + UText text = UTEXT_INITIALIZER; + UTextFromTextBuffer(&text, *this, firstRow, lastRow + 1, &status); + + if (!_urlRegex) + { + static constexpr std::wstring_view linkPattern{ LR"(\b(?:https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|$!:,.;]*[A-Za-z0-9+&@#/%=~_|$])" }; + _urlRegex = createURegularExpression(linkPattern, 0, &status); + assert(_urlRegex); + } + + uregex_setUText(_urlRegex, &text, &status); + + if (uregex_find(_urlRegex, -1, &status)) + { + do + { + const auto nativeIndexBeg = uregex_start64(_urlRegex, 0, &status); + const auto nativeIndexEnd = uregex_end64(_urlRegex, 0, &status); + const auto range = BufferRangeFromUText(&text, nativeIndexBeg, nativeIndexEnd); + intervals.push_back(PointTree::interval(range.start, range.end, 0)); + } while (uregex_findNext(_urlRegex, &status)); + } + + return PointTree{ std::move(intervals) }; } -// Method Description: -// - Copies the patterns the other buffer knows about into this one -// Arguments: -// - The other buffer -void TextBuffer::CopyPatterns(const TextBuffer& OtherBuffer) +// Searches through the entire (committed) text buffer for `needle` and returns the coordinates in absolute coordinates. +// The end coordinates of the returned ranges are considered inclusive. +std::vector TextBuffer::SearchText(const std::wstring_view& needle, bool caseInsensitive) const { - _idsAndPatterns = OtherBuffer._idsAndPatterns; - _currentPatternId = OtherBuffer._currentPatternId; + return SearchText(needle, caseInsensitive, 0, til::CoordTypeMax); } -// Method Description: -// - Finds patterns within the requested region of the text buffer -// Arguments: -// - The firstRow to start searching from -// - The lastRow to search -// Return value: -// - An interval tree containing the patterns found -PointTree TextBuffer::GetPatterns(const til::CoordType firstRow, const til::CoordType lastRow) const +// Searches through the given rows [rowBeg,rowEnd) for `needle` and returns the coordinates in absolute coordinates. +// While the end coordinates of the returned ranges are considered inclusive, the [rowBeg,rowEnd) range is half-open. +std::vector TextBuffer::SearchText(const std::wstring_view& needle, bool caseInsensitive, til::CoordType rowBeg, til::CoordType rowEnd) const { - PointTree::interval_vector intervals; + rowEnd = std::min(rowEnd, _estimateOffsetOfLastCommittedRow() + 1); - std::wstring concatAll; - const auto rowSize = GetRowByOffset(0).size(); - concatAll.reserve(gsl::narrow_cast(rowSize) * gsl::narrow_cast(lastRow - firstRow + 1)); + std::vector results; - // to deal with text that spans multiple lines, we will first concatenate - // all the text into one string and find the patterns in that string - for (til::CoordType i = firstRow; i <= lastRow; ++i) + // All whitespace strings would match the not-yet-written parts of the TextBuffer which would be weird. + if (allWhitespace(needle) || rowBeg >= rowEnd) { - auto& row = GetRowByOffset(i); - concatAll += row.GetText(); + return results; } - // for each pattern we know of, iterate through the string - for (const auto& idAndPattern : _idsAndPatterns) - { - std::wregex regexObj{ idAndPattern.second }; + uint32_t flags = UREGEX_LITERAL; + WI_SetFlagIf(flags, UREGEX_CASE_INSENSITIVE, caseInsensitive); - // search through the run with our regex object - auto words_begin = std::wsregex_iterator(concatAll.begin(), concatAll.end(), regexObj); - auto words_end = std::wsregex_iterator(); + UErrorCode status = U_ZERO_ERROR; +#pragma warning(suppress : 26477) // Use 'nullptr' rather than 0 or NULL (es.47). + UText text = UTEXT_INITIALIZER; + UTextFromTextBuffer(&text, *this, rowBeg, rowEnd, &status); - til::CoordType lenUpToThis = 0; - for (auto i = words_begin; i != words_end; ++i) + const unique_URegularExpression re{ createURegularExpression(needle, flags, &status) }; + uregex_setUText(re.get(), &text, &status); + + if (uregex_find(re.get(), -1, &status)) + { + do { - // record the locations - - // when we find a match, the prefix is text that is between this - // match and the previous match, so we use the size of the prefix - // along with the size of the match to determine the locations - til::CoordType prefixSize = 0; - for (const auto str = i->prefix().str(); const auto& glyph : til::utf16_iterator{ str }) - { - prefixSize += IsGlyphFullWidth(glyph) ? 2 : 1; - } - const auto start = lenUpToThis + prefixSize; - til::CoordType matchSize = 0; - for (const auto str = i->str(); const auto& glyph : til::utf16_iterator{ str }) - { - matchSize += IsGlyphFullWidth(glyph) ? 2 : 1; - } - const auto end = start + matchSize; - lenUpToThis = end; - - const til::point startCoord{ start % rowSize, start / rowSize }; - const til::point endCoord{ end % rowSize, end / rowSize }; - - // store the intervals - // NOTE: these intervals are relative to the VIEWPORT not the buffer - // Keeping these relative to the viewport for now because its the renderer - // that actually uses these locations and the renderer works relative to - // the viewport - intervals.push_back(PointTree::interval(startCoord, endCoord, idAndPattern.first)); - } + const auto nativeIndexBeg = uregex_start64(re.get(), 0, &status); + const auto nativeIndexEnd = uregex_end64(re.get(), 0, &status); + results.emplace_back(BufferRangeFromUText(&text, nativeIndexBeg, nativeIndexEnd)); + } while (uregex_findNext(re.get(), &status)); } - PointTree result(std::move(intervals)); - return result; + + return results; } const std::vector& TextBuffer::GetMarks() const noexcept diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index d4cf6f74214..23e7b77d254 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -59,6 +59,8 @@ filling in the last row, and updating the screen. #include "../buffer/out/textBufferCellIterator.hpp" #include "../buffer/out/textBufferTextIterator.hpp" +struct URegularExpression; + namespace Microsoft::Console::Render { class Renderer; @@ -122,7 +124,7 @@ class TextBuffer final ROW& GetScratchpadRow(); ROW& GetScratchpadRow(const TextAttribute& attributes); const ROW& GetRowByOffset(til::CoordType index) const; - ROW& GetRowByOffset(til::CoordType index); + ROW& GetMutableRowByOffset(til::CoordType index); TextBufferCellIterator GetCellDataAt(const til::point at) const; TextBufferCellIterator GetCellLineDataAt(const til::point at) const; @@ -164,6 +166,7 @@ class TextBuffer final Cursor& GetCursor() noexcept; const Cursor& GetCursor() const noexcept; + uint64_t GetMutationCount() const noexcept; const til::CoordType GetFirstRowIndex() const noexcept; const Microsoft::Console::Types::Viewport GetSize() const noexcept; @@ -262,10 +265,9 @@ class TextBuffer final const std::optional lastCharacterViewport, std::optional> positionInfo); - const size_t AddPatternRecognizer(const std::wstring_view regexString); - void ClearPatternRecognizers() noexcept; - void CopyPatterns(const TextBuffer& OtherBuffer); - interval_tree::IntervalTree GetPatterns(const til::CoordType firstRow, const til::CoordType lastRow) const; + interval_tree::IntervalTree GetPatterns(const til::CoordType firstRow, const til::CoordType lastRow); + std::vector SearchText(const std::wstring_view& needle, bool caseInsensitive) const; + std::vector SearchText(const std::wstring_view& needle, bool caseInsensitive, til::CoordType rowBeg, til::CoordType rowEnd) const; const std::vector& GetMarks() const noexcept; void ClearMarksInRange(const til::point start, const til::point end); @@ -285,6 +287,7 @@ class TextBuffer final void _construct(const std::byte* until) noexcept; void _destroy() const noexcept; ROW& _getRowByOffsetDirect(size_t offset); + ROW& _getRow(til::CoordType y) const; til::CoordType _estimateOffsetOfLastCommittedRow() const noexcept; void _SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept; @@ -293,7 +296,6 @@ class TextBuffer final void _AdjustWrapOnCurrentRow(const bool fSet); // Assist with maintaining proper buffer state for Double Byte character sequences void _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute); - bool _AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute); void _ExpandTextRow(til::inclusive_rect& selectionRow) const; DelimiterClass _GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const; til::point _GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const; @@ -311,9 +313,6 @@ class TextBuffer final std::unordered_map _hyperlinkCustomIdMap; uint16_t _currentHyperlinkId = 1; - std::unordered_map _idsAndPatterns; - size_t _currentPatternId = 0; - // This block describes the state of the underlying virtual memory buffer that holds all ROWs, text and attributes. // Initially memory is only allocated with MEM_RESERVE to reduce the private working set of conhost. // ROWs are laid out like this in memory: @@ -373,12 +372,15 @@ class TextBuffer final TextAttribute _currentAttributes; til::CoordType _firstRow = 0; // indexes top row (not necessarily 0) + uint64_t _mutationCount = 0; Cursor _cursor; - - bool _isActiveBuffer = false; - std::vector _marks; + // While safe RAII wrappers for URegularExpression* would improve maintainability, it would make + // it more difficult to avoid having to forward declare (or outright expose) more ICU stuff. + // This may make switching to other Unicode libraries easier in the distant future. + URegularExpression* _urlRegex = nullptr; + bool _isActiveBuffer = false; #ifdef UNIT_TESTING friend class TextBufferTests; diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 87e98c0a1b7..c1d67d95d9e 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1539,31 +1539,22 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - caseSensitive: boolean that represents if the current search is case sensitive // Return Value: // - - void ControlCore::Search(const winrt::hstring& text, - const bool goForward, - const bool caseSensitive) + void ControlCore::Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive) { - if (text.size() == 0) + auto lock = _terminal->LockForWriting(); + _terminal->SetBlockSelection(false); + + if (_searcher.IsStale() || _searcherText != text || _searcherGoForward != goForward || _searcherCaseSensitive != caseSensitive) { - return; + _searcher = { *GetRenderData(), text, !goForward, !caseSensitive }; + _searcherText = text; + _searcherGoForward = goForward; + _searcherCaseSensitive = caseSensitive; } - const auto direction = goForward ? - Search::Direction::Forward : - Search::Direction::Backward; - - const auto sensitivity = caseSensitive ? - Search::Sensitivity::CaseSensitive : - Search::Sensitivity::CaseInsensitive; - - ::Search search(*GetRenderData(), text.c_str(), direction, sensitivity); - auto lock = _terminal->LockForWriting(); - const auto foundMatch{ search.FindNext() }; + const auto foundMatch = _searcher.SelectNext(); if (foundMatch) { - _terminal->SetBlockSelection(false); - search.Select(); - // 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. @@ -1573,8 +1564,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation // 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(foundMatch); - _FoundMatchHandlers(*this, *foundResults); + _FoundMatchHandlers(*this, winrt::make(foundMatch)); + } + + void ControlCore::ClearSearch() + { + _searcher = {}; } void ControlCore::Close() diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 5f9f23a3e89..88b7c6d1ee1 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -205,9 +205,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation void SetSelectionAnchor(const til::point position); void SetEndSelectionPoint(const til::point position); - void Search(const winrt::hstring& text, - const bool goForward, - const bool caseSensitive); + void Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive); + void ClearSearch(); void LeftClickOnTerminal(const til::point terminalPosition, const int numberOfClicks, @@ -305,6 +304,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation std::unique_ptr<::Microsoft::Console::Render::IRenderEngine> _renderEngine{ nullptr }; std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer{ nullptr }; + ::Search _searcher; + winrt::hstring _searcherText; + bool _searcherGoForward = false; + bool _searcherCaseSensitive = false; + winrt::handle _lastSwapChainHandle{ nullptr }; FontInfoDesired _desiredFont; diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index 8157346dcd1..0f1cc8be87f 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -128,6 +128,7 @@ namespace Microsoft.Terminal.Control void ResumeRendering(); void BlinkAttributeTick(); void Search(String text, Boolean goForward, Boolean caseSensitive); + void ClearSearch(); Microsoft.Terminal.Core.Color BackgroundColor { get; }; SelectionData SelectionInfo { get; }; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 2bce8ecc0d0..3f362ad6852 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -414,6 +414,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void TermControl::_CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& /*sender*/, const RoutedEventArgs& /*args*/) { + _core.ClearSearch(); _searchBox->Visibility(Visibility::Collapsed); // Set focus back to terminal control diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 75161e5c7f0..ada1bed5b62 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -111,7 +111,6 @@ void Terminal::UpdateSettings(ICoreSettings settings) if (_mainBuffer) { // Clear the patterns first - _mainBuffer->ClearPatternRecognizers(); _detectURLs = settings.DetectURLs(); _updateUrlDetection(); } @@ -1337,9 +1336,6 @@ void Terminal::_updateUrlDetection() { if (_detectURLs) { - // Add regex pattern recognizers to the buffer - // For now, we only add the URI regex pattern - _hyperlinkPatternId = _activeBuffer().AddPatternRecognizer(linkPattern); UpdatePatternsUnderLock(); } else @@ -1483,12 +1479,8 @@ void Terminal::ColorSelection(const TextAttribute& attr, winrt::Microsoft::Termi if (!text.empty()) { - Search search(*this, text, Search::Direction::Forward, Search::Sensitivity::CaseInsensitive, { 0, 0 }); - - while (search.FindNext()) - { - search.Color(attr); - } + const Search search(*this, text, false, true); + search.ColorAll(attr); } } } diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 8e63819bdb6..a0110cfbfb9 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -17,7 +17,6 @@ #include -inline constexpr std::wstring_view linkPattern{ LR"(\b(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|$!:,.;]*[A-Za-z0-9+&@#/%=~_|$])" }; inline constexpr size_t TaskbarMinProgress{ 10 }; // You have to forward decl the ICoreSettings here, instead of including the header. diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index e19f158adf8..4c31cbc4bbc 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -193,7 +193,6 @@ void Terminal::UseAlternateScreenBuffer(const TextAttribute& attrs) const auto cursorSize = _mainBuffer->GetCursor().GetSize(); ClearSelection(); - _mainBuffer->ClearPatternRecognizers(); // Create a new alt buffer _altBuffer = std::make_unique(_altBufferSize, @@ -272,7 +271,6 @@ void Terminal::UseMainScreenBuffer() } // update all the hyperlinks on the screen - _mainBuffer->ClearPatternRecognizers(); _updateUrlDetection(); // GH#3321: Make sure we let the TerminalInput know that we switched diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index d85d4e105f8..cc858f74bef 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -247,7 +247,7 @@ try AdjustCursorPosition(screenInfo, pos, keepCursorVisible, psScrollY); const auto y = cursor.GetPosition().y; - auto& row = textBuffer.GetRowByOffset(y); + auto& row = textBuffer.GetMutableRowByOffset(y); pos.x = textBuffer.GetSize().RightExclusive(); pos.y = y; @@ -408,7 +408,7 @@ try pos.x = 0; } - textBuffer.GetRowByOffset(pos.y).SetWrapForced(false); + textBuffer.GetMutableRowByOffset(pos.y).SetWrapForced(false); pos.y = pos.y + 1; AdjustCursorPosition(screenInfo, pos, keepCursorVisible, psScrollY); continue; diff --git a/src/host/selectionInput.cpp b/src/host/selectionInput.cpp index d85c5835bd0..37ad00c96a6 100644 --- a/src/host/selectionInput.cpp +++ b/src/host/selectionInput.cpp @@ -708,11 +708,8 @@ bool Selection::_HandleColorSelection(const INPUT_KEY_INFO* const pInputKeyInfo) Telemetry::Instance().LogColorSelectionUsed(); - Search search(gci.renderData, str, Search::Direction::Forward, Search::Sensitivity::CaseInsensitive); - while (search.FindNext()) - { - search.Color(selectionAttr); - } + const Search search(gci.renderData, str, false, true); + search.ColorAll(selectionAttr); } } CATCH_LOG(); diff --git a/src/host/telemetry.cpp b/src/host/telemetry.cpp index c8318f118a0..21cff15e815 100644 --- a/src/host/telemetry.cpp +++ b/src/host/telemetry.cpp @@ -21,10 +21,6 @@ TRACELOGGING_DEFINE_PROVIDER(g_hConhostV2EventTraceProvider, // Disable 4351 so we can initialize the arrays to 0 without a warning. #pragma warning(disable : 4351) Telemetry::Telemetry() : - _fpFindStringLengthAverage(0), - _fpDirectionDownAverage(0), - _fpMatchCaseAverage(0), - _uiFindNextClickedTotal(0), _uiColorSelectionUsed(0), _tStartedAt(0), _wchProcessFileNames(), @@ -177,40 +173,6 @@ void Telemetry::LogApiCall(const ApiCall api) _rguiTimesApiUsed[api]++; } -// Log usage of the Find Dialog. -void Telemetry::LogFindDialogNextClicked(const unsigned int uiStringLength, const bool fDirectionDown, const bool fMatchCase) -{ - // Don't send telemetry for every time it's used, as this will help reduce the load on our servers. - // Instead just create a running average of the string length, the direction down radio - // button, and match case checkbox. - _fpFindStringLengthAverage = ((_fpFindStringLengthAverage * _uiFindNextClickedTotal + uiStringLength) / (_uiFindNextClickedTotal + 1)); - _fpDirectionDownAverage = ((_fpDirectionDownAverage * _uiFindNextClickedTotal + (fDirectionDown ? 1 : 0)) / (_uiFindNextClickedTotal + 1)); - _fpMatchCaseAverage = ((_fpMatchCaseAverage * _uiFindNextClickedTotal + (fMatchCase ? 1 : 0)) / (_uiFindNextClickedTotal + 1)); - _uiFindNextClickedTotal++; -} - -// Find dialog was closed, now send out the telemetry. -void Telemetry::FindDialogClosed() -{ - // clang-format off -#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.") - // clang-format on - TraceLoggingWriteTagged(_activity, - "FindDialogUsed", - TraceLoggingValue(_fpFindStringLengthAverage, "StringLengthAverage"), - TraceLoggingValue(_fpDirectionDownAverage, "DirectionDownAverage"), - TraceLoggingValue(_fpMatchCaseAverage, "MatchCaseAverage"), - TraceLoggingValue(_uiFindNextClickedTotal, "FindNextButtonClickedTotal"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - - // Get ready for the next time the dialog is used. - _fpFindStringLengthAverage = 0; - _fpDirectionDownAverage = 0; - _fpMatchCaseAverage = 0; - _uiFindNextClickedTotal = 0; -} - // Tries to find the process name amongst our previous process names by doing a binary search. // The main difference between this and the standard bsearch library call, is that if this // can't find the string, it returns the position the new string should be inserted at. This saves diff --git a/src/host/telemetry.hpp b/src/host/telemetry.hpp index 45cbea796a4..4cb2560a3b5 100644 --- a/src/host/telemetry.hpp +++ b/src/host/telemetry.hpp @@ -44,9 +44,7 @@ class Telemetry void LogQuickEditPasteRawUsed(); void LogColorSelectionUsed(); - void LogFindDialogNextClicked(const unsigned int iStringLength, const bool fDirectionDown, const bool fMatchCase); void LogProcessConnected(const HANDLE hProcess); - void FindDialogClosed(); void WriteFinalTraceLog(); void LogRipMessage(_In_z_ const char* pszMessage, ...) const; @@ -138,10 +136,6 @@ class Telemetry TraceLoggingActivity _activity; - float _fpFindStringLengthAverage; - float _fpDirectionDownAverage; - float _fpMatchCaseAverage; - unsigned int _uiFindNextClickedTotal; unsigned int _uiColorSelectionUsed; time_t _tStartedAt; WCHAR const* const c_pwszBashExeName = L"bash.exe"; diff --git a/src/inc/til/at.h b/src/inc/til/at.h index 9b7c804e839..bb1596a4d69 100644 --- a/src/inc/til/at.h +++ b/src/inc/til/at.h @@ -16,6 +16,7 @@ namespace til template constexpr auto at(T&& cont, const I i) noexcept -> decltype(auto) { +#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1). #pragma warning(suppress : 26482) // Suppress bounds.2 check for indexing with constant expressions #pragma warning(suppress : 26446) // Suppress bounds.4 check for subscript operator. #pragma warning(suppress : 26445) // Suppress lifetime check for a reference to std::span or std::string_view diff --git a/src/interactivity/win32/find.cpp b/src/interactivity/win32/find.cpp index 896c5885a1b..a0d949130b7 100644 --- a/src/interactivity/win32/find.cpp +++ b/src/interactivity/win32/find.cpp @@ -22,17 +22,18 @@ INT_PTR CALLBACK FindDialogProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM l auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); // This bool is used to track which option - up or down - was used to perform the last search. That way, the next time the // find dialog is opened, it will default to the last used option. - static auto fFindSearchUp = true; + static auto reverse = true; + static auto caseInsensitive = true; static std::wstring lastFindString; + static Search searcher; - WCHAR szBuf[SEARCH_STRING_LENGTH + 1]; switch (Message) { case WM_INITDIALOG: SetWindowLongPtrW(hWnd, DWLP_USER, lParam); - SendDlgItemMessageW(hWnd, ID_CONSOLE_FINDSTR, EM_LIMITTEXT, ARRAYSIZE(szBuf) - 1, 0); - CheckRadioButton(hWnd, ID_CONSOLE_FINDUP, ID_CONSOLE_FINDDOWN, (fFindSearchUp ? ID_CONSOLE_FINDUP : ID_CONSOLE_FINDDOWN)); - SetDlgItemText(hWnd, ID_CONSOLE_FINDSTR, lastFindString.c_str()); + CheckRadioButton(hWnd, ID_CONSOLE_FINDUP, ID_CONSOLE_FINDDOWN, (reverse ? ID_CONSOLE_FINDUP : ID_CONSOLE_FINDDOWN)); + CheckDlgButton(hWnd, ID_CONSOLE_FINDCASE, !caseInsensitive); + SetDlgItemTextW(hWnd, ID_CONSOLE_FINDSTR, lastFindString.c_str()); return TRUE; case WM_COMMAND: { @@ -40,44 +41,49 @@ INT_PTR CALLBACK FindDialogProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM l { case IDOK: { - const auto StringLength = (USHORT)GetDlgItemTextW(hWnd, ID_CONSOLE_FINDSTR, szBuf, ARRAYSIZE(szBuf)); - if (StringLength == 0) - { - lastFindString.clear(); - break; - } - const auto IgnoreCase = IsDlgButtonChecked(hWnd, ID_CONSOLE_FINDCASE) == 0; - const auto Reverse = IsDlgButtonChecked(hWnd, ID_CONSOLE_FINDDOWN) == 0; - fFindSearchUp = !!Reverse; - auto& ScreenInfo = gci.GetActiveOutputBuffer(); - - std::wstring wstr(szBuf, StringLength); - lastFindString = wstr; LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); - Search search(gci.renderData, - wstr, - Reverse ? Search::Direction::Backward : Search::Direction::Forward, - IgnoreCase ? Search::Sensitivity::CaseInsensitive : Search::Sensitivity::CaseSensitive); - - if (search.FindNext()) + if (searcher.IsStale()) { - Telemetry::Instance().LogFindDialogNextClicked(StringLength, (Reverse != 0), (IgnoreCase == 0)); - search.Select(); - return TRUE; + auto length = SendDlgItemMessageW(hWnd, ID_CONSOLE_FINDSTR, WM_GETTEXTLENGTH, 0, 0); + lastFindString.resize(length); + length = GetDlgItemTextW(hWnd, ID_CONSOLE_FINDSTR, lastFindString.data(), gsl::narrow_cast(length + 1)); + lastFindString.resize(length); + + caseInsensitive = IsDlgButtonChecked(hWnd, ID_CONSOLE_FINDCASE) == 0; + reverse = IsDlgButtonChecked(hWnd, ID_CONSOLE_FINDDOWN) == 0; + + searcher = Search{ gci.renderData, lastFindString, reverse, caseInsensitive }; } - else + + if (searcher.SelectNext()) { - // The string wasn't found. - ScreenInfo.SendNotifyBeep(); + return TRUE; } + + std::ignore = gci.GetActiveOutputBuffer().SendNotifyBeep(); break; } case IDCANCEL: - Telemetry::Instance().FindDialogClosed(); EndDialog(hWnd, 0); + searcher = Search{}; return TRUE; + case ID_CONSOLE_FINDSTR: + case ID_CONSOLE_FINDCASE: + case ID_CONSOLE_FINDUP: + case ID_CONSOLE_FINDDOWN: + { + const auto hi = HIWORD(wParam); + // ID_CONSOLE_FINDSTR emits EN_CHANGE and the other 3 emit 0 when changing. + if (hi == 0 || hi == EN_CHANGE) + { + searcher = Search{}; + } + break; + } + default: + break; } break; } diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 92af043e4f6..d49a5b816b7 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -894,7 +894,7 @@ void AdaptDispatch::_SelectiveEraseRect(TextBuffer& textBuffer, const til::rect& { for (auto row = eraseRect.top; row < eraseRect.bottom; row++) { - auto& rowBuffer = textBuffer.GetRowByOffset(row); + auto& rowBuffer = textBuffer.GetMutableRowByOffset(row); for (auto col = eraseRect.left; col < eraseRect.right; col++) { // Only unprotected cells are affected. @@ -996,7 +996,7 @@ void AdaptDispatch::_ChangeRectAttributes(TextBuffer& textBuffer, const til::rec { for (auto row = changeRect.top; row < changeRect.bottom; row++) { - auto& rowBuffer = textBuffer.GetRowByOffset(row); + auto& rowBuffer = textBuffer.GetMutableRowByOffset(row); for (auto col = changeRect.left; col < changeRect.right; col++) { auto attr = rowBuffer.GetAttrByColumn(col); @@ -2407,7 +2407,7 @@ void AdaptDispatch::_DoLineFeed(TextBuffer& textBuffer, const bool withReturn, c // If the line was forced to wrap, set the wrap status. // When explicitly moving down a row, clear the wrap status. - textBuffer.GetRowByOffset(currentPosition.y).SetWrapForced(wrapForced); + textBuffer.GetMutableRowByOffset(currentPosition.y).SetWrapForced(wrapForced); // If a carriage return was requested, we move to the leftmost column or // the left margin, depending on whether we started within the margins. @@ -2453,7 +2453,7 @@ void AdaptDispatch::_DoLineFeed(TextBuffer& textBuffer, const bool withReturn, c else { const auto eraseAttributes = _GetEraseAttributes(textBuffer); - textBuffer.GetRowByOffset(newPosition.y).Reset(eraseAttributes); + textBuffer.GetMutableRowByOffset(newPosition.y).Reset(eraseAttributes); } } else diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index da6352358e8..56fa391aeff 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -450,7 +450,7 @@ try // Technically, we'll truncate early if there's an embedded null in the BSTR. // But we're probably fine in this circumstance. - const std::wstring queryFontName{ val.bstrVal }; + const std::wstring_view queryFontName{ val.bstrVal, SysStringLen(val.bstrVal) }; if (queryFontName == _pData->GetFontInfo().GetFaceName()) { Clone(ppRetVal); @@ -608,45 +608,55 @@ try }); RETURN_HR_IF(E_FAIL, !_pData->IsUiaDataInitialized()); - const std::wstring queryText{ text, SysStringLen(text) }; - const auto bufferSize = _getOptimizedBufferSize(); - const auto sensitivity = ignoreCase ? Search::Sensitivity::CaseInsensitive : Search::Sensitivity::CaseSensitive; - - auto searchDirection = Search::Direction::Forward; - auto searchAnchor = _start; - if (searchBackward) + const std::wstring_view queryText{ text, SysStringLen(text) }; + const auto results = _pData->GetTextBuffer().SearchText(queryText, ignoreCase != FALSE, _start.y, _end.y + 1); + if (results.empty()) { - searchDirection = Search::Direction::Backward; - - // we need to convert the end to inclusive - // because Search operates with an inclusive til::point - searchAnchor = _end; - bufferSize.DecrementInBounds(searchAnchor, true); + return S_OK; } - Search searcher{ *_pData, queryText, searchDirection, sensitivity, searchAnchor }; + const auto highestIndex = results.size() - 1; + const til::point_span* hit = nullptr; - if (searcher.FindNext()) + if (searchBackward) { - const auto foundLocation = searcher.GetFoundLocation(); - const auto start = foundLocation.first; - - // we need to increment the position of end because it's exclusive - auto end = foundLocation.second; - bufferSize.IncrementInBounds(end, true); + for (size_t i = highestIndex;; --i) + { + hit = &til::at(results, i); + if (hit->start < _end) + { + break; + } - // make sure what was found is within the bounds of the current range - if ((searchDirection == Search::Direction::Forward && end < _end) || - (searchDirection == Search::Direction::Backward && start > _start)) + if (i <= 0) + { + return S_OK; + } + } + } + else + { + for (size_t i = 0;; ++i) { - RETURN_IF_FAILED(Clone(ppRetVal)); - auto& range = static_cast(**ppRetVal); - range._start = start; - range._end = end; + hit = &til::at(results, i); + if (hit->start >= _start) + { + break; + } - UiaTracing::TextRange::FindText(*this, queryText, searchBackward, ignoreCase, range); + if (i >= highestIndex) + { + return S_OK; + } } } + + RETURN_IF_FAILED(Clone(ppRetVal)); + auto& range = static_cast(**ppRetVal); + range._start = hit->start; + range._end = hit->end; + + UiaTracing::TextRange::FindText(*this, queryText, searchBackward, ignoreCase, range); return S_OK; } CATCH_RETURN(); diff --git a/src/types/UiaTracing.cpp b/src/types/UiaTracing.cpp index 9a04103448e..dae05a073b9 100644 --- a/src/types/UiaTracing.cpp +++ b/src/types/UiaTracing.cpp @@ -63,14 +63,14 @@ UiaTracing::~UiaTracing() noexcept TraceLoggingUnregister(g_UiaProviderTraceProvider); } -inline std::wstring UiaTracing::_getValue(const ScreenInfoUiaProviderBase& siup) noexcept +std::wstring UiaTracing::_getValue(const ScreenInfoUiaProviderBase& siup) noexcept { std::wstringstream stream; stream << "_id: " << siup.GetId(); return stream.str(); } -inline std::wstring UiaTracing::_getValue(const UiaTextRangeBase& utr) noexcept +std::wstring UiaTracing::_getValue(const UiaTextRangeBase& utr) noexcept try { const auto start = utr.GetEndpoint(TextPatternRangeEndpoint_Start); @@ -90,7 +90,7 @@ catch (...) return {}; } -inline std::wstring UiaTracing::_getValue(const TextPatternRangeEndpoint endpoint) noexcept +std::wstring UiaTracing::_getValue(const TextPatternRangeEndpoint endpoint) noexcept { switch (endpoint) { @@ -103,7 +103,7 @@ inline std::wstring UiaTracing::_getValue(const TextPatternRangeEndpoint endpoin } } -inline std::wstring UiaTracing::_getValue(const TextUnit unit) noexcept +std::wstring UiaTracing::_getValue(const TextUnit unit) noexcept { switch (unit) { @@ -126,7 +126,7 @@ inline std::wstring UiaTracing::_getValue(const TextUnit unit) noexcept } } -inline std::wstring UiaTracing::_getValue(const VARIANT val) noexcept +std::wstring UiaTracing::_getValue(const VARIANT val) noexcept { // This is not a comprehensive conversion of VARIANT result to string // We're only including the one's we need at this time. @@ -148,7 +148,7 @@ inline std::wstring UiaTracing::_getValue(const VARIANT val) noexcept } } -inline std::wstring UiaTracing::_getValue(const AttributeType attrType) noexcept +std::wstring UiaTracing::_getValue(const AttributeType attrType) noexcept { switch (attrType) { @@ -263,7 +263,7 @@ void UiaTracing::TextRange::FindAttribute(const UiaTextRangeBase& utr, TEXTATTRI } } -void UiaTracing::TextRange::FindText(const UiaTextRangeBase& base, std::wstring text, bool searchBackward, bool ignoreCase, const UiaTextRangeBase& result) noexcept +void UiaTracing::TextRange::FindText(const UiaTextRangeBase& base, const std::wstring_view& text, bool searchBackward, bool ignoreCase, const UiaTextRangeBase& result) noexcept { EnsureRegistration(); if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) @@ -272,7 +272,7 @@ void UiaTracing::TextRange::FindText(const UiaTextRangeBase& base, std::wstring g_UiaProviderTraceProvider, "UiaTextRange::FindText", TraceLoggingValue(_getValue(base).c_str(), "base"), - TraceLoggingValue(text.c_str(), "text"), + TraceLoggingCountedWideString(text.data(), (ULONG)text.size(), "text"), TraceLoggingValue(searchBackward, "searchBackward"), TraceLoggingValue(ignoreCase, "ignoreCase"), TraceLoggingValue(_getValue(result).c_str(), "result"), @@ -326,7 +326,7 @@ void UiaTracing::TextRange::GetEnclosingElement(const UiaTextRangeBase& utr) noe } } -void UiaTracing::TextRange::GetText(const UiaTextRangeBase& utr, int maxLength, std::wstring result) noexcept +void UiaTracing::TextRange::GetText(const UiaTextRangeBase& utr, int maxLength, const std::wstring_view& result) noexcept { EnsureRegistration(); if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) @@ -336,7 +336,7 @@ void UiaTracing::TextRange::GetText(const UiaTextRangeBase& utr, int maxLength, "UiaTextRange::GetText", TraceLoggingValue(_getValue(utr).c_str(), "base"), TraceLoggingValue(maxLength, "maxLength"), - TraceLoggingValue(result.c_str(), "result"), + TraceLoggingCountedWideString(result.data(), (ULONG)result.size(), "result"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); } diff --git a/src/types/UiaTracing.h b/src/types/UiaTracing.h index f9752caf480..58c95231a10 100644 --- a/src/types/UiaTracing.h +++ b/src/types/UiaTracing.h @@ -46,11 +46,11 @@ namespace Microsoft::Console::Types static void CompareEndpoints(const UiaTextRangeBase& base, const TextPatternRangeEndpoint endpoint, const UiaTextRangeBase& other, TextPatternRangeEndpoint otherEndpoint, int result) noexcept; static void ExpandToEnclosingUnit(TextUnit unit, const UiaTextRangeBase& result) noexcept; static void FindAttribute(const UiaTextRangeBase& base, TEXTATTRIBUTEID attributeId, VARIANT val, BOOL searchBackwards, const UiaTextRangeBase& result, AttributeType attrType = AttributeType::Standard) noexcept; - static void FindText(const UiaTextRangeBase& base, std::wstring text, bool searchBackward, bool ignoreCase, const UiaTextRangeBase& result) noexcept; + static void FindText(const UiaTextRangeBase& base, const std::wstring_view& text, bool searchBackward, bool ignoreCase, const UiaTextRangeBase& result) noexcept; static void GetAttributeValue(const UiaTextRangeBase& base, TEXTATTRIBUTEID id, VARIANT result, AttributeType attrType = AttributeType::Standard) noexcept; static void GetBoundingRectangles(const UiaTextRangeBase& base) noexcept; static void GetEnclosingElement(const UiaTextRangeBase& base) noexcept; - static void GetText(const UiaTextRangeBase& base, int maxLength, std::wstring result) noexcept; + static void GetText(const UiaTextRangeBase& base, int maxLength, const std::wstring_view& result) noexcept; static void Move(TextUnit unit, int count, int resultCount, const UiaTextRangeBase& result) noexcept; static void MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count, int resultCount, const UiaTextRangeBase& result) noexcept; static void MoveEndpointByRange(TextPatternRangeEndpoint endpoint, const UiaTextRangeBase& other, TextPatternRangeEndpoint otherEndpoint, const UiaTextRangeBase& result) noexcept; @@ -104,13 +104,13 @@ namespace Microsoft::Console::Types UiaTracing& operator=(const UiaTracing&) = delete; UiaTracing& operator=(UiaTracing&&) = delete; - static inline std::wstring _getValue(const ScreenInfoUiaProviderBase& siup) noexcept; - static inline std::wstring _getValue(const UiaTextRangeBase& utr) noexcept; - static inline std::wstring _getValue(const TextPatternRangeEndpoint endpoint) noexcept; - static inline std::wstring _getValue(const TextUnit unit) noexcept; + static std::wstring _getValue(const ScreenInfoUiaProviderBase& siup) noexcept; + static std::wstring _getValue(const UiaTextRangeBase& utr) noexcept; + static std::wstring _getValue(const TextPatternRangeEndpoint endpoint) noexcept; + static std::wstring _getValue(const TextUnit unit) noexcept; - static inline std::wstring _getValue(const AttributeType attrType) noexcept; - static inline std::wstring _getValue(const VARIANT val) noexcept; + static std::wstring _getValue(const AttributeType attrType) noexcept; + static std::wstring _getValue(const VARIANT val) noexcept; // these are used to assign IDs to new UiaTextRanges and ScreenInfoUiaProviders respectively static IdType _utrId; From a346a2a60a836240fee62ec6002e3ceb9ebf967e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 21 Aug 2023 14:56:12 +0200 Subject: [PATCH 40/61] Improve documentation --- src/buffer/out/Row.cpp | 8 +++-- src/buffer/out/Row.hpp | 4 +-- src/buffer/out/UTextAdapter.cpp | 61 ++++++++++++++++++++++++++++++--- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index c933de6950e..467b7de8d27 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -90,6 +90,8 @@ CharToColumnMapper::CharToColumnMapper(const wchar_t* chars, const uint16_t* cha { } +// If given a position (`offset`) inside the ROW's text, this function will return the corresponding column. +// This function in particular returns the glyph's first column. til::CoordType CharToColumnMapper::GetLeadingColumnAt(ptrdiff_t offset) noexcept { offset = offset < 0 ? 0 : (offset > _lastCharOffset ? _lastCharOffset : offset); @@ -137,6 +139,8 @@ til::CoordType CharToColumnMapper::GetLeadingColumnAt(ptrdiff_t offset) noexcept return col; } +// If given a position (`offset`) inside the ROW's text, this function will return the corresponding column. +// This function in particular returns the glyph's last column (this matters for wide glyphs). til::CoordType CharToColumnMapper::GetTrailingColumnAt(ptrdiff_t offset) noexcept { auto col = GetLeadingColumnAt(offset); @@ -1027,13 +1031,13 @@ std::wstring_view ROW::GetText(til::CoordType columnBegin, til::CoordType column return { _chars.data() + chBeg, chEnd - chBeg }; } -til::CoordType ROW::GetLeftAlignedColumnAtChar(const ptrdiff_t offset) const noexcept +til::CoordType ROW::GetLeadingColumnAtCharOffset(const ptrdiff_t offset) const noexcept { auto mapper = _createCharToColumnMapper(offset); return mapper.GetLeadingColumnAt(offset); } -til::CoordType ROW::GetRightAlignedColumnAtChar(const ptrdiff_t offset) const noexcept +til::CoordType ROW::GetTrailingColumnAtCharOffset(const ptrdiff_t offset) const noexcept { auto mapper = _createCharToColumnMapper(offset); return mapper.GetTrailingColumnAt(offset); diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index 379b1856bcc..586aa12b95b 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -157,8 +157,8 @@ class ROW final DbcsAttribute DbcsAttrAt(til::CoordType column) const noexcept; std::wstring_view GetText() const noexcept; std::wstring_view GetText(til::CoordType columnBegin, til::CoordType columnEnd) const noexcept; - til::CoordType GetLeftAlignedColumnAtChar(ptrdiff_t offset) const noexcept; - til::CoordType GetRightAlignedColumnAtChar(ptrdiff_t offset) const noexcept; + til::CoordType GetLeadingColumnAtCharOffset(ptrdiff_t offset) const noexcept; + til::CoordType GetTrailingColumnAtCharOffset(ptrdiff_t offset) const noexcept; DelimiterClass DelimiterClassAt(til::CoordType column, const std::wstring_view& wordDelimiters) const noexcept; auto AttrBegin() const noexcept { return _attr.begin(); } diff --git a/src/buffer/out/UTextAdapter.cpp b/src/buffer/out/UTextAdapter.cpp index d23d883b336..f51f496b44b 100644 --- a/src/buffer/out/UTextAdapter.cpp +++ b/src/buffer/out/UTextAdapter.cpp @@ -27,6 +27,24 @@ constexpr til::CoordType& accessCurrentRow(UText* ut) noexcept return ut->b; } +// An excerpt from the ICU documentation: +// +// Clone a UText. Much like opening a UText where the source text is itself another UText. +// +// A shallow clone replicates only the UText data structures; it does not make +// a copy of the underlying text. Shallow clones can be used as an efficient way to +// have multiple iterators active in a single text string that is not being modified. +// +// A shallow clone operation must not fail except for truly exceptional conditions such +// as memory allocation failures. +// +// @param dest A UText struct to be filled in with the result of the clone operation, +// or NULL if the clone function should heap-allocate a new UText struct. +// @param src The UText to be cloned. +// @param deep true to request a deep clone, false for a shallow clone. +// @param status Errors are returned here. For deep clones, U_UNSUPPORTED_ERROR should +// be returned if the text provider is unable to clone the original text. +// @return The newly created clone, or NULL if the clone operation failed. static UText* U_CALLCONV utextClone(UText* dest, const UText* src, UBool deep, UErrorCode* status) noexcept { __assume(status != nullptr); @@ -46,7 +64,13 @@ static UText* U_CALLCONV utextClone(UText* dest, const UText* src, UBool deep, U return dest; } -static int64_t U_CALLCONV utextLength(UText* ut) noexcept +// An excerpt from the ICU documentation: +// +// Gets the length of the text. +// +// @param ut the UText to get the length of. +// @return the length, in the native units of the original text string. +static int64_t U_CALLCONV utextNativeLength(UText* ut) noexcept try { auto length = accessLength(ut); @@ -71,6 +95,19 @@ catch (...) return 0; } +// An excerpt from the ICU documentation: +// +// Get the description of the text chunk containing the text at a requested native index. +// The UText's iteration position will be left at the requested index. +// If the index is out of bounds, the iteration position will be left +// at the start or end of the string, as appropriate. +// +// @param ut the UText being accessed. +// @param nativeIndex Requested index of the text to be accessed. +// @param forward If true, then the returned chunk must contain text starting from the index, so that start<=indexchunkNativeStart); + ret.start.x = textBuffer.GetRowByOffset(y).GetLeadingColumnAtCharOffset(nativeIndexBeg - ut->chunkNativeStart); ret.start.y = y; } else @@ -250,7 +303,7 @@ til::point_span BufferRangeFromUText(UText* ut, int64_t nativeIndexBeg, int64_t if (utextAccess(ut, nativeIndexEnd, true)) { const auto y = accessCurrentRow(ut); - ret.end.x = textBuffer.GetRowByOffset(y).GetLeftAlignedColumnAtChar(nativeIndexEnd - ut->chunkNativeStart); + ret.end.x = textBuffer.GetRowByOffset(y).GetTrailingColumnAtCharOffset(nativeIndexEnd - ut->chunkNativeStart); ret.end.y = y; } else From dba1629d06e132184817ed7757afe891a2f8fec8 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 21 Aug 2023 14:59:18 +0200 Subject: [PATCH 41/61] Spelling fix --- .github/actions/spelling/expect/expect.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 3a69241e2fd..acdb6a50b6e 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1440,6 +1440,7 @@ prc prealigned prect prefast +preflighting prefs prepopulated presorted @@ -1953,6 +1954,7 @@ UAX UBool ucd uch +UChars udk UDM uer From 8146ecdffe8e75ef137b98f4480d0c8b8588bafc Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 21 Aug 2023 15:15:16 +0200 Subject: [PATCH 42/61] Fix a bug and x86 woes --- src/buffer/out/UTextAdapter.cpp | 6 +++--- src/types/UiaTextRangeBase.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/buffer/out/UTextAdapter.cpp b/src/buffer/out/UTextAdapter.cpp index f51f496b44b..41f9e799ae8 100644 --- a/src/buffer/out/UTextAdapter.cpp +++ b/src/buffer/out/UTextAdapter.cpp @@ -231,7 +231,7 @@ try const auto& textBuffer = *static_cast(ut->context); const auto y = accessCurrentRow(ut); const auto offset = ut->chunkNativeStart - nativeStart; - const auto text = textBuffer.GetRowByOffset(y).GetText().substr(offset); + const auto text = textBuffer.GetRowByOffset(y).GetText().substr(gsl::narrow_cast(std::max(0, offset))); const auto length = std::min(gsl::narrow_cast(destCapacity), text.size()); memcpy(dest, text.data(), length * sizeof(char16_t)); @@ -292,7 +292,7 @@ til::point_span BufferRangeFromUText(UText* ut, int64_t nativeIndexBeg, int64_t if (utextAccess(ut, nativeIndexBeg, true)) { const auto y = accessCurrentRow(ut); - ret.start.x = textBuffer.GetRowByOffset(y).GetLeadingColumnAtCharOffset(nativeIndexBeg - ut->chunkNativeStart); + ret.start.x = textBuffer.GetRowByOffset(y).GetLeadingColumnAtCharOffset(gsl::narrow_cast(nativeIndexBeg - ut->chunkNativeStart)); ret.start.y = y; } else @@ -303,7 +303,7 @@ til::point_span BufferRangeFromUText(UText* ut, int64_t nativeIndexBeg, int64_t if (utextAccess(ut, nativeIndexEnd, true)) { const auto y = accessCurrentRow(ut); - ret.end.x = textBuffer.GetRowByOffset(y).GetTrailingColumnAtCharOffset(nativeIndexEnd - ut->chunkNativeStart); + ret.end.x = textBuffer.GetRowByOffset(y).GetTrailingColumnAtCharOffset(gsl::narrow_cast(nativeIndexEnd - ut->chunkNativeStart)); ret.end.y = y; } else diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 56fa391aeff..f1175f34c2d 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -623,7 +623,7 @@ try for (size_t i = highestIndex;; --i) { hit = &til::at(results, i); - if (hit->start < _end) + if (hit->end < _end) { break; } From 5ca26aa3dd0f92a407bec8c8166080d578a13d3f Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 21 Aug 2023 15:32:19 +0200 Subject: [PATCH 43/61] More x86 woes, Improve UiaTextRangeBase --- src/buffer/out/UTextAdapter.cpp | 5 +++-- src/types/UiaTextRangeBase.cpp | 23 ++++++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/buffer/out/UTextAdapter.cpp b/src/buffer/out/UTextAdapter.cpp index 41f9e799ae8..a929c1e7594 100644 --- a/src/buffer/out/UTextAdapter.cpp +++ b/src/buffer/out/UTextAdapter.cpp @@ -232,11 +232,12 @@ try const auto y = accessCurrentRow(ut); const auto offset = ut->chunkNativeStart - nativeStart; const auto text = textBuffer.GetRowByOffset(y).GetText().substr(gsl::narrow_cast(std::max(0, offset))); - const auto length = std::min(gsl::narrow_cast(destCapacity), text.size()); + const auto destCapacitySizeT = gsl::narrow_cast(destCapacity); + const auto length = std::min(destCapacitySizeT, text.size()); memcpy(dest, text.data(), length * sizeof(char16_t)); - if (length < destCapacity) + if (length < destCapacitySizeT) { #pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1). dest[length] = 0; diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index f1175f34c2d..e3f9a0dde62 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -620,6 +620,7 @@ try if (searchBackward) { + // Find the last result that is in the [_start,_end) range. for (size_t i = highestIndex;; --i) { hit = &til::at(results, i); @@ -628,6 +629,7 @@ try break; } + // ...but exit when none are found. if (i <= 0) { return S_OK; @@ -636,6 +638,7 @@ try } else { + // Find the first result that is in the [_start,_end) range. for (size_t i = 0;; ++i) { hit = &til::at(results, i); @@ -644,6 +647,7 @@ try break; } + // ...but exit when none are found. if (i >= highestIndex) { return S_OK; @@ -651,12 +655,21 @@ try } } - RETURN_IF_FAILED(Clone(ppRetVal)); - auto& range = static_cast(**ppRetVal); - range._start = hit->start; - range._end = hit->end; + const auto start = hit->start; + auto end = hit->end; + + // we need to increment the position of end because it's exclusive + _getOptimizedBufferSize().IncrementInBounds(end, true); + + if (start >= _start && end <= _end) + { + RETURN_IF_FAILED(Clone(ppRetVal)); + auto& range = static_cast(**ppRetVal); + range._start = start; + range._end = end; + UiaTracing::TextRange::FindText(*this, queryText, searchBackward, ignoreCase, range); + } - UiaTracing::TextRange::FindText(*this, queryText, searchBackward, ignoreCase, range); return S_OK; } CATCH_RETURN(); From e0fb764c05fa5d52f397845d6a247c48a02612a0 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 21 Aug 2023 16:21:23 +0200 Subject: [PATCH 44/61] Fix unit tests --- src/buffer/out/search.h | 4 - src/buffer/out/ut_textbuffer/ReflowTests.cpp | 2 +- src/host/selection.cpp | 18 +++-- src/host/ut_host/ScreenBufferTests.cpp | 2 +- src/host/ut_host/SearchTests.cpp | 78 ++++++++----------- src/host/ut_host/TextBufferTests.cpp | 36 ++++----- src/inc/test/CommonState.hpp | 2 +- .../UiaTextRangeTests.cpp | 4 +- 8 files changed, 68 insertions(+), 78 deletions(-) diff --git a/src/buffer/out/search.h b/src/buffer/out/search.h index b8361188b16..67a75ba3e45 100644 --- a/src/buffer/out/search.h +++ b/src/buffer/out/search.h @@ -39,8 +39,4 @@ class Search final ptrdiff_t _index = 0; ptrdiff_t _step = 0; uint64_t _mutationCount = 0; - -#ifdef UNIT_TESTING - friend class SearchTests; -#endif }; diff --git a/src/buffer/out/ut_textbuffer/ReflowTests.cpp b/src/buffer/out/ut_textbuffer/ReflowTests.cpp index 2d4a5c4a1e7..3c99a9fb583 100644 --- a/src/buffer/out/ut_textbuffer/ReflowTests.cpp +++ b/src/buffer/out/ut_textbuffer/ReflowTests.cpp @@ -739,7 +739,7 @@ class ReflowTests til::CoordType y = 0; for (const auto& testRow : testBuffer.rows) { - auto& row{ buffer->GetRowByOffset(y) }; + auto& row{ buffer->GetMutableRowByOffset(y) }; row.SetWrapForced(testRow.wrap); diff --git a/src/host/selection.cpp b/src/host/selection.cpp index 88b00fd45de..97145a6013a 100644 --- a/src/host/selection.cpp +++ b/src/host/selection.cpp @@ -104,7 +104,10 @@ void Selection::_SetSelectionVisibility(const bool fMakeVisible) _PaintSelection(); } - LOG_IF_FAILED(ServiceLocator::LocateConsoleWindow()->SignalUia(UIA_Text_TextSelectionChangedEventId)); + if (const auto window = ServiceLocator::LocateConsoleWindow()) + { + LOG_IF_FAILED(window->SignalUia(UIA_Text_TextSelectionChangedEventId)); + } } // Routine Description: @@ -269,12 +272,14 @@ void Selection::ExtendSelection(_In_ til::point coordBufferPos) _PaintSelection(); // Fire off an event to let accessibility apps know the selection has changed. - auto pNotifier = ServiceLocator::LocateAccessibilityNotifier(); - if (pNotifier) + if (const auto pNotifier = ServiceLocator::LocateAccessibilityNotifier()) { pNotifier->NotifyConsoleCaretEvent(IAccessibilityNotifier::ConsoleCaretEventFlags::CaretSelection, PACKCOORD(coordBufferPos)); } - LOG_IF_FAILED(ServiceLocator::LocateConsoleWindow()->SignalUia(UIA_Text_TextSelectionChangedEventId)); + if (const auto window = ServiceLocator::LocateConsoleWindow()) + { + LOG_IF_FAILED(window->SignalUia(UIA_Text_TextSelectionChangedEventId)); + } } // Routine Description: @@ -366,7 +371,10 @@ void Selection::ClearSelection(const bool fStartingNewSelection) { _CancelMarkSelection(); } - LOG_IF_FAILED(ServiceLocator::LocateConsoleWindow()->SignalUia(UIA_Text_TextSelectionChangedEventId)); + if (const auto window = ServiceLocator::LocateConsoleWindow()) + { + LOG_IF_FAILED(window->SignalUia(UIA_Text_TextSelectionChangedEventId)); + } _dwSelectionFlags = 0; diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index b26801be0a1..1dd058f057d 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -3650,7 +3650,7 @@ void _FillLine(til::point position, T fillContent, TextAttribute fillAttr) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer(); - auto& row = si.GetTextBuffer().GetRowByOffset(position.y); + auto& row = si.GetTextBuffer().GetMutableRowByOffset(position.y); row.WriteCells({ fillContent, fillAttr }, position.x, false); } diff --git a/src/host/ut_host/SearchTests.cpp b/src/host/ut_host/SearchTests.cpp index faa3935e518..d786cd0ddd2 100644 --- a/src/host/ut_host/SearchTests.cpp +++ b/src/host/ut_host/SearchTests.cpp @@ -53,109 +53,95 @@ class SearchTests TEST_METHOD_CLEANUP(MethodCleanup) { m_state->CleanupNewTextBufferInfo(); - + Selection::Instance().ClearSelection(); return true; } - void DoFoundChecks(Search& s, til::point& coordStartExpected, til::CoordType lineDelta) + static void DoFoundChecks(Search& s, til::point coordStartExpected, til::CoordType lineDelta) { + const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto coordEndExpected = coordStartExpected; coordEndExpected.x += 1; - VERIFY_IS_TRUE(s.FindNext()); - VERIFY_ARE_EQUAL(coordStartExpected, s._coordSelStart); - VERIFY_ARE_EQUAL(coordEndExpected, s._coordSelEnd); + VERIFY_IS_TRUE(s.SelectNext()); + VERIFY_ARE_EQUAL(coordStartExpected, gci.renderData.GetSelectionAnchor()); + VERIFY_ARE_EQUAL(coordEndExpected, gci.renderData.GetSelectionEnd()); coordStartExpected.y += lineDelta; coordEndExpected.y += lineDelta; - VERIFY_IS_TRUE(s.FindNext()); - VERIFY_ARE_EQUAL(coordStartExpected, s._coordSelStart); - VERIFY_ARE_EQUAL(coordEndExpected, s._coordSelEnd); + VERIFY_IS_TRUE(s.SelectNext()); + VERIFY_ARE_EQUAL(coordStartExpected, gci.renderData.GetSelectionAnchor()); + VERIFY_ARE_EQUAL(coordEndExpected, gci.renderData.GetSelectionEnd()); coordStartExpected.y += lineDelta; coordEndExpected.y += lineDelta; - VERIFY_IS_TRUE(s.FindNext()); - VERIFY_ARE_EQUAL(coordStartExpected, s._coordSelStart); - VERIFY_ARE_EQUAL(coordEndExpected, s._coordSelEnd); + VERIFY_IS_TRUE(s.SelectNext()); + VERIFY_ARE_EQUAL(coordStartExpected, gci.renderData.GetSelectionAnchor()); + VERIFY_ARE_EQUAL(coordEndExpected, gci.renderData.GetSelectionEnd()); coordStartExpected.y += lineDelta; coordEndExpected.y += lineDelta; - VERIFY_IS_TRUE(s.FindNext()); - VERIFY_ARE_EQUAL(coordStartExpected, s._coordSelStart); - VERIFY_ARE_EQUAL(coordEndExpected, s._coordSelEnd); - - VERIFY_IS_FALSE(s.FindNext()); + VERIFY_IS_TRUE(s.SelectNext()); + VERIFY_ARE_EQUAL(coordStartExpected, gci.renderData.GetSelectionAnchor()); + VERIFY_ARE_EQUAL(coordEndExpected, gci.renderData.GetSelectionEnd()); } TEST_METHOD(ForwardCaseSensitive) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - til::point coordStartExpected; - Search s(gci.renderData, L"AB", Search::Direction::Forward, Search::Sensitivity::CaseSensitive); - DoFoundChecks(s, coordStartExpected, 1); + Search s(gci.renderData, L"AB", false, false); + DoFoundChecks(s, {}, 1); } TEST_METHOD(ForwardCaseSensitiveJapanese) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - - til::point coordStartExpected = { 2, 0 }; - Search s(gci.renderData, L"\x304b", Search::Direction::Forward, Search::Sensitivity::CaseSensitive); - DoFoundChecks(s, coordStartExpected, 1); + Search s(gci.renderData, L"\x304b", false, false); + DoFoundChecks(s, { 2, 0 }, 1); } TEST_METHOD(ForwardCaseInsensitive) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - til::point coordStartExpected; - Search s(gci.renderData, L"ab", Search::Direction::Forward, Search::Sensitivity::CaseInsensitive); - DoFoundChecks(s, coordStartExpected, 1); + Search s(gci.renderData, L"ab", false, true); + DoFoundChecks(s, {}, 1); } TEST_METHOD(ForwardCaseInsensitiveJapanese) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - - til::point coordStartExpected = { 2, 0 }; - Search s(gci.renderData, L"\x304b", Search::Direction::Forward, Search::Sensitivity::CaseInsensitive); - DoFoundChecks(s, coordStartExpected, 1); + Search s(gci.renderData, L"\x304b", false, true); + DoFoundChecks(s, { 2, 0 }, 1); } TEST_METHOD(BackwardCaseSensitive) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - - til::point coordStartExpected = { 0, 3 }; - Search s(gci.renderData, L"AB", Search::Direction::Backward, Search::Sensitivity::CaseSensitive); - DoFoundChecks(s, coordStartExpected, -1); + Search s(gci.renderData, L"AB", true, false); + DoFoundChecks(s, { 0, 3 }, -1); } TEST_METHOD(BackwardCaseSensitiveJapanese) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - - til::point coordStartExpected = { 2, 3 }; - Search s(gci.renderData, L"\x304b", Search::Direction::Backward, Search::Sensitivity::CaseSensitive); - DoFoundChecks(s, coordStartExpected, -1); + Search s(gci.renderData, L"\x304b", true, false); + DoFoundChecks(s, { 2, 3 }, -1); } TEST_METHOD(BackwardCaseInsensitive) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - - til::point coordStartExpected = { 0, 3 }; - Search s(gci.renderData, L"ab", Search::Direction::Backward, Search::Sensitivity::CaseInsensitive); - DoFoundChecks(s, coordStartExpected, -1); + Search s(gci.renderData, L"ab", true, true); + DoFoundChecks(s, { 0, 3 }, -1); } TEST_METHOD(BackwardCaseInsensitiveJapanese) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - - til::point coordStartExpected = { 2, 3 }; - Search s(gci.renderData, L"\x304b", Search::Direction::Backward, Search::Sensitivity::CaseInsensitive); - DoFoundChecks(s, coordStartExpected, -1); + Search s(gci.renderData, L"\x304b", true, true); + DoFoundChecks(s, { 2, 3 }, -1); } }; diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index bcb4cec4a99..44781d38a72 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -188,7 +188,7 @@ void TextBufferTests::TestWrapFlag() { auto& textBuffer = GetTbi(); - auto& Row = textBuffer.GetRowByOffset(0); + auto& Row = textBuffer.GetMutableRowByOffset(0); // no wrap by default VERIFY_IS_FALSE(Row.WasWrapForced()); @@ -278,7 +278,7 @@ void TextBufferTests::TestDoubleBytePadFlag() { auto& textBuffer = GetTbi(); - auto& Row = textBuffer.GetRowByOffset(0); + auto& Row = textBuffer.GetMutableRowByOffset(0); // no padding by default VERIFY_IS_FALSE(Row.WasDoubleBytePadded()); @@ -300,7 +300,7 @@ void TextBufferTests::DoBoundaryTest(PCWCHAR const pwszInputString, { auto& textBuffer = GetTbi(); - auto& row = textBuffer.GetRowByOffset(0); + auto& row = textBuffer.GetMutableRowByOffset(0); // copy string into buffer for (til::CoordType i = 0; i < cLength; ++i) @@ -571,7 +571,7 @@ void TextBufferTests::TestSetWrapOnCurrentRow() auto sCurrentRow = textBuffer.GetCursor().GetPosition().y; - auto& Row = textBuffer.GetRowByOffset(sCurrentRow); + auto& Row = textBuffer.GetMutableRowByOffset(sCurrentRow); Log::Comment(L"Testing off to on"); @@ -622,7 +622,7 @@ void TextBufferTests::TestIncrementCircularBuffer() textBuffer._firstRow = iRowToTestIndex; // fill first row with some stuff - auto& FirstRow = textBuffer.GetRowByOffset(0); + auto& FirstRow = textBuffer.GetMutableRowByOffset(0); FirstRow.ReplaceCharacters(0, 1, { L"A" }); // ensure it does say that it contains text @@ -1847,7 +1847,7 @@ void TextBufferTests::ResizeTraditionalRotationPreservesHighUnicode() // This is the negative squared latin capital letter B emoji: 🅱 // It's encoded in UTF-16, as needed by the buffer. const auto bButton = L"\xD83C\xDD71"; - _buffer->GetRowByOffset(pos.y).ReplaceCharacters(pos.x, 2, bButton); + _buffer->GetMutableRowByOffset(pos.y).ReplaceCharacters(pos.x, 2, bButton); // Read back the text at that position and ensure that it matches what we wrote. const auto readBack = _buffer->GetTextDataAt(pos); @@ -1888,7 +1888,7 @@ void TextBufferTests::ScrollBufferRotationPreservesHighUnicode() // This is the fire emoji: 🔥 // It's encoded in UTF-16, as needed by the buffer. const auto fire = L"\xD83D\xDD25"; - _buffer->GetRowByOffset(pos.y).ReplaceCharacters(pos.x, 2, fire); + _buffer->GetMutableRowByOffset(pos.y).ReplaceCharacters(pos.x, 2, fire); // Read back the text at that position and ensure that it matches what we wrote. const auto readBack = _buffer->GetTextDataAt(pos); @@ -1923,7 +1923,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeRowRemoval() // This is the eggplant emoji: 🍆 // It's encoded in UTF-16, as needed by the buffer. const auto emoji = L"\xD83C\xDF46"; - _buffer->GetRowByOffset(pos.y).ReplaceCharacters(pos.x, 2, emoji); + _buffer->GetMutableRowByOffset(pos.y).ReplaceCharacters(pos.x, 2, emoji); // Read back the text at that position and ensure that it matches what we wrote. const auto readBack = _buffer->GetTextDataAt(pos); @@ -1953,7 +1953,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeColumnRemoval() // This is the peach emoji: 🍑 // It's encoded in UTF-16, as needed by the buffer. const auto emoji = L"\xD83C\xDF51"; - _buffer->GetRowByOffset(pos.y).ReplaceCharacters(pos.x, 2, emoji); + _buffer->GetMutableRowByOffset(pos.y).ReplaceCharacters(pos.x, 2, emoji); // Read back the text at that position and ensure that it matches what we wrote. const auto readBack = _buffer->GetTextDataAt(pos); @@ -1993,7 +1993,7 @@ void TextBufferTests::TestOverwriteChars() UINT cursorSize = 12; TextAttribute attr{ 0x7f }; TextBuffer buffer{ bufferSize, attr, cursorSize, false, _renderer }; - auto& row = buffer.GetRowByOffset(0); + auto& row = buffer.GetMutableRowByOffset(0); // scientist emoji U+1F9D1 U+200D U+1F52C #define complex1 L"\U0001F9D1\U0000200D\U0001F52C" @@ -2009,17 +2009,17 @@ void TextBufferTests::TestOverwriteChars() // Test overwriting wide chars with wide chars slightly shifted left/right. row.ReplaceCharacters(1, 2, complex1); row.ReplaceCharacters(7, 2, complex1); - VERIFY_ARE_EQUAL(L" " complex1 L" " complex1 L" ", row.GetText()); + VERIFY_ARE_EQUAL(L" " complex1 L" " complex1, row.GetText()); // Test overwriting wide chars with wide chars. row.ReplaceCharacters(1, 2, complex2); row.ReplaceCharacters(7, 2, complex2); - VERIFY_ARE_EQUAL(L" " complex2 L" " complex2 L" ", row.GetText()); + VERIFY_ARE_EQUAL(L" " complex2 L" " complex2, row.GetText()); // Test overwriting wide chars with narrow chars. row.ReplaceCharacters(1, 1, simple); row.ReplaceCharacters(8, 1, simple); - VERIFY_ARE_EQUAL(L" " simple L" " simple L" ", row.GetText()); + VERIFY_ARE_EQUAL(L" " simple L" " simple, row.GetText()); // Test clearing narrow/wide chars. row.ReplaceCharacters(0, 1, simple); @@ -2049,7 +2049,7 @@ void TextBufferTests::TestRowReplaceText() static constexpr UINT cursorSize = 12; const TextAttribute attr{ 0x7f }; TextBuffer buffer{ bufferSize, attr, cursorSize, false, _renderer }; - auto& row = buffer.GetRowByOffset(0); + auto& row = buffer.GetMutableRowByOffset(0); #define complex L"\U0001F41B" @@ -2755,14 +2755,14 @@ void TextBufferTests::HyperlinkTrim() const auto id = _buffer->GetHyperlinkId(url, customId); TextAttribute newAttr{ 0x7f }; newAttr.SetHyperlinkId(id); - _buffer->GetRowByOffset(pos.y).SetAttrToEnd(pos.x, newAttr); + _buffer->GetMutableRowByOffset(pos.y).SetAttrToEnd(pos.x, newAttr); _buffer->AddHyperlinkToMap(url, id); // Set a different hyperlink id somewhere else in the buffer const til::point otherPos{ 70, 5 }; const auto otherId = _buffer->GetHyperlinkId(otherUrl, otherCustomId); newAttr.SetHyperlinkId(otherId); - _buffer->GetRowByOffset(otherPos.y).SetAttrToEnd(otherPos.x, newAttr); + _buffer->GetMutableRowByOffset(otherPos.y).SetAttrToEnd(otherPos.x, newAttr); _buffer->AddHyperlinkToMap(otherUrl, otherId); // Increment the circular buffer @@ -2799,12 +2799,12 @@ void TextBufferTests::NoHyperlinkTrim() const auto id = _buffer->GetHyperlinkId(url, customId); TextAttribute newAttr{ 0x7f }; newAttr.SetHyperlinkId(id); - _buffer->GetRowByOffset(pos.y).SetAttrToEnd(pos.x, newAttr); + _buffer->GetMutableRowByOffset(pos.y).SetAttrToEnd(pos.x, newAttr); _buffer->AddHyperlinkToMap(url, id); // Set the same hyperlink id somewhere else in the buffer const til::point otherPos{ 70, 5 }; - _buffer->GetRowByOffset(otherPos.y).SetAttrToEnd(otherPos.x, newAttr); + _buffer->GetMutableRowByOffset(otherPos.y).SetAttrToEnd(otherPos.x, newAttr); // Increment the circular buffer _buffer->IncrementCircularBuffer(); diff --git a/src/inc/test/CommonState.hpp b/src/inc/test/CommonState.hpp index 9a0e1272b7d..1803acb0ae8 100644 --- a/src/inc/test/CommonState.hpp +++ b/src/inc/test/CommonState.hpp @@ -237,7 +237,7 @@ class CommonState for (til::CoordType iRow = 0; iRow < cRowsToFill; iRow++) { - ROW& row = textBuffer.GetRowByOffset(iRow); + ROW& row = textBuffer.GetMutableRowByOffset(iRow); FillRow(&row, iRow & 1); } diff --git a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index 8278bfa952a..34b4e20caef 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -361,7 +361,7 @@ class UiaTextRangeTests for (auto i = 0; i < _pTextBuffer->TotalRowCount() / 2; ++i) { const std::wstring_view glyph{ i % 2 == 0 ? L" " : L"X" }; - auto& row = _pTextBuffer->GetRowByOffset(i); + auto& row = _pTextBuffer->GetMutableRowByOffset(i); const auto width = row.size(); for (uint16_t x = 0; x < width; ++x) @@ -489,7 +489,7 @@ class UiaTextRangeTests // Let's start by filling the text buffer with something useful: for (auto i = 0; i < _pTextBuffer->TotalRowCount(); ++i) { - auto& row = _pTextBuffer->GetRowByOffset(i); + auto& row = _pTextBuffer->GetMutableRowByOffset(i); const auto width = row.size(); for (uint16_t x = 0; x < width; ++x) From ecc735d0077be911e6f3f49ad2f27ad7889c864f Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 21 Aug 2023 19:21:50 +0200 Subject: [PATCH 45/61] Implement backwards text iteration --- src/buffer/out/UTextAdapter.cpp | 104 ++++++++++++++++---------------- src/buffer/out/textBuffer.cpp | 4 +- 2 files changed, 56 insertions(+), 52 deletions(-) diff --git a/src/buffer/out/UTextAdapter.cpp b/src/buffer/out/UTextAdapter.cpp index a929c1e7594..6ade9b4b5c3 100644 --- a/src/buffer/out/UTextAdapter.cpp +++ b/src/buffer/out/UTextAdapter.cpp @@ -111,12 +111,15 @@ catch (...) static UBool U_CALLCONV utextAccess(UText* ut, int64_t nativeIndex, UBool forward) noexcept try { + if (nativeIndex < 0) + { + nativeIndex = 0; + } + + auto neededIndex = nativeIndex; if (!forward) { - // Even after reading the ICU documentation I'm a little unclear on how to handle the forward flag. - // I _think_ it's basically nothing but "nativeIndex--" for us, but I didn't want to verify it - // because right now we never use any ICU functions that require backwards text access anyways. - return false; + neededIndex--; } const auto& textBuffer = *static_cast(ut->context); @@ -126,60 +129,58 @@ try auto y = accessCurrentRow(ut); std::wstring_view text; - if (nativeIndex >= start && nativeIndex < limit) - { - return true; - } - - if (nativeIndex < start) + if (neededIndex < start || neededIndex >= limit) { - for (;;) + if (neededIndex < start) { - --y; - if (y < range.begin) + do { - return false; - } - - text = textBuffer.GetRowByOffset(y).GetText(); - limit = start; - start -= text.size(); - - if (nativeIndex >= start) - { - break; - } + --y; + if (y < range.begin) + { + return false; + } + + text = textBuffer.GetRowByOffset(y).GetText(); + limit = start; + start -= text.size(); + } while (neededIndex < start); } - } - else - { - for (;;) + else { - ++y; - if (y >= range.end) + do { - return false; - } + ++y; + if (y >= range.end) + { + return false; + } + + text = textBuffer.GetRowByOffset(y).GetText(); + start = limit; + limit += text.size(); + } while (neededIndex >= limit); + } + + accessCurrentRow(ut) = y; + ut->chunkNativeStart = start; + ut->chunkNativeLimit = limit; + ut->chunkLength = gsl::narrow_cast(text.size()); +#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). + ut->chunkContents = reinterpret_cast(text.data()); + ut->nativeIndexingLimit = ut->chunkLength; + } - text = textBuffer.GetRowByOffset(y).GetText(); - start = limit; - limit += text.size(); + auto offset = gsl::narrow_cast(nativeIndex - start); - if (nativeIndex < limit) - { - break; - } - } + // Don't leave the offset on a trailing surrogate pair. See U16_SET_CP_START. + // This assumes that the TextBuffer contains valid UTF-16 which may theoretically not be the case. + if (offset > 0 && offset < ut->chunkLength && U16_IS_TRAIL(til::at(ut->chunkContents, offset))) + { + offset--; } - accessCurrentRow(ut) = y; - ut->chunkNativeStart = start; - ut->chunkNativeLimit = limit; - ut->chunkOffset = gsl::narrow_cast(nativeIndex - start); - ut->chunkLength = gsl::narrow_cast(text.size()); -#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). - ut->chunkContents = reinterpret_cast(text.data()); - ut->nativeIndexingLimit = ut->chunkLength; + ut->chunkOffset = offset; return true; } catch (...) @@ -281,7 +282,8 @@ UText* UTextFromTextBuffer(UText* ut, const TextBuffer& textBuffer, til::CoordTy return ut; } -// Returns an inclusive point range given a text start and end position. This function is designed to be used with uregex_start64/uregex_end64. +// Returns an inclusive point range given a text start and end position. +// This function is designed to be used with uregex_start64/uregex_end64. til::point_span BufferRangeFromUText(UText* ut, int64_t nativeIndexBeg, int64_t nativeIndexEnd) { // The parameters are given as a half-open [beg,end) range, but the point_span we return in closed [beg,end]. @@ -293,7 +295,7 @@ til::point_span BufferRangeFromUText(UText* ut, int64_t nativeIndexBeg, int64_t if (utextAccess(ut, nativeIndexBeg, true)) { const auto y = accessCurrentRow(ut); - ret.start.x = textBuffer.GetRowByOffset(y).GetLeadingColumnAtCharOffset(gsl::narrow_cast(nativeIndexBeg - ut->chunkNativeStart)); + ret.start.x = textBuffer.GetRowByOffset(y).GetLeadingColumnAtCharOffset(ut->chunkOffset); ret.start.y = y; } else @@ -304,7 +306,7 @@ til::point_span BufferRangeFromUText(UText* ut, int64_t nativeIndexBeg, int64_t if (utextAccess(ut, nativeIndexEnd, true)) { const auto y = accessCurrentRow(ut); - ret.end.x = textBuffer.GetRowByOffset(y).GetTrailingColumnAtCharOffset(gsl::narrow_cast(nativeIndexEnd - ut->chunkNativeStart)); + ret.end.x = textBuffer.GetRowByOffset(y).GetTrailingColumnAtCharOffset(ut->chunkOffset); ret.end.y = y; } else diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 5219a1a2f51..27d0c7b2b3a 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -2792,7 +2792,9 @@ PointTree TextBuffer::GetPatterns(const til::CoordType firstRow, const til::Coor { const auto nativeIndexBeg = uregex_start64(_urlRegex, 0, &status); const auto nativeIndexEnd = uregex_end64(_urlRegex, 0, &status); - const auto range = BufferRangeFromUText(&text, nativeIndexBeg, nativeIndexEnd); + auto range = BufferRangeFromUText(&text, nativeIndexBeg, nativeIndexEnd); + // PointTree uses half-open ranges. + range.end.x++; intervals.push_back(PointTree::interval(range.start, range.end, 0)); } while (uregex_findNext(_urlRegex, &status)); } From 56c79112e5b001641e4934d994e4effb26e9d919 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 21 Aug 2023 19:26:27 +0200 Subject: [PATCH 46/61] Address feedback --- src/buffer/out/Row.cpp | 2 +- src/cascadia/TerminalControl/ControlCore.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 467b7de8d27..f25ef795e6b 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -10,7 +10,7 @@ #include "textBuffer.hpp" #include "../../types/inc/GlyphWidth.hpp" -// It would be nice to add checked array access in the future, but it's a little annoying to do so without imparting +// It would be nice to add checked array access in the future, but it's a little annoying to do so without impacting // performance (including Debug performance). Other languages are a little bit more ergonomic there than C++. #pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).) #pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4). diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index c1d67d95d9e..3e0a9fe7e9c 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1542,7 +1542,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation void ControlCore::Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive) { auto lock = _terminal->LockForWriting(); - _terminal->SetBlockSelection(false); if (_searcher.IsStale() || _searcherText != text || _searcherGoForward != goForward || _searcherCaseSensitive != caseSensitive) { @@ -1558,6 +1557,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // 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. + _terminal->SetBlockSelection(false); _renderer->TriggerSelection(); _UpdateSelectionMarkersHandlers(*this, winrt::make(true)); } From c731eae08c97633dd3dd7aa7c4a99d85dbe351f0 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 22 Aug 2023 18:55:44 +0200 Subject: [PATCH 47/61] Address feedback --- src/buffer/out/Row.cpp | 2 +- src/buffer/out/UTextAdapter.cpp | 38 ++++--- src/buffer/out/UTextAdapter.h | 8 +- src/buffer/out/sources.inc | 3 +- src/buffer/out/textBuffer.cpp | 63 +---------- src/buffer/out/textBuffer.hpp | 1 - src/cascadia/TerminalCore/Terminal.cpp | 101 +++++++++++++++++- src/cascadia/TerminalCore/Terminal.hpp | 1 + .../TerminalCore/TerminalSelection.cpp | 2 +- 9 files changed, 134 insertions(+), 85 deletions(-) diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index f25ef795e6b..649825267bf 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -94,7 +94,7 @@ CharToColumnMapper::CharToColumnMapper(const wchar_t* chars, const uint16_t* cha // This function in particular returns the glyph's first column. til::CoordType CharToColumnMapper::GetLeadingColumnAt(ptrdiff_t offset) noexcept { - offset = offset < 0 ? 0 : (offset > _lastCharOffset ? _lastCharOffset : offset); + offset = clamp(offset, 0, _lastCharOffset); auto col = _currentColumn; const auto currentOffset = _charOffsets[col]; diff --git a/src/buffer/out/UTextAdapter.cpp b/src/buffer/out/UTextAdapter.cpp index 6ade9b4b5c3..dc5549d7327 100644 --- a/src/buffer/out/UTextAdapter.cpp +++ b/src/buffer/out/UTextAdapter.cpp @@ -262,30 +262,40 @@ static constexpr UTextFuncs utextFuncs{ }; // Creates a UText from the given TextBuffer that spans rows [rowBeg,RowEnd). -UText* UTextFromTextBuffer(UText* ut, const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd, UErrorCode* status) noexcept +UText UTextFromTextBuffer(const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd, UErrorCode* status) noexcept { __assume(status != nullptr); - ut = utext_setup(ut, 0, status); - if (*status > U_ZERO_ERROR) - { - return nullptr; - } + UText ut = UTEXT_INITIALIZER; + ut.providerProperties = (1 << UTEXT_PROVIDER_LENGTH_IS_EXPENSIVE) | (1 << UTEXT_PROVIDER_STABLE_CHUNKS); + ut.pFuncs = &utextFuncs; + ut.context = &textBuffer; + accessCurrentRow(&ut) = rowBeg - 1; // the utextAccess() below will advance this by 1. + accessRowRange(&ut) = { rowBeg, rowEnd }; - ut->providerProperties = (1 << UTEXT_PROVIDER_LENGTH_IS_EXPENSIVE) | (1 << UTEXT_PROVIDER_STABLE_CHUNKS); - ut->pFuncs = &utextFuncs; - ut->context = &textBuffer; - accessCurrentRow(ut) = rowBeg - 1; // the utextAccess() below will advance this by 1. - accessRowRange(ut) = { rowBeg, rowEnd }; - - utextAccess(ut, 0, true); + utextAccess(&ut, 0, true); return ut; } +URegularExpression* CreateURegularExpression(const std::wstring_view& pattern, uint32_t flags, UErrorCode* status) noexcept +{ +#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). + const auto re = uregex_open(reinterpret_cast(pattern.data()), gsl::narrow_cast(pattern.size()), flags, nullptr, status); + // ICU describes the time unit as being dependent on CPU performance and "typically [in] the order of milliseconds", + // but this claim seems highly outdated already. On my CPU from 2021, a limit of 4096 equals roughly 600ms. + uregex_setTimeLimit(re, 4096, status); + uregex_setStackLimit(re, 4 * 1024 * 1024, status); + return re; +} + // Returns an inclusive point range given a text start and end position. // This function is designed to be used with uregex_start64/uregex_end64. -til::point_span BufferRangeFromUText(UText* ut, int64_t nativeIndexBeg, int64_t nativeIndexEnd) +til::point_span BufferRangeFromMatch(UText* ut, URegularExpression* re) { + UErrorCode status = U_ZERO_ERROR; + const auto nativeIndexBeg = uregex_start64(re, 0, &status); + auto nativeIndexEnd = uregex_end64(re, 0, &status); + // The parameters are given as a half-open [beg,end) range, but the point_span we return in closed [beg,end]. nativeIndexEnd--; diff --git a/src/buffer/out/UTextAdapter.h b/src/buffer/out/UTextAdapter.h index 06d413902a4..ff523e26032 100644 --- a/src/buffer/out/UTextAdapter.h +++ b/src/buffer/out/UTextAdapter.h @@ -7,7 +7,9 @@ #include class TextBuffer; -struct UText; -UText* UTextFromTextBuffer(UText* ut, const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd, UErrorCode* status) noexcept; -til::point_span BufferRangeFromUText(UText* ut, int64_t nativeIndexBeg, int64_t nativeIndexEnd); +using unique_URegularExpression = wistd::unique_ptr>; + +UText UTextFromTextBuffer(const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd, UErrorCode* status) noexcept; +URegularExpression* CreateURegularExpression(const std::wstring_view& pattern, uint32_t flags, UErrorCode* status) noexcept; +til::point_span BufferRangeFromMatch(UText* ut, URegularExpression* re); diff --git a/src/buffer/out/sources.inc b/src/buffer/out/sources.inc index af3d278749c..6611cc0aa5e 100644 --- a/src/buffer/out/sources.inc +++ b/src/buffer/out/sources.inc @@ -40,7 +40,8 @@ SOURCES= \ ..\textBuffer.cpp \ ..\textBufferCellIterator.cpp \ ..\textBufferTextIterator.cpp \ - ..\search.cpp \ + ..\search.cpp \ + ..\UTextAdapter.cpp \ INCLUDES= \ $(INCLUDES); \ diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 27d0c7b2b3a..d7ce7b60575 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -18,7 +18,6 @@ using namespace Microsoft::Console; using namespace Microsoft::Console::Types; using PointTree = interval_tree::IntervalTree; -using unique_URegularExpression = wistd::unique_ptr>; constexpr bool allWhitespace(const std::wstring_view& text) noexcept { @@ -32,17 +31,6 @@ constexpr bool allWhitespace(const std::wstring_view& text) noexcept return true; } -static URegularExpression* createURegularExpression(const std::wstring_view& pattern, uint32_t flags, UErrorCode* error) noexcept -{ -#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). - const auto re = uregex_open(reinterpret_cast(pattern.data()), gsl::narrow_cast(pattern.size()), flags, nullptr, error); - // ICU describes the time unit as being dependent on CPU performance and "typically [in] the order of milliseconds", - // but this claim seems highly outdated already. On my CPU from 2021, a limit of 4096 equals roughly 600ms. - uregex_setTimeLimit(re, 4096, error); - uregex_setStackLimit(re, 4 * 1024 * 1024, error); - return re; -} - static std::atomic s_mutationCountInitialValue; // Routine Description: @@ -2761,47 +2749,6 @@ void TextBuffer::CopyHyperlinkMaps(const TextBuffer& other) _currentHyperlinkId = other._currentHyperlinkId; } -// Method Description: -// - Finds patterns within the requested region of the text buffer -// Arguments: -// - The firstRow to start searching from -// - The lastRow to search -// Return value: -// - An interval tree containing the patterns found -PointTree TextBuffer::GetPatterns(const til::CoordType firstRow, const til::CoordType lastRow) -{ - PointTree::interval_vector intervals; - - UErrorCode status = U_ZERO_ERROR; -#pragma warning(suppress : 26477) // Use 'nullptr' rather than 0 or NULL (es.47). - UText text = UTEXT_INITIALIZER; - UTextFromTextBuffer(&text, *this, firstRow, lastRow + 1, &status); - - if (!_urlRegex) - { - static constexpr std::wstring_view linkPattern{ LR"(\b(?:https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|$!:,.;]*[A-Za-z0-9+&@#/%=~_|$])" }; - _urlRegex = createURegularExpression(linkPattern, 0, &status); - assert(_urlRegex); - } - - uregex_setUText(_urlRegex, &text, &status); - - if (uregex_find(_urlRegex, -1, &status)) - { - do - { - const auto nativeIndexBeg = uregex_start64(_urlRegex, 0, &status); - const auto nativeIndexEnd = uregex_end64(_urlRegex, 0, &status); - auto range = BufferRangeFromUText(&text, nativeIndexBeg, nativeIndexEnd); - // PointTree uses half-open ranges. - range.end.x++; - intervals.push_back(PointTree::interval(range.start, range.end, 0)); - } while (uregex_findNext(_urlRegex, &status)); - } - - return PointTree{ std::move(intervals) }; -} - // Searches through the entire (committed) text buffer for `needle` and returns the coordinates in absolute coordinates. // The end coordinates of the returned ranges are considered inclusive. std::vector TextBuffer::SearchText(const std::wstring_view& needle, bool caseInsensitive) const @@ -2827,20 +2774,16 @@ std::vector TextBuffer::SearchText(const std::wstring_view& nee WI_SetFlagIf(flags, UREGEX_CASE_INSENSITIVE, caseInsensitive); UErrorCode status = U_ZERO_ERROR; -#pragma warning(suppress : 26477) // Use 'nullptr' rather than 0 or NULL (es.47). - UText text = UTEXT_INITIALIZER; - UTextFromTextBuffer(&text, *this, rowBeg, rowEnd, &status); + auto text = UTextFromTextBuffer(*this, rowBeg, rowEnd, &status); - const unique_URegularExpression re{ createURegularExpression(needle, flags, &status) }; + const unique_URegularExpression re{ CreateURegularExpression(needle, flags, &status) }; uregex_setUText(re.get(), &text, &status); if (uregex_find(re.get(), -1, &status)) { do { - const auto nativeIndexBeg = uregex_start64(re.get(), 0, &status); - const auto nativeIndexEnd = uregex_end64(re.get(), 0, &status); - results.emplace_back(BufferRangeFromUText(&text, nativeIndexBeg, nativeIndexEnd)); + results.emplace_back(BufferRangeFromMatch(&text, re.get())); } while (uregex_findNext(re.get(), &status)); } diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 23e7b77d254..90b64591701 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -265,7 +265,6 @@ class TextBuffer final const std::optional lastCharacterViewport, std::optional> positionInfo); - interval_tree::IntervalTree GetPatterns(const til::CoordType firstRow, const til::CoordType lastRow); std::vector SearchText(const std::wstring_view& needle, bool caseInsensitive) const; std::vector SearchText(const std::wstring_view& needle, bool caseInsensitive, til::CoordType rowBeg, til::CoordType rowEnd) const; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index ada1bed5b62..c0f339f90dc 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -9,7 +9,9 @@ #include "../../types/inc/utils.hpp" #include "../../types/inc/colorTable.hpp" #include "../../buffer/out/search.h" +#include "../../buffer/out/UTextAdapter.h" +#include #include using namespace winrt::Microsoft::Terminal::Core; @@ -568,7 +570,7 @@ std::wstring Terminal::GetHyperlinkAtBufferPosition(const til::point bufferPos) { // Hyperlink is outside of the current view. // We need to find if there's a pattern at that location. - const auto patterns = _activeBuffer().GetPatterns(bufferPos.y, bufferPos.y); + const auto patterns = _getPatterns(bufferPos.y, bufferPos.y); // NOTE: patterns is stored with top y-position being 0, // so we need to cleverly set the y-pos to 0. @@ -1207,9 +1209,8 @@ bool Terminal::IsCursorBlinkingAllowed() const noexcept // - INVARIANT: this function can only be called if the caller has the writing lock on the terminal void Terminal::UpdatePatternsUnderLock() { - auto oldTree = _patternIntervalTree; - _patternIntervalTree = _activeBuffer().GetPatterns(_VisibleStartIndex(), _VisibleEndIndex()); - _InvalidatePatternTree(oldTree); + _InvalidatePatternTree(_patternIntervalTree); + _patternIntervalTree = _getPatterns(_VisibleStartIndex(), _VisibleEndIndex()); _InvalidatePatternTree(_patternIntervalTree); } @@ -1344,6 +1345,98 @@ void Terminal::_updateUrlDetection() } } +// Interns URegularExpression instances so that they can be reused. This method is thread-safe. +static unique_URegularExpression internURegularExpression(const std::wstring_view& pattern) +{ + struct CacheValue + { + unique_URegularExpression re; + size_t generation = 0; + }; + + struct CacheKeyHasher + { + using is_transparent = void; + + std::size_t operator()(const std::wstring_view& str) const + { + return til::hash(str); + } + }; + + struct SharedState + { + wil::srwlock lock; + std::unordered_map> set; + size_t totalInsertions = 0; + }; + + static SharedState shared; + static constexpr size_t cacheSizeLimit = 128; + + UErrorCode status = U_ZERO_ERROR; + + { + const auto guard = shared.lock.lock_shared(); + if (const auto it = shared.set.find(pattern); it != shared.set.end()) + { + return unique_URegularExpression{ uregex_clone(it->second.re.get(), &status) }; + } + } + + // Even if the URegularExpression creation failed, we'll insert it into the cache, because there's no point in retrying. + // (Apart from OOM but in that case this application will crash anyways in 3.. 2.. 1..) + unique_URegularExpression re{ CreateURegularExpression(pattern, 0, &status) }; + unique_URegularExpression clone{ uregex_clone(re.get(), &status) }; + std::wstring key{ pattern }; + + const auto guard = shared.lock.lock_exclusive(); + + shared.set.insert_or_assign(std::move(key), CacheValue{ std::move(re), shared.totalInsertions }); + shared.totalInsertions++; + + // If the cache is full remove the oldest element (oldest = lowest generation, just like with humans). + if (shared.set.size() > cacheSizeLimit) + { + shared.set.erase(std::min_element(shared.set.begin(), shared.set.end(), [](const auto& it, const auto& smallest) { + return it.second.generation < smallest.second.generation; + })); + } + + return clone; +} + +PointTree Terminal::_getPatterns(til::CoordType beg, til::CoordType end) const +{ + static constexpr std::array patterns{ + LR"(\b(?:https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|$!:,.;]*[A-Za-z0-9+&@#/%=~_|$])", + }; + + PointTree::interval_vector intervals; + + UErrorCode status = U_ZERO_ERROR; + auto text = UTextFromTextBuffer(_activeBuffer(), beg, end + 1, &status); + + for (size_t i = 0; i < patterns.size(); ++i) + { + const auto re = internURegularExpression(patterns[i]); + uregex_setUText(re.get(), &text, &status); + + if (uregex_find(re.get(), -1, &status)) + { + do + { + auto range = BufferRangeFromMatch(&text, re.get()); + // PointTree uses half-open ranges. + range.end.x++; + intervals.push_back(PointTree::interval(range.start, range.end, 0)); + } while (uregex_findNext(re.get(), &status)); + } + } + + return PointTree{ std::move(intervals) }; +} + // NOTE: This is the version of AddMark that comes from the UI. The VT api call into this too. void Terminal::AddMark(const ScrollMark& mark, const til::point& start, diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index a0110cfbfb9..8a76201822c 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -448,6 +448,7 @@ class Microsoft::Terminal::Core::Terminal final : bool _inAltBuffer() const noexcept; TextBuffer& _activeBuffer() const noexcept; void _updateUrlDetection(); + interval_tree::IntervalTree _getPatterns(til::CoordType beg, til::CoordType end) const; #pragma region TextSelection // These methods are defined in TerminalSelection.cpp diff --git a/src/cascadia/TerminalCore/TerminalSelection.cpp b/src/cascadia/TerminalCore/TerminalSelection.cpp index 018bf0623c6..110e2ebbaea 100644 --- a/src/cascadia/TerminalCore/TerminalSelection.cpp +++ b/src/cascadia/TerminalCore/TerminalSelection.cpp @@ -495,7 +495,7 @@ void Terminal::SelectHyperlink(const SearchDirection dir) const til::point bufferEnd{ bufferSize.RightInclusive(), ViewEndIndex() }; while (!result && bufferSize.IsInBounds(searchStart) && bufferSize.IsInBounds(searchEnd) && searchStart <= searchEnd && bufferStart <= searchStart && searchEnd <= bufferEnd) { - auto patterns = _activeBuffer().GetPatterns(searchStart.y, searchEnd.y); + auto patterns = _getPatterns(searchStart.y, searchEnd.y); resultList = patterns.findContained(convertToSearchArea(searchStart), convertToSearchArea(searchEnd)); result = extractResultFromList(resultList); if (!result) From ca4ec38485ce50a43b41fb2ff3ff2b1c4a72f188 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 22 Aug 2023 22:49:56 +0200 Subject: [PATCH 48/61] Address feedback, Use namespaces --- src/buffer/out/UTextAdapter.cpp | 12 +++++++----- src/buffer/out/UTextAdapter.h | 12 +++++++----- src/buffer/out/textBuffer.cpp | 10 +++------- src/buffer/out/textBuffer.hpp | 4 ---- src/cascadia/TerminalCore/Terminal.cpp | 14 +++++++------- 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/buffer/out/UTextAdapter.cpp b/src/buffer/out/UTextAdapter.cpp index dc5549d7327..986a0de446c 100644 --- a/src/buffer/out/UTextAdapter.cpp +++ b/src/buffer/out/UTextAdapter.cpp @@ -202,6 +202,9 @@ catch (...) // @param destCapacity The size, in UChars, of the destination buffer. May be zero for precomputing the required size. // @param status receives any error status. If U_BUFFER_OVERFLOW_ERROR: Returns number of UChars for preflighting. // @return Number of UChars in the data. Does not include a trailing NUL. +// +// NOTE: utextExtract's correctness hasn't been verified yet. The code remains, just incase its functionality is needed in the future. +#pragma warning(suppress : 4505) // 'utextExtract': unreferenced function with internal linkage has been removed static int32_t U_CALLCONV utextExtract(UText* ut, int64_t nativeStart, int64_t nativeLimit, char16_t* dest, int32_t destCapacity, UErrorCode* status) noexcept try { @@ -258,11 +261,10 @@ static constexpr UTextFuncs utextFuncs{ .clone = utextClone, .nativeLength = utextNativeLength, .access = utextAccess, - .extract = utextExtract, }; // Creates a UText from the given TextBuffer that spans rows [rowBeg,RowEnd). -UText UTextFromTextBuffer(const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd, UErrorCode* status) noexcept +UText Microsoft::Console::ICU::UTextFromTextBuffer(const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd, UErrorCode* status) noexcept { __assume(status != nullptr); @@ -277,7 +279,7 @@ UText UTextFromTextBuffer(const TextBuffer& textBuffer, til::CoordType rowBeg, t return ut; } -URegularExpression* CreateURegularExpression(const std::wstring_view& pattern, uint32_t flags, UErrorCode* status) noexcept +Microsoft::Console::ICU::unique_uregex Microsoft::Console::ICU::CreateRegex(const std::wstring_view& pattern, uint32_t flags, UErrorCode* status) noexcept { #pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). const auto re = uregex_open(reinterpret_cast(pattern.data()), gsl::narrow_cast(pattern.size()), flags, nullptr, status); @@ -285,12 +287,12 @@ URegularExpression* CreateURegularExpression(const std::wstring_view& pattern, u // but this claim seems highly outdated already. On my CPU from 2021, a limit of 4096 equals roughly 600ms. uregex_setTimeLimit(re, 4096, status); uregex_setStackLimit(re, 4 * 1024 * 1024, status); - return re; + return unique_uregex{ re }; } // Returns an inclusive point range given a text start and end position. // This function is designed to be used with uregex_start64/uregex_end64. -til::point_span BufferRangeFromMatch(UText* ut, URegularExpression* re) +til::point_span Microsoft::Console::ICU::BufferRangeFromMatch(UText* ut, URegularExpression* re) { UErrorCode status = U_ZERO_ERROR; const auto nativeIndexBeg = uregex_start64(re, 0, &status); diff --git a/src/buffer/out/UTextAdapter.h b/src/buffer/out/UTextAdapter.h index ff523e26032..158fd25ca2d 100644 --- a/src/buffer/out/UTextAdapter.h +++ b/src/buffer/out/UTextAdapter.h @@ -3,13 +3,15 @@ #pragma once -// Can't forward declare the UErrorCode enum. Thanks C++. #include class TextBuffer; -using unique_URegularExpression = wistd::unique_ptr>; +namespace Microsoft::Console::ICU +{ + using unique_uregex = wistd::unique_ptr>; -UText UTextFromTextBuffer(const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd, UErrorCode* status) noexcept; -URegularExpression* CreateURegularExpression(const std::wstring_view& pattern, uint32_t flags, UErrorCode* status) noexcept; -til::point_span BufferRangeFromMatch(UText* ut, URegularExpression* re); + UText UTextFromTextBuffer(const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd, UErrorCode* status) noexcept; + unique_uregex CreateRegex(const std::wstring_view& pattern, uint32_t flags, UErrorCode* status) noexcept; + til::point_span BufferRangeFromMatch(UText* ut, URegularExpression* re); +} diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index d7ce7b60575..24762954632 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -69,10 +69,6 @@ TextBuffer::~TextBuffer() { _destroy(); } - if (_urlRegex) - { - uregex_close(_urlRegex); - } } // I put these functions in a block at the start of the class, because they're the most @@ -2774,16 +2770,16 @@ std::vector TextBuffer::SearchText(const std::wstring_view& nee WI_SetFlagIf(flags, UREGEX_CASE_INSENSITIVE, caseInsensitive); UErrorCode status = U_ZERO_ERROR; - auto text = UTextFromTextBuffer(*this, rowBeg, rowEnd, &status); + auto text = ICU::UTextFromTextBuffer(*this, rowBeg, rowEnd, &status); - const unique_URegularExpression re{ CreateURegularExpression(needle, flags, &status) }; + const auto re = ICU::CreateRegex(needle, flags, &status); uregex_setUText(re.get(), &text, &status); if (uregex_find(re.get(), -1, &status)) { do { - results.emplace_back(BufferRangeFromMatch(&text, re.get())); + results.emplace_back(ICU::BufferRangeFromMatch(&text, re.get())); } while (uregex_findNext(re.get(), &status)); } diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 90b64591701..19744b1bed4 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -375,10 +375,6 @@ class TextBuffer final Cursor _cursor; std::vector _marks; - // While safe RAII wrappers for URegularExpression* would improve maintainability, it would make - // it more difficult to avoid having to forward declare (or outright expose) more ICU stuff. - // This may make switching to other Unicode libraries easier in the distant future. - URegularExpression* _urlRegex = nullptr; bool _isActiveBuffer = false; #ifdef UNIT_TESTING diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index c0f339f90dc..9259ff069e0 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -1346,11 +1346,11 @@ void Terminal::_updateUrlDetection() } // Interns URegularExpression instances so that they can be reused. This method is thread-safe. -static unique_URegularExpression internURegularExpression(const std::wstring_view& pattern) +static ICU::unique_uregex internURegularExpression(const std::wstring_view& pattern) { struct CacheValue { - unique_URegularExpression re; + ICU::unique_uregex re; size_t generation = 0; }; @@ -1380,14 +1380,14 @@ static unique_URegularExpression internURegularExpression(const std::wstring_vie const auto guard = shared.lock.lock_shared(); if (const auto it = shared.set.find(pattern); it != shared.set.end()) { - return unique_URegularExpression{ uregex_clone(it->second.re.get(), &status) }; + return ICU::unique_uregex{ uregex_clone(it->second.re.get(), &status) }; } } // Even if the URegularExpression creation failed, we'll insert it into the cache, because there's no point in retrying. // (Apart from OOM but in that case this application will crash anyways in 3.. 2.. 1..) - unique_URegularExpression re{ CreateURegularExpression(pattern, 0, &status) }; - unique_URegularExpression clone{ uregex_clone(re.get(), &status) }; + auto re = ICU::CreateRegex(pattern, 0, &status); + ICU::unique_uregex clone{ uregex_clone(re.get(), &status) }; std::wstring key{ pattern }; const auto guard = shared.lock.lock_exclusive(); @@ -1415,7 +1415,7 @@ PointTree Terminal::_getPatterns(til::CoordType beg, til::CoordType end) const PointTree::interval_vector intervals; UErrorCode status = U_ZERO_ERROR; - auto text = UTextFromTextBuffer(_activeBuffer(), beg, end + 1, &status); + auto text = ICU::UTextFromTextBuffer(_activeBuffer(), beg, end + 1, &status); for (size_t i = 0; i < patterns.size(); ++i) { @@ -1426,7 +1426,7 @@ PointTree Terminal::_getPatterns(til::CoordType beg, til::CoordType end) const { do { - auto range = BufferRangeFromMatch(&text, re.get()); + auto range = ICU::BufferRangeFromMatch(&text, re.get()); // PointTree uses half-open ranges. range.end.x++; intervals.push_back(PointTree::interval(range.start, range.end, 0)); From d53d4bd99cf09550a3ea6c5b81ef19abe2fe2d9c Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 22 Aug 2023 22:50:40 +0200 Subject: [PATCH 49/61] Spelling fix --- .github/actions/spelling/allow/apis.txt | 1 + .github/actions/spelling/expect/expect.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 1f85a1afab2..71b2922ca03 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -187,6 +187,7 @@ snprintf spsc sregex SRWLOC +srwlock SRWLOCK STDCPP STDMETHOD diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index acdb6a50b6e..fd5a01dc8f2 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1300,6 +1300,7 @@ onecoreuuid ONECOREWINDOWS onehalf oneseq +OOM openbash opencode opencon From 1e527c4090ba8488ef98a020c2813393ba80c20e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 23 Aug 2023 17:09:24 +0200 Subject: [PATCH 50/61] Remove selection coloring from Search --- src/buffer/out/search.cpp | 13 ---------- src/buffer/out/search.h | 3 --- src/cascadia/TerminalCore/Terminal.cpp | 24 +++++++++++++------ src/cascadia/TerminalCore/Terminal.hpp | 1 - .../TerminalCore/TerminalSelection.cpp | 13 ---------- src/host/renderData.cpp | 13 ---------- src/host/renderData.hpp | 1 - src/host/selectionInput.cpp | 8 +++++-- src/host/ut_host/VtIoTests.cpp | 4 ---- src/renderer/inc/IRenderData.hpp | 1 - 10 files changed, 23 insertions(+), 58 deletions(-) diff --git a/src/buffer/out/search.cpp b/src/buffer/out/search.cpp index 1735872ae10..2c38c80ae23 100644 --- a/src/buffer/out/search.cpp +++ b/src/buffer/out/search.cpp @@ -5,7 +5,6 @@ #include "search.h" #include "textBuffer.hpp" -#include "../types/inc/GlyphWidth.hpp" using namespace Microsoft::Console::Types; @@ -85,15 +84,3 @@ bool Search::SelectNext() _renderData->SelectNewRegion(selStart, selEnd); return true; } - -// Routine Description: -// - Applies the supplied TextAttribute to all search results. -// Arguments: -// - attr - The attribute to apply to the result -void Search::ColorAll(const TextAttribute& attr) const -{ - for (const auto& s : _results) - { - _renderData->ColorSelection(s.start, s.end, attr); - } -} diff --git a/src/buffer/out/search.h b/src/buffer/out/search.h index 67a75ba3e45..6adb2be9bd3 100644 --- a/src/buffer/out/search.h +++ b/src/buffer/out/search.h @@ -17,7 +17,6 @@ Revision History: #pragma once -#include "TextAttribute.hpp" #include "textBuffer.hpp" #include "../renderer/inc/IRenderData.hpp" @@ -30,8 +29,6 @@ class Search final bool IsStale() const noexcept; bool SelectNext(); - void ColorAll(const TextAttribute& attr) const; - private: // _renderData is a pointer so that Search() is constexpr default constructable. Microsoft::Console::Render::IRenderData* _renderData = nullptr; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 9259ff069e0..a8602a5681a 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -1552,28 +1552,38 @@ std::wstring_view Terminal::CurrentCommand() const void Terminal::ColorSelection(const TextAttribute& attr, winrt::Microsoft::Terminal::Core::MatchMode matchMode) { + const auto colorSelection = [this](const til::point coordStart, const til::point coordEnd, const TextAttribute& attr) { + auto& textBuffer = _activeBuffer(); + const auto spanLength = textBuffer.SpanLength(coordStart, coordEnd); + textBuffer.Write(OutputCellIterator(attr, spanLength), coordStart); + }; + for (const auto [start, end] : _GetSelectionSpans()) { try { if (matchMode == winrt::Microsoft::Terminal::Core::MatchMode::None) { - ColorSelection(start, end, attr); + colorSelection(start, end, attr); } else if (matchMode == winrt::Microsoft::Terminal::Core::MatchMode::All) { - const auto textBuffer = _activeBuffer().GetPlainText(start, end); - std::wstring_view text{ textBuffer }; + const auto& textBuffer = _activeBuffer(); + const auto text = textBuffer.GetPlainText(start, end); + std::wstring_view textView{ text }; if (IsBlockSelection()) { - text = Utils::TrimPaste(text); + textView = Utils::TrimPaste(textView); } - if (!text.empty()) + if (!textView.empty()) { - const Search search(*this, text, false, true); - search.ColorAll(attr); + const auto hits = textBuffer.SearchText(textView, true); + for (const auto& s : hits) + { + colorSelection(s.start, s.end, attr); + } } } } diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 8a76201822c..ac5a18ec91f 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -216,7 +216,6 @@ class Microsoft::Terminal::Core::Terminal final : const til::point GetSelectionAnchor() const noexcept override; const til::point GetSelectionEnd() const noexcept override; const std::wstring_view GetConsoleTitle() const noexcept override; - void ColorSelection(const til::point coordSelectionStart, const til::point coordSelectionEnd, const TextAttribute) override; const bool IsUiaDataInitialized() const noexcept override; #pragma endregion diff --git a/src/cascadia/TerminalCore/TerminalSelection.cpp b/src/cascadia/TerminalCore/TerminalSelection.cpp index 110e2ebbaea..5f89c533896 100644 --- a/src/cascadia/TerminalCore/TerminalSelection.cpp +++ b/src/cascadia/TerminalCore/TerminalSelection.cpp @@ -887,16 +887,3 @@ void Terminal::_ScrollToPoint(const til::point pos) _activeBuffer().TriggerScroll(); } } - -// Method Description: -// - apply the TextAttribute "attr" to the active buffer -// Arguments: -// - coordStart - where to begin applying attr -// - coordEnd - where to end applying attr (inclusive) -// - attr - the text attributes to apply -void Terminal::ColorSelection(const til::point coordStart, const til::point coordEnd, const TextAttribute attr) -{ - const auto spanLength = _activeBuffer().SpanLength(coordStart, coordEnd); - - _activeBuffer().Write(OutputCellIterator(attr, spanLength), coordStart); -} diff --git a/src/host/renderData.cpp b/src/host/renderData.cpp index 1f1dadda001..d327c0b9d61 100644 --- a/src/host/renderData.cpp +++ b/src/host/renderData.cpp @@ -418,16 +418,3 @@ const til::point RenderData::GetSelectionEnd() const noexcept return { x_pos, y_pos }; } - -// Routine Description: -// - Given two points in the buffer space, color the selection between the two with the given attribute. -// - This will create an internal selection rectangle covering the two points, assume a line selection, -// and use the first point as the anchor for the selection (as if the mouse click started at that point) -// Arguments: -// - coordSelectionStart - Anchor point (start of selection) for the region to be colored -// - coordSelectionEnd - Other point referencing the rectangle inscribing the selection area -// - attr - Color to apply to region. -void RenderData::ColorSelection(const til::point coordSelectionStart, const til::point coordSelectionEnd, const TextAttribute attr) -{ - Selection::Instance().ColorSelection(coordSelectionStart, coordSelectionEnd, attr); -} diff --git a/src/host/renderData.hpp b/src/host/renderData.hpp index 4d796812a81..5e649353cd7 100644 --- a/src/host/renderData.hpp +++ b/src/host/renderData.hpp @@ -56,6 +56,5 @@ class RenderData final : void SelectNewRegion(const til::point coordStart, const til::point coordEnd) override; const til::point GetSelectionAnchor() const noexcept override; const til::point GetSelectionEnd() const noexcept override; - void ColorSelection(const til::point coordSelectionStart, const til::point coordSelectionEnd, const TextAttribute attr) override; const bool IsUiaDataInitialized() const noexcept override { return true; } }; diff --git a/src/host/selectionInput.cpp b/src/host/selectionInput.cpp index 37ad00c96a6..69d40b9ae79 100644 --- a/src/host/selectionInput.cpp +++ b/src/host/selectionInput.cpp @@ -708,8 +708,12 @@ bool Selection::_HandleColorSelection(const INPUT_KEY_INFO* const pInputKeyInfo) Telemetry::Instance().LogColorSelectionUsed(); - const Search search(gci.renderData, str, false, true); - search.ColorAll(selectionAttr); + const auto& textBuffer = gci.renderData.GetTextBuffer(); + const auto hits = textBuffer.SearchText(str, true); + for (const auto& s : hits) + { + ColorSelection(s.start, s.end, selectionAttr); + } } } CATCH_LOG(); diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 329dd3f46ec..902f711771d 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -373,10 +373,6 @@ class MockRenderData : public IRenderData return {}; } - void ColorSelection(const til::point /*coordSelectionStart*/, const til::point /*coordSelectionEnd*/, const TextAttribute /*attr*/) - { - } - const bool IsUiaDataInitialized() const noexcept { return true; diff --git a/src/renderer/inc/IRenderData.hpp b/src/renderer/inc/IRenderData.hpp index e45dc388ac7..69e0dc2a0e6 100644 --- a/src/renderer/inc/IRenderData.hpp +++ b/src/renderer/inc/IRenderData.hpp @@ -73,7 +73,6 @@ namespace Microsoft::Console::Render virtual void SelectNewRegion(const til::point coordStart, const til::point coordEnd) = 0; virtual const til::point GetSelectionAnchor() const noexcept = 0; virtual const til::point GetSelectionEnd() const noexcept = 0; - virtual void ColorSelection(const til::point coordSelectionStart, const til::point coordSelectionEnd, const TextAttribute attr) = 0; virtual const bool IsUiaDataInitialized() const noexcept = 0; }; } From 1fc01d8197eb2fe87e2cfe096e219344c24d5944 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 23 Aug 2023 17:09:39 +0200 Subject: [PATCH 51/61] Address Dustin's feedback --- src/buffer/out/search.cpp | 4 ++-- src/buffer/out/search.h | 2 +- src/buffer/out/textBuffer.cpp | 12 ++++++------ src/buffer/out/textBuffer.hpp | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/buffer/out/search.cpp b/src/buffer/out/search.cpp index 2c38c80ae23..b1b7cb15296 100644 --- a/src/buffer/out/search.cpp +++ b/src/buffer/out/search.cpp @@ -25,7 +25,7 @@ Search::Search(Microsoft::Console::Render::IRenderData& renderData, const std::w const auto& textBuffer = _renderData->GetTextBuffer(); _results = textBuffer.SearchText(str, caseInsensitive); - _mutationCount = textBuffer.GetMutationCount(); + _lastMutationId = textBuffer.GetLastMutationId(); if (_results.empty()) { @@ -58,7 +58,7 @@ Search::Search(Microsoft::Console::Render::IRenderData& renderData, const std::w bool Search::IsStale() const noexcept { - return !_renderData || _renderData->GetTextBuffer().GetMutationCount() != _mutationCount; + return !_renderData || _renderData->GetTextBuffer().GetLastMutationId() != _lastMutationId; } // Routine Description: diff --git a/src/buffer/out/search.h b/src/buffer/out/search.h index 6adb2be9bd3..d5fbf41f4a8 100644 --- a/src/buffer/out/search.h +++ b/src/buffer/out/search.h @@ -35,5 +35,5 @@ class Search final std::vector _results; ptrdiff_t _index = 0; ptrdiff_t _step = 0; - uint64_t _mutationCount = 0; + uint64_t _lastMutationId = 0; }; diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 24762954632..48883217c55 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -31,7 +31,7 @@ constexpr bool allWhitespace(const std::wstring_view& text) noexcept return true; } -static std::atomic s_mutationCountInitialValue; +static std::atomic s_lastMutationIdInitialValue; // Routine Description: // - Creates a new instance of TextBuffer @@ -51,9 +51,9 @@ TextBuffer::TextBuffer(til::size screenBufferSize, Microsoft::Console::Render::Renderer& renderer) : _renderer{ renderer }, _currentAttributes{ defaultAttributes }, - // This way every TextBuffer will start with a ""unique"" _mutationCount + // This way every TextBuffer will start with a ""unique"" _lastMutationId // and so it'll compare unequal with the counter of other TextBuffers. - _mutationCount{ s_mutationCountInitialValue.fetch_add(0x100000000) }, + _lastMutationId{ s_lastMutationIdInitialValue.fetch_add(0x100000000) }, _cursor{ cursorSize, *this }, _isActiveBuffer{ isActiveBuffer } { @@ -225,7 +225,7 @@ const ROW& TextBuffer::GetRowByOffset(const til::CoordType index) const // (what corresponds to the top row of the screen buffer). ROW& TextBuffer::GetMutableRowByOffset(const til::CoordType index) { - _mutationCount++; + _lastMutationId++; return _getRow(index); } @@ -907,9 +907,9 @@ const Cursor& TextBuffer::GetCursor() const noexcept return _cursor; } -uint64_t TextBuffer::GetMutationCount() const noexcept +uint64_t TextBuffer::GetLastMutationId() const noexcept { - return _mutationCount; + return _lastMutationId; } const TextAttribute& TextBuffer::GetCurrentAttributes() const noexcept diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 19744b1bed4..a57474b6b81 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -166,7 +166,7 @@ class TextBuffer final Cursor& GetCursor() noexcept; const Cursor& GetCursor() const noexcept; - uint64_t GetMutationCount() const noexcept; + uint64_t GetLastMutationId() const noexcept; const til::CoordType GetFirstRowIndex() const noexcept; const Microsoft::Console::Types::Viewport GetSize() const noexcept; @@ -371,7 +371,7 @@ class TextBuffer final TextAttribute _currentAttributes; til::CoordType _firstRow = 0; // indexes top row (not necessarily 0) - uint64_t _mutationCount = 0; + uint64_t _lastMutationId = 0; Cursor _cursor; std::vector _marks; From 7cccf568a7381cb001a4cba7757f300026eb27a3 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 23 Aug 2023 17:09:50 +0200 Subject: [PATCH 52/61] Optimize GetReadableColumnCount --- src/buffer/out/Row.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 649825267bf..73d98c3a08a 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -215,10 +215,11 @@ LineRendition ROW::GetLineRendition() const noexcept // Console APIs treat the buffer as a large NxM matrix after all. til::CoordType ROW::GetReadableColumnCount() const noexcept { - const til::CoordType columnCount = _columnCount; - const til::CoordType scale = _lineRendition != LineRendition::SingleWidth; - const til::CoordType padding = _doubleBytePadded; - return (columnCount - padding) >> scale; + if (_lineRendition == LineRendition::SingleWidth) [[likely]] + { + return _columnCount - _doubleBytePadded; + } + return (_columnCount - (_doubleBytePadded << 1)) >> 1; } // Routine Description: From 362693b3269ee8611b2600b12cf8e7a85e0576a3 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 23 Aug 2023 17:12:24 +0200 Subject: [PATCH 53/61] Fix AuditMode failures --- src/buffer/out/UTextAdapter.cpp | 4 +--- src/buffer/out/UTextAdapter.h | 2 +- src/buffer/out/textBuffer.cpp | 4 ++-- src/cascadia/TerminalCore/Terminal.cpp | 5 ++--- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/buffer/out/UTextAdapter.cpp b/src/buffer/out/UTextAdapter.cpp index 986a0de446c..97f04a88fcf 100644 --- a/src/buffer/out/UTextAdapter.cpp +++ b/src/buffer/out/UTextAdapter.cpp @@ -264,10 +264,8 @@ static constexpr UTextFuncs utextFuncs{ }; // Creates a UText from the given TextBuffer that spans rows [rowBeg,RowEnd). -UText Microsoft::Console::ICU::UTextFromTextBuffer(const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd, UErrorCode* status) noexcept +UText Microsoft::Console::ICU::UTextFromTextBuffer(const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd) noexcept { - __assume(status != nullptr); - UText ut = UTEXT_INITIALIZER; ut.providerProperties = (1 << UTEXT_PROVIDER_LENGTH_IS_EXPENSIVE) | (1 << UTEXT_PROVIDER_STABLE_CHUNKS); ut.pFuncs = &utextFuncs; diff --git a/src/buffer/out/UTextAdapter.h b/src/buffer/out/UTextAdapter.h index 158fd25ca2d..c8c325143ef 100644 --- a/src/buffer/out/UTextAdapter.h +++ b/src/buffer/out/UTextAdapter.h @@ -11,7 +11,7 @@ namespace Microsoft::Console::ICU { using unique_uregex = wistd::unique_ptr>; - UText UTextFromTextBuffer(const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd, UErrorCode* status) noexcept; + UText UTextFromTextBuffer(const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd) noexcept; unique_uregex CreateRegex(const std::wstring_view& pattern, uint32_t flags, UErrorCode* status) noexcept; til::point_span BufferRangeFromMatch(UText* ut, URegularExpression* re); } diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 48883217c55..f5bfdeda3ce 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -2766,12 +2766,12 @@ std::vector TextBuffer::SearchText(const std::wstring_view& nee return results; } + auto text = ICU::UTextFromTextBuffer(*this, rowBeg, rowEnd); + uint32_t flags = UREGEX_LITERAL; WI_SetFlagIf(flags, UREGEX_CASE_INSENSITIVE, caseInsensitive); UErrorCode status = U_ZERO_ERROR; - auto text = ICU::UTextFromTextBuffer(*this, rowBeg, rowEnd, &status); - const auto re = ICU::CreateRegex(needle, flags, &status); uregex_setUText(re.get(), &text, &status); diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index a8602a5681a..c64160a29aa 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -1412,10 +1412,9 @@ PointTree Terminal::_getPatterns(til::CoordType beg, til::CoordType end) const LR"(\b(?:https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|$!:,.;]*[A-Za-z0-9+&@#/%=~_|$])", }; - PointTree::interval_vector intervals; - + auto text = ICU::UTextFromTextBuffer(_activeBuffer(), beg, end + 1); UErrorCode status = U_ZERO_ERROR; - auto text = ICU::UTextFromTextBuffer(_activeBuffer(), beg, end + 1, &status); + PointTree::interval_vector intervals; for (size_t i = 0; i < patterns.size(); ++i) { From d3e69f788c6948a996d9150371d1c306c489f4e2 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 23 Aug 2023 14:38:49 -0500 Subject: [PATCH 54/61] It's so much nicer --- src/buffer/out/search.cpp | 5 +++++ src/buffer/out/search.h | 1 + src/cascadia/TerminalControl/ControlCore.cpp | 8 +++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/buffer/out/search.cpp b/src/buffer/out/search.cpp index 56d5b33c7af..73c62f7ce8c 100644 --- a/src/buffer/out/search.cpp +++ b/src/buffer/out/search.cpp @@ -89,3 +89,8 @@ const std::vector& Search::Results() const noexcept { return _results; } + +size_t Search::CurrentMatch() const noexcept +{ + return _index; +} diff --git a/src/buffer/out/search.h b/src/buffer/out/search.h index 769b0ee87f4..b7ec9a6a75e 100644 --- a/src/buffer/out/search.h +++ b/src/buffer/out/search.h @@ -30,6 +30,7 @@ class Search final bool SelectNext(); const std::vector& Results() const noexcept; + size_t CurrentMatch() const noexcept; private: // _renderData is a pointer so that Search() is constexpr default constructable. diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 4121809d905..a5b88e316de 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1575,6 +1575,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } const auto foundMatch = _searcher.SelectNext(); + auto foundResults = winrt::make_self(foundMatch); if (foundMatch) { // this is used for search, @@ -1585,11 +1586,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation _UpdateSelectionMarkersHandlers(*this, winrt::make(true)); _terminal->AlwaysNotifyOnBufferRotation(true); + + foundResults->TotalMatches(gsl::narrow(_searcher.Results().size())); + foundResults->CurrentMatch(gsl::narrow(_searcher.CurrentMatch())); } // Raise a FoundMatch event, which the control will use to notify // narrator if there was any results in the buffer - _FoundMatchHandlers(*this, winrt::make(foundMatch)); + // _FoundMatchHandlers(*this, winrt::make(foundMatch)); + + _FoundMatchHandlers(*this, *foundResults); } // // Method Description: From 574f30b4249a576413087a4bc11bb7250055d694 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 23 Aug 2023 14:40:21 -0500 Subject: [PATCH 55/61] =?UTF-8?q?=F0=9F=9A=A7=20dead=20code=20construction?= =?UTF-8?q?=20crew?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cascadia/TerminalControl/ControlCore.cpp | 243 ------------------- src/cascadia/TerminalControl/ControlCore.h | 37 --- 2 files changed, 280 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index a5b88e316de..373480cddc2 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -213,19 +213,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation core->_ScrollPositionChangedHandlers(*core, update); } }); - - // shared->updateSearchStatus = std::make_shared>( - // _dispatcher, - // SearchAfterChangeDelay, - // [weakThis = get_weak()](const auto& update) { - // if (auto core{ weakThis.get() }) - // { - // // Begin a search - // core->_terminal->AlwaysNotifyOnBufferRotation(true); - // core->_searchState.emplace(update); - // core->_SearchAsync(std::nullopt); - // } - // }); } ControlCore::~ControlCore() @@ -1593,240 +1580,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Raise a FoundMatch event, which the control will use to notify // narrator if there was any results in the buffer - // _FoundMatchHandlers(*this, winrt::make(foundMatch)); - _FoundMatchHandlers(*this, *foundResults); } - // // 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: - // // - - // fire_and_forget ControlCore::_SearchAsync(std::optional 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()) - // { - // std::vector> matches; - // if (!_searchState->text.empty()) - // { - // // Collect up all the matches on a BG thread. - // co_await winrt::resume_background(); - - // auto core = weakThis.get(); - // if (!core) - // { - // // We've been destroyed since we started this coroutine. - // // Just bail - // co_return; - // } - // // here, `this` is safe to use for the rest of the method. `core` will keep us alive. - - // // 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) - // { - // { - // auto lock = _terminal->LockForWriting(); - // // Clear the selection reset the anchor - // _terminal->ClearSelection(); - // _renderer->TriggerSelection(); - // } - - // const auto sensitivity = caseSensitive ? Search::Sensitivity::CaseSensitive : Search::Sensitivity::CaseInsensitive; - - // const SearchState searchState{ text, sensitivity, goForward }; - - // { - // const auto shared = _shared.lock_shared(); - // if (shared->updateSearchStatus) - // { - // shared->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: - // // - - // void ControlCore::_SelectSearchResult(std::optional goForward) - // { - // if (_searchState.has_value() && _searchState->matches.has_value()) - // { - // auto& state = _searchState.value(); - // auto& matches = state.matches.value(); - - // 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(true); - // foundResults->TotalMatches(gsl::narrow(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: - // // - - // 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(); - // 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: - // // - - // void SearchState::UpdateIndex(bool goForward) - // { - // if (matches.has_value()) - // { - // const int numMatches = ::base::saturated_cast(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: - // // - - // // 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> SearchState::GetCurrentMatch() - // { - // if (matches.has_value() && - // currentMatchIndex > -1 && - // static_cast(currentMatchIndex) < matches->size()) - // { - // return til::at(matches.value(), currentMatchIndex); - // } - // else - // { - // return std::nullopt; - // } - // } - // void ControlCore::ExitSearch() - // { - // _terminal->AlwaysNotifyOnBufferRotation(false); - // _searchState.reset(); - // } - Windows::Foundation::Collections::IVector ControlCore::SearchResultRows() { - // auto results = std::vector(); - // if (_bufferChangedSinceSearch) - // { - // return winrt::single_threaded_vector(); - // } - - // // use a map to remove duplicates - // std::map rows; - - // if (_searchState.has_value() && _searchState->matches.has_value()) - // { - // for (auto&& [start, end] : *(_searchState->matches)) - // { - // const auto row = start.y; - - // // First check if it's in the map - // if (rows.find(row) == rows.end()) - // { - // rows[row] = true; - // results.push_back(row); - // } - // } - // } - // return winrt::single_threaded_vector(std::move(results)); - auto results = std::vector(); if (_searcher.IsStale()) { @@ -1851,7 +1609,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation { _terminal->AlwaysNotifyOnBufferRotation(false); _searcher = {}; - // >>>>>>> dev/migrie/fhl/search-marks } void ControlCore::Close() diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 792dba1e360..05dba62743b 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -65,35 +65,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation } }; - // struct SearchState - // { - // public: - // static std::atomic _searchIdGenerator; - - // SearchState(const winrt::hstring& text, const Search::Sensitivity sensitivity, const bool forward) : - // text(text), - // sensitivity(sensitivity), - // goForward(forward), - // searchId(_searchIdGenerator.fetch_add(1)) - // { - // } - - // // Why is this an optional vector, instead of just checking if it's empty? - // // * No _searchState? Then we have no search started. - // // * _searchState, no _matches? We haven't run the search yet. - // // * _searchState, _matches has 0 results? We didn't find anything - // std::optional>> matches; - - // void UpdateIndex(bool goForward); - // std::optional> GetCurrentMatch(); - - // winrt::hstring text; - // Search::Sensitivity sensitivity; - // bool goForward{ true }; - // size_t searchId; - // int32_t currentMatchIndex{ -1 }; - // }; - struct ControlCore : ControlCoreT { public: @@ -315,7 +286,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation std::shared_ptr> tsfTryRedrawCanvas; std::unique_ptr> updatePatternLocations; std::shared_ptr> updateScrollBar; - // std::shared_ptr> updateSearchStatus; }; std::atomic _initializedTerminal{ false }; @@ -369,9 +339,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation uint64_t _owningHwnd{ 0 }; - // std::optional _searchState; - // bool _bufferChangedSinceSearch{ true }; - winrt::Windows::System::DispatcherQueue _dispatcher{ nullptr }; til::shared_mutex _shared; @@ -425,10 +392,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool _isBackgroundTransparent(); void _focusChanged(bool focused); - // fire_and_forget _SearchAsync(std::optional goForward); - // void _SelectSearchResult(std::optional goForward); - // bool _SearchOne(::Search& search); - void _selectSpan(til::point_span s); void _contextMenuSelectMark( From 198decc6e09aa445d90d19c0c4c5b4736926c91b Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 23 Aug 2023 14:45:05 -0500 Subject: [PATCH 56/61] =?UTF-8?q?=F0=9F=9A=A7=20dead=20code=20construction?= =?UTF-8?q?=20crew=20part=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cascadia/TerminalControl/ControlCore.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 373480cddc2..fc1f75fcb55 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -44,8 +44,6 @@ constexpr const auto SearchAfterChangeDelay = std::chrono::milliseconds(200); namespace winrt::Microsoft::Terminal::Control::implementation { - // std::atomic SearchState::_searchIdGenerator{ 0 }; - static winrt::Microsoft::Terminal::Core::OptionalColor OptionalFromColor(const til::color& c) { Core::OptionalColor result; @@ -1910,7 +1908,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (shared->updatePatternLocations) { (*shared->updatePatternLocations)(); - // _bufferChangedSinceSearch = true; } } catch (...) From 971e7c5c91b65d2d5739f1a97fc58ebeb499cbe2 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 28 Aug 2023 11:38:49 -0500 Subject: [PATCH 57/61] update scrollbar as we scroll the buffer --- src/buffer/out/search.cpp | 8 ++++++++ src/buffer/out/search.h | 2 ++ src/cascadia/TerminalControl/ControlCore.cpp | 7 +++++-- src/cascadia/TerminalControl/TermControl.cpp | 17 ++++++++++------- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/buffer/out/search.cpp b/src/buffer/out/search.cpp index de5b6874e27..70fd77da743 100644 --- a/src/buffer/out/search.cpp +++ b/src/buffer/out/search.cpp @@ -8,6 +8,14 @@ using namespace Microsoft::Console::Types; +bool Search::ResetIfStale(Microsoft::Console::Render::IRenderData& renderData) +{ + return ResetIfStale(renderData, + _needle, + _step == -1, // this is the opposite of the initializer below + _caseInsensitive); +} + bool Search::ResetIfStale(Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool reverse, bool caseInsensitive) { const auto& textBuffer = renderData.GetTextBuffer(); diff --git a/src/buffer/out/search.h b/src/buffer/out/search.h index d392ea0a8b6..ae68ba72c61 100644 --- a/src/buffer/out/search.h +++ b/src/buffer/out/search.h @@ -25,6 +25,7 @@ class Search final public: Search() = default; + bool ResetIfStale(Microsoft::Console::Render::IRenderData& renderData); bool ResetIfStale(Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool reverse, bool caseInsensitive); void MovePastCurrentSelection(); @@ -36,6 +37,7 @@ class Search final const std::vector& Results() const noexcept; size_t CurrentMatch() const noexcept; + bool CurrentDirection() const noexcept; private: // _renderData is a pointer so that Search() is constexpr default constructable. diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index e1bd2068c68..e1128a2c21b 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1570,10 +1570,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation _renderer->TriggerSelection(); _UpdateSelectionMarkersHandlers(*this, winrt::make(true)); - _terminal->AlwaysNotifyOnBufferRotation(true); - foundResults->TotalMatches(gsl::narrow(_searcher.Results().size())); foundResults->CurrentMatch(gsl::narrow(_searcher.CurrentMatch())); + + _terminal->AlwaysNotifyOnBufferRotation(true); } // Raise a FoundMatch event, which the control will use to notify @@ -1583,6 +1583,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation Windows::Foundation::Collections::IVector ControlCore::SearchResultRows() { + auto lock = _terminal->LockForWriting(); + _searcher.ResetIfStale(*GetRenderData()); + auto results = std::vector(); // use a map to remove duplicates diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 1a8cf339f88..b6791f9c49a 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -330,15 +330,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation drawPip(m.Start.Y, false, brush); } - const auto searchMatches{ _core.SearchResultRows() }; - if (searchMatches.Size() > 0 && _searchBox->Visibility() == Visibility::Visible) + if (_searchBox) { - const til::color fgColor{ _core.ForegroundColor() }; - Media::SolidColorBrush searchMarkBrush{}; - searchMarkBrush.Color(fgColor); - for (const auto m : searchMatches) + const auto searchMatches{ _core.SearchResultRows() }; + if (searchMatches.Size() > 0 && _searchBox->Visibility() == Visibility::Visible) { - drawPip(m, true, searchMarkBrush); + const til::color fgColor{ _core.ForegroundColor() }; + Media::SolidColorBrush searchMarkBrush{}; + searchMarkBrush.Color(fgColor); + for (const auto m : searchMatches) + { + drawPip(m, true, searchMarkBrush); + } } } } From 5da6ceb36a6ecae7eca2b23f44420d8614d67e51 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 29 Aug 2023 13:30:31 -0500 Subject: [PATCH 58/61] DRIVE-BY: Fix a potential crash in Search with 0 results --- src/buffer/out/search.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/buffer/out/search.cpp b/src/buffer/out/search.cpp index 70fd77da743..eef5fac17cb 100644 --- a/src/buffer/out/search.cpp +++ b/src/buffer/out/search.cpp @@ -79,8 +79,10 @@ void Search::MovePastPoint(const til::point anchor) noexcept void Search::FindNext() noexcept { - const auto count = gsl::narrow_cast(_results.size()); - _index = (_index + _step + count) % count; + if (const auto count{ gsl::narrow_cast(_results.size()) }) + { + _index = (_index + _step + count) % count; + } } const til::point_span* Search::GetCurrent() const noexcept From 4413534863417b17417b6db9ad34257230140c68 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 31 Aug 2023 07:08:07 -0500 Subject: [PATCH 59/61] notes --- src/cascadia/TerminalControl/ControlCore.cpp | 27 ++++++++++---------- src/cascadia/TerminalControl/ControlCore.h | 2 ++ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index e1128a2c21b..e5bc93d3e71 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1584,23 +1584,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation Windows::Foundation::Collections::IVector ControlCore::SearchResultRows() { auto lock = _terminal->LockForWriting(); - _searcher.ResetIfStale(*GetRenderData()); - - auto results = std::vector(); - - // use a map to remove duplicates - std::map rows; - for (const auto& match : _searcher.Results()) + if (_searcher.ResetIfStale(*GetRenderData())) { - const auto row = match.start.y; - // First check if it's in the map - if (rows.find(row) == rows.end()) + auto results = std::vector(); + + auto lastRow = til::CoordTypeMin; + for (const auto& match : _searcher.Results()) { - rows[row] = true; - results.push_back(row); + const auto row{ match.start.y }; + if (row != lastRow) + { + results.push_back(row); + lastRow = row; + } } + _cachedSearchResultRows = winrt::single_threaded_vector(std::move(results)); } - return winrt::single_threaded_vector(std::move(results)); + + return _cachedSearchResultRows; } void ControlCore::ClearSearch() diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index b7b4f9e152e..c168eccfc2f 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -341,6 +341,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation til::point _contextMenuBufferPosition{ 0, 0 }; + Windows::Foundation::Collections::IVector _cachedSearchResultRows{ winrt::single_threaded_vector() }; + void _setupDispatcherAndCallbacks(); bool _setFontSizeUnderLock(float fontSize); From b22e5c1d7864f34c743fadf8c9a07b522394af2b Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 5 Sep 2023 08:46:51 -0500 Subject: [PATCH 60/61] don't prealloc the results vector just to toss it --- src/cascadia/TerminalControl/ControlCore.cpp | 2 +- src/cascadia/TerminalControl/ControlCore.h | 2 +- src/cascadia/TerminalControl/TermControl.cpp | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index e5bc93d3e71..f6066049839 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1601,7 +1601,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _cachedSearchResultRows = winrt::single_threaded_vector(std::move(results)); } - return _cachedSearchResultRows; + return _cachedSearchResultRows ? _cachedSearchResultRows : winrt::single_threaded_vector(); } void ControlCore::ClearSearch() diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index c168eccfc2f..c21730f4789 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -341,7 +341,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation til::point _contextMenuBufferPosition{ 0, 0 }; - Windows::Foundation::Collections::IVector _cachedSearchResultRows{ winrt::single_threaded_vector() }; + Windows::Foundation::Collections::IVector _cachedSearchResultRows{ nullptr }; void _setupDispatcherAndCallbacks(); diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index b6791f9c49a..36dd5309446 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -333,7 +333,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (_searchBox) { const auto searchMatches{ _core.SearchResultRows() }; - if (searchMatches.Size() > 0 && _searchBox->Visibility() == Visibility::Visible) + if (searchMatches && + searchMatches.Size() > 0 && + _searchBox->Visibility() == Visibility::Visible) { const til::color fgColor{ _core.ForegroundColor() }; Media::SolidColorBrush searchMarkBrush{}; From bd2f2405ccb7bad6d82703abbcc9e1e27f4fca95 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 5 Sep 2023 13:57:54 -0500 Subject: [PATCH 61/61] nits --- src/buffer/out/search.h | 2 +- src/cascadia/TerminalControl/ControlCore.cpp | 3 +-- src/cascadia/TerminalControl/SearchBoxControl.cpp | 6 +++++- src/cascadia/TerminalControl/SearchBoxControl.h | 3 ++- src/cascadia/TerminalControl/SearchBoxControl.idl | 3 ++- src/cascadia/TerminalControl/TermControl.cpp | 7 +++---- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/buffer/out/search.h b/src/buffer/out/search.h index ae68ba72c61..2c6bc80a724 100644 --- a/src/buffer/out/search.h +++ b/src/buffer/out/search.h @@ -42,7 +42,7 @@ class Search final private: // _renderData is a pointer so that Search() is constexpr default constructable. Microsoft::Console::Render::IRenderData* _renderData = nullptr; - std::wstring_view _needle; + std::wstring _needle; bool _reverse = false; bool _caseInsensitive = false; uint64_t _lastMutationId = 0; diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index f6066049839..7530909aaab 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1601,7 +1601,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _cachedSearchResultRows = winrt::single_threaded_vector(std::move(results)); } - return _cachedSearchResultRows ? _cachedSearchResultRows : winrt::single_threaded_vector(); + return _cachedSearchResultRows; } void ControlCore::ClearSearch() @@ -1904,7 +1904,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation _terminal->Write(hstr); // Start the throttled update of where our hyperlinks are. - const auto shared = _shared.lock_shared(); if (shared->updatePatternLocations) { diff --git a/src/cascadia/TerminalControl/SearchBoxControl.cpp b/src/cascadia/TerminalControl/SearchBoxControl.cpp index ad7518e553a..bd5aeae3433 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.cpp +++ b/src/cascadia/TerminalControl/SearchBoxControl.cpp @@ -358,9 +358,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - enable: if true, the buttons should be enabled // Return Value: // - - void SearchBoxControl::SetNavigationEnabled(bool enabled) + void SearchBoxControl::NavigationEnabled(bool enabled) { GoBackwardButton().IsEnabled(enabled); GoForwardButton().IsEnabled(enabled); } + bool SearchBoxControl::NavigationEnabled() + { + return GoBackwardButton().IsEnabled() || GoForwardButton().IsEnabled(); + } } diff --git a/src/cascadia/TerminalControl/SearchBoxControl.h b/src/cascadia/TerminalControl/SearchBoxControl.h index e5e368e74ed..5ab9ee291bc 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.h +++ b/src/cascadia/TerminalControl/SearchBoxControl.h @@ -34,7 +34,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation void PopulateTextbox(const winrt::hstring& text); bool ContainsFocus(); void SetStatus(int32_t totalMatches, int32_t currentMatch); - void SetNavigationEnabled(bool enabled); + bool NavigationEnabled(); + void NavigationEnabled(bool enabled); void GoBackwardClicked(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::Windows::UI::Xaml::RoutedEventArgs& /*e*/); void GoForwardClicked(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::Windows::UI::Xaml::RoutedEventArgs& /*e*/); diff --git a/src/cascadia/TerminalControl/SearchBoxControl.idl b/src/cascadia/TerminalControl/SearchBoxControl.idl index 00a7c0fe06d..de909157376 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.idl +++ b/src/cascadia/TerminalControl/SearchBoxControl.idl @@ -12,7 +12,8 @@ namespace Microsoft.Terminal.Control void PopulateTextbox(String text); Boolean ContainsFocus(); void SetStatus(Int32 totalMatches, Int32 currentMatch); - void SetNavigationEnabled(Boolean enabled); + + Boolean NavigationEnabled; event SearchHandler Search; event SearchHandler SearchChanged; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 36dd5309446..fedd24f8e14 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -413,9 +413,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation } // Method Description: - // - Search text in text buffer. This is triggered if the user click - // search button or press enter. - // In the live search mode it will be also triggered once every time search criteria changes + // - Search text in text buffer. This is triggered if the user clicks the + // search button, presses enter, or changes the search criteria. // Arguments: // - text: the text to search // - goForward: boolean that represents if the current search direction is forward @@ -3494,7 +3493,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (_searchBox) { _searchBox->SetStatus(args.TotalMatches(), args.CurrentMatch()); - _searchBox->SetNavigationEnabled(true); + _searchBox->NavigationEnabled(true); } }