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

Highlight all search results while the search box is open #16227

Merged
merged 20 commits into from
Dec 15, 2023
Merged
22 changes: 22 additions & 0 deletions src/buffer/out/search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,28 @@ const til::point_span* Search::GetCurrent() const noexcept
return nullptr;
}

void Search::HighlightResults() const
{
std::vector<til::inclusive_rect> toSelect;
const auto& textBuffer = _renderData->GetTextBuffer();

for (const auto& r : _results)
{
const auto rbStart = textBuffer.BufferToScreenPosition(r.start);
const auto rbEnd = textBuffer.BufferToScreenPosition(r.end);

til::inclusive_rect re;
re.top = rbStart.y;
re.bottom = rbEnd.y;
re.left = rbStart.x;
re.right = rbEnd.x;

toSelect.emplace_back(re);
}

_renderData->SelectSearchRegions(std::move(toSelect));
}

// Routine Description:
// - Takes the found word and selects it in the screen buffer

Expand Down
1 change: 1 addition & 0 deletions src/buffer/out/search.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class Search final
void FindNext() noexcept;

const til::point_span* GetCurrent() const noexcept;
void HighlightResults() const;
bool SelectCurrent() const;

const std::vector<til::point_span>& Results() const noexcept;
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/ControlCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1604,6 +1604,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation

if (_searcher.ResetIfStale(*GetRenderData(), text, !goForward, !caseSensitive))
{
_searcher.HighlightResults();
_searcher.MoveToCurrentSelection();
_cachedSearchResultRows = {};
}
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,12 @@ class Microsoft::Terminal::Core::Terminal final :

std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& attr) const noexcept override;
std::vector<Microsoft::Console::Types::Viewport> GetSelectionRects() noexcept override;
std::vector<Microsoft::Console::Types::Viewport> GetSearchSelectionRects() noexcept override;
const bool IsSelectionActive() const noexcept override;
const bool IsBlockSelection() const noexcept override;
void ClearSelection() override;
void SelectNewRegion(const til::point coordStart, const til::point coordEnd) override;
void SelectSearchRegions(std::vector<til::inclusive_rect> source) override;
const til::point GetSelectionAnchor() const noexcept override;
const til::point GetSelectionEnd() const noexcept override;
const std::wstring_view GetConsoleTitle() const noexcept override;
Expand Down Expand Up @@ -370,6 +372,7 @@ class Microsoft::Terminal::Core::Terminal final :
til::point pivot;
};
std::optional<SelectionAnchors> _selection;
std::vector<til::inclusive_rect> _searchSelections;
bool _blockSelection = false;
std::wstring _wordDelimiters;
SelectionExpansion _multiClickSelectionMode = SelectionExpansion::Char;
Expand Down Expand Up @@ -459,6 +462,7 @@ class Microsoft::Terminal::Core::Terminal final :
#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp
std::vector<til::inclusive_rect> _GetSelectionRects() const noexcept;
std::vector<til::inclusive_rect> _GetSearchSelectionRects(Microsoft::Console::Types::Viewport viewport) const noexcept;
std::vector<til::point_span> _GetSelectionSpans() const noexcept;
std::pair<til::point, til::point> _PivotSelection(const til::point targetPos, bool& targetStart) const noexcept;
std::pair<til::point, til::point> _ExpandSelectionAnchors(std::pair<til::point, til::point> anchors) const;
Expand Down
35 changes: 35 additions & 0 deletions src/cascadia/TerminalCore/TerminalSelection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,40 @@ std::vector<til::inclusive_rect> Terminal::_GetSelectionRects() const noexcept
return result;
}

// Method Description:
// - Helper to determine the selected region of the buffer. Used for rendering.
// Return Value:
// - A vector of rectangles representing the regions to select, line by line. They are absolute coordinates relative to the buffer origin.
std::vector<til::inclusive_rect> Terminal::_GetSearchSelectionRects(Microsoft::Console::Types::Viewport viewport) const noexcept
{
std::vector<til::inclusive_rect> result;
try
{
auto lowerIt = std::lower_bound(_searchSelections.begin(), _searchSelections.end(), viewport.Top(), [](const til::inclusive_rect& rect, til::CoordType value) {
return rect.top < value;
});

auto upperIt = std::upper_bound(_searchSelections.begin(), _searchSelections.end(), viewport.BottomExclusive(), [](til::CoordType value, const til::inclusive_rect& rect) {
return value < rect.top;
});

for (auto selection = lowerIt; selection != upperIt; ++selection)
{
const auto start = til::point{ selection->left, selection->top };
const auto end = til::point{ selection->right, selection->top };
const auto adj = _activeBuffer().GetTextRects(start, end, _blockSelection, false);
for (auto a : adj)
{
result.emplace_back(a);
}
}

return result;
}
CATCH_LOG();
return result;
}

