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

Implement Shift+MultiClick Selection Expansion #6322

Merged
9 commits merged into from
Jun 25, 2020
46 changes: 27 additions & 19 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1078,35 +1078,43 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const unsigned int MAX_CLICK_COUNT = 3;
const auto multiClickMapper = clickCount > MAX_CLICK_COUNT ? ((clickCount + MAX_CLICK_COUNT - 1) % MAX_CLICK_COUNT) + 1 : clickCount;

if (multiClickMapper == 3)
::Terminal::SelectionExpansionMode mode = ::Terminal::SelectionExpansionMode::Cell;
if (multiClickMapper == 1)
{
_terminal->MultiClickSelection(terminalPosition, ::Terminal::SelectionExpansionMode::Line);
_selectionNeedsToBeCopied = true;
mode = ::Terminal::SelectionExpansionMode::Cell;
}
else if (multiClickMapper == 2)
{
_terminal->MultiClickSelection(terminalPosition, ::Terminal::SelectionExpansionMode::Word);
mode = ::Terminal::SelectionExpansionMode::Word;
}
else if (multiClickMapper == 3)
{
mode = ::Terminal::SelectionExpansionMode::Line;
}

// Update the selection appropriately
if (shiftEnabled && _terminal->IsSelectionActive())
{
// Shift+Click: only set expand on the "end" selection point
_terminal->SetSelectionEnd(terminalPosition, mode);
_selectionNeedsToBeCopied = true;
}
else if (mode == ::Terminal::SelectionExpansionMode::Cell)
{
// Single Click: reset the selection and begin a new one
_terminal->ClearSelection();
_singleClickTouchdownPos = cursorPosition;
_selectionNeedsToBeCopied = false; // there's no selection, so there's nothing to update
}
else
{
if (shiftEnabled && _terminal->IsSelectionActive())
{
_terminal->SetSelectionEnd(terminalPosition, ::Terminal::SelectionExpansionMode::Cell);
_selectionNeedsToBeCopied = true;
}
else
{
// A single click down resets the selection and begins a new one.
_terminal->ClearSelection();
_singleClickTouchdownPos = cursorPosition;
_selectionNeedsToBeCopied = false; // there's no selection, so there's nothing to update
}

_lastMouseClickTimestamp = point.Timestamp();
_lastMouseClickPos = cursorPosition;
// Multi-Click Selection: expand both "start" and "end" selection points
_terminal->MultiClickSelection(terminalPosition, mode);
_selectionNeedsToBeCopied = true;
}

_lastMouseClickTimestamp = point.Timestamp();
_lastMouseClickPos = cursorPosition;
_renderer->TriggerSelection();
}
else if (point.Properties().IsRightButtonPressed())
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ class Microsoft::Terminal::Core::Terminal final :
#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp
std::vector<SMALL_RECT> _GetSelectionRects() const noexcept;
std::pair<COORD, COORD> _PivotSelection(const COORD targetPos) const;
std::pair<COORD, COORD> _PivotSelection(const COORD targetPos, bool& targetStart) const;
std::pair<COORD, COORD> _ExpandSelectionAnchors(std::pair<COORD, COORD> anchors) const;
COORD _ConvertToBufferCell(const COORD viewportPos) const;
#pragma endregion
Expand Down
26 changes: 22 additions & 4 deletions src/cascadia/TerminalCore/TerminalSelection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,20 +151,38 @@ void Terminal::SetSelectionEnd(const COORD viewportPos, std::optional<SelectionE
// Otherwise, we may accidentally expand during other selection-based actions
_multiClickSelectionMode = newExpansionMode.has_value() ? *newExpansionMode : _multiClickSelectionMode;

const auto anchors = _PivotSelection(textBufferPos);
std::tie(_selection->start, _selection->end) = _ExpandSelectionAnchors(anchors);
bool targetStart = false;
const auto anchors = _PivotSelection(textBufferPos, targetStart);
const auto expandedAnchors = _ExpandSelectionAnchors(anchors);

if (newExpansionMode.has_value())
{
// shift-click operations only expand the target side
auto& anchorToExpand = targetStart ? _selection->start : _selection->end;
anchorToExpand = targetStart ? expandedAnchors.first : expandedAnchors.second;

// the other anchor should then become the pivot (we don't expand it)
auto& anchorToPivot = targetStart ? _selection->end : _selection->start;
anchorToPivot = _selection->pivot;
}
else
{
// expand both anchors
std::tie(_selection->start, _selection->end) = expandedAnchors;
}
}

// Method Description:
// - returns a new pair of selection anchors for selecting around the pivot
// - This ensures start < end when compared
// Arguments:
// - targetPos: the (x,y) coordinate we are moving to on the text buffer
// - targetStart: if true, target will be the new start. Otherwise, target will be the new end.
// Return Value:
// - the new start/end for a selection
std::pair<COORD, COORD> Terminal::_PivotSelection(const COORD targetPos) const
std::pair<COORD, COORD> Terminal::_PivotSelection(const COORD targetPos, bool& targetStart) const
{
if (_buffer->GetSize().CompareInBounds(targetPos, _selection->pivot) <= 0)
if (targetStart = _buffer->GetSize().CompareInBounds(targetPos, _selection->pivot) <= 0)
{
// target is before pivot
// treat target as start
Expand Down
169 changes: 169 additions & 0 deletions src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,5 +672,174 @@ namespace TerminalCoreUnitTests
selection = term.GetViewport().ConvertToOrigin(selectionRects.at(1)).ToInclusive();
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 0, 11, 99, 11 }));
}

