Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for regex search to conhost and Terminal #17316

Merged
merged 4 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spelling/expect/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,7 @@ FILTERONPASTE
FINDCASE
FINDDLG
FINDDOWN
FINDREGEX
FINDSTRINGEXACT
FINDUP
FIter
Expand Down
17 changes: 12 additions & 5 deletions src/buffer/out/search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,26 @@

using namespace Microsoft::Console::Types;

bool Search::IsStale(const Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool caseInsensitive) const noexcept
bool Search::IsStale(const Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, SearchFlag flags) const noexcept
{
return _renderData != &renderData ||
_needle != needle ||
_caseInsensitive != caseInsensitive ||
_flags != flags ||
_lastMutationId != renderData.GetTextBuffer().GetLastMutationId();
}

bool Search::Reset(Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool caseInsensitive, bool reverse)
bool Search::Reset(Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, SearchFlag flags, bool reverse)
{
const auto& textBuffer = renderData.GetTextBuffer();

_renderData = &renderData;
_needle = needle;
_caseInsensitive = caseInsensitive;
_flags = flags;
_lastMutationId = textBuffer.GetLastMutationId();

_results = textBuffer.SearchText(needle, caseInsensitive);
auto result = textBuffer.SearchText(needle, _flags);
_ok = result.has_value();
_results = std::move(result).value_or(std::vector<til::point_span>{});
_index = reverse ? gsl::narrow_cast<ptrdiff_t>(_results.size()) - 1 : 0;
_step = reverse ? -1 : 1;
return true;
Expand Down Expand Up @@ -144,3 +146,8 @@ ptrdiff_t Search::CurrentMatch() const noexcept
{
return _index;
}

bool Search::IsOk() const noexcept
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
bool Search::IsOk() const noexcept
bool Search::IsSearchInvalid() const noexcept

maybe? Just something a bit more specific would be nice imo, because search->IsOk() is kinda ambiguous out-of-context.

{
return _ok;
}
18 changes: 15 additions & 3 deletions src/buffer/out/search.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,23 @@ Revision History:
#include "textBuffer.hpp"
#include "../renderer/inc/IRenderData.hpp"

enum class SearchFlag : unsigned int
{
None = 0,

CaseInsensitive = 1 << 0,
RegularExpression = 1 << 1,
};

DEFINE_ENUM_FLAG_OPERATORS(SearchFlag);

class Search final
{
public:
Search() = default;

bool IsStale(const Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool caseInsensitive) const noexcept;
bool Reset(Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool caseInsensitive, bool reverse);
bool IsStale(const Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, SearchFlag flags) const noexcept;
bool Reset(Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, SearchFlag flags, bool reverse);

void MoveToCurrentSelection();
void MoveToPoint(til::point anchor) noexcept;
Expand All @@ -39,14 +49,16 @@ class Search final
const std::vector<til::point_span>& Results() const noexcept;
std::vector<til::point_span>&& ExtractResults() noexcept;
ptrdiff_t CurrentMatch() const noexcept;
bool IsOk() const noexcept;

private:
// _renderData is a pointer so that Search() is constexpr default constructable.
Microsoft::Console::Render::IRenderData* _renderData = nullptr;
std::wstring _needle;
bool _caseInsensitive = false;
SearchFlag _flags{};
uint64_t _lastMutationId = 0;

bool _ok{ false };
std::vector<til::point_span> _results;
ptrdiff_t _index = 0;
ptrdiff_t _step = 0;
Expand Down
28 changes: 22 additions & 6 deletions src/buffer/out/textBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "../../types/inc/GlyphWidth.hpp"
#include "../renderer/base/renderer.hpp"
#include "../types/inc/utils.hpp"
#include "search.h"

using namespace Microsoft::Console;
using namespace Microsoft::Console::Types;
Expand Down Expand Up @@ -3184,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<til::point_span> TextBuffer::SearchText(const std::wstring_view& needle, bool caseInsensitive) const
std::optional<std::vector<til::point_span>> TextBuffer::SearchText(const std::wstring_view& needle, SearchFlag flags) const
{
return SearchText(needle, caseInsensitive, 0, til::CoordTypeMax);
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<til::point_span> TextBuffer::SearchText(const std::wstring_view& needle, bool caseInsensitive, 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<std::vector<til::point_span>> TextBuffer::SearchText(const std::wstring_view& needle, SearchFlag flags, til::CoordType rowBeg, til::CoordType rowEnd) const
{
rowEnd = std::min(rowEnd, _estimateOffsetOfLastCommittedRow() + 1);

Expand All @@ -3205,11 +3207,25 @@ std::vector<til::point_span> TextBuffer::SearchText(const std::wstring_view& nee

auto text = ICU::UTextFromTextBuffer(*this, rowBeg, rowEnd);

uint32_t flags = UREGEX_LITERAL;
WI_SetFlagIf(flags, UREGEX_CASE_INSENSITIVE, caseInsensitive);
uint32_t icuFlags{ 0 };
WI_SetFlagIf(icuFlags, UREGEX_CASE_INSENSITIVE, WI_IsFlagSet(flags, SearchFlag::CaseInsensitive));

if (WI_IsFlagSet(flags, SearchFlag::RegularExpression))
{
WI_SetFlag(icuFlags, UREGEX_MULTILINE);
}
else
{
WI_SetFlag(icuFlags, UREGEX_LITERAL);
}

UErrorCode status = U_ZERO_ERROR;
const auto re = ICU::CreateRegex(needle, flags, &status);
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))
Expand Down
5 changes: 3 additions & 2 deletions src/buffer/out/textBuffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ filling in the last row, and updating the screen.
#include "../buffer/out/textBufferTextIterator.hpp"

struct URegularExpression;
enum class SearchFlag : unsigned int;

namespace Microsoft::Console::Render
{
Expand Down Expand Up @@ -293,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<til::point_span> SearchText(const std::wstring_view& needle, bool caseInsensitive) const;
std::vector<til::point_span> SearchText(const std::wstring_view& needle, bool caseInsensitive, til::CoordType rowBeg, til::CoordType rowEnd) const;
std::optional<std::vector<til::point_span>> SearchText(const std::wstring_view& needle, SearchFlag flags) const;
std::optional<std::vector<til::point_span>> SearchText(const std::wstring_view& needle, SearchFlag flags, til::CoordType rowBeg, til::CoordType rowEnd) const;

// Mark handling
std::vector<ScrollMark> GetMarkRows() const;
Expand Down
7 changes: 4 additions & 3 deletions src/buffer/out/ut_textbuffer/UTextAdapterTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "WexTestClass.h"
#include "../textBuffer.hpp"
#include "../../renderer/inc/DummyRenderer.hpp"
#include "../search.h"

template<>
class WEX::TestExecution::VerifyOutputTraits<std::vector<til::point_span>>
Expand Down Expand Up @@ -49,15 +50,15 @@ class UTextAdapterTests
};

auto expected = std::vector{ s(0, 2), s(8, 10) };
auto actual = buffer.SearchText(L"abc", false);
auto actual = buffer.SearchText(L"abc", SearchFlag::None);
VERIFY_ARE_EQUAL(expected, actual);

expected = std::vector{ s(5, 5) };
actual = buffer.SearchText(L"𝒷", false);
actual = buffer.SearchText(L"𝒷", SearchFlag::None);
VERIFY_ARE_EQUAL(expected, actual);

expected = std::vector{ s(12, 15) };
actual = buffer.SearchText(L"ネコ", false);
actual = buffer.SearchText(L"ネコ", SearchFlag::None);
VERIFY_ARE_EQUAL(expected, actual);
}
};
10 changes: 7 additions & 3 deletions src/cascadia/TerminalControl/ControlCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1654,10 +1654,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - resetOnly: If true, only Reset() will be called, if anything. FindNext() will never be called.
// Return Value:
// - <none>
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();
const auto searchInvalidated = _searcher.IsStale(*_terminal.get(), text, !caseSensitive);
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)
{
Expand All @@ -1666,7 +1669,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (searchInvalidated)
{
oldResults = _searcher.ExtractResults();
_searcher.Reset(*_terminal.get(), text, !caseSensitive, !goForward);
_searcher.Reset(*_terminal.get(), text, flags, !goForward);

if (SnapSearchResultToSelection())
{
Expand Down Expand Up @@ -1700,6 +1703,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
.TotalMatches = totalMatches,
.CurrentMatch = currentMatch,
.SearchInvalidated = searchInvalidated,
.SearchRegexInvalid = !_searcher.IsOk(),
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalControl/ControlCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<til::point_span>& SearchResultRows() const noexcept;
void ClearSearch();
void SnapSearchResultToSelection(bool snap) noexcept;
Expand Down
3 changes: 2 additions & 1 deletion src/cascadia/TerminalControl/ControlCore.idl
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ namespace Microsoft.Terminal.Control
Int32 TotalMatches;
Int32 CurrentMatch;
Boolean SearchInvalidated;
Boolean SearchRegexInvalid;
};

[default_interface] runtimeclass SelectionColor
Expand Down Expand Up @@ -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;

Expand Down
14 changes: 13 additions & 1 deletion src/cascadia/TerminalControl/Resources/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,16 @@ Please either install the missing font or choose another one.</value>
<value>Restored</value>
<comment>"Restored" as in "This content was restored"</comment>
</data>
</root>
<data name="SearchBox_RegularExpression.ToolTipService.ToolTip" xml:space="preserve">
<value>Regular Expression</value>
<comment>The tooltip text for the button on the search box control governing the use of "regular expressions" ("regex").</comment>
</data>
<data name="SearchBox_RegularExpression.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Regular Expression Search</value>
<comment>The accessibility description text for the button on the search box control governing the use of "regular expressions" ("regex").</comment>
</data>
<data name="SearchRegexInvalid" xml:space="preserve">
<value>invalid</value>
<comment>This brief message is displayed when a regular expression is invalid.</comment>
</data>
</root>
38 changes: 29 additions & 9 deletions src/cascadia/TerminalControl/SearchBoxControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down Expand Up @@ -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
Expand All @@ -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);
}
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -399,7 +405,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - <none>
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:
Expand All @@ -411,7 +417,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - <none>
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:
Expand Down Expand Up @@ -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),
Expand All @@ -521,9 +533,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - currentMatch - the index of the current match (0-based)
// Return Value:
// - <none>
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);
}

Expand Down
4 changes: 3 additions & 1 deletion src/cascadia/TerminalControl/SearchBoxControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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*/);
Expand All @@ -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);

Expand Down
Loading
Loading