mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
Introduce the concept of "selection spans" instead of "rects" (#17638)
This commit is contained in:
parent
65219d40ce
commit
1ef497970f
@ -208,7 +208,7 @@ public:
|
||||
const std::vector<size_t> GetPatternId(const til::point location) const override;
|
||||
|
||||
std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& attr) const noexcept override;
|
||||
std::vector<Microsoft::Console::Types::Viewport> GetSelectionRects() noexcept override;
|
||||
std::span<const til::point_span> GetSelectionSpans() const noexcept override;
|
||||
std::span<const til::point_span> GetSearchHighlights() const noexcept override;
|
||||
const til::point_span* GetSearchHighlightFocused() const noexcept override;
|
||||
const bool IsSelectionActive() const noexcept override;
|
||||
@ -356,6 +356,9 @@ private:
|
||||
std::vector<til::point_span> _searchHighlights;
|
||||
size_t _searchHighlightFocused = 0;
|
||||
|
||||
mutable std::vector<til::point_span> _lastSelectionSpans;
|
||||
mutable til::generation_t _lastSelectionGeneration{};
|
||||
|
||||
CursorType _defaultCursorShape = CursorType::Legacy;
|
||||
|
||||
til::enumset<Mode> _systemMode{ Mode::AutoWrap };
|
||||
@ -466,7 +469,6 @@ private:
|
||||
|
||||
#pragma region TextSelection
|
||||
// These methods are defined in TerminalSelection.cpp
|
||||
std::vector<til::inclusive_rect> _GetSelectionRects() 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;
|
||||
|
||||
@ -42,27 +42,6 @@ DEFINE_ENUM_FLAG_OPERATORS(Terminal::SelectionEndpoint);
|
||||
* The pivot never moves until a new selection is created. It ensures that that cell will always be selected.
|
||||
*/
|
||||
|
||||
// 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::_GetSelectionRects() const noexcept
|
||||
{
|
||||
std::vector<til::inclusive_rect> result;
|
||||
|
||||
if (!IsSelectionActive())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return _activeBuffer().GetTextRects(_selection->start, _selection->end, _selection->blockSelection, false);
|
||||
}
|
||||
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:
|
||||
|
||||
@ -127,17 +127,16 @@ std::pair<COLORREF, COLORREF> Terminal::GetAttributeColors(const TextAttribute&
|
||||
return GetRenderSettings().GetAttributeColors(attr);
|
||||
}
|
||||
|
||||
std::vector<Microsoft::Console::Types::Viewport> Terminal::GetSelectionRects() noexcept
|
||||
std::span<const til::point_span> Terminal::GetSelectionSpans() const noexcept
|
||||
try
|
||||
{
|
||||
std::vector<Viewport> result;
|
||||
|
||||
for (const auto& lineRect : _GetSelectionRects())
|
||||
if (_selection.generation() != _lastSelectionGeneration)
|
||||
{
|
||||
result.emplace_back(Viewport::FromInclusive(lineRect));
|
||||
_lastSelectionSpans = _GetSelectionSpans();
|
||||
_lastSelectionGeneration = _selection.generation();
|
||||
}
|
||||
|
||||
return result;
|
||||
return _lastSelectionSpans;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
||||
@ -336,7 +336,6 @@ namespace ControlUnitTests
|
||||
true);
|
||||
Log::Comment(L"Verify that there's one selection");
|
||||
VERIFY_IS_TRUE(core->HasSelection());
|
||||
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
||||
|
||||
Log::Comment(L"Drag the mouse down a whole row");
|
||||
const til::point terminalPosition2{ 1, 1 };
|
||||
@ -349,7 +348,6 @@ namespace ControlUnitTests
|
||||
true);
|
||||
Log::Comment(L"Verify that there's now two selections (one on each row)");
|
||||
VERIFY_IS_TRUE(core->HasSelection());
|
||||
VERIFY_ARE_EQUAL(2u, core->_terminal->GetSelectionRects().size());
|
||||
|
||||
Log::Comment(L"Release the mouse");
|
||||
interactivity->PointerReleased(noMouseDown,
|
||||
@ -358,7 +356,6 @@ namespace ControlUnitTests
|
||||
cursorPosition2.to_core_point());
|
||||
Log::Comment(L"Verify that there's still two selections");
|
||||
VERIFY_IS_TRUE(core->HasSelection());
|
||||
VERIFY_ARE_EQUAL(2u, core->_terminal->GetSelectionRects().size());
|
||||
|
||||
Log::Comment(L"click outside the current selection");
|
||||
const til::point terminalPosition3{ 2, 2 };
|
||||
@ -370,7 +367,6 @@ namespace ControlUnitTests
|
||||
cursorPosition3.to_core_point());
|
||||
Log::Comment(L"Verify that there's now no selection");
|
||||
VERIFY_IS_FALSE(core->HasSelection());
|
||||
VERIFY_ARE_EQUAL(0u, core->_terminal->GetSelectionRects().size());
|
||||
|
||||
Log::Comment(L"Drag the mouse");
|
||||
const til::point terminalPosition4{ 3, 2 };
|
||||
@ -383,7 +379,6 @@ namespace ControlUnitTests
|
||||
true);
|
||||
Log::Comment(L"Verify that there's now one selection");
|
||||
VERIFY_IS_TRUE(core->HasSelection());
|
||||
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
||||
}
|
||||
|
||||
void ControlInteractivityTests::ScrollWithSelection()
|
||||
@ -438,7 +433,6 @@ namespace ControlUnitTests
|
||||
true);
|
||||
Log::Comment(L"Verify that there's one selection");
|
||||
VERIFY_IS_TRUE(core->HasSelection());
|
||||
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
||||
|
||||
Log::Comment(L"Verify the location of the selection");
|
||||
// The viewport is on row 21, so the selection will be on:
|
||||
@ -586,7 +580,6 @@ namespace ControlUnitTests
|
||||
true);
|
||||
Log::Comment(L"Verify that there's one selection");
|
||||
VERIFY_IS_TRUE(core->HasSelection());
|
||||
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
||||
|
||||
Log::Comment(L"Verify that it started on the first cell we clicked on, not the one we dragged to");
|
||||
til::point expectedAnchor{ 0, 0 };
|
||||
@ -631,7 +624,6 @@ namespace ControlUnitTests
|
||||
true);
|
||||
Log::Comment(L"Verify that there's one selection");
|
||||
VERIFY_IS_TRUE(core->HasSelection());
|
||||
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
||||
|
||||
Log::Comment(L"Verify that it started on the first cell we clicked on, not the one we dragged to");
|
||||
til::point expectedAnchor{ 0, 0 };
|
||||
@ -801,7 +793,6 @@ namespace ControlUnitTests
|
||||
true);
|
||||
Log::Comment(L"Verify that there's one selection");
|
||||
VERIFY_IS_TRUE(core->HasSelection());
|
||||
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
||||
|
||||
Log::Comment(L"Verify the location of the selection");
|
||||
// The viewport is on row (historySize + 5), so the selection will be on:
|
||||
@ -860,7 +851,6 @@ namespace ControlUnitTests
|
||||
cursorPosition0.to_core_point(),
|
||||
true);
|
||||
VERIFY_IS_TRUE(core->HasSelection());
|
||||
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
||||
{
|
||||
const auto anchor{ core->_terminal->GetSelectionAnchor() };
|
||||
const auto end{ core->_terminal->GetSelectionEnd() };
|
||||
@ -885,7 +875,6 @@ namespace ControlUnitTests
|
||||
cursorPosition1.to_core_point(),
|
||||
true);
|
||||
VERIFY_IS_TRUE(core->HasSelection());
|
||||
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
||||
{
|
||||
const auto anchor{ core->_terminal->GetSelectionAnchor() };
|
||||
const auto end{ core->_terminal->GetSelectionEnd() };
|
||||
|
||||
@ -38,7 +38,6 @@ namespace
|
||||
HRESULT Invalidate(const til::rect* /*psrRegion*/) noexcept { return S_OK; }
|
||||
HRESULT InvalidateCursor(const til::rect* /*psrRegion*/) noexcept { return S_OK; }
|
||||
HRESULT InvalidateSystem(const til::rect* /*prcDirtyClient*/) noexcept { return S_OK; }
|
||||
HRESULT InvalidateSelection(const std::vector<til::rect>& /*rectangles*/) noexcept { return S_OK; }
|
||||
HRESULT InvalidateScroll(const til::point* pcoordDelta) noexcept
|
||||
{
|
||||
_triggerScrollDelta = *pcoordDelta;
|
||||
|
||||
@ -32,16 +32,17 @@ namespace TerminalCoreUnitTests
|
||||
// - expected: the expected value of the selection rect
|
||||
// Return Value:
|
||||
// - N/A
|
||||
void ValidateSingleRowSelection(Terminal& term, const til::inclusive_rect& expected)
|
||||
void ValidateLinearSelection(Terminal& term, const til::point start, const til::point end)
|
||||
{
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
auto selectionSpans = term.GetSelectionSpans();
|
||||
|
||||
// Validate selection area
|
||||
VERIFY_ARE_EQUAL(selectionRects.size(), static_cast<size_t>(1));
|
||||
auto selection = term.GetViewport().ConvertToOrigin(selectionRects[0]).ToInclusive();
|
||||
VERIFY_ARE_EQUAL(selectionSpans.size(), static_cast<size_t>(1));
|
||||
|
||||
VERIFY_ARE_EQUAL(selection, expected);
|
||||
auto& sp{ selectionSpans[0] };
|
||||
VERIFY_ARE_EQUAL(start, sp.start, L"start");
|
||||
VERIFY_ARE_EQUAL(end, sp.end, L"end");
|
||||
}
|
||||
|
||||
TextBuffer& GetTextBuffer(Terminal& term)
|
||||
@ -59,7 +60,7 @@ namespace TerminalCoreUnitTests
|
||||
auto clickPos = til::point{ 5, 10 };
|
||||
term.SetSelectionAnchor(clickPos);
|
||||
|
||||
ValidateSingleRowSelection(term, { 5, 10, 5, 10 });
|
||||
ValidateLinearSelection(term, { 5, 10 }, { 5, 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(SelectArea)
|
||||
@ -79,36 +80,8 @@ namespace TerminalCoreUnitTests
|
||||
// Simulate move to (x,y) = (15,20)
|
||||
term.SetSelectionEnd({ 15, 20 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
|
||||
// Validate selection area
|
||||
VERIFY_ARE_EQUAL(selectionRects.size(), static_cast<size_t>(11));
|
||||
|
||||
auto viewport = term.GetViewport();
|
||||
auto rightBoundary = viewport.RightInclusive();
|
||||
for (auto selectionRect : selectionRects)
|
||||
{
|
||||
auto selection = viewport.ConvertToOrigin(selectionRect).ToInclusive();
|
||||
|
||||
if (rowValue == 10)
|
||||
{
|
||||
// Verify top line
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ 5, 10, rightBoundary, 10 }));
|
||||
}
|
||||
else if (rowValue == 20)
|
||||
{
|
||||
// Verify bottom line
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ 0, 20, 15, 20 }));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Verify other lines (full)
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ 0, rowValue, rightBoundary, rowValue }));
|
||||
}
|
||||
|
||||
rowValue++;
|
||||
}
|
||||
ValidateLinearSelection(term, { 5, rowValue }, { 15, 20 });
|
||||
}
|
||||
|
||||
TEST_METHOD(OverflowTests)
|
||||
@ -117,56 +90,56 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Test SetSelectionAnchor(til::point) and SetSelectionEnd(til::point)
|
||||
// Behavior: clamp coord to viewport.
|
||||
auto ValidateSingleClickSelection = [&](til::CoordType scrollback, const til::inclusive_rect& expected) {
|
||||
auto ValidateSingleClickSelection = [&](til::CoordType scrollback, const til::point start, const til::point end) {
|
||||
Terminal term{ Terminal::TestDummyMarker{} };
|
||||
DummyRenderer renderer{ &term };
|
||||
term.Create({ 10, 10 }, scrollback, renderer);
|
||||
|
||||
// NOTE: SetSelectionEnd(til::point) is called within SetSelectionAnchor(til::point)
|
||||
term.SetSelectionAnchor(maxCoord);
|
||||
ValidateSingleRowSelection(term, expected);
|
||||
ValidateLinearSelection(term, start, end);
|
||||
};
|
||||
|
||||
// Test a Double Click Selection
|
||||
// Behavior: clamp coord to viewport.
|
||||
// Then, do double click selection.
|
||||
auto ValidateDoubleClickSelection = [&](til::CoordType scrollback, const til::inclusive_rect& expected) {
|
||||
auto ValidateDoubleClickSelection = [&](til::CoordType scrollback, const til::point start, const til::point end) {
|
||||
Terminal term{ Terminal::TestDummyMarker{} };
|
||||
DummyRenderer renderer{ &term };
|
||||
term.Create({ 10, 10 }, scrollback, renderer);
|
||||
|
||||
term.MultiClickSelection(maxCoord, Terminal::SelectionExpansion::Word);
|
||||
ValidateSingleRowSelection(term, expected);
|
||||
ValidateLinearSelection(term, start, end);
|
||||
};
|
||||
|
||||
// Test a Triple Click Selection
|
||||
// Behavior: clamp coord to viewport.
|
||||
// Then, do triple click selection.
|
||||
auto ValidateTripleClickSelection = [&](til::CoordType scrollback, const til::inclusive_rect& expected) {
|
||||
auto ValidateTripleClickSelection = [&](til::CoordType scrollback, const til::point start, const til::point end) {
|
||||
Terminal term{ Terminal::TestDummyMarker{} };
|
||||
DummyRenderer renderer{ &term };
|
||||
term.Create({ 10, 10 }, scrollback, renderer);
|
||||
|
||||
term.MultiClickSelection(maxCoord, Terminal::SelectionExpansion::Line);
|
||||
ValidateSingleRowSelection(term, expected);
|
||||
ValidateLinearSelection(term, start, end);
|
||||
};
|
||||
|
||||
// Test with no scrollback
|
||||
Log::Comment(L"Single click selection with NO scrollback value");
|
||||
ValidateSingleClickSelection(0, { 9, 9, 9, 9 });
|
||||
ValidateSingleClickSelection(0, { 9, 9 }, { 9, 9 });
|
||||
Log::Comment(L"Double click selection with NO scrollback value");
|
||||
ValidateDoubleClickSelection(0, { 0, 9, 9, 9 });
|
||||
ValidateDoubleClickSelection(0, { 0, 9 }, { 9, 9 });
|
||||
Log::Comment(L"Triple click selection with NO scrollback value");
|
||||
ValidateTripleClickSelection(0, { 0, 9, 9, 9 });
|
||||
ValidateTripleClickSelection(0, { 0, 9 }, { 9, 9 });
|
||||
|
||||
// Test with max scrollback
|
||||
const til::CoordType expected_row = SHRT_MAX - 1;
|
||||
Log::Comment(L"Single click selection with MAXIMUM scrollback value");
|
||||
ValidateSingleClickSelection(SHRT_MAX, { 9, expected_row, 9, expected_row });
|
||||
ValidateSingleClickSelection(SHRT_MAX, { 9, expected_row }, { 9, expected_row });
|
||||
Log::Comment(L"Double click selection with MAXIMUM scrollback value");
|
||||
ValidateDoubleClickSelection(SHRT_MAX, { 0, expected_row, 9, expected_row });
|
||||
ValidateDoubleClickSelection(SHRT_MAX, { 0, expected_row }, { 9, expected_row });
|
||||
Log::Comment(L"Triple click selection with MAXIMUM scrollback value");
|
||||
ValidateTripleClickSelection(SHRT_MAX, { 0, expected_row, 9, expected_row });
|
||||
ValidateTripleClickSelection(SHRT_MAX, { 0, expected_row }, { 9, expected_row });
|
||||
}
|
||||
|
||||
TEST_METHOD(SelectFromOutofBounds)
|
||||
@ -190,25 +163,25 @@ namespace TerminalCoreUnitTests
|
||||
// should clamp to right boundary
|
||||
term.SetSelectionAnchor({ 20, 5 });
|
||||
Log::Comment(L"Out of bounds: X-value too large");
|
||||
ValidateSingleRowSelection(term, { rightBoundary, 5, rightBoundary, 5 });
|
||||
ValidateLinearSelection(term, { rightBoundary, 5 }, { rightBoundary, 5 });
|
||||
|
||||
// Case 2: Simulate click past left (x,y) = (-20,5)
|
||||
// should clamp to left boundary
|
||||
term.SetSelectionAnchor({ -20, 5 });
|
||||
Log::Comment(L"Out of bounds: X-value too negative");
|
||||
ValidateSingleRowSelection(term, { leftBoundary, 5, leftBoundary, 5 });
|
||||
ValidateLinearSelection(term, { leftBoundary, 5 }, { leftBoundary, 5 });
|
||||
|
||||
// Case 3: Simulate click past top (x,y) = (5,-20)
|
||||
// should clamp to top boundary
|
||||
term.SetSelectionAnchor({ 5, -20 });
|
||||
Log::Comment(L"Out of bounds: Y-value too negative");
|
||||
ValidateSingleRowSelection(term, { 5, topBoundary, 5, topBoundary });
|
||||
ValidateLinearSelection(term, { 5, topBoundary }, { 5, topBoundary });
|
||||
|
||||
// Case 4: Simulate click past bottom (x,y) = (5,20)
|
||||
// should clamp to bottom boundary
|
||||
term.SetSelectionAnchor({ 5, 20 });
|
||||
Log::Comment(L"Out of bounds: Y-value too large");
|
||||
ValidateSingleRowSelection(term, { 5, bottomBoundary, 5, bottomBoundary });
|
||||
ValidateLinearSelection(term, { 5, bottomBoundary }, { 5, bottomBoundary });
|
||||
}
|
||||
|
||||
TEST_METHOD(SelectToOutOfBounds)
|
||||
@ -232,74 +205,22 @@ namespace TerminalCoreUnitTests
|
||||
// Case 1: Move out of right boundary
|
||||
Log::Comment(L"Out of bounds: X-value too large");
|
||||
term.SetSelectionEnd({ 20, 5 });
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 5, 5, rightBoundary, 5 }));
|
||||
ValidateLinearSelection(term, { 5, 5 }, { rightBoundary, 5 });
|
||||
|
||||
// Case 2: Move out of left boundary
|
||||
Log::Comment(L"Out of bounds: X-value negative");
|
||||
term.SetSelectionEnd({ -20, 5 });
|
||||
ValidateSingleRowSelection(term, { leftBoundary, 5, 5, 5 });
|
||||
ValidateLinearSelection(term, { leftBoundary, 5 }, { 5, 5 });
|
||||
|
||||
// Case 3: Move out of top boundary
|
||||
Log::Comment(L"Out of bounds: Y-value negative");
|
||||
term.SetSelectionEnd({ 5, -20 });
|
||||
{
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
|
||||
// Validate selection area
|
||||
VERIFY_ARE_EQUAL(selectionRects.size(), static_cast<size_t>(6));
|
||||
for (auto selectionRect : selectionRects)
|
||||
{
|
||||
auto selection = viewport.ConvertToOrigin(selectionRect).ToInclusive();
|
||||
auto rowValue = selectionRect.BottomInclusive();
|
||||
|
||||
if (rowValue == 0)
|
||||
{
|
||||
// Verify top line
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ 5, rowValue, rightBoundary, rowValue }));
|
||||
}
|
||||
else if (rowValue == 5)
|
||||
{
|
||||
// Verify last line
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ leftBoundary, rowValue, 5, rowValue }));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Verify other lines (full)
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ leftBoundary, rowValue, rightBoundary, rowValue }));
|
||||
}
|
||||
}
|
||||
}
|
||||
ValidateLinearSelection(term, { 5, 0 }, { 5, 5 });
|
||||
|
||||
// Case 4: Move out of bottom boundary
|
||||
Log::Comment(L"Out of bounds: Y-value too large");
|
||||
term.SetSelectionEnd({ 5, 20 });
|
||||
{
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
|
||||
// Validate selection area
|
||||
VERIFY_ARE_EQUAL(selectionRects.size(), static_cast<size_t>(5));
|
||||
for (auto selectionRect : selectionRects)
|
||||
{
|
||||
auto selection = viewport.ConvertToOrigin(selectionRect).ToInclusive();
|
||||
auto rowValue = selectionRect.BottomInclusive();
|
||||
|
||||
if (rowValue == 5)
|
||||
{
|
||||
// Verify top line
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ 5, 5, rightBoundary, 5 }));
|
||||
}
|
||||
else if (rowValue == 9)
|
||||
{
|
||||
// Verify bottom line
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ leftBoundary, rowValue, 5, rowValue }));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Verify other lines (full)
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ leftBoundary, rowValue, rightBoundary, rowValue }));
|
||||
}
|
||||
}
|
||||
}
|
||||
ValidateLinearSelection(term, { 5, 5 }, { 5, 9 });
|
||||
}
|
||||
|
||||
TEST_METHOD(SelectBoxArea)
|
||||
@ -321,19 +242,15 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 15, 20 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
auto selectionSpans = term.GetSelectionSpans();
|
||||
|
||||
// Validate selection area
|
||||
VERIFY_ARE_EQUAL(selectionRects.size(), static_cast<size_t>(11));
|
||||
VERIFY_ARE_EQUAL(selectionSpans.size(), static_cast<size_t>(11));
|
||||
|
||||
auto viewport = term.GetViewport();
|
||||
for (auto selectionRect : selectionRects)
|
||||
for (auto&& sp : selectionSpans)
|
||||
{
|
||||
auto selection = viewport.ConvertToOrigin(selectionRect).ToInclusive();
|
||||
|
||||
// Verify all lines
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ 5, rowValue, 15, rowValue }));
|
||||
|
||||
VERIFY_ARE_EQUAL((til::point{ 5, rowValue }), sp.start);
|
||||
VERIFY_ARE_EQUAL((til::point{ 15, rowValue }), sp.end);
|
||||
rowValue++;
|
||||
}
|
||||
}
|
||||
@ -342,8 +259,12 @@ namespace TerminalCoreUnitTests
|
||||
{
|
||||
Terminal term{ Terminal::TestDummyMarker{} };
|
||||
DummyRenderer renderer{ &term };
|
||||
til::CoordType scrollbackLines = 5;
|
||||
term.Create({ 100, 100 }, scrollbackLines, renderer);
|
||||
til::CoordType scrollbackLines = 100;
|
||||
term.Create({ 120, 30 }, scrollbackLines, renderer);
|
||||
|
||||
const til::CoordType contentScrollLines = 15;
|
||||
// Simulate a content-initiated scroll down by 15 lines
|
||||
term.SetViewportPosition({ 0, contentScrollLines });
|
||||
|
||||
// Used for two things:
|
||||
// - click y-pos
|
||||
@ -356,36 +277,19 @@ namespace TerminalCoreUnitTests
|
||||
// Simulate move to (x,y) = (15,20)
|
||||
term.SetSelectionEnd({ 15, 20 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
ValidateLinearSelection(term, { 5, contentScrollLines + rowValue }, { 15, contentScrollLines + 20 });
|
||||
|
||||
// Validate selection area
|
||||
VERIFY_ARE_EQUAL(selectionRects.size(), static_cast<size_t>(11));
|
||||
const til::CoordType userScrollViewportTop = 10;
|
||||
// Simulate a user-initiated scroll *up* to line 10 (NOTE: Not *up by 10 lines*)
|
||||
term.UserScrollViewport(userScrollViewportTop);
|
||||
|
||||
auto viewport = term.GetViewport();
|
||||
auto rightBoundary = viewport.RightInclusive();
|
||||
for (auto selectionRect : selectionRects)
|
||||
{
|
||||
auto selection = viewport.ConvertToOrigin(selectionRect).ToInclusive();
|
||||
// Simulate click at (x,y) = (5,10)
|
||||
term.SetSelectionAnchor({ 5, rowValue });
|
||||
|
||||
if (rowValue == 10)
|
||||
{
|
||||
// Verify top line
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ 5, 10, rightBoundary, 10 }));
|
||||
}
|
||||
else if (rowValue == 20)
|
||||
{
|
||||
// Verify bottom line
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ 0, 20, 15, 20 }));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Verify other lines (full)
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ 0, rowValue, rightBoundary, rowValue }));
|
||||
}
|
||||
// Simulate move to (x,y) = (15,20)
|
||||
term.SetSelectionEnd({ 15, 20 });
|
||||
|
||||
rowValue++;
|
||||
}
|
||||
ValidateLinearSelection(term, { 5, userScrollViewportTop + rowValue }, { 15, userScrollViewportTop + 20 });
|
||||
}
|
||||
|
||||
TEST_METHOD(SelectWideGlyph_Trailing)
|
||||
@ -408,7 +312,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Validate selection area
|
||||
// Selection should expand one to the left to get the leading half of the wide glyph
|
||||
ValidateSingleRowSelection(term, { 4, 10, 5, 10 });
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 5, 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(SelectWideGlyph_Leading)
|
||||
@ -431,7 +335,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Validate selection area
|
||||
// Selection should expand one to the left to get the leading half of the wide glyph
|
||||
ValidateSingleRowSelection(term, { 4, 10, 5, 10 });
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 5, 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(SelectWideGlyphsInBoxSelection)
|
||||
@ -460,29 +364,30 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 7, 12 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
auto selectionSpans = term.GetSelectionSpans();
|
||||
|
||||
// Validate selection area
|
||||
VERIFY_ARE_EQUAL(selectionRects.size(), static_cast<size_t>(5));
|
||||
VERIFY_ARE_EQUAL(selectionSpans.size(), static_cast<size_t>(5));
|
||||
|
||||
auto viewport = term.GetViewport();
|
||||
til::CoordType rowValue = 8;
|
||||
for (auto selectionRect : selectionRects)
|
||||
for (auto&& sp : selectionSpans)
|
||||
{
|
||||
auto selection = viewport.ConvertToOrigin(selectionRect).ToInclusive();
|
||||
|
||||
if (rowValue == 10)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ 4, rowValue, 7, rowValue }));
|
||||
VERIFY_ARE_EQUAL((til::point{ 4, rowValue }), sp.start);
|
||||
VERIFY_ARE_EQUAL((til::point{ 7, rowValue }), sp.end);
|
||||
}
|
||||
else if (rowValue == 11)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ 5, rowValue, 8, rowValue }));
|
||||
VERIFY_ARE_EQUAL((til::point{ 5, rowValue }), sp.start);
|
||||
VERIFY_ARE_EQUAL((til::point{ 8, rowValue }), sp.end);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Verify all lines
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ 5, rowValue, 7, rowValue }));
|
||||
VERIFY_ARE_EQUAL((til::point{ 5, rowValue }), sp.start);
|
||||
VERIFY_ARE_EQUAL((til::point{ 7, rowValue }), sp.end);
|
||||
}
|
||||
|
||||
rowValue++;
|
||||
@ -509,7 +414,7 @@ namespace TerminalCoreUnitTests
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Word);
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect{ 4, 10, gsl::narrow<til::CoordType>(4 + text.size() - 1), 10 });
|
||||
ValidateLinearSelection(term, { 4, 10 }, { gsl::narrow<til::CoordType>(4 + text.size() - 1), 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(DoubleClick_Delimiter)
|
||||
@ -526,11 +431,8 @@ namespace TerminalCoreUnitTests
|
||||
auto clickPos = til::point{ 5, 10 };
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Word);
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 0, 10, 99, 10 }));
|
||||
ValidateLinearSelection(term, { 0, 10 }, { 99, 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(DoubleClick_DelimiterClass)
|
||||
@ -558,7 +460,7 @@ namespace TerminalCoreUnitTests
|
||||
// ">" is in class 1
|
||||
// the white space to the right of the ">" is in class 0
|
||||
// Double-clicking the ">" should only highlight that cell
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 15, 10, 15, 10 }));
|
||||
ValidateLinearSelection(term, { 15, 10 }, { 15, 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(DoubleClickDrag_Right)
|
||||
@ -587,7 +489,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 21, 10 });
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 4, 10, 32, 10 }));
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 32, 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(DoubleClickDrag_Left)
|
||||
@ -616,7 +518,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 5, 10 });
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 4, 10, 32, 10 }));
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 32, 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(TripleClick_GeneralCase)
|
||||
@ -630,7 +532,7 @@ namespace TerminalCoreUnitTests
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Line);
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 0, 10, 99, 10 }));
|
||||
ValidateLinearSelection(term, { 0, 10 }, { 99, 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(TripleClickDrag_Horizontal)
|
||||
@ -647,7 +549,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 7, 10 });
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 0, 10, 99, 10 }));
|
||||
ValidateLinearSelection(term, { 0, 10 }, { 99, 10 });
|
||||
}
|
||||
|
||||
TEST_METHOD(TripleClickDrag_Vertical)
|
||||
@ -663,19 +565,7 @@ namespace TerminalCoreUnitTests
|
||||
// Simulate move to (x,y) = (5,11)
|
||||
term.SetSelectionEnd({ 5, 11 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
|
||||
// Validate selection area
|
||||
VERIFY_ARE_EQUAL(selectionRects.size(), static_cast<size_t>(2));
|
||||
|
||||
// verify first selection rect
|
||||
auto selection = term.GetViewport().ConvertToOrigin(selectionRects.at(0)).ToInclusive();
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ 0, 10, 99, 10 }));
|
||||
|
||||
// verify second selection rect
|
||||
selection = term.GetViewport().ConvertToOrigin(selectionRects.at(1)).ToInclusive();
|
||||
VERIFY_ARE_EQUAL(selection, til::inclusive_rect({ 0, 11, 99, 11 }));
|
||||
ValidateLinearSelection(term, { 0, 10 }, { 99, 11 });
|
||||
}
|
||||
|
||||
TEST_METHOD(ShiftClick)
|
||||
@ -699,7 +589,7 @@ namespace TerminalCoreUnitTests
|
||||
term.MultiClickSelection({ 5, 10 }, Terminal::SelectionExpansion::Word);
|
||||
|
||||
// Validate selection area: "doubleClickMe" selected
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 4, 10, 16, 10 }));
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 16, 10 });
|
||||
}
|
||||
|
||||
// Step 2: Shift+Click to "dragThroughHere"
|
||||
@ -712,7 +602,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 21, 10 }, Terminal::SelectionExpansion::Char);
|
||||
|
||||
// Validate selection area: "doubleClickMe drag" selected
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 4, 10, 21, 10 }));
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 21, 10 });
|
||||
}
|
||||
|
||||
// Step 3: Shift+Double-Click at "dragThroughHere"
|
||||
@ -725,7 +615,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 21, 10 }, Terminal::SelectionExpansion::Word);
|
||||
|
||||
// Validate selection area: "doubleClickMe dragThroughHere" selected
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 4, 10, 32, 10 }));
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 32, 10 });
|
||||
}
|
||||
|
||||
// Step 4: Shift+Triple-Click at "dragThroughHere"
|
||||
@ -738,7 +628,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 21, 10 }, Terminal::SelectionExpansion::Line);
|
||||
|
||||
// Validate selection area: "doubleClickMe dragThroughHere..." selected
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 4, 10, 99, 10 }));
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 99, 10 });
|
||||
}
|
||||
|
||||
// Step 5: Shift+Double-Click at "dragThroughHere"
|
||||
@ -751,7 +641,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 21, 10 }, Terminal::SelectionExpansion::Word);
|
||||
|
||||
// Validate selection area: "doubleClickMe dragThroughHere" selected
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 4, 10, 32, 10 }));
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 32, 10 });
|
||||
}
|
||||
|
||||
// Step 6: Drag past "dragThroughHere"
|
||||
@ -765,7 +655,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 35, 10 });
|
||||
|
||||
// Validate selection area: "doubleClickMe dragThroughHere..." selected
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 4, 10, 99, 10 }));
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 99, 10 });
|
||||
}
|
||||
|
||||
// Step 6: Drag back to "dragThroughHere"
|
||||
@ -778,7 +668,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 21, 10 });
|
||||
|
||||
// Validate selection area: "doubleClickMe dragThroughHere" selected
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 4, 10, 32, 10 }));
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 32, 10 });
|
||||
}
|
||||
|
||||
// Step 7: Drag within "dragThroughHere"
|
||||
@ -791,7 +681,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionEnd({ 25, 10 });
|
||||
|
||||
// Validate selection area: "doubleClickMe dragThroughHere" still selected
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 4, 10, 32, 10 }));
|
||||
ValidateLinearSelection(term, { 4, 10 }, { 32, 10 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -807,7 +697,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SelectNewRegion({ 10, 10 }, { 20, 10 });
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 10, 10, 20, 10 }));
|
||||
ValidateLinearSelection(term, { 10, 10 }, { 20, 10 });
|
||||
}
|
||||
|
||||
// Step 2: Drag to (5,10)
|
||||
@ -816,7 +706,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Validate selection area
|
||||
// NOTE: Pivot should be (10, 10)
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 5, 10, 10, 10 }));
|
||||
ValidateLinearSelection(term, { 5, 10 }, { 10, 10 });
|
||||
}
|
||||
|
||||
// Step 3: Drag back to (20,10)
|
||||
@ -825,7 +715,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Validate selection area
|
||||
// NOTE: Pivot should still be (10, 10)
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 10, 10, 20, 10 }));
|
||||
ValidateLinearSelection(term, { 10, 10 }, { 20, 10 });
|
||||
}
|
||||
|
||||
// Step 4: Shift+Click at (5,10)
|
||||
@ -834,7 +724,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Validate selection area
|
||||
// NOTE: Pivot should still be (10, 10)
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 5, 10, 10, 10 }));
|
||||
ValidateLinearSelection(term, { 5, 10 }, { 10, 10 });
|
||||
}
|
||||
|
||||
// Step 5: Shift+Click back at (20,10)
|
||||
@ -843,7 +733,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Validate selection area
|
||||
// NOTE: Pivot should still be (10, 10)
|
||||
ValidateSingleRowSelection(term, til::inclusive_rect({ 10, 10, 20, 10 }));
|
||||
ValidateLinearSelection(term, { 10, 10 }, { 20, 10 });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -59,24 +59,11 @@ const FontInfo& RenderData::GetFontInfo() const noexcept
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieves one rectangle per line describing the area of the viewport
|
||||
// - Retrieves one span 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::GetSelectionRects() noexcept
|
||||
std::span<const til::point_span> RenderData::GetSelectionSpans() const noexcept
|
||||
{
|
||||
std::vector<Viewport> result;
|
||||
|
||||
try
|
||||
{
|
||||
for (const auto& select : Selection::Instance().GetSelectionRects())
|
||||
{
|
||||
result.emplace_back(Viewport::FromInclusive(select));
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
return result;
|
||||
return Selection::Instance().GetSelectionSpans();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@ -368,7 +355,7 @@ const til::point RenderData::GetSelectionEnd() const noexcept
|
||||
// - SelectionAnchor: the initial position where the selection was started
|
||||
// - SelectionRect: the rectangular region denoting a portion of the buffer that is selected
|
||||
|
||||
// The following is an excerpt from Selection::s_GetSelectionRects
|
||||
// The following is an excerpt from Selection::GetSelectionSpans
|
||||
// if the anchor (start of select) was in the top right or bottom left of the box,
|
||||
// we need to remove rectangular overlap in the middle.
|
||||
// e.g.
|
||||
|
||||
@ -25,7 +25,7 @@ public:
|
||||
TextBuffer& GetTextBuffer() const noexcept override;
|
||||
const FontInfo& GetFontInfo() const noexcept override;
|
||||
|
||||
std::vector<Microsoft::Console::Types::Viewport> GetSelectionRects() noexcept override;
|
||||
std::span<const til::point_span> GetSelectionSpans() const noexcept override;
|
||||
|
||||
void LockConsole() noexcept override;
|
||||
void UnlockConsole() noexcept override;
|
||||
|
||||
@ -20,14 +20,14 @@ Selection& Selection::Instance()
|
||||
return *_instance;
|
||||
}
|
||||
|
||||
void Selection::_RegenerateSelectionRects() const
|
||||
void Selection::_RegenerateSelectionSpans() const
|
||||
{
|
||||
if (_lastSelectionGeneration == _d.generation())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastSelectionRects.clear();
|
||||
_lastSelectionSpans.clear();
|
||||
|
||||
if (!_d->fSelectionVisible)
|
||||
{
|
||||
@ -44,23 +44,17 @@ void Selection::_RegenerateSelectionRects() const
|
||||
endSelectionAnchor.y = (_d->coordSelectionAnchor.y == _d->srSelectionRect.top) ? _d->srSelectionRect.bottom : _d->srSelectionRect.top;
|
||||
|
||||
const auto blockSelection = !IsLineSelection();
|
||||
auto rects = screenInfo.GetTextBuffer().GetTextRects(_d->coordSelectionAnchor, endSelectionAnchor, blockSelection, false);
|
||||
_lastSelectionRects = std::move(rects);
|
||||
_lastSelectionSpans = screenInfo.GetTextBuffer().GetTextSpans(_d->coordSelectionAnchor,
|
||||
endSelectionAnchor,
|
||||
blockSelection,
|
||||
false);
|
||||
_lastSelectionGeneration = _d.generation();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Determines the line-by-line selection rectangles based on global selection state.
|
||||
// Arguments:
|
||||
// - <none> - Uses internal state to know what area is selected already.
|
||||
// Return Value:
|
||||
// - Returns a vector where each til::inclusive_rect is one Row worth of the area to be selected.
|
||||
// - Returns empty vector if no rows are selected.
|
||||
// - Throws exceptions for out of memory issues
|
||||
std::vector<til::inclusive_rect> Selection::GetSelectionRects() const
|
||||
std::span<const til::point_span> Selection::GetSelectionSpans() const
|
||||
{
|
||||
_RegenerateSelectionRects();
|
||||
return _lastSelectionRects;
|
||||
_RegenerateSelectionSpans();
|
||||
return { _lastSelectionSpans.cbegin(), _lastSelectionSpans.cend() };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
||||
@ -29,7 +29,7 @@ class Selection
|
||||
public:
|
||||
~Selection() = default;
|
||||
|
||||
std::vector<til::inclusive_rect> GetSelectionRects() const;
|
||||
std::span<const til::point_span> GetSelectionSpans() const;
|
||||
|
||||
void ShowSelection();
|
||||
void HideSelection();
|
||||
@ -182,11 +182,11 @@ private:
|
||||
};
|
||||
til::generational<SelectionData> _d{};
|
||||
|
||||
mutable std::vector<til::inclusive_rect> _lastSelectionRects;
|
||||
mutable std::vector<til::point_span> _lastSelectionSpans;
|
||||
mutable til::generation_t _lastSelectionGeneration;
|
||||
|
||||
void _ExtendSelection(SelectionData* d, _In_ til::point coordBufferPos);
|
||||
void _RegenerateSelectionRects() const;
|
||||
void _RegenerateSelectionSpans() const;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class SelectionTests;
|
||||
|
||||
@ -671,51 +671,35 @@ bool Selection::_HandleColorSelection(const INPUT_KEY_INFO* const pInputKeyInfo)
|
||||
selectionAttr.SetIndexedForeground256(colorIndex);
|
||||
}
|
||||
|
||||
// If shift was pressed as well, then this is actually a
|
||||
// find-and-color request. Otherwise just color the selection.
|
||||
const auto& textBuffer = gci.renderData.GetTextBuffer();
|
||||
if (fShiftPressed)
|
||||
{
|
||||
try
|
||||
// Search the selection and color *that*
|
||||
const auto req = TextBuffer::CopyRequest::FromConfig(textBuffer,
|
||||
til::point{ _d->srSelectionRect.left, _d->srSelectionRect.top },
|
||||
til::point{ _d->srSelectionRect.right, _d->srSelectionRect.bottom },
|
||||
true /* multi-line search doesn't make sense; concatenate all lines */,
|
||||
false /* we filtered out block search above */,
|
||||
true /* trim block selection */,
|
||||
true);
|
||||
const auto str = textBuffer.GetPlainText(req);
|
||||
// Clear the selection and call the search / mark function.
|
||||
ClearSelection();
|
||||
|
||||
const auto hits = textBuffer.SearchText(str, SearchFlag::CaseInsensitive).value_or(std::vector<til::point_span>{});
|
||||
for (const auto& s : hits)
|
||||
{
|
||||
const auto selectionRects = GetSelectionRects();
|
||||
if (selectionRects.size() > 0)
|
||||
{
|
||||
// Pull the selection out of the buffer to pass to the
|
||||
// search function. Clamp to max search string length.
|
||||
// We just copy the bytes out of the row buffer.
|
||||
|
||||
std::wstring str;
|
||||
for (const auto& selectRect : selectionRects)
|
||||
{
|
||||
auto it = screenInfo.GetCellDataAt({ selectRect.left, selectRect.top });
|
||||
|
||||
for (til::CoordType i = 0; i < (selectRect.right - selectRect.left + 1);)
|
||||
{
|
||||
str.append(it->Chars());
|
||||
i += it->Columns();
|
||||
it += it->Columns();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the selection and call the search / mark function.
|
||||
ClearSelection();
|
||||
|
||||
const auto& textBuffer = gci.renderData.GetTextBuffer();
|
||||
const auto hits = textBuffer.SearchText(str, SearchFlag::CaseInsensitive).value_or(std::vector<til::point_span>{});
|
||||
for (const auto& s : hits)
|
||||
{
|
||||
ColorSelection(s.start, s.end, selectionAttr);
|
||||
}
|
||||
}
|
||||
ColorSelection(s.start, s.end, selectionAttr);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto selectionRects = GetSelectionRects();
|
||||
for (const auto& selectionRect : selectionRects)
|
||||
const auto selection = GetSelectionSpans();
|
||||
for (auto&& sp : selection)
|
||||
{
|
||||
ColorSelection(selectionRect, selectionAttr);
|
||||
sp.iterate_rows(textBuffer.GetSize().Width(), [&](til::CoordType row, til::CoordType beg, til::CoordType end) {
|
||||
ColorSelection({ beg, row, end, row }, selectionAttr);
|
||||
});
|
||||
}
|
||||
ClearSelection();
|
||||
}
|
||||
|
||||
@ -56,30 +56,30 @@ class SelectionTests
|
||||
return true;
|
||||
}
|
||||
|
||||
void VerifyGetSelectionRects_BoxMode()
|
||||
void VerifyGetSelectionSpans_BoxMode()
|
||||
{
|
||||
const auto selectionRects = m_pSelection->GetSelectionRects();
|
||||
const auto selectionSpans = m_pSelection->GetSelectionSpans();
|
||||
const UINT cRectanglesExpected = m_pSelection->_d->srSelectionRect.bottom - m_pSelection->_d->srSelectionRect.top + 1;
|
||||
|
||||
if (VERIFY_ARE_EQUAL(cRectanglesExpected, selectionRects.size()))
|
||||
if (VERIFY_ARE_EQUAL(cRectanglesExpected, selectionSpans.size()))
|
||||
{
|
||||
for (auto iRect = 0; iRect < gsl::narrow<int>(selectionRects.size()); iRect++)
|
||||
for (auto iRect = 0; iRect < gsl::narrow<int>(selectionSpans.size()); iRect++)
|
||||
{
|
||||
// ensure each rectangle is exactly the width requested (block selection)
|
||||
const auto psrRect = &selectionRects[iRect];
|
||||
const auto& span = selectionSpans[iRect];
|
||||
|
||||
const auto sRectangleLineNumber = (til::CoordType)iRect + m_pSelection->_d->srSelectionRect.top;
|
||||
|
||||
VERIFY_ARE_EQUAL(psrRect->top, sRectangleLineNumber);
|
||||
VERIFY_ARE_EQUAL(psrRect->bottom, sRectangleLineNumber);
|
||||
VERIFY_ARE_EQUAL(span.start.y, sRectangleLineNumber);
|
||||
VERIFY_ARE_EQUAL(span.end.y, sRectangleLineNumber);
|
||||
|
||||
VERIFY_ARE_EQUAL(psrRect->left, m_pSelection->_d->srSelectionRect.left);
|
||||
VERIFY_ARE_EQUAL(psrRect->right, m_pSelection->_d->srSelectionRect.right);
|
||||
VERIFY_ARE_EQUAL(span.start.x, m_pSelection->_d->srSelectionRect.left);
|
||||
VERIFY_ARE_EQUAL(span.end.x, m_pSelection->_d->srSelectionRect.right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestGetSelectionRects_BoxMode)
|
||||
TEST_METHOD(TestGetSelectionSpans_BoxMode)
|
||||
{
|
||||
{
|
||||
auto selection{ m_pSelection->_d.write() };
|
||||
@ -99,7 +99,7 @@ class SelectionTests
|
||||
selection->fLineSelection = false;
|
||||
selection->fUseAlternateSelection = false;
|
||||
|
||||
VerifyGetSelectionRects_BoxMode();
|
||||
VerifyGetSelectionSpans_BoxMode();
|
||||
}
|
||||
|
||||
{
|
||||
@ -108,7 +108,7 @@ class SelectionTests
|
||||
selection->fLineSelection = true;
|
||||
selection->fUseAlternateSelection = true;
|
||||
|
||||
VerifyGetSelectionRects_BoxMode();
|
||||
VerifyGetSelectionSpans_BoxMode();
|
||||
}
|
||||
|
||||
{
|
||||
@ -118,7 +118,7 @@ class SelectionTests
|
||||
selection->coordSelectionAnchor.x = selection->srSelectionRect.right;
|
||||
selection->coordSelectionAnchor.y = selection->srSelectionRect.top;
|
||||
|
||||
VerifyGetSelectionRects_BoxMode();
|
||||
VerifyGetSelectionSpans_BoxMode();
|
||||
}
|
||||
|
||||
{
|
||||
@ -127,129 +127,35 @@ class SelectionTests
|
||||
selection->coordSelectionAnchor.x = selection->srSelectionRect.left;
|
||||
selection->coordSelectionAnchor.y = selection->srSelectionRect.bottom;
|
||||
|
||||
VerifyGetSelectionRects_BoxMode();
|
||||
VerifyGetSelectionSpans_BoxMode();
|
||||
}
|
||||
|
||||
{
|
||||
auto selection{ m_pSelection->_d.write() };
|
||||
|
||||
// #4 bottom-right to top-left selection
|
||||
selection->coordSelectionAnchor.x = selection->srSelectionRect.right;
|
||||
selection->coordSelectionAnchor.y = selection->srSelectionRect.bottom;
|
||||
|
||||
VerifyGetSelectionRects_BoxMode();
|
||||
VerifyGetSelectionSpans_BoxMode();
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyGetSelectionRects_LineMode()
|
||||
void VerifyGetSelectionSpans_LineMode(const til::point start, const til::point end)
|
||||
{
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
const auto selectionSpans = m_pSelection->GetSelectionSpans();
|
||||
|
||||
const auto selectionRects = m_pSelection->GetSelectionRects();
|
||||
const UINT cRectanglesExpected = m_pSelection->_d->srSelectionRect.bottom - m_pSelection->_d->srSelectionRect.top + 1;
|
||||
|
||||
if (VERIFY_ARE_EQUAL(cRectanglesExpected, selectionRects.size()))
|
||||
if (VERIFY_ARE_EQUAL(1u, selectionSpans.size()))
|
||||
{
|
||||
// RULES:
|
||||
// 1. If we're only selection one line, select the entire region between the two rectangles.
|
||||
// Else if we're selecting multiple lines...
|
||||
// 2. Extend all lines except the last line to the right edge of the screen
|
||||
// Extend all lines except the first line to the left edge of the screen
|
||||
// 3. If our anchor is in the top-right or bottom-left corner of the rectangle...
|
||||
// The inside portion of our rectangle on the first and last lines is invalid.
|
||||
// Remove from selection (but preserve the anchors themselves).
|
||||
|
||||
// RULE #1: If 1 line, entire region selected.
|
||||
auto fHaveOneLine = selectionRects.size() == 1;
|
||||
|
||||
if (fHaveOneLine)
|
||||
{
|
||||
auto srSelectionRect = m_pSelection->_d->srSelectionRect;
|
||||
VERIFY_ARE_EQUAL(srSelectionRect.top, srSelectionRect.bottom);
|
||||
|
||||
const auto psrRect = &selectionRects[0];
|
||||
|
||||
VERIFY_ARE_EQUAL(psrRect->top, srSelectionRect.top);
|
||||
VERIFY_ARE_EQUAL(psrRect->bottom, srSelectionRect.bottom);
|
||||
|
||||
VERIFY_ARE_EQUAL(psrRect->left, srSelectionRect.left);
|
||||
VERIFY_ARE_EQUAL(psrRect->right, srSelectionRect.right);
|
||||
}
|
||||
else
|
||||
{
|
||||
// RULE #2 : Check extension to edges
|
||||
for (UINT iRect = 0; iRect < selectionRects.size(); iRect++)
|
||||
{
|
||||
// ensure each rectangle is exactly the width requested (block selection)
|
||||
const auto psrRect = &selectionRects[iRect];
|
||||
|
||||
const auto sRectangleLineNumber = (til::CoordType)iRect + m_pSelection->_d->srSelectionRect.top;
|
||||
|
||||
VERIFY_ARE_EQUAL(psrRect->top, sRectangleLineNumber);
|
||||
VERIFY_ARE_EQUAL(psrRect->bottom, sRectangleLineNumber);
|
||||
|
||||
auto fIsFirstLine = iRect == 0;
|
||||
auto fIsLastLine = iRect == selectionRects.size() - 1;
|
||||
|
||||
// for all lines except the last, the line should reach the right edge of the buffer
|
||||
if (!fIsLastLine)
|
||||
{
|
||||
// buffer size = 80, then selection goes 0 to 79. Thus X - 1.
|
||||
VERIFY_ARE_EQUAL(psrRect->right, gci.GetActiveOutputBuffer().GetTextBuffer().GetSize().RightInclusive());
|
||||
}
|
||||
|
||||
// for all lines except the first, the line should reach the left edge of the buffer
|
||||
if (!fIsFirstLine)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(psrRect->left, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// RULE #3: Check first and last line have invalid regions removed, if applicable
|
||||
UINT iFirst = 0;
|
||||
auto iLast = gsl::narrow<UINT>(selectionRects.size() - 1u);
|
||||
|
||||
const auto psrFirst = &selectionRects[iFirst];
|
||||
const auto psrLast = &selectionRects[iLast];
|
||||
|
||||
auto fRemoveRegion = false;
|
||||
|
||||
auto srSelectionRect = m_pSelection->_d->srSelectionRect;
|
||||
auto coordAnchor = m_pSelection->_d->coordSelectionAnchor;
|
||||
|
||||
// if the anchor is in the top right or bottom left corner, we must have removed a region. otherwise, it stays as is.
|
||||
if (coordAnchor.y == srSelectionRect.top && coordAnchor.x == srSelectionRect.right)
|
||||
{
|
||||
fRemoveRegion = true;
|
||||
}
|
||||
else if (coordAnchor.y == srSelectionRect.bottom && coordAnchor.x == srSelectionRect.left)
|
||||
{
|
||||
fRemoveRegion = true;
|
||||
}
|
||||
|
||||
// now check the first row's left based on removal
|
||||
if (!fRemoveRegion)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(psrFirst->left, srSelectionRect.left);
|
||||
}
|
||||
else
|
||||
{
|
||||
VERIFY_ARE_EQUAL(psrFirst->left, srSelectionRect.right);
|
||||
}
|
||||
|
||||
// and the last row's right based on removal
|
||||
if (!fRemoveRegion)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(psrLast->right, srSelectionRect.right);
|
||||
}
|
||||
else
|
||||
{
|
||||
VERIFY_ARE_EQUAL(psrLast->right, srSelectionRect.left);
|
||||
}
|
||||
}
|
||||
auto& span{ selectionSpans[0] };
|
||||
VERIFY_ARE_EQUAL(start, span.start, L"start");
|
||||
VERIFY_ARE_EQUAL(end, span.end, L"end");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestGetSelectionRects_LineMode)
|
||||
// All of the logic tested herein is trying to determine where the selection
|
||||
// must have started, given a rectangle and the point where the mouse was last seen.
|
||||
TEST_METHOD(TestGetSelectionSpans_LineMode)
|
||||
{
|
||||
{
|
||||
auto selection{ m_pSelection->_d.write() };
|
||||
@ -262,6 +168,16 @@ class SelectionTests
|
||||
selection->srSelectionRect.left = 1;
|
||||
selection->srSelectionRect.right = 10;
|
||||
|
||||
/*
|
||||
| RECT |
|
||||
0123456789ABCDEF
|
||||
--0+---------+
|
||||
1| |
|
||||
2| |
|
||||
--3+---------+
|
||||
4
|
||||
*/
|
||||
|
||||
// #1 top-left to bottom right selection first
|
||||
selection->coordSelectionAnchor.x = selection->srSelectionRect.left;
|
||||
selection->coordSelectionAnchor.y = selection->srSelectionRect.top;
|
||||
@ -270,7 +186,19 @@ class SelectionTests
|
||||
selection->fLineSelection = true;
|
||||
selection->fUseAlternateSelection = false;
|
||||
|
||||
VerifyGetSelectionRects_LineMode();
|
||||
/*
|
||||
Mouse at 0,0; therefore, the selection "begins" at 3,10
|
||||
Selection extends to bottom right corner of rectangle
|
||||
|
||||
| RECT |
|
||||
0123456789ABCDEF
|
||||
--0*#########*#####
|
||||
1################
|
||||
2################
|
||||
--3*#########*
|
||||
4
|
||||
*/
|
||||
VerifyGetSelectionSpans_LineMode({ 1, 0 }, { 10, 3 });
|
||||
}
|
||||
|
||||
{
|
||||
@ -279,7 +207,8 @@ class SelectionTests
|
||||
selection->fLineSelection = false;
|
||||
selection->fUseAlternateSelection = true;
|
||||
|
||||
VerifyGetSelectionRects_LineMode();
|
||||
// Same as above.
|
||||
VerifyGetSelectionSpans_LineMode({ 1, 0 }, { 10, 3 });
|
||||
}
|
||||
|
||||
{
|
||||
@ -289,29 +218,69 @@ class SelectionTests
|
||||
selection->coordSelectionAnchor.x = selection->srSelectionRect.right;
|
||||
selection->coordSelectionAnchor.y = selection->srSelectionRect.top;
|
||||
|
||||
VerifyGetSelectionRects_LineMode();
|
||||
/*
|
||||
Mouse at 0,10; therefore, the selection must have started at 3,0
|
||||
Selection does not include bottom-most line
|
||||
|
||||
| RECT |
|
||||
0123456789ABCDEF
|
||||
--0+ *#####
|
||||
1################
|
||||
2################
|
||||
--3* +
|
||||
4
|
||||
*/
|
||||
|
||||
VerifyGetSelectionSpans_LineMode({ 10, 0 }, { 1, 3 });
|
||||
}
|
||||
|
||||
{
|
||||
auto selection{ m_pSelection->_d.write() };
|
||||
|
||||
// #3 bottom-left to top-right selection
|
||||
selection->coordSelectionAnchor.x = selection->srSelectionRect.left;
|
||||
selection->coordSelectionAnchor.y = selection->srSelectionRect.bottom;
|
||||
|
||||
VerifyGetSelectionRects_LineMode();
|
||||
/*
|
||||
Mouse at 3,1; therefore, the selection must have started at 0,10
|
||||
Selection extends from top right to bottom left
|
||||
|
||||
| RECT |
|
||||
0123456789ABCDEF
|
||||
--0+ *#####
|
||||
1################
|
||||
2################
|
||||
--3* +
|
||||
4
|
||||
*/
|
||||
VerifyGetSelectionSpans_LineMode({ 10, 0 }, { 1, 3 });
|
||||
}
|
||||
|
||||
{
|
||||
auto selection{ m_pSelection->_d.write() };
|
||||
|
||||
// #4 bottom-right to top-left selection
|
||||
selection->coordSelectionAnchor.x = selection->srSelectionRect.right;
|
||||
selection->coordSelectionAnchor.y = selection->srSelectionRect.bottom;
|
||||
|
||||
VerifyGetSelectionRects_LineMode();
|
||||
/*
|
||||
Mouse at 3,10; therefore, the selection must have started at 0,0
|
||||
Just like case #1, selection covers all lines and top left/bottom right of rect.
|
||||
|
||||
| RECT |
|
||||
0123456789ABCDEF
|
||||
--0*#########*#####
|
||||
1################
|
||||
2################
|
||||
--3*#########*
|
||||
4
|
||||
*/
|
||||
VerifyGetSelectionSpans_LineMode({ 1, 0 }, { 10, 3 });
|
||||
}
|
||||
|
||||
{
|
||||
auto selection{ m_pSelection->_d.write() };
|
||||
|
||||
// Part II: Single line selection
|
||||
selection->srSelectionRect.top = 2;
|
||||
selection->srSelectionRect.bottom = 2;
|
||||
@ -323,17 +292,18 @@ class SelectionTests
|
||||
VERIFY_IS_TRUE(selection->srSelectionRect.bottom == selection->srSelectionRect.top);
|
||||
selection->coordSelectionAnchor.y = selection->srSelectionRect.bottom;
|
||||
|
||||
VerifyGetSelectionRects_LineMode();
|
||||
VerifyGetSelectionSpans_LineMode({ 1, 2 }, { 10, 2 });
|
||||
}
|
||||
|
||||
{
|
||||
auto selection{ m_pSelection->_d.write() };
|
||||
|
||||
// #2: right to left selection
|
||||
selection->coordSelectionAnchor.x = selection->srSelectionRect.right;
|
||||
VERIFY_IS_TRUE(selection->srSelectionRect.bottom == selection->srSelectionRect.top);
|
||||
selection->coordSelectionAnchor.y = selection->srSelectionRect.top;
|
||||
|
||||
VerifyGetSelectionRects_LineMode();
|
||||
VerifyGetSelectionSpans_LineMode({ 1, 2 }, { 10, 2 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -48,11 +48,6 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT BgfxEngine::InvalidateSelection(const std::vector<til::rect>& /*rectangles*/) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT BgfxEngine::InvalidateScroll(const til::point* /*pcoordDelta*/) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
|
||||
@ -35,7 +35,6 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT Invalidate(const til::rect* psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const til::rect* psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const til::rect* prcDirtyClient) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<til::rect>& rectangles) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateScroll(const til::point* pcoordDelta) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
|
||||
|
||||
|
||||
@ -321,9 +321,6 @@ void Clipboard::StoreSelectionToClipboard(const bool copyFormatting)
|
||||
return;
|
||||
}
|
||||
|
||||
// read selection area.
|
||||
const auto selectionRects = selection.GetSelectionRects();
|
||||
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
const auto& buffer = gci.GetActiveOutputBuffer().GetTextBuffer();
|
||||
const auto& renderSettings = gci.GetRenderSettings();
|
||||
|
||||
@ -83,27 +83,13 @@ constexpr HRESULT vec2_narrow(U x, U y, vec2<T>& out) noexcept
|
||||
return Invalidate(&rect);
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::InvalidateSelection(const std::vector<til::rect>& rectangles) noexcept
|
||||
{
|
||||
for (const auto& rect : rectangles)
|
||||
{
|
||||
// BeginPaint() protects against invalid out of bounds numbers.
|
||||
// TODO: rect can contain invalid out of bounds coordinates when the selection is being
|
||||
// dragged outside of the viewport (and the window begins scrolling automatically).
|
||||
_api.invalidatedRows.start = gsl::narrow_cast<u16>(std::min<int>(_api.invalidatedRows.start, std::max<int>(0, rect.top)));
|
||||
_api.invalidatedRows.end = gsl::narrow_cast<u16>(std::max<int>(_api.invalidatedRows.end, std::max<int>(0, rect.bottom)));
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::InvalidateHighlight(std::span<const til::point_span> highlights, const TextBuffer& buffer) noexcept
|
||||
void AtlasEngine::_invalidateSpans(std::span<const til::point_span> spans, const TextBuffer& buffer) noexcept
|
||||
{
|
||||
const auto viewportOrigin = til::point{ _api.viewportOffset.x, _api.viewportOffset.y };
|
||||
const auto viewport = til::rect{ 0, 0, _api.s->viewportCellCount.x, _api.s->viewportCellCount.y };
|
||||
const auto cellCountX = static_cast<til::CoordType>(_api.s->viewportCellCount.x);
|
||||
for (const auto& hi : highlights)
|
||||
for (auto&& sp : spans)
|
||||
{
|
||||
hi.iterate_rows(cellCountX, [&](til::CoordType row, til::CoordType beg, til::CoordType end) {
|
||||
sp.iterate_rows(til::CoordTypeMax, [&](til::CoordType row, til::CoordType beg, til::CoordType end) {
|
||||
const auto shift = buffer.GetLineRendition(row) != LineRendition::SingleWidth ? 1 : 0;
|
||||
beg <<= shift;
|
||||
end <<= shift;
|
||||
@ -114,7 +100,22 @@ constexpr HRESULT vec2_narrow(U x, U y, vec2<T>& out) noexcept
|
||||
_api.invalidatedRows.end = gsl::narrow_cast<u16>(std::max<int>(_api.invalidatedRows.end, std::max<int>(0, rect.bottom)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::InvalidateSelection(std::span<const til::rect> selections) noexcept
|
||||
{
|
||||
if (!selections.empty())
|
||||
{
|
||||
// INVARIANT: This assumes that `selections` is sorted by increasing Y
|
||||
_api.invalidatedRows.start = gsl::narrow_cast<u16>(std::min<int>(_api.invalidatedRows.start, std::max<int>(0, selections.front().top)));
|
||||
_api.invalidatedRows.end = gsl::narrow_cast<u16>(std::max<int>(_api.invalidatedRows.end, std::max<int>(0, selections.back().bottom)));
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT AtlasEngine::InvalidateHighlight(std::span<const til::point_span> highlights, const TextBuffer& buffer) noexcept
|
||||
{
|
||||
_invalidateSpans(highlights, buffer);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -31,7 +31,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
[[nodiscard]] HRESULT Invalidate(const til::rect* psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const til::rect* psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const til::rect* prcDirtyClient) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<til::rect>& rectangles) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSelection(std::span<const til::rect> selections) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateHighlight(std::span<const til::point_span> highlights, const TextBuffer& buffer) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateScroll(const til::point* pcoordDelta) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
|
||||
@ -98,6 +98,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
[[nodiscard]] HRESULT _updateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, float>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept;
|
||||
void _resolveFontMetrics(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontSettings* fontMetrics = nullptr);
|
||||
[[nodiscard]] bool _updateWithNearbyFontCollection() noexcept;
|
||||
void _invalidateSpans(std::span<const til::point_span> spans, const TextBuffer& buffer) noexcept;
|
||||
|
||||
// AtlasEngine.r.cpp
|
||||
ATLAS_ATTR_COLD void _recreateAdapter();
|
||||
|
||||
@ -7,6 +7,11 @@
|
||||
using namespace Microsoft::Console;
|
||||
using namespace Microsoft::Console::Render;
|
||||
|
||||
[[nodiscard]] HRESULT RenderEngineBase::InvalidateSelection(std::span<const til::rect> /*selections*/) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT RenderEngineBase::InvalidateHighlight(std::span<const til::point_span> /*highlights*/, const TextBuffer& /*renditions*/) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
|
||||
@ -325,32 +325,46 @@ void Renderer::TriggerTeardown() noexcept
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Renderer::TriggerSelection()
|
||||
try
|
||||
{
|
||||
try
|
||||
const auto spans = _pData->GetSelectionSpans();
|
||||
if (spans.size() != _lastSelectionPaintSize || (!spans.empty() && _lastSelectionPaintSpan != til::point_span{ spans.front().start, spans.back().end }))
|
||||
{
|
||||
// Get selection rectangles
|
||||
auto rects = _GetSelectionRects();
|
||||
std::vector<til::rect> newSelectionViewportRects;
|
||||
|
||||
// Make a viewport representing the coordinates that are currently presentable.
|
||||
const til::rect viewport{ _pData->GetViewport().Dimensions() };
|
||||
|
||||
// Restrict all previous selection rectangles to inside the current viewport bounds
|
||||
for (auto& sr : _previousSelection)
|
||||
_lastSelectionPaintSize = spans.size();
|
||||
if (_lastSelectionPaintSize)
|
||||
{
|
||||
sr &= viewport;
|
||||
_lastSelectionPaintSpan = til::point_span{ spans.front().start, spans.back().end };
|
||||
|
||||
const auto& buffer = _pData->GetTextBuffer();
|
||||
const auto bufferWidth = buffer.GetSize().Width();
|
||||
const til::rect vp{ _viewport.ToExclusive() };
|
||||
for (auto&& sp : spans)
|
||||
{
|
||||
sp.iterate_rows(bufferWidth, [&](til::CoordType row, til::CoordType min, til::CoordType max) {
|
||||
const auto shift = buffer.GetLineRendition(row) != LineRendition::SingleWidth ? 1 : 0;
|
||||
max += 1; // Selection spans are inclusive (still)
|
||||
min <<= shift;
|
||||
max <<= shift;
|
||||
til::rect r{ min, row, max, row + 1 };
|
||||
newSelectionViewportRects.emplace_back(r.to_origin(vp));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
FOREACH_ENGINE(pEngine)
|
||||
{
|
||||
LOG_IF_FAILED(pEngine->InvalidateSelection(_previousSelection));
|
||||
LOG_IF_FAILED(pEngine->InvalidateSelection(rects));
|
||||
LOG_IF_FAILED(pEngine->InvalidateSelection(_lastSelectionRectsByViewport));
|
||||
LOG_IF_FAILED(pEngine->InvalidateSelection(newSelectionViewportRects));
|
||||
}
|
||||
|
||||
_previousSelection = std::move(rects);
|
||||
std::exchange(_lastSelectionRectsByViewport, newSelectionViewportRects);
|
||||
|
||||
NotifyPaintFrame();
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
// Routine Description:
|
||||
// - Called when the search highlight areas in the console have changed.
|
||||
@ -1261,6 +1275,7 @@ void Renderer::_PaintCursor(_In_ IRenderEngine* const pEngine)
|
||||
RenderFrameInfo info;
|
||||
info.searchHighlights = _pData->GetSearchHighlights();
|
||||
info.searchHighlightFocused = _pData->GetSearchHighlightFocused();
|
||||
info.selectionSpans = _pData->GetSelectionSpans();
|
||||
return pEngine->PrepareRenderInfo(std::move(info));
|
||||
}
|
||||
|
||||
@ -1277,15 +1292,11 @@ void Renderer::_PaintSelection(_In_ IRenderEngine* const pEngine)
|
||||
std::span<const til::rect> dirtyAreas;
|
||||
LOG_IF_FAILED(pEngine->GetDirtyArea(dirtyAreas));
|
||||
|
||||
// Get selection rectangles
|
||||
const auto rectangles = _GetSelectionRects();
|
||||
|
||||
std::vector<til::rect> dirtySearchRectangles;
|
||||
for (auto& dirtyRect : dirtyAreas)
|
||||
for (auto&& dirtyRect : dirtyAreas)
|
||||
{
|
||||
for (const auto& rect : rectangles)
|
||||
for (const auto& rect : _lastSelectionRectsByViewport)
|
||||
{
|
||||
if (const auto rectCopy = rect & dirtyRect)
|
||||
if (const auto rectCopy{ rect & dirtyRect })
|
||||
{
|
||||
LOG_IF_FAILED(pEngine->PaintSelection(rectCopy));
|
||||
}
|
||||
@ -1330,32 +1341,6 @@ void Renderer::_PaintSelection(_In_ IRenderEngine* const pEngine)
|
||||
return pEngine->ScrollFrame();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Helper to determine the selected region of the buffer.
|
||||
// Return Value:
|
||||
// - A vector of rectangles representing the regions to select, line by line.
|
||||
std::vector<til::rect> Renderer::_GetSelectionRects() const
|
||||
{
|
||||
const auto& buffer = _pData->GetTextBuffer();
|
||||
auto rects = _pData->GetSelectionRects();
|
||||
// Adjust rectangles to viewport
|
||||
auto view = _pData->GetViewport();
|
||||
|
||||
std::vector<til::rect> result;
|
||||
result.reserve(rects.size());
|
||||
|
||||
for (auto rect : rects)
|
||||
{
|
||||
// Convert buffer offsets to the equivalent range of screen cells
|
||||
// expected by callers, taking line rendition into account.
|
||||
const auto lineRendition = buffer.GetLineRendition(rect.Top());
|
||||
rect = Viewport::FromInclusive(BufferToScreenLine(rect.ToInclusive(), lineRendition));
|
||||
result.emplace_back(view.ConvertToOrigin(rect).ToExclusive());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Offsets all of the selection rectangles we might be holding onto
|
||||
// as the previously selected area. If the whole viewport scrolls,
|
||||
@ -1369,7 +1354,7 @@ void Renderer::_ScrollPreviousSelection(const til::point delta)
|
||||
{
|
||||
if (delta != til::point{ 0, 0 })
|
||||
{
|
||||
for (auto& rc : _previousSelection)
|
||||
for (auto& rc : _lastSelectionRectsByViewport)
|
||||
{
|
||||
rc += delta;
|
||||
}
|
||||
|
||||
@ -109,7 +109,6 @@ namespace Microsoft::Console::Render
|
||||
void _PaintCursor(_In_ IRenderEngine* const pEngine);
|
||||
[[nodiscard]] HRESULT _UpdateDrawingBrushes(_In_ IRenderEngine* const pEngine, const TextAttribute attr, const bool usingSoftFont, const bool isSettingDefaultBrushes);
|
||||
[[nodiscard]] HRESULT _PerformScrolling(_In_ IRenderEngine* const pEngine);
|
||||
std::vector<til::rect> _GetSelectionRects() const;
|
||||
void _ScrollPreviousSelection(const til::point delta);
|
||||
[[nodiscard]] HRESULT _PaintTitle(IRenderEngine* const pEngine);
|
||||
bool _isInHoveredInterval(til::point coordTarget) const noexcept;
|
||||
@ -131,11 +130,14 @@ namespace Microsoft::Console::Render
|
||||
CursorOptions _currentCursorOptions;
|
||||
std::optional<CompositionCache> _compositionCache;
|
||||
std::vector<Cluster> _clusterBuffer;
|
||||
std::vector<til::rect> _previousSelection;
|
||||
std::function<void()> _pfnBackgroundColorChanged;
|
||||
std::function<void()> _pfnFrameColorChanged;
|
||||
std::function<void()> _pfnRendererEnteredErrorState;
|
||||
bool _destructing = false;
|
||||
bool _forceUpdateViewport = false;
|
||||
|
||||
til::point_span _lastSelectionPaintSpan{};
|
||||
size_t _lastSelectionPaintSize{};
|
||||
std::vector<til::rect> _lastSelectionRectsByViewport{};
|
||||
};
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ namespace Microsoft::Console::Render
|
||||
|
||||
[[nodiscard]] HRESULT SetHwnd(const HWND hwnd) noexcept;
|
||||
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<til::rect>& rectangles) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSelection(std::span<const til::rect> selections) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateScroll(const til::point* const pcoordDelta) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const til::rect* const prcDirtyClient) noexcept override;
|
||||
[[nodiscard]] HRESULT Invalidate(const til::rect* const psrRegion) noexcept override;
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include "gdirenderer.hpp"
|
||||
#include "../../types/inc/Viewport.hpp"
|
||||
#include "../buffer/out/textBuffer.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
@ -46,13 +47,12 @@ HRESULT GdiEngine::InvalidateScroll(const til::point* const pcoordDelta) noexcep
|
||||
// - rectangles - Vector of rectangles to draw, line by line
|
||||
// Return Value:
|
||||
// - HRESULT S_OK or GDI-based error code
|
||||
HRESULT GdiEngine::InvalidateSelection(const std::vector<til::rect>& rectangles) noexcept
|
||||
HRESULT GdiEngine::InvalidateSelection(std::span<const til::rect> selections) noexcept
|
||||
{
|
||||
for (const auto& rect : rectangles)
|
||||
for (auto&& rect : selections)
|
||||
{
|
||||
RETURN_IF_FAILED(Invalidate(&rect));
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -46,9 +46,9 @@ namespace Microsoft::Console::Render
|
||||
virtual til::point GetTextBufferEndPosition() const noexcept = 0;
|
||||
virtual TextBuffer& GetTextBuffer() const noexcept = 0;
|
||||
virtual const FontInfo& GetFontInfo() const noexcept = 0;
|
||||
virtual std::vector<Microsoft::Console::Types::Viewport> GetSelectionRects() noexcept = 0;
|
||||
virtual std::span<const til::point_span> GetSearchHighlights() const noexcept = 0;
|
||||
virtual const til::point_span* GetSearchHighlightFocused() const noexcept = 0;
|
||||
virtual std::span<const til::point_span> GetSelectionSpans() const noexcept = 0;
|
||||
virtual void LockConsole() noexcept = 0;
|
||||
virtual void UnlockConsole() noexcept = 0;
|
||||
|
||||
|
||||
@ -32,6 +32,7 @@ namespace Microsoft::Console::Render
|
||||
{
|
||||
std::span<const til::point_span> searchHighlights;
|
||||
const til::point_span* searchHighlightFocused;
|
||||
std::span<const til::point_span> selectionSpans;
|
||||
};
|
||||
|
||||
enum class GridLines
|
||||
@ -66,7 +67,7 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] virtual HRESULT Invalidate(const til::rect* psrRegion) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateCursor(const til::rect* psrRegion) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateSystem(const til::rect* prcDirtyClient) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateSelection(const std::vector<til::rect>& rectangles) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateSelection(std::span<const til::rect> selections) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateHighlight(std::span<const til::point_span> highlights, const TextBuffer& buffer) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateScroll(const til::point* pcoordDelta) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateAll() noexcept = 0;
|
||||
|
||||
@ -24,6 +24,7 @@ namespace Microsoft::Console::Render
|
||||
class RenderEngineBase : public IRenderEngine
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] HRESULT InvalidateSelection(std::span<const til::rect> selections) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateHighlight(std::span<const til::point_span> highlights, const TextBuffer& buffer) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateTitle(const std::wstring_view proposedTitle) noexcept override;
|
||||
|
||||
|
||||
@ -20,7 +20,6 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
|
||||
_textBufferChanged{ false },
|
||||
_cursorChanged{ false },
|
||||
_isEnabled{ true },
|
||||
_prevSelection{},
|
||||
_prevCursorRegion{},
|
||||
RenderEngineBase()
|
||||
{
|
||||
@ -110,40 +109,11 @@ CATCH_RETURN();
|
||||
// - rectangles - One or more rectangles describing character positions on the grid
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT UiaEngine::InvalidateSelection(const std::vector<til::rect>& rectangles) noexcept
|
||||
[[nodiscard]] HRESULT UiaEngine::InvalidateSelection(std::span<const til::rect> /*rectangles*/) noexcept
|
||||
{
|
||||
// early exit: different number of rows
|
||||
if (_prevSelection.size() != rectangles.size())
|
||||
{
|
||||
try
|
||||
{
|
||||
_selectionChanged = true;
|
||||
_prevSelection = rectangles;
|
||||
}
|
||||
CATCH_LOG_RETURN_HR(E_FAIL);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < rectangles.size(); i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto prevRect = _prevSelection.at(i);
|
||||
const auto newRect = rectangles.at(i);
|
||||
|
||||
// if any value is different, selection has changed
|
||||
if (prevRect.top != newRect.top || prevRect.right != newRect.right || prevRect.left != newRect.left || prevRect.bottom != newRect.bottom)
|
||||
{
|
||||
_selectionChanged = true;
|
||||
_prevSelection = rectangles;
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
CATCH_LOG_RETURN_HR(E_FAIL);
|
||||
}
|
||||
|
||||
// assume selection has not changed
|
||||
_selectionChanged = false;
|
||||
// INVARIANT: Renderer checks the incoming selection spans and only calls InvalidateSelection
|
||||
// if they have actually changed.
|
||||
_selectionChanged = true;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT Invalidate(const til::rect* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const til::rect* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const til::rect* const prcDirtyClient) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<til::rect>& rectangles) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSelection(std::span<const til::rect> rectangles) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateScroll(const til::point* const pcoordDelta) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
|
||||
[[nodiscard]] HRESULT NotifyNewText(const std::wstring_view newText) noexcept override;
|
||||
@ -74,7 +74,6 @@ namespace Microsoft::Console::Render
|
||||
|
||||
Microsoft::Console::Types::IUiaEventDispatcher* _dispatcher;
|
||||
|
||||
std::vector<til::rect> _prevSelection;
|
||||
til::rect _prevCursorRegion;
|
||||
};
|
||||
}
|
||||
|
||||
@ -181,11 +181,6 @@ CATCH_RETURN()
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT WddmConEngine::InvalidateSelection(const std::vector<til::rect>& /*rectangles*/) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT WddmConEngine::InvalidateScroll(const til::point* const /*pcoordDelta*/) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
|
||||
@ -28,7 +28,6 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT Invalidate(const til::rect* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const til::rect* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const til::rect* const prcDirtyClient) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<til::rect>& rectangles) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateScroll(const til::point* const pcoordDelta) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user