TEST_METHOD(ShiftClick)
{
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 100, 100 }, 0, emptyRT);

// set word delimiters for terminal
auto settings = winrt::make<MockTermSettings>(0, 100, 100);
term.UpdateSettings(settings);

// Insert text at position (4,10)
const std::wstring_view text = L"doubleClickMe dragThroughHere";
term.SetCursorPosition(4, 10);
term.Write(text);

// Step 1: Create a selection on "doubleClickMe"
{
// Simulate double click at (x,y) = (5,10)
term.MultiClickSelection({ 5, 10 }, Terminal::SelectionExpansionMode::Word);

// Validate selection area: "doubleClickMe" selected
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 16, 10 }));
}

// Step 2: Shift+Click to "dragThroughHere"
{
// Simulate Shift+Click at (x,y) = (21,10)
//
// buffer: doubleClickMe dragThroughHere
// ^ ^
// start finish
term.SetSelectionEnd({ 21, 10 }, ::Terminal::SelectionExpansionMode::Cell);

// Validate selection area: "doubleClickMe drag" selected
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 21, 10 }));
}

// Step 3: Shift+Double-Click at "dragThroughHere"
{
// Simulate Shift+DoubleClick at (x,y) = (21,10)
//
// buffer: doubleClickMe dragThroughHere
// ^ ^ ^
// start click finish
term.SetSelectionEnd({ 21, 10 }, ::Terminal::SelectionExpansionMode::Word);

// Validate selection area: "doubleClickMe dragThroughHere" selected
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 32, 10 }));
}

// Step 4: Shift+Triple-Click at "dragThroughHere"
{
// Simulate Shift+TripleClick at (x,y) = (21,10)
//
// buffer: doubleClickMe dragThroughHere |
// ^ ^ ^
// start click finish (boundary)
term.SetSelectionEnd({ 21, 10 }, ::Terminal::SelectionExpansionMode::Line);

// Validate selection area: "doubleClickMe dragThroughHere..." selected
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 99, 10 }));
}
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved

// Step 5: Shift+Double-Click at "dragThroughHere"
{
// Simulate Shift+DoubleClick at (x,y) = (21,10)
//
// buffer: doubleClickMe dragThroughHere
// ^ ^ ^
// start click finish
term.SetSelectionEnd({ 21, 10 }, ::Terminal::SelectionExpansionMode::Word);

// Validate selection area: "doubleClickMe dragThroughHere" selected
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 32, 10 }));
}

// Step 6: Drag past "dragThroughHere"
{
// Simulate drag to (x,y) = (35,10)
// Since we were preceded by a double-click, we're in "word" expansion mode
//
// buffer: doubleClickMe dragThroughHere |
// ^ ^
// start finish (boundary)
term.SetSelectionEnd({ 35, 10 });

// Validate selection area: "doubleClickMe dragThroughHere..." selected
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 99, 10 }));
}

// Step 6: Drag back to "dragThroughHere"
{
// Simulate drag to (x,y) = (21,10)
//
// buffer: doubleClickMe dragThroughHere
// ^ ^ ^
// start drag finish
term.SetSelectionEnd({ 21, 10 });

// Validate selection area: "doubleClickMe dragThroughHere" selected
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 32, 10 }));
}

// Step 7: Drag within "dragThroughHere"
{
// Simulate drag to (x,y) = (25,10)
//
// buffer: doubleClickMe dragThroughHere
// ^ ^ ^
// start drag finish
term.SetSelectionEnd({ 25, 10 });

// Validate selection area: "doubleClickMe dragThroughHere" still selected
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 32, 10 }));
}
}

TEST_METHOD(Pivot)
{
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 100, 100 }, 0, emptyRT);

// Step 1: Create a selection
{
// (10,10) to (20, 10)
term.SelectNewRegion({ 10, 10 }, { 20, 10 });

// Validate selection area
ValidateSingleRowSelection(term, SMALL_RECT({ 10, 10, 20, 10 }));
}

// Step 2: Drag to (5,10)
{
term.SetSelectionEnd({ 5, 10 });

// Validate selection area
// NOTE: Pivot should be (10, 10)
ValidateSingleRowSelection(term, SMALL_RECT({ 5, 10, 10, 10 }));
}

// Step 3: Drag back to (20,10)
{
term.SetSelectionEnd({ 20, 10 });

// Validate selection area
// NOTE: Pivot should still be (10, 10)
ValidateSingleRowSelection(term, SMALL_RECT({ 10, 10, 20, 10 }));
}

// Step 4: Shift+Click at (5,10)
{
term.SetSelectionEnd({ 5, 10 }, ::Terminal::SelectionExpansionMode::Cell);

// Validate selection area
// NOTE: Pivot should still be (10, 10)
ValidateSingleRowSelection(term, SMALL_RECT({ 5, 10, 10, 10 }));
}

// Step 5: Shift+Click back at (20,10)
{
term.SetSelectionEnd({ 20, 10 }, ::Terminal::SelectionExpansionMode::Cell);

// Validate selection area
// NOTE: Pivot should still be (10, 10)
ValidateSingleRowSelection(term, SMALL_RECT({ 10, 10, 20, 10 }));
}
}
};
}