diff --git a/src/buffer/out/search.cpp b/src/buffer/out/search.cpp index bfa66569dff..31c7081b2b9 100644 --- a/src/buffer/out/search.cpp +++ b/src/buffer/out/search.cpp @@ -25,7 +25,9 @@ bool Search::Reset(Microsoft::Console::Render::IRenderData& renderData, const st _flags = flags; _lastMutationId = textBuffer.GetLastMutationId(); - _ok = textBuffer.SearchText(needle, _flags, _results); + auto result = textBuffer.SearchText(needle, _flags); + _ok = result.has_value(); + _results = std::move(result).value_or(std::vector{}); _index = reverse ? gsl::narrow_cast(_results.size()) - 1 : 0; _step = reverse ? -1 : 1; return true; @@ -144,3 +146,8 @@ ptrdiff_t Search::CurrentMatch() const noexcept { return _index; } + +bool Search::IsOk() const noexcept +{ + return _ok; +} diff --git a/src/buffer/out/search.h b/src/buffer/out/search.h index 3ceb71564da..763b5d40c73 100644 --- a/src/buffer/out/search.h +++ b/src/buffer/out/search.h @@ -49,6 +49,7 @@ class Search final const std::vector& Results() const noexcept; std::vector&& ExtractResults() noexcept; ptrdiff_t CurrentMatch() const noexcept; + bool IsOk() const noexcept; private: // _renderData is a pointer so that Search() is constexpr default constructable. @@ -57,6 +58,7 @@ class Search final SearchFlag _flags{}; uint64_t _lastMutationId = 0; + bool _ok{ false }; std::vector _results; ptrdiff_t _index = 0; ptrdiff_t _step = 0; diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index ec5157b2b6e..af60c0443dc 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -3185,14 +3185,15 @@ void TextBuffer::CopyHyperlinkMaps(const TextBuffer& other) // 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, SearchFlag flags) const +std::optional> TextBuffer::SearchText(const std::wstring_view& needle, SearchFlag flags) const { return SearchText(needle, flags, 0, til::CoordTypeMax); } // 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, SearchFlag flags, til::CoordType rowBeg, til::CoordType rowEnd) const +// Returns nullopt if the parameters were invalid (e.g. regex search was requested with an invalid regex) +std::optional> TextBuffer::SearchText(const std::wstring_view& needle, SearchFlag flags, til::CoordType rowBeg, til::CoordType rowEnd) const { rowEnd = std::min(rowEnd, _estimateOffsetOfLastCommittedRow() + 1); @@ -3220,6 +3221,11 @@ std::vector TextBuffer::SearchText(const std::wstring_view& nee UErrorCode status = U_ZERO_ERROR; const auto re = ICU::CreateRegex(needle, icuFlags, &status); + if (status > U_ZERO_ERROR) + { + return std::nullopt; + } + uregex_setUText(re.get(), &text, &status); if (uregex_find(re.get(), -1, &status)) diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 3bbb7cbdfca..6f21ad41cb4 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -294,8 +294,8 @@ class TextBuffer final static void Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer, const Microsoft::Console::Types::Viewport* lastCharacterViewport = nullptr, PositionInformation* positionInfo = nullptr); - std::vector SearchText(const std::wstring_view& needle, SearchFlag flags) const; - std::vector SearchText(const std::wstring_view& needle, SearchFlag flags, til::CoordType rowBeg, til::CoordType rowEnd) const; + std::optional> SearchText(const std::wstring_view& needle, SearchFlag flags) const; + std::optional> SearchText(const std::wstring_view& needle, SearchFlag flags, til::CoordType rowBeg, til::CoordType rowEnd) const; // Mark handling std::vector GetMarkRows() const; diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 340301c64c2..5e6b79e2862 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1654,11 +1654,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - resetOnly: If true, only Reset() will be called, if anything. FindNext() will never be called. // Return Value: // - - SearchResults ControlCore::Search(const std::wstring_view& text, const bool goForward, const bool caseSensitive, const bool resetOnly) + SearchResults ControlCore::Search(const std::wstring_view& text, const bool goForward, const bool caseSensitive, const bool regularExpression, const bool resetOnly) { const auto lock = _terminal->LockForWriting(); SearchFlag flags{}; WI_SetFlagIf(flags, SearchFlag::CaseInsensitive, !caseSensitive); + WI_SetFlagIf(flags, SearchFlag::RegularExpression, regularExpression); const auto searchInvalidated = _searcher.IsStale(*_terminal.get(), text, flags); if (searchInvalidated || !resetOnly) @@ -1702,6 +1703,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation .TotalMatches = totalMatches, .CurrentMatch = currentMatch, .SearchInvalidated = searchInvalidated, + .SearchRegexInvalid = !_searcher.IsOk(), }; } diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 1eb4b532c49..0798cc6c1fe 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -219,7 +219,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void SetSelectionAnchor(const til::point position); void SetEndSelectionPoint(const til::point position); - SearchResults Search(const std::wstring_view& text, bool goForward, bool caseSensitive, bool reset); + SearchResults Search(const std::wstring_view& text, bool goForward, bool caseSensitive, bool regularExpression, bool reset); const std::vector& SearchResultRows() const noexcept; void ClearSearch(); void SnapSearchResultToSelection(bool snap) noexcept; diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index d10a963786e..4ded4e2ed89 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -54,6 +54,7 @@ namespace Microsoft.Terminal.Control Int32 TotalMatches; Int32 CurrentMatch; Boolean SearchInvalidated; + Boolean SearchRegexInvalid; }; [default_interface] runtimeclass SelectionColor @@ -134,7 +135,7 @@ namespace Microsoft.Terminal.Control void ResumeRendering(); void BlinkAttributeTick(); - SearchResults Search(String text, Boolean goForward, Boolean caseSensitive, Boolean reset); + SearchResults Search(String text, Boolean goForward, Boolean caseSensitive, Boolean regularExpression, Boolean reset); void ClearSearch(); Boolean SnapSearchResultToSelection; diff --git a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw index 328aa4a03cd..53bff1ce7f2 100644 --- a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw @@ -300,4 +300,16 @@ Please either install the missing font or choose another one. Restored "Restored" as in "This content was restored" - \ No newline at end of file + + Regular Expression + The tooltip text for the button on the search box control governing the use of "regular expressions" ("regex"). + + + Regular Expression Search + The accessibility description text for the button on the search box control governing the use of "regular expressions" ("regex"). + + + invalid + This brief message is displayed when a regular expression is invalid. + + diff --git a/src/cascadia/TerminalControl/SearchBoxControl.cpp b/src/cascadia/TerminalControl/SearchBoxControl.cpp index eaaae04add9..46e27de04d4 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.cpp +++ b/src/cascadia/TerminalControl/SearchBoxControl.cpp @@ -30,6 +30,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _focusableElements.insert(TextBox()); _focusableElements.insert(CloseButton()); _focusableElements.insert(CaseSensitivityButton()); + _focusableElements.insert(RegexButton()); _focusableElements.insert(GoForwardButton()); _focusableElements.insert(GoBackwardButton()); @@ -224,6 +225,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation return CaseSensitivityButton().IsChecked().GetBoolean(); } + bool SearchBoxControl::RegularExpression() + { + return RegexButton().IsChecked().GetBoolean(); + } + // Method Description: // - Handler for pressing Enter on TextBox, trigger // text search @@ -245,11 +251,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto state = CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift); if (WI_IsFlagSet(state, CoreVirtualKeyStates::Down)) { - Search.raise(Text(), !GoForward(), CaseSensitive()); + Search.raise(Text(), !GoForward(), CaseSensitive(), RegularExpression()); } else { - Search.raise(Text(), GoForward(), CaseSensitive()); + Search.raise(Text(), GoForward(), CaseSensitive(), RegularExpression()); } e.Handled(true); } @@ -340,7 +346,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } // kick off search - Search.raise(Text(), GoForward(), CaseSensitive()); + Search.raise(Text(), GoForward(), CaseSensitive(), RegularExpression()); } // Method Description: @@ -361,7 +367,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } // kick off search - Search.raise(Text(), GoForward(), CaseSensitive()); + Search.raise(Text(), GoForward(), CaseSensitive(), RegularExpression()); } // Method Description: @@ -399,7 +405,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - void SearchBoxControl::TextBoxTextChanged(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/) { - SearchChanged.raise(Text(), GoForward(), CaseSensitive()); + SearchChanged.raise(Text(), GoForward(), CaseSensitive(), RegularExpression()); } // Method Description: @@ -411,7 +417,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - void SearchBoxControl::CaseSensitivityButtonClicked(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/) { - SearchChanged.raise(Text(), GoForward(), CaseSensitive()); + SearchChanged.raise(Text(), GoForward(), CaseSensitive(), RegularExpression()); + } + + void SearchBoxControl::RegexButtonClicked(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::RoutedEventArgs const& /*e*/) + { + SearchChanged.raise(Text(), GoForward(), CaseSensitive(), RegularExpression()); } // Method Description: @@ -504,7 +515,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation double SearchBoxControl::_GetStatusMaxWidth() { const auto fontSize = StatusBox().FontSize(); - const auto maxLength = std::max({ _TextWidth(_FormatStatus(-1, -1), fontSize), + const auto maxLength = std::max({ _TextWidth(RS_(L"SearchRegexInvalid"), fontSize), + _TextWidth(_FormatStatus(-1, -1), fontSize), _TextWidth(_FormatStatus(0, -1), fontSize), _TextWidth(_FormatStatus(MaximumTotalResultsToShowInStatus, MaximumTotalResultsToShowInStatus - 1), fontSize), _TextWidth(_FormatStatus(MaximumTotalResultsToShowInStatus + 1, MaximumTotalResultsToShowInStatus - 1), fontSize), @@ -521,9 +533,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - currentMatch - the index of the current match (0-based) // Return Value: // - - void SearchBoxControl::SetStatus(int32_t totalMatches, int32_t currentMatch) + void SearchBoxControl::SetStatus(int32_t totalMatches, int32_t currentMatch, bool searchRegexInvalid) { - const auto status = _FormatStatus(totalMatches, currentMatch); + hstring status; + if (searchRegexInvalid) + { + status = RS_(L"SearchRegexInvalid"); + } + else + { + status = _FormatStatus(totalMatches, currentMatch); + } StatusBox().Text(status); } diff --git a/src/cascadia/TerminalControl/SearchBoxControl.h b/src/cascadia/TerminalControl/SearchBoxControl.h index 60ea00da481..d774e42b569 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.h +++ b/src/cascadia/TerminalControl/SearchBoxControl.h @@ -38,10 +38,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation winrt::hstring Text(); bool GoForward(); bool CaseSensitive(); + bool RegularExpression(); void SetFocusOnTextbox(); void PopulateTextbox(const winrt::hstring& text); bool ContainsFocus(); - void SetStatus(int32_t totalMatches, int32_t currentMatch); + void SetStatus(int32_t totalMatches, int32_t currentMatch, bool searchRegexInvalid); void ClearStatus(); void GoBackwardClicked(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::Windows::UI::Xaml::RoutedEventArgs& /*e*/); @@ -50,6 +51,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation 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); + void RegexButtonClicked(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); void SearchBoxPointerPressedHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e); void SearchBoxPointerReleasedHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e); diff --git a/src/cascadia/TerminalControl/SearchBoxControl.idl b/src/cascadia/TerminalControl/SearchBoxControl.idl index 3abf4f50229..61c133b1cce 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.idl +++ b/src/cascadia/TerminalControl/SearchBoxControl.idl @@ -3,7 +3,7 @@ namespace Microsoft.Terminal.Control { - delegate void SearchHandler(String query, Boolean goForward, Boolean isCaseSensitive); + delegate void SearchHandler(String query, Boolean goForward, Boolean isCaseSensitive, Boolean regularExpression); [default_interface] runtimeclass SearchBoxControl : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged { @@ -11,7 +11,6 @@ namespace Microsoft.Terminal.Control void SetFocusOnTextbox(); void PopulateTextbox(String text); Boolean ContainsFocus(); - void SetStatus(Int32 totalMatches, Int32 currentMatch); void ClearStatus(); Windows.Foundation.Rect ContentClipRect{ get; }; Double OpenAnimationStartPoint{ get; }; diff --git a/src/cascadia/TerminalControl/SearchBoxControl.xaml b/src/cascadia/TerminalControl/SearchBoxControl.xaml index c17fcc3c010..7523cdcfc83 100644 --- a/src/cascadia/TerminalControl/SearchBoxControl.xaml +++ b/src/cascadia/TerminalControl/SearchBoxControl.xaml @@ -243,6 +243,17 @@ + + + +