// Method Description:
// - Identical to GetTextRects if it's a block selection, else returns a single span for the whole selection.
// Return Value:
Expand Down Expand Up @@ -824,6 +858,7 @@ void Terminal::_MoveByBuffer(SelectionDirection direction, til::point& pos) noex
void Terminal::ClearSelection()
{
_assertLocked();
_searchSelections.clear();
_selection = std::nullopt;
_selectionMode = SelectionInteractionMode::None;
_selectionIsTargetingUrl = false;
Expand Down
35 changes: 35 additions & 0 deletions src/cascadia/TerminalCore/terminalrenderdata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,24 @@ catch (...)
return {};
}

std::vector<Microsoft::Console::Types::Viewport> Terminal::GetSearchSelectionRects() noexcept
try
{
std::vector<Viewport> result;

for (const auto& lineRect : _GetSearchSelectionRects(_GetVisibleViewport()))
{
result.emplace_back(Viewport::FromInclusive(lineRect));
}

return result;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return {};
}

void Terminal::SelectNewRegion(const til::point coordStart, const til::point coordEnd)
{
#pragma warning(push)
Expand Down Expand Up @@ -188,6 +206,23 @@ void Terminal::SelectNewRegion(const til::point coordStart, const til::point coo
SetSelectionEnd(realCoordEnd, SelectionExpansion::Char);
}

void Terminal::SelectSearchRegions(std::vector<til::inclusive_rect> rects)
{
Copy link
Member

Choose a reason for hiding this comment

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

@lhecker do we need to use any of your locks here?

Copy link
Member

Choose a reason for hiding this comment

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

I'd say no. I moved all locking calls to outside of the Terminal class to make the code more consistent. If you were talking about the _assertLocked(), then I'd also say no, because this will be indirectly covered through the _VisibleStartIndex (we could add _assertLocked() to all member functions in the future if we wanted to).

_searchSelections.clear();
for (auto& rect : rects)
{
rect.top -= _VisibleStartIndex();
rect.bottom -= _VisibleStartIndex();

const auto realStart = _ConvertToBufferCell(til::point{ rect.left, rect.top });
const auto realEnd = _ConvertToBufferCell(til::point{ rect.right, rect.bottom });

auto rr = til::inclusive_rect{ realStart.x, realStart.y, realEnd.x, realEnd.y };

_searchSelections.emplace_back(rr);
}
Copy link
Member

Choose a reason for hiding this comment

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

I was expecting to find something here poking the renderer.. like TriggerSelection. Is that already happening because we're also making a new selection when we search?

I am worried about the hidden coupling here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think right now it is relying on this call to trigger selection.

_renderer->TriggerSelection();

Something does feel weird when I compare it to have the normal selection from _search.SelectCurrent() works.

Would it make sense to move the _searcher.HighlightSelections() here?

// We don't want to show the markers so manually tell it to clear it.

Or to add a TriggerSelection call here?

_searcher.MoveToCurrentSelection();

Copy link
Member

Choose a reason for hiding this comment

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

That's a really good question. I think that you'll want to do it any time you are done changing _searchSelections. @lhecker might have a better opinion here.

I tried out your branch! It works really, really well. Congrats!

I only found one issue.

When I delete the search with backspace, or close it, the highlights stick around... but the search is no longer active. So next time the screen updates, they all go away.

I think this might be because of the missing TriggerSelection!

Copy link
Contributor Author

@e82eric e82eric Dec 6, 2023

Choose a reason for hiding this comment

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

somewhat long story on the selections not getting cleared when the last char is deleted from the search box. I was trying to match the behavior from the release that I had on my machine and it looked like the "current" selection highlight stuck around after deleting the last char and then I cross referenced visual studio and it looked like it also left the highlights so I thought it might be a pattern and didn't want to change any existing behavior.

For clearing all selections, I think this would work.
e82eric@4dc6305

For leaving the current selection highlighted but clearing all of the other highlights, I think this would work.
e82eric@3854b8b

For clearing the selections after the search box is closed, I actually like the behavior where it leaves the selections highlighted, It gives me context if I want to go into mark mode (about what is going to get selected).

I think it is possible to clear the selections after closing the search box.
e82eric@8a61353

Let me know if any of these make sense or if there is another approach and I can update the PR.

Copy link
Member

Choose a reason for hiding this comment

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

That all makes sense!

}

const std::wstring_view Terminal::GetConsoleTitle() const noexcept
{
_assertLocked();
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ namespace
HRESULT PaintBufferLine(std::span<const Cluster> /*clusters*/, til::point /*coord*/, bool /*fTrimLeft*/, bool /*lineWrapped*/) noexcept { return S_OK; }
HRESULT PaintBufferGridLines(GridLineSet /*lines*/, COLORREF /*color*/, size_t /*cchLine*/, til::point /*coordTarget*/) noexcept { return S_OK; }
HRESULT PaintSelection(const til::rect& /*rect*/) noexcept { return S_OK; }
HRESULT PaintSelections(const std::vector<til::rect>& /*rects*/) noexcept { return S_OK; }
HRESULT PaintCursor(const CursorOptions& /*options*/) noexcept { return S_OK; }
HRESULT UpdateDrawingBrushes(const TextAttribute& /*textAttributes*/, const RenderSettings& /*renderSettings*/, gsl::not_null<IRenderData*> /*pData*/, bool /*usingSoftFont*/, bool /*isSettingDefaultBrushes*/) noexcept { return S_OK; }
HRESULT UpdateFont(const FontInfoDesired& /*FontInfoDesired*/, _Out_ FontInfo& /*FontInfo*/) noexcept { return S_OK; }
Expand Down
14 changes: 14 additions & 0 deletions src/host/renderData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ std::vector<Viewport> RenderData::GetSelectionRects() noexcept
return result;
}

// Method Description:
// - Retrieves one rectangle per line describing the area of the viewport
// that should be highlighted in some way to represent a user-interactive selection
// Return Value:
// - Vector of Viewports describing the area selected
std::vector<Viewport> RenderData::GetSearchSelectionRects() noexcept
{
return {};
Copy link
Member

Choose a reason for hiding this comment

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

Conhost performance question: it's cheap to return an empty vector, right?

Gut check... it looks like it is "cheap enough", at least on x64:

#include <vector>
std::vector<int> foo() {
    return {};
}
__$ReturnUdt$ = 32
std::vector<int,std::allocator<int> > foo(void) PROC  ; foo, COMDAT
$LN13:
        sub     rsp, 24
        xor     eax, eax
        mov     QWORD PTR [rcx], rax
        mov     QWORD PTR [rcx+8], rax
        mov     QWORD PTR [rcx+16], rax
        mov     rax, rcx
        add     rsp, 24
        ret     0
std::vector<int,std::allocator<int> > foo(void) ENDP  ; foo

}

// Method Description:
// - Lock the console for reading the contents of the buffer. Ensures that the
// contents of the console won't be changed in the middle of a paint
Expand Down Expand Up @@ -369,6 +379,10 @@ void RenderData::SelectNewRegion(const til::point coordStart, const til::point c
Selection::Instance().SelectNewRegion(coordStart, coordEnd);
}

void RenderData::SelectSearchRegions(std::vector<til::inclusive_rect> source)
{
}

// Routine Description:
// - Gets the current selection anchor position
// Arguments:
Expand Down
2 changes: 2 additions & 0 deletions src/host/renderData.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class RenderData final :
const FontInfo& GetFontInfo() const noexcept override;

std::vector<Microsoft::Console::Types::Viewport> GetSelectionRects() noexcept override;
std::vector<Microsoft::Console::Types::Viewport> GetSearchSelectionRects() noexcept override;

void LockConsole() noexcept override;
void UnlockConsole() noexcept override;
Expand Down Expand Up @@ -54,6 +55,7 @@ class RenderData final :
const bool IsBlockSelection() const noexcept override;
void ClearSelection() override;
void SelectNewRegion(const til::point coordStart, const til::point coordEnd) override;
void SelectSearchRegions(std::vector<til::inclusive_rect> source) override;
const til::point GetSelectionAnchor() const noexcept override;
const til::point GetSelectionEnd() const noexcept override;
const bool IsUiaDataInitialized() const noexcept override { return true; }
Expand Down
9 changes: 9 additions & 0 deletions src/host/ut_host/VtIoTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@ class MockRenderData : public IRenderData
return std::vector<Microsoft::Console::Types::Viewport>{};
}

