Skip to content

Commit ecb5631

Browse files
authored
Add support for regex search to conhost and Terminal (microsoft#17316)
This is broken down into individual reviewable commits. [Here is](https://github.com/microsoft/terminal/assets/189190/3b2ffd50-1350-4f3c-86b0-75abbd846969) a video of it in action! Part of microsoft#3920
1 parent baba406 commit ecb5631

File tree

24 files changed

+203
-63
lines changed

24 files changed

+203
-63
lines changed

.github/actions/spelling/expect/expect.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@ FILTERONPASTE
609609
FINDCASE
610610
FINDDLG
611611
FINDDOWN
612+
FINDREGEX
612613
FINDSTRINGEXACT
613614
FINDUP
614615
FIter

src/buffer/out/search.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,26 @@
88

99
using namespace Microsoft::Console::Types;
1010

11-
bool Search::IsStale(const Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool caseInsensitive) const noexcept
11+
bool Search::IsStale(const Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, SearchFlag flags) const noexcept
1212
{
1313
return _renderData != &renderData ||
1414
_needle != needle ||
15-
_caseInsensitive != caseInsensitive ||
15+
_flags != flags ||
1616
_lastMutationId != renderData.GetTextBuffer().GetLastMutationId();
1717
}
1818

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

2323
_renderData = &renderData;
2424
_needle = needle;
25-
_caseInsensitive = caseInsensitive;
25+
_flags = flags;
2626
_lastMutationId = textBuffer.GetLastMutationId();
2727

28-
_results = textBuffer.SearchText(needle, caseInsensitive);
28+
auto result = textBuffer.SearchText(needle, _flags);
29+
_ok = result.has_value();
30+
_results = std::move(result).value_or(std::vector<til::point_span>{});
2931
_index = reverse ? gsl::narrow_cast<ptrdiff_t>(_results.size()) - 1 : 0;
3032
_step = reverse ? -1 : 1;
3133
return true;
@@ -144,3 +146,8 @@ ptrdiff_t Search::CurrentMatch() const noexcept
144146
{
145147
return _index;
146148
}
149+
150+
bool Search::IsOk() const noexcept
151+
{
152+
return _ok;
153+
}

src/buffer/out/search.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,23 @@ Revision History:
2020
#include "textBuffer.hpp"
2121
#include "../renderer/inc/IRenderData.hpp"
2222

23+
enum class SearchFlag : unsigned int
24+
{
25+
None = 0,
26+
27+
CaseInsensitive = 1 << 0,
28+
RegularExpression = 1 << 1,
29+
};
30+
31+
DEFINE_ENUM_FLAG_OPERATORS(SearchFlag);
32+
2333
class Search final
2434
{
2535
public:
2636
Search() = default;
2737

28-
bool IsStale(const Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool caseInsensitive) const noexcept;
29-
bool Reset(Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, bool caseInsensitive, bool reverse);
38+
bool IsStale(const Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, SearchFlag flags) const noexcept;
39+
bool Reset(Microsoft::Console::Render::IRenderData& renderData, const std::wstring_view& needle, SearchFlag flags, bool reverse);
3040

3141
void MoveToCurrentSelection();
3242
void MoveToPoint(til::point anchor) noexcept;
@@ -39,14 +49,16 @@ class Search final
3949
const std::vector<til::point_span>& Results() const noexcept;
4050
std::vector<til::point_span>&& ExtractResults() noexcept;
4151
ptrdiff_t CurrentMatch() const noexcept;
52+
bool IsOk() const noexcept;
4253

4354
private:
4455
// _renderData is a pointer so that Search() is constexpr default constructable.
4556
Microsoft::Console::Render::IRenderData* _renderData = nullptr;
4657
std::wstring _needle;
47-
bool _caseInsensitive = false;
58+
SearchFlag _flags{};
4859
uint64_t _lastMutationId = 0;
4960

61+
bool _ok{ false };
5062
std::vector<til::point_span> _results;
5163
ptrdiff_t _index = 0;
5264
ptrdiff_t _step = 0;

src/buffer/out/textBuffer.cpp

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "../../types/inc/GlyphWidth.hpp"
1313
#include "../renderer/base/renderer.hpp"
1414
#include "../types/inc/utils.hpp"
15+
#include "search.h"
1516

1617
using namespace Microsoft::Console;
1718
using namespace Microsoft::Console::Types;
@@ -3184,14 +3185,15 @@ void TextBuffer::CopyHyperlinkMaps(const TextBuffer& other)
31843185

31853186
// Searches through the entire (committed) text buffer for `needle` and returns the coordinates in absolute coordinates.
31863187
// The end coordinates of the returned ranges are considered inclusive.
3187-
std::vector<til::point_span> TextBuffer::SearchText(const std::wstring_view& needle, bool caseInsensitive) const
3188+
std::optional<std::vector<til::point_span>> TextBuffer::SearchText(const std::wstring_view& needle, SearchFlag flags) const
31883189
{
3189-
return SearchText(needle, caseInsensitive, 0, til::CoordTypeMax);
3190+
return SearchText(needle, flags, 0, til::CoordTypeMax);
31903191
}
31913192

31923193
// Searches through the given rows [rowBeg,rowEnd) for `needle` and returns the coordinates in absolute coordinates.
31933194
// While the end coordinates of the returned ranges are considered inclusive, the [rowBeg,rowEnd) range is half-open.
3194-
std::vector<til::point_span> TextBuffer::SearchText(const std::wstring_view& needle, bool caseInsensitive, til::CoordType rowBeg, til::CoordType rowEnd) const
3195+
// Returns nullopt if the parameters were invalid (e.g. regex search was requested with an invalid regex)
3196+
std::optional<std::vector<til::point_span>> TextBuffer::SearchText(const std::wstring_view& needle, SearchFlag flags, til::CoordType rowBeg, til::CoordType rowEnd) const
31953197
{
31963198
rowEnd = std::min(rowEnd, _estimateOffsetOfLastCommittedRow() + 1);
31973199

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

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

3208-
uint32_t flags = UREGEX_LITERAL;
3209-
WI_SetFlagIf(flags, UREGEX_CASE_INSENSITIVE, caseInsensitive);
3210+
uint32_t icuFlags{ 0 };
3211+
WI_SetFlagIf(icuFlags, UREGEX_CASE_INSENSITIVE, WI_IsFlagSet(flags, SearchFlag::CaseInsensitive));
3212+
3213+
if (WI_IsFlagSet(flags, SearchFlag::RegularExpression))
3214+
{
3215+
WI_SetFlag(icuFlags, UREGEX_MULTILINE);
3216+
}
3217+
else
3218+
{
3219+
WI_SetFlag(icuFlags, UREGEX_LITERAL);
3220+
}
32103221

32113222
UErrorCode status = U_ZERO_ERROR;
3212-
const auto re = ICU::CreateRegex(needle, flags, &status);
3223+
const auto re = ICU::CreateRegex(needle, icuFlags, &status);
3224+
if (status > U_ZERO_ERROR)
3225+
{
3226+
return std::nullopt;
3227+
}
3228+
32133229
uregex_setUText(re.get(), &text, &status);
32143230

32153231
if (uregex_find(re.get(), -1, &status))

src/buffer/out/textBuffer.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ filling in the last row, and updating the screen.
5858
#include "../buffer/out/textBufferTextIterator.hpp"
5959

6060
struct URegularExpression;
61+
enum class SearchFlag : unsigned int;
6162

6263
namespace Microsoft::Console::Render
6364
{
@@ -293,8 +294,8 @@ class TextBuffer final
293294

294295
static void Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer, const Microsoft::Console::Types::Viewport* lastCharacterViewport = nullptr, PositionInformation* positionInfo = nullptr);
295296

296-
std::vector<til::point_span> SearchText(const std::wstring_view& needle, bool caseInsensitive) const;
297-
std::vector<til::point_span> SearchText(const std::wstring_view& needle, bool caseInsensitive, til::CoordType rowBeg, til::CoordType rowEnd) const;
297+
std::optional<std::vector<til::point_span>> SearchText(const std::wstring_view& needle, SearchFlag flags) const;
298+
std::optional<std::vector<til::point_span>> SearchText(const std::wstring_view& needle, SearchFlag flags, til::CoordType rowBeg, til::CoordType rowEnd) const;
298299

299300
// Mark handling
300301
std::vector<ScrollMark> GetMarkRows() const;

src/buffer/out/ut_textbuffer/UTextAdapterTests.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "WexTestClass.h"
77
#include "../textBuffer.hpp"
88
#include "../../renderer/inc/DummyRenderer.hpp"
9+
#include "../search.h"
910

1011
template<>
1112
class WEX::TestExecution::VerifyOutputTraits<std::vector<til::point_span>>
@@ -49,15 +50,15 @@ class UTextAdapterTests
4950
};
5051

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

5556
expected = std::vector{ s(5, 5) };
56-
actual = buffer.SearchText(L"𝒷", false);
57+
actual = buffer.SearchText(L"𝒷", SearchFlag::None);
5758
VERIFY_ARE_EQUAL(expected, actual);
5859

5960
expected = std::vector{ s(12, 15) };
60-
actual = buffer.SearchText(L"ネコ", false);
61+
actual = buffer.SearchText(L"ネコ", SearchFlag::None);
6162
VERIFY_ARE_EQUAL(expected, actual);
6263
}
6364
};

src/cascadia/TerminalControl/ControlCore.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,10 +1654,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
16541654
// - resetOnly: If true, only Reset() will be called, if anything. FindNext() will never be called.
16551655
// Return Value:
16561656
// - <none>
1657-
SearchResults ControlCore::Search(const std::wstring_view& text, const bool goForward, const bool caseSensitive, const bool resetOnly)
1657+
SearchResults ControlCore::Search(const std::wstring_view& text, const bool goForward, const bool caseSensitive, const bool regularExpression, const bool resetOnly)
16581658
{
16591659
const auto lock = _terminal->LockForWriting();
1660-
const auto searchInvalidated = _searcher.IsStale(*_terminal.get(), text, !caseSensitive);
1660+
SearchFlag flags{};
1661+
WI_SetFlagIf(flags, SearchFlag::CaseInsensitive, !caseSensitive);
1662+
WI_SetFlagIf(flags, SearchFlag::RegularExpression, regularExpression);
1663+
const auto searchInvalidated = _searcher.IsStale(*_terminal.get(), text, flags);
16611664

16621665
if (searchInvalidated || !resetOnly)
16631666
{
@@ -1666,7 +1669,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
16661669
if (searchInvalidated)
16671670
{
16681671
oldResults = _searcher.ExtractResults();
1669-
_searcher.Reset(*_terminal.get(), text, !caseSensitive, !goForward);
1672+
_searcher.Reset(*_terminal.get(), text, flags, !goForward);
16701673

16711674
if (SnapSearchResultToSelection())
16721675
{
@@ -1700,6 +1703,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
17001703
.TotalMatches = totalMatches,
17011704
.CurrentMatch = currentMatch,
17021705
.SearchInvalidated = searchInvalidated,
1706+
.SearchRegexInvalid = !_searcher.IsOk(),
17031707
};
17041708
}
17051709

src/cascadia/TerminalControl/ControlCore.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
219219
void SetSelectionAnchor(const til::point position);
220220
void SetEndSelectionPoint(const til::point position);
221221

222-
SearchResults Search(const std::wstring_view& text, bool goForward, bool caseSensitive, bool reset);
222+
SearchResults Search(const std::wstring_view& text, bool goForward, bool caseSensitive, bool regularExpression, bool reset);
223223
const std::vector<til::point_span>& SearchResultRows() const noexcept;
224224
void ClearSearch();
225225
void SnapSearchResultToSelection(bool snap) noexcept;

src/cascadia/TerminalControl/ControlCore.idl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ namespace Microsoft.Terminal.Control
5454
Int32 TotalMatches;
5555
Int32 CurrentMatch;
5656
Boolean SearchInvalidated;
57+
Boolean SearchRegexInvalid;
5758
};
5859

5960
[default_interface] runtimeclass SelectionColor
@@ -134,7 +135,7 @@ namespace Microsoft.Terminal.Control
134135
void ResumeRendering();
135136
void BlinkAttributeTick();
136137

137-
SearchResults Search(String text, Boolean goForward, Boolean caseSensitive, Boolean reset);
138+
SearchResults Search(String text, Boolean goForward, Boolean caseSensitive, Boolean regularExpression, Boolean reset);
138139
void ClearSearch();
139140
Boolean SnapSearchResultToSelection;
140141

src/cascadia/TerminalControl/Resources/en-US/Resources.resw

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,4 +300,16 @@ Please either install the missing font or choose another one.</value>
300300
<value>Restored</value>
301301
<comment>"Restored" as in "This content was restored"</comment>
302302
</data>
303-
</root>
303+
<data name="SearchBox_RegularExpression.ToolTipService.ToolTip" xml:space="preserve">
304+
<value>Regular Expression</value>
305+
<comment>The tooltip text for the button on the search box control governing the use of "regular expressions" ("regex").</comment>
306+
</data>
307+
<data name="SearchBox_RegularExpression.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
308+
<value>Regular Expression Search</value>
309+
<comment>The accessibility description text for the button on the search box control governing the use of "regular expressions" ("regex").</comment>
310+
</data>
311+
<data name="SearchRegexInvalid" xml:space="preserve">
312+
<value>invalid</value>
313+
<comment>This brief message is displayed when a regular expression is invalid.</comment>
314+
</data>
315+
</root>

0 commit comments

Comments
 (0)