Chunk Selection Expansion for Double/Triple Click Selection (#2184)
Double/Triple click create a selection expanding beyond one cell. This PR makes it so that when you're dragging your mouse to expand the selection, you expand to the next delimiter defined by double/triple click. So, double click expands by doubleClickDelimiter ranges. Triple click expands by line. When you double/triple click, a word/line is selected. When you drag, that word/line will remain selected after the expansion occurs. Closes #1933 ## Details Rather than resizing the selection when the mouse event occurs, I figured I'd do what I did with wide glyph selection: expand at render time. We needed an enum `multiClickSelectionMode` to keep track of which expansion mode we're in. Minor modifications to `_ExpandDoubleClickSelection*(COORD)` had to be made so that we can re-use them. Actual expansion occurs in `_GetSelectionRects()` ## Validation Steps Performed - generic double click test - `dir` or `ls` - double click a word - drag up - Works! ✔ - double click on delimiter test - `dir` or `ls` - double click a word delimiter (i.e.: space between words) - drag up - Works! ✔ - generic triple click test - `dir` or `ls` - triple click a line - drag up - Works! ✔ - ALT + double click test - `dir` or `ls` - hold ALT - double click a word - drag up - Works! ✔ repeat above tests in following scenarios: - when at top of scrollback - drag down instead of up
This commit is contained in:
parent
82de43bce9
commit
1f41fd35cf
|
@ -164,7 +164,13 @@ private:
|
|||
|
||||
bool _snapOnInput;
|
||||
|
||||
// Text Selection
|
||||
#pragma region Text Selection
|
||||
enum SelectionExpansionMode
|
||||
{
|
||||
Cell,
|
||||
Word,
|
||||
Line
|
||||
};
|
||||
COORD _selectionAnchor;
|
||||
COORD _endSelectionPosition;
|
||||
bool _boxSelection;
|
||||
|
@ -172,6 +178,8 @@ private:
|
|||
SHORT _selectionAnchor_YOffset;
|
||||
SHORT _endSelectionPosition_YOffset;
|
||||
std::wstring _wordDelimiters;
|
||||
SelectionExpansionMode _multiClickSelectionMode;
|
||||
#pragma endregion
|
||||
|
||||
std::shared_mutex _readWriteLock;
|
||||
|
||||
|
@ -214,8 +222,8 @@ private:
|
|||
std::vector<SMALL_RECT> _GetSelectionRects() const;
|
||||
const SHORT _ExpandWideGlyphSelectionLeft(const SHORT xPos, const SHORT yPos) const;
|
||||
const SHORT _ExpandWideGlyphSelectionRight(const SHORT xPos, const SHORT yPos) const;
|
||||
void _ExpandDoubleClickSelectionLeft(const COORD position);
|
||||
void _ExpandDoubleClickSelectionRight(const COORD position);
|
||||
COORD _ExpandDoubleClickSelectionLeft(const COORD position) const;
|
||||
COORD _ExpandDoubleClickSelectionRight(const COORD position) const;
|
||||
const bool _isWordDelimiter(std::wstring_view cellChar) const;
|
||||
const COORD _ConvertToBufferCell(const COORD viewportPos) const;
|
||||
#pragma endregion
|
||||
|
|
|
@ -63,6 +63,27 @@ std::vector<SMALL_RECT> Terminal::_GetSelectionRects() const
|
|||
selectionRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : bufferSize.RightInclusive();
|
||||
}
|
||||
|
||||
// expand selection for Double/Triple Click
|
||||
if (_multiClickSelectionMode == SelectionExpansionMode::Word)
|
||||
{
|
||||
const auto cellChar = _buffer->GetCellDataAt(selectionAnchorWithOffset)->Chars();
|
||||
if (_selectionAnchor == _endSelectionPosition && _isWordDelimiter(cellChar))
|
||||
{
|
||||
// only highlight the cell if you double click a delimiter
|
||||
}
|
||||
else
|
||||
{
|
||||
selectionRow.Left = _ExpandDoubleClickSelectionLeft({ selectionRow.Left, row }).X;
|
||||
selectionRow.Right = _ExpandDoubleClickSelectionRight({ selectionRow.Right, row }).X;
|
||||
}
|
||||
}
|
||||
else if (_multiClickSelectionMode == SelectionExpansionMode::Line)
|
||||
{
|
||||
selectionRow.Left = 0;
|
||||
selectionRow.Right = bufferSize.RightInclusive();
|
||||
}
|
||||
|
||||
// expand selection for Wide Glyphs
|
||||
selectionRow.Left = _ExpandWideGlyphSelectionLeft(selectionRow.Left, row);
|
||||
selectionRow.Right = _ExpandWideGlyphSelectionRight(selectionRow.Right, row);
|
||||
|
||||
|
@ -142,16 +163,24 @@ void Terminal::DoubleClickSelection(const COORD position)
|
|||
if (_isWordDelimiter(cellChar))
|
||||
{
|
||||
SetSelectionAnchor(position);
|
||||
_multiClickSelectionMode = SelectionExpansionMode::Word;
|
||||
return;
|
||||
}
|
||||
|
||||
// scan leftwards until delimiter is found and
|
||||
// set selection anchor to one right of that spot
|
||||
_ExpandDoubleClickSelectionLeft(position);
|
||||
_selectionAnchor = _ExpandDoubleClickSelectionLeft(positionWithOffsets);
|
||||
THROW_IF_FAILED(ShortSub(_selectionAnchor.Y, gsl::narrow<SHORT>(_ViewStartIndex()), &_selectionAnchor.Y));
|
||||
_selectionAnchor_YOffset = gsl::narrow<SHORT>(_ViewStartIndex());
|
||||
|
||||
// scan rightwards until delimiter is found and
|
||||
// set endSelectionPosition to one left of that spot
|
||||
_ExpandDoubleClickSelectionRight(position);
|
||||
_endSelectionPosition = _ExpandDoubleClickSelectionRight(positionWithOffsets);
|
||||
THROW_IF_FAILED(ShortSub(_endSelectionPosition.Y, gsl::narrow<SHORT>(_ViewStartIndex()), &_endSelectionPosition.Y));
|
||||
_endSelectionPosition_YOffset = gsl::narrow<SHORT>(_ViewStartIndex());
|
||||
|
||||
_selectionActive = true;
|
||||
_multiClickSelectionMode = SelectionExpansionMode::Word;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -162,6 +191,8 @@ void Terminal::TripleClickSelection(const COORD position)
|
|||
{
|
||||
SetSelectionAnchor({ 0, position.Y });
|
||||
SetEndSelectionPosition({ _buffer->GetSize().RightInclusive(), position.Y });
|
||||
|
||||
_multiClickSelectionMode = SelectionExpansionMode::Line;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -181,6 +212,8 @@ void Terminal::SetSelectionAnchor(const COORD position)
|
|||
|
||||
_selectionActive = true;
|
||||
SetEndSelectionPosition(position);
|
||||
|
||||
_multiClickSelectionMode = SelectionExpansionMode::Cell;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -253,16 +286,10 @@ const std::wstring Terminal::RetrieveSelectedTextFromBuffer(bool trimTrailingWhi
|
|||
// Arguments:
|
||||
// - position: viewport coordinate for selection
|
||||
// Return Value:
|
||||
// - update _selectionAnchor to new expanded location
|
||||
void Terminal::_ExpandDoubleClickSelectionLeft(const COORD position)
|
||||
// - updated copy of "position" to new expanded location (with vertical offset)
|
||||
COORD Terminal::_ExpandDoubleClickSelectionLeft(const COORD position) const
|
||||
{
|
||||
// don't change the value if at/outside the boundary
|
||||
if (position.X <= 0 || position.X >= _buffer->GetSize().RightInclusive())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
COORD positionWithOffsets = _ConvertToBufferCell(position);
|
||||
COORD positionWithOffsets = position;
|
||||
const auto bufferViewport = _buffer->GetSize();
|
||||
auto cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars();
|
||||
while (positionWithOffsets.X != 0 && !_isWordDelimiter(cellChar))
|
||||
|
@ -271,16 +298,13 @@ void Terminal::_ExpandDoubleClickSelectionLeft(const COORD position)
|
|||
cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars();
|
||||
}
|
||||
|
||||
if (positionWithOffsets.X != 0 || _isWordDelimiter(cellChar))
|
||||
if (positionWithOffsets.X != 0 && _isWordDelimiter(cellChar))
|
||||
{
|
||||
// move off of delimiter to highlight properly
|
||||
bufferViewport.IncrementInBounds(positionWithOffsets);
|
||||
}
|
||||
|
||||
THROW_IF_FAILED(ShortSub(positionWithOffsets.Y, gsl::narrow<SHORT>(_ViewStartIndex()), &positionWithOffsets.Y));
|
||||
_selectionAnchor = positionWithOffsets;
|
||||
_selectionAnchor_YOffset = gsl::narrow<SHORT>(_ViewStartIndex());
|
||||
_selectionActive = true;
|
||||
return positionWithOffsets;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -288,16 +312,10 @@ void Terminal::_ExpandDoubleClickSelectionLeft(const COORD position)
|
|||
// Arguments:
|
||||
// - position: viewport coordinate for selection
|
||||
// Return Value:
|
||||
// - update _endSelectionPosition to new expanded location
|
||||
void Terminal::_ExpandDoubleClickSelectionRight(const COORD position)
|
||||
// - updated copy of "position" to new expanded location (with vertical offset)
|
||||
COORD Terminal::_ExpandDoubleClickSelectionRight(const COORD position) const
|
||||
{
|
||||
// don't change the value if at/outside the boundary
|
||||
if (position.X <= 0 || position.X >= _buffer->GetSize().RightInclusive())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
COORD positionWithOffsets = _ConvertToBufferCell(position);
|
||||
COORD positionWithOffsets = position;
|
||||
const auto bufferViewport = _buffer->GetSize();
|
||||
auto cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars();
|
||||
while (positionWithOffsets.X != _buffer->GetSize().RightInclusive() && !_isWordDelimiter(cellChar))
|
||||
|
@ -306,15 +324,13 @@ void Terminal::_ExpandDoubleClickSelectionRight(const COORD position)
|
|||
cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars();
|
||||
}
|
||||
|
||||
if (positionWithOffsets.X != bufferViewport.RightInclusive() || _isWordDelimiter(cellChar))
|
||||
if (positionWithOffsets.X != bufferViewport.RightInclusive() && _isWordDelimiter(cellChar))
|
||||
{
|
||||
// move off of delimiter to highlight properly
|
||||
bufferViewport.DecrementInBounds(positionWithOffsets);
|
||||
}
|
||||
|
||||
THROW_IF_FAILED(ShortSub(positionWithOffsets.Y, gsl::narrow<SHORT>(_ViewStartIndex()), &positionWithOffsets.Y));
|
||||
_endSelectionPosition = positionWithOffsets;
|
||||
_endSelectionPosition_YOffset = gsl::narrow<SHORT>(_ViewStartIndex());
|
||||
return positionWithOffsets;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -336,7 +352,11 @@ const bool Terminal::_isWordDelimiter(std::wstring_view cellChar) const
|
|||
// - the corresponding location on the buffer
|
||||
const COORD Terminal::_ConvertToBufferCell(const COORD viewportPos) const
|
||||
{
|
||||
// Force position to be valid
|
||||
COORD positionWithOffsets = viewportPos;
|
||||
positionWithOffsets.X = std::clamp(viewportPos.X, static_cast<SHORT>(0), _buffer->GetSize().RightInclusive());
|
||||
positionWithOffsets.Y = std::clamp(viewportPos.Y, static_cast<SHORT>(0), _buffer->GetSize().BottomInclusive());
|
||||
|
||||
THROW_IF_FAILED(ShortSub(viewportPos.Y, gsl::narrow<SHORT>(_scrollOffset), &positionWithOffsets.Y));
|
||||
THROW_IF_FAILED(ShortAdd(positionWithOffsets.Y, gsl::narrow<SHORT>(_ViewStartIndex()), &positionWithOffsets.Y));
|
||||
return positionWithOffsets;
|
||||
|
|
59
src/cascadia/UnitTests_TerminalCore/MockTermSettings.h
Normal file
59
src/cascadia/UnitTests_TerminalCore/MockTermSettings.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
#include "precomp.h"
|
||||
#include <WexTestClass.h>
|
||||
|
||||
#include "DefaultSettings.h"
|
||||
|
||||
#include "winrt/Microsoft.Terminal.Settings.h"
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
class MockTermSettings : public winrt::implements<MockTermSettings, ICoreSettings>
|
||||
{
|
||||
public:
|
||||
MockTermSettings(int32_t historySize, int32_t initialRows, int32_t initialCols) :
|
||||
_historySize(historySize),
|
||||
_initialRows(initialRows),
|
||||
_initialCols(initialCols)
|
||||
{
|
||||
}
|
||||
|
||||
// property getters - all implemented
|
||||
int32_t HistorySize() { return _historySize; }
|
||||
int32_t InitialRows() { return _initialRows; }
|
||||
int32_t InitialCols() { return _initialCols; }
|
||||
uint32_t DefaultForeground() { return COLOR_WHITE; }
|
||||
uint32_t DefaultBackground() { return COLOR_BLACK; }
|
||||
bool SnapOnInput() { return false; }
|
||||
uint32_t CursorColor() { return COLOR_WHITE; }
|
||||
CursorStyle CursorShape() const noexcept { return CursorStyle::Vintage; }
|
||||
uint32_t CursorHeight() { return 42UL; }
|
||||
winrt::hstring WordDelimiters() { return winrt::to_hstring(DEFAULT_WORD_DELIMITERS.c_str()); }
|
||||
|
||||
// other implemented methods
|
||||
uint32_t GetColorTableEntry(int32_t) const { return 123; }
|
||||
|
||||
// property setters - all unimplemented
|
||||
void HistorySize(int32_t) {}
|
||||
void InitialRows(int32_t) {}
|
||||
void InitialCols(int32_t) {}
|
||||
void DefaultForeground(uint32_t) {}
|
||||
void DefaultBackground(uint32_t) {}
|
||||
void SnapOnInput(bool) {}
|
||||
void CursorColor(uint32_t) {}
|
||||
void CursorShape(CursorStyle const&) noexcept {}
|
||||
void CursorHeight(uint32_t) {}
|
||||
void WordDelimiters(winrt::hstring) {}
|
||||
|
||||
// other unimplemented methods
|
||||
void SetColorTableEntry(int32_t /* index */, uint32_t /* value */) {}
|
||||
|
||||
private:
|
||||
int32_t _historySize;
|
||||
int32_t _initialRows;
|
||||
int32_t _initialCols;
|
||||
};
|
||||
}
|
|
@ -4,64 +4,16 @@
|
|||
#include "precomp.h"
|
||||
#include <WexTestClass.h>
|
||||
|
||||
#include "DefaultSettings.h"
|
||||
#include "../cascadia/TerminalCore/Terminal.hpp"
|
||||
#include "MockTermSettings.h"
|
||||
#include "../renderer/inc/DummyRenderTarget.hpp"
|
||||
#include "consoletaeftemplates.hpp"
|
||||
|
||||
#include "winrt/Microsoft.Terminal.Settings.h"
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace Microsoft::Terminal::Core;
|
||||
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
class MockTermSettings : public winrt::implements<MockTermSettings, ICoreSettings>
|
||||
{
|
||||
public:
|
||||
MockTermSettings(int32_t historySize, int32_t initialRows, int32_t initialCols) :
|
||||
_historySize(historySize),
|
||||
_initialRows(initialRows),
|
||||
_initialCols(initialCols)
|
||||
{
|
||||
}
|
||||
|
||||
// property getters - all implemented
|
||||
int32_t HistorySize() { return _historySize; }
|
||||
int32_t InitialRows() { return _initialRows; }
|
||||
int32_t InitialCols() { return _initialCols; }
|
||||
uint32_t DefaultForeground() { return COLOR_WHITE; }
|
||||
uint32_t DefaultBackground() { return COLOR_BLACK; }
|
||||
bool SnapOnInput() { return false; }
|
||||
uint32_t CursorColor() { return COLOR_WHITE; }
|
||||
CursorStyle CursorShape() const noexcept { return CursorStyle::Vintage; }
|
||||
uint32_t CursorHeight() { return 42UL; }
|
||||
winrt::hstring WordDelimiters() { return winrt::to_hstring(DEFAULT_WORD_DELIMITERS.c_str()); }
|
||||
|
||||
// other implemented methods
|
||||
uint32_t GetColorTableEntry(int32_t) const { return 123; }
|
||||
|
||||
// property setters - all unimplemented
|
||||
void HistorySize(int32_t) {}
|
||||
void InitialRows(int32_t) {}
|
||||
void InitialCols(int32_t) {}
|
||||
void DefaultForeground(uint32_t) {}
|
||||
void DefaultBackground(uint32_t) {}
|
||||
void SnapOnInput(bool) {}
|
||||
void CursorColor(uint32_t) {}
|
||||
void CursorShape(CursorStyle const&) noexcept {}
|
||||
void CursorHeight(uint32_t) {}
|
||||
void WordDelimiters(winrt::hstring) {}
|
||||
|
||||
// other unimplemented methods
|
||||
void SetColorTableEntry(int32_t /* index */, uint32_t /* value */) {}
|
||||
|
||||
private:
|
||||
int32_t _historySize;
|
||||
int32_t _initialRows;
|
||||
int32_t _initialCols;
|
||||
};
|
||||
|
||||
#define WCS(x) WCSHELPER(x)
|
||||
#define WCSHELPER(x) L#x
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <WexTestClass.h>
|
||||
|
||||
#include "../cascadia/TerminalCore/Terminal.hpp"
|
||||
#include "../cascadia/UnitTests_TerminalCore/MockTermSettings.h"
|
||||
#include "../renderer/inc/DummyRenderTarget.hpp"
|
||||
#include "consoletaeftemplates.hpp"
|
||||
|
||||
|
@ -16,7 +17,7 @@ using namespace WEX::Logging;
|
|||
using namespace WEX::TestExecution;
|
||||
|
||||
using namespace Microsoft::Terminal::Core;
|
||||
using namespace Microsoft::Console::Render;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
|
@ -183,7 +184,7 @@ namespace TerminalCoreUnitTests
|
|||
{
|
||||
#ifdef _X86_
|
||||
Log::Comment(L"This test is unreliable on x86 but is fine elsewhere. Disabled on x86.");
|
||||
Log::Result(WEX::Logging::TestResults::Skipped);
|
||||
Log::Result(TestResults::Skipped);
|
||||
return;
|
||||
#else
|
||||
Terminal term;
|
||||
|
@ -218,7 +219,7 @@ namespace TerminalCoreUnitTests
|
|||
{
|
||||
#ifdef _X86_
|
||||
Log::Comment(L"This test is unreliable on x86 but is fine elsewhere. Disabled on x86.");
|
||||
Log::Result(WEX::Logging::TestResults::Skipped);
|
||||
Log::Result(TestResults::Skipped);
|
||||
return;
|
||||
#else
|
||||
Terminal term;
|
||||
|
@ -253,7 +254,7 @@ namespace TerminalCoreUnitTests
|
|||
{
|
||||
#ifdef _X86_
|
||||
Log::Comment(L"This test is unreliable on x86 but is fine elsewhere. Disabled on x86.");
|
||||
Log::Result(WEX::Logging::TestResults::Skipped);
|
||||
Log::Result(TestResults::Skipped);
|
||||
return;
|
||||
#else
|
||||
Terminal term;
|
||||
|
@ -309,5 +310,199 @@ namespace TerminalCoreUnitTests
|
|||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
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.DoubleClickSelection(clickPos);
|
||||
|
||||
// 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.at(0)).ToInclusive();
|
||||
VERIFY_ARE_EQUAL(selection, 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.DoubleClickSelection(clickPos);
|
||||
|
||||
// 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.at(0)).ToInclusive();
|
||||
VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 5, 10, 5, 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.DoubleClickSelection({ 5, 10 });
|
||||
|
||||
// Simulate move to (x,y) = (21,10)
|
||||
//
|
||||
// buffer: doubleClickMe dragThroughHere
|
||||
// ^ ^
|
||||
// start finish
|
||||
term.SetEndSelectionPosition({ 21, 10 });
|
||||
|
||||
// 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.at(0)).ToInclusive();
|
||||
VERIFY_ARE_EQUAL(selection, 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.DoubleClickSelection({ 21, 10 });
|
||||
|
||||
// Simulate move to (x,y) = (5,10)
|
||||
//
|
||||
// buffer: doubleClickMe dragThroughHere
|
||||
// ^ ^
|
||||
// finish start
|
||||
term.SetEndSelectionPosition({ 5, 10 });
|
||||
|
||||
// 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.at(0)).ToInclusive();
|
||||
VERIFY_ARE_EQUAL(selection, 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.TripleClickSelection(clickPos);
|
||||
|
||||
// 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.at(0)).ToInclusive();
|
||||
VERIFY_ARE_EQUAL(selection, 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.TripleClickSelection(clickPos);
|
||||
|
||||
// Simulate move to (x,y) = (7,10)
|
||||
term.SetEndSelectionPosition({ 7, 10 });
|
||||
|
||||
// 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.at(0)).ToInclusive();
|
||||
VERIFY_ARE_EQUAL(selection, 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.TripleClickSelection(clickPos);
|
||||
|
||||
// Simulate move to (x,y) = (5,11)
|
||||
term.SetEndSelectionPosition({ 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 }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="MockTermSettings.h" />
|
||||
<ClInclude Include="precomp.h" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
|
|
Loading…
Reference in a new issue