std::vector<Microsoft::Console::Types::Viewport> GetSearchSelectionRects() noexcept override
{
return std::vector<Microsoft::Console::Types::Viewport>{};
}

void LockConsole() noexcept override
{
}
Expand Down Expand Up @@ -363,6 +368,10 @@ class MockRenderData : public IRenderData
{
}

void SelectSearchRegions(std::vector<til::inclusive_rect> /*source*/) override
{
}

const til::point GetSelectionAnchor() const noexcept
{
return {};
Expand Down
5 changes: 5 additions & 0 deletions src/interactivity/onecore/BgfxEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ CATCH_RETURN()
return S_OK;
}

[[nodiscard]] HRESULT BgfxEngine::PaintSelections(const std::vector<til::rect>& /*rects*/) noexcept
{
return S_OK;
}

[[nodiscard]] HRESULT BgfxEngine::PaintCursor(const CursorOptions& options) noexcept
try
{
Expand Down
1 change: 1 addition & 0 deletions src/interactivity/onecore/BgfxEngine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ namespace Microsoft::Console::Render
const bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, til::point const coordTarget) noexcept override;
[[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override;
[[nodiscard]] HRESULT PaintSelections(const std::vector<til::rect>& rects) noexcept override;

[[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override;

Expand Down
33 changes: 33 additions & 0 deletions src/renderer/atlas/AtlasEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,39 @@ try
}
CATCH_RETURN()

[[nodiscard]] HRESULT AtlasEngine::PaintSelections(const std::vector<til::rect>& rects) noexcept
try
{
// Unfortunately there's no step after Renderer::_PaintBufferOutput that
// would inform us that it's done with the last AtlasEngine::PaintBufferLine.
// As such we got to call _flushBufferLine() here just to be sure.
_flushBufferLine();

for (auto rect : rects)
{
const auto y = gsl::narrow_cast<u16>(clamp<til::CoordType>(rect.top, 0, _p.s->viewportCellCount.y));
const auto from = gsl::narrow_cast<u16>(clamp<til::CoordType>(rect.left, 0, _p.s->viewportCellCount.x - 1));
const auto to = gsl::narrow_cast<u16>(clamp<til::CoordType>(rect.right, from, _p.s->viewportCellCount.x));

if (rect.bottom <= 0 || rect.top >= _p.s->viewportCellCount.y)
{
continue;
}

auto& row = *_p.rows[y];

auto s = SearchSelection{ from, to };
row.searchSelections.emplace_back(s);

_p.dirtyRectInPx.left = std::min(_p.dirtyRectInPx.left, from * _p.s->font->cellSize.x);
_p.dirtyRectInPx.top = std::min(_p.dirtyRectInPx.top, y * _p.s->font->cellSize.y);
_p.dirtyRectInPx.right = std::max(_p.dirtyRectInPx.right, to * _p.s->font->cellSize.x);
_p.dirtyRectInPx.bottom = std::max(_p.dirtyRectInPx.bottom, _p.dirtyRectInPx.top + _p.s->font->cellSize.y);
}
return S_OK;
}
CATCH_RETURN()

[[nodiscard]] HRESULT AtlasEngine::PaintCursor(const CursorOptions& options) noexcept
try
{
Expand Down
1 change: 1 addition & 0 deletions src/renderer/atlas/AtlasEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ namespace Microsoft::Console::Render::Atlas
[[nodiscard]] HRESULT PaintBufferLine(std::span<const Cluster> clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept override;
[[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override;
[[nodiscard]] HRESULT PaintSelections(const std::vector<til::rect>& rects) noexcept override;
[[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override;
[[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, gsl::not_null<IRenderData*> pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept override;
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override;
Expand Down
33 changes: 33 additions & 0 deletions src/renderer/atlas/BackendD3D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ void BackendD3D::Render(RenderingPayload& p)
_drawBackground(p);
_drawCursorBackground(p);
_drawText(p);
_drawSearchSelections(p);
_drawSelection(p);
#if ATLAS_DEBUG_SHOW_DIRTY
_debugShowDirty(p);
Expand Down Expand Up @@ -2066,6 +2067,38 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off
return addedInstances;
}

void BackendD3D::_drawSearchSelections(const RenderingPayload& p)
{
u16 y = 0;

for (const auto& row : p.rows)
{
if (!row->searchSelections.empty())
{
for (auto s : row->searchSelections)
{
if (s.from != row->selectionFrom || s.to != row->selectionTo)
{
_appendQuad() = {
.shadingType = ShadingType::Selection,
.position = {
p.s->font->cellSize.x * s.from,
p.s->font->cellSize.y * y,
},
.size = {
static_cast<u16>(p.s->font->cellSize.x * (s.to - s.from)),
p.s->font->cellSize.y,
},
.color = p.s->misc->searchSelectionColor,
};
}
}
}

y++;
}
}

void BackendD3D::_drawSelection(const RenderingPayload& p)
{
u16 y = 0;
Expand Down
1 change: 1 addition & 0 deletions src/renderer/atlas/BackendD3D.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ namespace Microsoft::Console::Render::Atlas
ATLAS_ATTR_COLD void _drawCursorForeground();
ATLAS_ATTR_COLD size_t _drawCursorForegroundSlowPath(const CursorRect& c, size_t offset);
void _drawSelection(const RenderingPayload& p);
void _drawSearchSelections(const RenderingPayload& p);
void _executeCustomShader(RenderingPayload& p);

wil::com_ptr<ID3D11RenderTargetView> _renderTargetView;
Expand Down
Loading