terminal/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp
Carlos Zamora c070be12d3
Implement Keyboard Selection (#10824)
Implements the following keyboard selection non-configurable key bindings:
- shift+arrow --> move endpoint by character
- ctrl+shift+left/right --> move endpoint by word
- shift+home/end --> move to beginning/end of line
- ctrl+shift+home/end --> move to beginning/end of buffer

This was purposefully done in the ControlCore layer to make keyboard selection an innate part of how the terminal functions (aka a shared component across terminal consumers).

## References
#715 - Keyboard Selection
#2840 - Spec

## Detailed Description of the Pull Request / Additional comment
The most relevant section is `TerminalSelection.cpp`, where we define how each movement operates. It's basically a giant embedded switch-case statement. We leverage a lot of the work done in a11y to perform the movements.

## Validation Steps Performed
- General cases:
   - test all of the key bindings added
- Corner cases:
   - `char`: wide glyph support
   - `word`: move towards, away, and across the selection pivot
   - automatically scroll viewport
   - ESC (and other key combos) are still clearing the selection properly
2021-09-23 12:24:32 -07:00

846 lines
33 KiB
C++

/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*
* This File was generated using the VisualTAEF C++ Project Wizard.
* Class Name: SelectionTest
*/
#include "pch.h"
#include <WexTestClass.h>
#include "../cascadia/TerminalCore/Terminal.hpp"
#include "../cascadia/UnitTests_TerminalCore/MockTermSettings.h"
#include "../renderer/inc/DummyRenderTarget.hpp"
#include "consoletaeftemplates.hpp"
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace Microsoft::Terminal::Core;
using namespace winrt::Microsoft::Terminal::Core;
namespace TerminalCoreUnitTests
{
class SelectionTest
{
TEST_CLASS(SelectionTest);
// Method Description:
// - Validate a selection that spans only one row
// Arguments:
// - term: the terminal that is contains the selection
// - expected: the expected value of the selection rect
// Return Value:
// - N/A
void ValidateSingleRowSelection(Terminal& term, SMALL_RECT expected)
{
// Simulate renderer calling TriggerSelection and acquiring selection area
auto selectionRects = term.GetSelectionRects();
// Validate selection area
VERIFY_ARE_EQUAL(selectionRects.size(), static_cast<size_t>(1));
auto selection = term.GetViewport().ConvertToOrigin(selectionRects[0]).ToInclusive();
VERIFY_ARE_EQUAL(selection, expected);
}
TEST_METHOD(SelectUnit)
{
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 100, 100 }, 0, emptyRT);
// Simulate click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.SetSelectionAnchor(clickPos);
ValidateSingleRowSelection(term, { 5, 10, 5, 10 });
}
TEST_METHOD(SelectArea)
{
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 100, 100 }, 0, emptyRT);
// Used for two things:
// - click y-pos
// - keep track of row we're verifying
SHORT rowValue = 10;
// Simulate click at (x,y) = (5,10)
term.SetSelectionAnchor({ 5, rowValue });
// 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();
SHORT rightBoundary = viewport.RightInclusive();
for (auto selectionRect : selectionRects)
{
auto selection = viewport.ConvertToOrigin(selectionRect).ToInclusive();
if (rowValue == 10)
{
// Verify top line
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 5, 10, rightBoundary, 10 }));
}
else if (rowValue == 20)
{
// Verify bottom line
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 0, 20, 15, 20 }));
}
else
{
// Verify other lines (full)
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 0, rowValue, rightBoundary, rowValue }));
}
rowValue++;
}
}
TEST_METHOD(OverflowTests)
{
const COORD maxCoord = { SHRT_MAX, SHRT_MAX };
// Test SetSelectionAnchor(COORD) and SetSelectionEnd(COORD)
// Behavior: clamp coord to viewport.
auto ValidateSingleClickSelection = [&](SHORT scrollback, SMALL_RECT expected) {
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 10, 10 }, scrollback, emptyRT);
// NOTE: SetSelectionEnd(COORD) is called within SetSelectionAnchor(COORD)
term.SetSelectionAnchor(maxCoord);
ValidateSingleRowSelection(term, expected);
};
// Test a Double Click Selection
// Behavior: clamp coord to viewport.
// Then, do double click selection.
auto ValidateDoubleClickSelection = [&](SHORT scrollback, SMALL_RECT expected) {
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 10, 10 }, scrollback, emptyRT);
term.MultiClickSelection(maxCoord, Terminal::SelectionExpansion::Word);
ValidateSingleRowSelection(term, expected);
};
// Test a Triple Click Selection
// Behavior: clamp coord to viewport.
// Then, do triple click selection.
auto ValidateTripleClickSelection = [&](SHORT scrollback, SMALL_RECT expected) {
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 10, 10 }, scrollback, emptyRT);
term.MultiClickSelection(maxCoord, Terminal::SelectionExpansion::Line);
ValidateSingleRowSelection(term, expected);
};
// Test with no scrollback
Log::Comment(L"Single click selection with NO scrollback value");
ValidateSingleClickSelection(0, { 9, 9, 9, 9 });
Log::Comment(L"Double click selection with NO scrollback value");
ValidateDoubleClickSelection(0, { 0, 9, 9, 9 });
Log::Comment(L"Triple click selection with NO scrollback value");
ValidateTripleClickSelection(0, { 0, 9, 9, 9 });
// Test with max scrollback
const SHORT expected_row = SHRT_MAX - 1;
Log::Comment(L"Single click selection with MAXIMUM scrollback value");
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 });
Log::Comment(L"Triple click selection with MAXIMUM scrollback value");
ValidateTripleClickSelection(SHRT_MAX, { 0, expected_row, 9, expected_row });
}
TEST_METHOD(SelectFromOutofBounds)
{
/* NOTE:
ensuring that the selection anchors are clamped to be valid permits us to make the following assumption:
- All selection expansion functions will operate as if they were performed at the boundary
*/
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 10, 10 }, 0, emptyRT);
auto viewport = term.GetViewport();
const SHORT leftBoundary = viewport.Left();
const SHORT rightBoundary = viewport.RightInclusive();
const SHORT topBoundary = viewport.Top();
const SHORT bottomBoundary = viewport.BottomInclusive();
// Case 1: Simulate click past right (x,y) = (20,5)
// 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 });
// 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 });
// 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 });
// 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 });
}
TEST_METHOD(SelectToOutOfBounds)
{
/* NOTE:
ensuring that the selection anchors are clamped to be valid permits us to make the following assumption:
- All selection expansion functions will operate as if they were performed at the boundary
*/
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 10, 10 }, 0, emptyRT);
auto viewport = term.GetViewport();
const SHORT leftBoundary = 0;
const SHORT rightBoundary = viewport.RightInclusive();
// Simulate click at (x,y) = (5,5)
term.SetSelectionAnchor({ 5, 5 });
// Case 1: Move out of right boundary
Log::Comment(L"Out of bounds: X-value too large");
term.SetSelectionEnd({ 20, 5 });
ValidateSingleRowSelection(term, SMALL_RECT({ 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 });
// 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, SMALL_RECT({ 5, rowValue, rightBoundary, rowValue }));
}
else if (rowValue == 5)
{
// Verify last line
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ leftBoundary, rowValue, 5, rowValue }));
}
else
{
// Verify other lines (full)
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ leftBoundary, rowValue, rightBoundary, rowValue }));
}
}
}
// 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, SMALL_RECT({ 5, 5, rightBoundary, 5 }));
}
else if (rowValue == 9)
{
// Verify bottom line
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ leftBoundary, rowValue, 5, rowValue }));
}
else
{
// Verify other lines (full)
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ leftBoundary, rowValue, rightBoundary, rowValue }));
}
}
}
}
TEST_METHOD(SelectBoxArea)
{
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 100, 100 }, 0, emptyRT);
// Used for two things:
// - click y-pos
// - keep track of row we're verifying
SHORT rowValue = 10;
// Simulate ALT + click at (x,y) = (5,10)
term.SetSelectionAnchor({ 5, rowValue });
term.SetBlockSelection(true);
// 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();
for (auto selectionRect : selectionRects)
{
auto selection = viewport.ConvertToOrigin(selectionRect).ToInclusive();
// Verify all lines
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 5, rowValue, 15, rowValue }));
rowValue++;
}
}
TEST_METHOD(SelectAreaAfterScroll)
{
Terminal term;
DummyRenderTarget emptyRT;
SHORT scrollbackLines = 5;
term.Create({ 100, 100 }, scrollbackLines, emptyRT);
// Used for two things:
// - click y-pos
// - keep track of row we're verifying
SHORT rowValue = 10;
// Simulate click at (x,y) = (5,10)
term.SetSelectionAnchor({ 5, rowValue });
// 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();
SHORT rightBoundary = viewport.RightInclusive();
for (auto selectionRect : selectionRects)
{
auto selection = viewport.ConvertToOrigin(selectionRect).ToInclusive();
if (rowValue == 10)
{
// Verify top line
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 5, 10, rightBoundary, 10 }));
}
else if (rowValue == 20)
{
// Verify bottom line
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 0, 20, 15, 20 }));
}
else
{
// Verify other lines (full)
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 0, rowValue, rightBoundary, rowValue }));
}
rowValue++;
}
}
TEST_METHOD(SelectWideGlyph_Trailing)
{
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 100, 100 }, 0, emptyRT);
// This is the burrito emoji
// It's encoded in UTF-16, as needed by the buffer.
const auto burrito = L"\xD83C\xDF2F";
// Insert wide glyph at position (4,10)
term.SetCursorPosition(4, 10);
term.Write(burrito);
// Simulate click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.SetSelectionAnchor(clickPos);
// 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 });
}
TEST_METHOD(SelectWideGlyph_Leading)
{
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 100, 100 }, 0, emptyRT);
// This is the burrito emoji
// It's encoded in UTF-16, as needed by the buffer.
const auto burrito = L"\xD83C\xDF2F";
// Insert wide glyph at position (4,10)
term.SetCursorPosition(4, 10);
term.Write(burrito);
// Simulate click at (x,y) = (5,10)
auto clickPos = COORD{ 4, 10 };
term.SetSelectionAnchor(clickPos);
// 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 });
}
TEST_METHOD(SelectWideGlyphsInBoxSelection)
{
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 100, 100 }, 0, emptyRT);
// This is the burrito emoji
// It's encoded in UTF-16, as needed by the buffer.
const auto burrito = L"\xD83C\xDF2F";
// Insert wide glyph at position (4,10)
term.SetCursorPosition(4, 10);
term.Write(burrito);
// Insert wide glyph at position (7,11)
term.SetCursorPosition(7, 11);
term.Write(burrito);
// Simulate ALT + click at (x,y) = (5,8)
term.SetSelectionAnchor({ 5, 8 });
term.SetBlockSelection(true);
// Simulate move to (x,y) = (7,12)
term.SetSelectionEnd({ 7, 12 });
// Simulate renderer calling TriggerSelection and acquiring selection area
auto selectionRects = term.GetSelectionRects();
// Validate selection area
VERIFY_ARE_EQUAL(selectionRects.size(), static_cast<size_t>(5));
auto viewport = term.GetViewport();
SHORT rowValue = 8;
for (auto selectionRect : selectionRects)
{
auto selection = viewport.ConvertToOrigin(selectionRect).ToInclusive();
if (rowValue == 10)
{
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 4, rowValue, 7, rowValue }));
}
else if (rowValue == 11)
{
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 5, rowValue, 8, rowValue }));
}
else
{
// Verify all lines
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 5, rowValue, 7, rowValue }));
}
rowValue++;
}
}
TEST_METHOD(DoubleClick_GeneralCase)
{
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";
term.SetCursorPosition(4, 10);
term.Write(text);
// Simulate double click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Word);
// Validate selection area
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, (4 + gsl::narrow<SHORT>(text.size()) - 1), 10 }));
}
TEST_METHOD(DoubleClick_Delimiter)
{
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);
// Simulate click at (x,y) = (5,10)
auto clickPos = COORD{ 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, SMALL_RECT({ 0, 10, 99, 10 }));
}
TEST_METHOD(DoubleClick_DelimiterClass)
{
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"C:\\Terminal>";
term.SetCursorPosition(4, 10);
term.Write(text);
// Simulate click at (x,y) = (15,10)
// this is over the '>' char
auto clickPos = COORD{ 15, 10 };
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Word);
// ---Validate selection area---
// "Terminal" is in class 2
// ">" 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, SMALL_RECT({ 15, 10, 15, 10 }));
}
TEST_METHOD(DoubleClickDrag_Right)
{
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);
// Simulate double click at (x,y) = (5,10)
term.MultiClickSelection({ 5, 10 }, Terminal::SelectionExpansion::Word);
// Simulate move to (x,y) = (21,10)
//
// buffer: doubleClickMe dragThroughHere
// ^ ^
// start finish
term.SetSelectionEnd({ 21, 10 });
// Validate selection area
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 32, 10 }));
}
TEST_METHOD(DoubleClickDrag_Left)
{
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 (21,10)
const std::wstring_view text = L"doubleClickMe dragThroughHere";
term.SetCursorPosition(4, 10);
term.Write(text);
// Simulate double click at (x,y) = (21,10)
term.MultiClickSelection({ 21, 10 }, Terminal::SelectionExpansion::Word);
// Simulate move to (x,y) = (5,10)
//
// buffer: doubleClickMe dragThroughHere
// ^ ^
// finish start
term.SetSelectionEnd({ 5, 10 });
// Validate selection area
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 32, 10 }));
}
TEST_METHOD(TripleClick_GeneralCase)
{
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 100, 100 }, 0, emptyRT);
// Simulate click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Line);
// Validate selection area
ValidateSingleRowSelection(term, SMALL_RECT({ 0, 10, 99, 10 }));
}
TEST_METHOD(TripleClickDrag_Horizontal)
{
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 100, 100 }, 0, emptyRT);
// Simulate click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Line);
// Simulate move to (x,y) = (7,10)
term.SetSelectionEnd({ 7, 10 });
// Validate selection area
ValidateSingleRowSelection(term, SMALL_RECT({ 0, 10, 99, 10 }));
}
TEST_METHOD(TripleClickDrag_Vertical)
{
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 100, 100 }, 0, emptyRT);
// Simulate click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.MultiClickSelection(clickPos, Terminal::SelectionExpansion::Line);
// 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, SMALL_RECT({ 0, 10, 99, 10 }));
// verify second selection rect
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::SelectionExpansion::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::SelectionExpansion::Char);
// 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::SelectionExpansion::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::SelectionExpansion::Line);
// Validate selection area: "doubleClickMe dragThroughHere..." selected
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 99, 10 }));
}
// 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::SelectionExpansion::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::SelectionExpansion::Char);
// 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::SelectionExpansion::Char);
// Validate selection area
// NOTE: Pivot should still be (10, 10)
ValidateSingleRowSelection(term, SMALL_RECT({ 10, 10, 20, 10 }));
}
}
};
}