mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-11 13:56:33 -06:00
Selection is generally stored as an inclusive start and end. This PR makes the end exclusive which now allows degenerate selections, namely in mark mode. This also modifies mouse selection to round to the nearest cell boundary (see #5099) and improves word boundaries to be a bit more modern and make sense for degenerate selections (similar to #15787). Closes #5099 Closes #13447 Closes #17892 ## Detailed Description of the Pull Request / Additional comments - Buffer, Viewport, and Point - Introduced a few new functions here to find word boundaries, delimiter class runs, and glyph boundaries. - 📝These new functions should be able to replace a few other functions (i.e. `GetWordStart` --> `GetWordStart2`). That migration is going to be a part of #4423 to reduce the risk of breaking UIA. - Viewport: added a few functions to handle navigating the _exclusive_ bounds (namely allowing RightExclusive as a position for buffer coordinates). This is important for selection to be able to highlight the entire line. - 📝`BottomInclusiveRightExclusive()` will replace `EndExclusive` in the UIA code - Point: `iterate_rows_exclusive` is similar to `iterate_rows`, except it has handling for RightExclusive - Renderer - Use `iterate_rows_exclusive` for proper handling (this actually fixed a lot of our issues) - Remove some workarounds in `_drawHighlighted` (this is a boundary where we got inclusive coords and made them exclusive, but now we don't need that!) - Terminal - fix selection marker rendering - `_ConvertToBufferCell()`: add a param to allow for RightExclusive or clamp it to RightInclusive (original behavior). Both are useful! - Use new `GetWordStart2` and `GetWordEnd2` to improve word boundaries and make them feel right now that the selection an exclusive range. - Convert a few `IsInBounds` --> `IsInExclusiveBounds` for safety and correctness - Add `TriggerSelection` to `SelectNewRegion` - 📝 We normally called `TriggerSelection` in a different layer, but it turns out, UIA's `Select` function wouldn't actually update the renderer. Whoops! This fixes that. - TermControl - `_getTerminalPosition` now has a new param to round to the nearest cell (see #5099) - UIA - `TermControlUIAProvider::GetSelectionRange` no need to convert from inclusive range to exclusive range anymore! - `TextBuffer::GetPlainText` now works on an exclusive range, so no need to convert the range anymore! ## Validation Steps Performed This fundamental change impacts a lot of scenarios: - ✅Rendering selections - ✅Selection markers - ✅Copy text - ✅Session restore - ✅Mark mode navigation (i.e. character, word, line, buffer) - ✅Mouse selection (i.e. click+drag, shift+click, multi-click, alt+click) - ✅Hyperlinks (interaction and rendering) - ✅Accessibility (i.e. get selection, movement, text extraction, selecting text) - [ ] Prev/Next Command/Output (untested) - ✅Unit tests ## Follow-ups - Refs #4423 - Now that selection and UIA are both exclusive ranges, it should be a lot easier to deduplicate code between selection and UIA. We should be able to remove `EndExclusive` as well when we do that. This'll also be an opportunity to modernize that code and use more `til` classes.
1049 lines
49 KiB
C++
1049 lines
49 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "../TerminalControl/EventArgs.h"
|
|
#include "../TerminalControl/ControlInteractivity.h"
|
|
|
|
#include "../../inc/TestUtils.h"
|
|
#include "MockControlSettings.h"
|
|
#include "MockConnection.h"
|
|
|
|
using namespace ::Microsoft::Console;
|
|
using namespace WEX::Logging;
|
|
using namespace WEX::TestExecution;
|
|
using namespace WEX::Common;
|
|
|
|
using namespace winrt;
|
|
using namespace winrt::Microsoft::Terminal;
|
|
using namespace ::Microsoft::Terminal::Core;
|
|
using namespace ::Microsoft::Console::VirtualTerminal;
|
|
|
|
namespace ControlUnitTests
|
|
{
|
|
class ControlInteractivityTests
|
|
{
|
|
BEGIN_TEST_CLASS(ControlInteractivityTests)
|
|
TEST_CLASS_PROPERTY(L"TestTimeout", L"0:0:10") // 10s timeout
|
|
END_TEST_CLASS()
|
|
|
|
TEST_METHOD(TestAdjustAcrylic);
|
|
TEST_METHOD(TestScrollWithMouse);
|
|
|
|
TEST_METHOD(CreateSubsequentSelectionWithDragging);
|
|
TEST_METHOD(ScrollWithSelection);
|
|
TEST_METHOD(TestScrollWithTrackpad);
|
|
TEST_METHOD(TestQuickDragOnSelect);
|
|
|
|
TEST_METHOD(TestDragSelectOutsideBounds);
|
|
|
|
TEST_METHOD(PointerClickOutsideActiveRegion);
|
|
TEST_METHOD(IncrementCircularBufferWithSelection);
|
|
|
|
TEST_METHOD(GetMouseEventsInTest);
|
|
TEST_METHOD(AltBufferClampMouse);
|
|
|
|
TEST_CLASS_SETUP(ClassSetup)
|
|
{
|
|
winrt::init_apartment(winrt::apartment_type::single_threaded);
|
|
|
|
return true;
|
|
}
|
|
TEST_CLASS_CLEANUP(ClassCleanup)
|
|
{
|
|
winrt::uninit_apartment();
|
|
return true;
|
|
}
|
|
|
|
std::tuple<winrt::com_ptr<MockControlSettings>,
|
|
winrt::com_ptr<MockConnection>>
|
|
_createSettingsAndConnection()
|
|
{
|
|
Log::Comment(L"Create settings object");
|
|
auto settings = winrt::make_self<MockControlSettings>();
|
|
VERIFY_IS_NOT_NULL(settings);
|
|
|
|
Log::Comment(L"Create connection object");
|
|
auto conn = winrt::make_self<MockConnection>();
|
|
VERIFY_IS_NOT_NULL(conn);
|
|
|
|
return { settings, conn };
|
|
}
|
|
|
|
std::tuple<winrt::com_ptr<Control::implementation::ControlCore>,
|
|
winrt::com_ptr<Control::implementation::ControlInteractivity>>
|
|
_createCoreAndInteractivity(Control::IControlSettings settings,
|
|
TerminalConnection::ITerminalConnection conn)
|
|
{
|
|
Log::Comment(L"Create ControlInteractivity object");
|
|
auto interactivity = winrt::make_self<Control::implementation::ControlInteractivity>(settings, settings, conn);
|
|
VERIFY_IS_NOT_NULL(interactivity);
|
|
auto core = interactivity->_core;
|
|
core->_inUnitTests = true;
|
|
VERIFY_IS_NOT_NULL(core);
|
|
|
|
return { core, interactivity };
|
|
}
|
|
|
|
void _standardInit(winrt::com_ptr<Control::implementation::ControlCore> core,
|
|
winrt::com_ptr<Control::implementation::ControlInteractivity> interactivity)
|
|
{
|
|
// "Consolas" ends up with an actual size of 9x19 at 96DPI. So
|
|
// let's just arbitrarily start with a 270x380px (30x20 chars) window
|
|
core->Initialize(270, 380, 1.0);
|
|
#ifndef NDEBUG
|
|
core->_terminal->_suppressLockChecks = true;
|
|
#endif
|
|
VERIFY_IS_TRUE(core->_initializedTerminal);
|
|
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
|
|
interactivity->Initialize();
|
|
}
|
|
|
|
// Returns a scope_exit callback that should be used to ensure all
|
|
// output is drained.
|
|
auto _addInputCallback(const winrt::com_ptr<MockConnection>& conn,
|
|
std::deque<std::wstring>& expectedOutput)
|
|
{
|
|
conn->TerminalOutput([&](const hstring& hstr) {
|
|
VERIFY_IS_GREATER_THAN(expectedOutput.size(), 0u);
|
|
const auto expected = expectedOutput.front();
|
|
expectedOutput.pop_front();
|
|
Log::Comment(fmt::format(L"Received: \"{}\"", TerminalCoreUnitTests::TestUtils::ReplaceEscapes(hstr.c_str())).c_str());
|
|
Log::Comment(fmt::format(L"Expected: \"{}\"", TerminalCoreUnitTests::TestUtils::ReplaceEscapes(expected)).c_str());
|
|
VERIFY_ARE_EQUAL(expected, hstr);
|
|
});
|
|
|
|
return std::move(wil::scope_exit([&]() {
|
|
VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Validate we drained all the expected output");
|
|
}));
|
|
}
|
|
};
|
|
|
|
void ControlInteractivityTests::TestAdjustAcrylic()
|
|
{
|
|
Log::Comment(L"Test that scrolling the mouse wheel with Ctrl+Shift changes opacity");
|
|
Log::Comment(L"(This test won't log as it goes, because it does some 200 verifications.)");
|
|
|
|
WEX::TestExecution::SetVerifyOutput verifyOutputScope{ WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures };
|
|
|
|
BEGIN_TEST_METHOD_PROPERTIES()
|
|
TEST_METHOD_PROPERTY(L"Data:useAcrylic", L"{true, false}")
|
|
END_TEST_METHOD_PROPERTIES()
|
|
bool useAcrylic;
|
|
VERIFY_SUCCEEDED(TestData::TryGetValue(L"useAcrylic", useAcrylic), L"whether or not we should enable acrylic");
|
|
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
|
|
settings->UseAcrylic(useAcrylic);
|
|
settings->Opacity(0.5f);
|
|
|
|
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
|
|
|
|
// A callback to make sure that we're raising TransparencyChanged events
|
|
auto expectedOpacity = 0.5f;
|
|
auto opacityCallback = [&](auto&&, Control::TransparencyChangedEventArgs args) mutable {
|
|
VERIFY_ARE_EQUAL(expectedOpacity, args.Opacity());
|
|
VERIFY_ARE_EQUAL(expectedOpacity, core->Opacity());
|
|
// The Settings object's opacity shouldn't be changed
|
|
VERIFY_ARE_EQUAL(0.5f, settings->Opacity());
|
|
|
|
auto expectedUseAcrylic = expectedOpacity < 1.0f &&
|
|
(useAcrylic);
|
|
VERIFY_ARE_EQUAL(useAcrylic, settings->UseAcrylic());
|
|
VERIFY_ARE_EQUAL(expectedUseAcrylic, core->UseAcrylic());
|
|
};
|
|
core->TransparencyChanged(opacityCallback);
|
|
|
|
const auto modifiers = ControlKeyStates(ControlKeyStates::RightCtrlPressed | ControlKeyStates::ShiftPressed);
|
|
const Control::MouseButtonState buttonState{};
|
|
|
|
Log::Comment(L"Scroll in the positive direction, increasing opacity");
|
|
// Scroll more than enough times to get to 1.0 from .5.
|
|
for (auto i = 0; i < 55; i++)
|
|
{
|
|
// each mouse wheel only adjusts opacity by .01
|
|
expectedOpacity += 0.01f;
|
|
if (expectedOpacity >= 1.0f)
|
|
{
|
|
expectedOpacity = 1.0f;
|
|
}
|
|
|
|
// The mouse location and buttons don't matter here.
|
|
interactivity->MouseWheel(modifiers,
|
|
30,
|
|
Core::Point{ 0, 0 },
|
|
buttonState);
|
|
}
|
|
|
|
Log::Comment(L"Scroll in the negative direction, decreasing opacity");
|
|
// Scroll more than enough times to get to 0.0 from 1.0
|
|
for (auto i = 0; i < 105; i++)
|
|
{
|
|
// each mouse wheel only adjusts opacity by .01
|
|
expectedOpacity -= 0.01f;
|
|
if (expectedOpacity <= 0.0f)
|
|
{
|
|
expectedOpacity = 0.0f;
|
|
}
|
|
|
|
// The mouse location and buttons don't matter here.
|
|
interactivity->MouseWheel(modifiers,
|
|
-30,
|
|
Core::Point{ 0, 0 },
|
|
buttonState);
|
|
}
|
|
}
|
|
|
|
void ControlInteractivityTests::TestScrollWithMouse()
|
|
{
|
|
BEGIN_TEST_METHOD_PROPERTIES()
|
|
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
|
END_TEST_METHOD_PROPERTIES()
|
|
|
|
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
|
|
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
|
|
_standardInit(core, interactivity);
|
|
// For the sake of this test, scroll one line at a time
|
|
interactivity->_rowsToScroll = 1;
|
|
|
|
auto expectedTop = 0;
|
|
auto expectedViewHeight = 20;
|
|
auto expectedBufferHeight = 20;
|
|
|
|
auto scrollChangedHandler = [&](auto&&, const Control::ScrollPositionChangedArgs& args) mutable {
|
|
VERIFY_ARE_EQUAL(expectedTop, args.ViewTop());
|
|
VERIFY_ARE_EQUAL(expectedViewHeight, args.ViewHeight());
|
|
VERIFY_ARE_EQUAL(expectedBufferHeight, args.BufferSize());
|
|
};
|
|
core->ScrollPositionChanged(scrollChangedHandler);
|
|
interactivity->ScrollPositionChanged(scrollChangedHandler);
|
|
|
|
for (auto i = 0; i < 40; ++i)
|
|
{
|
|
Log::Comment(NoThrowString().Format(L"Writing line #%d", i));
|
|
// The \r\n in the 19th loop will cause the view to start moving
|
|
if (i >= 19)
|
|
{
|
|
expectedTop++;
|
|
expectedBufferHeight++;
|
|
}
|
|
|
|
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
|
|
}
|
|
// We printed that 40 times, but the final \r\n bumped the view down one MORE row.
|
|
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
VERIFY_ARE_EQUAL(20, core->ViewHeight());
|
|
VERIFY_ARE_EQUAL(41, core->BufferHeight());
|
|
|
|
Log::Comment(L"Scroll up a line");
|
|
const Control::MouseButtonState buttonState{};
|
|
const auto modifiers = ControlKeyStates();
|
|
expectedBufferHeight = 41;
|
|
expectedTop = 20;
|
|
|
|
interactivity->MouseWheel(modifiers,
|
|
WHEEL_DELTA,
|
|
Core::Point{ 0, 0 },
|
|
buttonState);
|
|
|
|
Log::Comment(L"Scroll up 19 more times, to the top");
|
|
for (auto i = 0; i < 20; ++i)
|
|
{
|
|
expectedTop--;
|
|
interactivity->MouseWheel(modifiers,
|
|
WHEEL_DELTA,
|
|
Core::Point{ 0, 0 },
|
|
buttonState);
|
|
}
|
|
Log::Comment(L"Scrolling up more should do nothing");
|
|
expectedTop = 0;
|
|
interactivity->MouseWheel(modifiers,
|
|
WHEEL_DELTA,
|
|
Core::Point{ 0, 0 },
|
|
buttonState);
|
|
interactivity->MouseWheel(modifiers,
|
|
WHEEL_DELTA,
|
|
Core::Point{ 0, 0 },
|
|
buttonState);
|
|
|
|
Log::Comment(L"Scroll down 21 more times, to the bottom");
|
|
for (auto i = 0; i < 21; ++i)
|
|
{
|
|
Log::Comment(NoThrowString().Format(L"---scroll down #%d---", i));
|
|
expectedTop++;
|
|
interactivity->MouseWheel(modifiers,
|
|
-WHEEL_DELTA,
|
|
Core::Point{ 0, 0 },
|
|
buttonState);
|
|
Log::Comment(NoThrowString().Format(L"internal scrollbar pos:%f", interactivity->_internalScrollbarPosition));
|
|
}
|
|
Log::Comment(L"Scrolling down more should do nothing");
|
|
expectedTop = 21;
|
|
interactivity->MouseWheel(modifiers,
|
|
-WHEEL_DELTA,
|
|
Core::Point{ 0, 0 },
|
|
buttonState);
|
|
interactivity->MouseWheel(modifiers,
|
|
-WHEEL_DELTA,
|
|
Core::Point{ 0, 0 },
|
|
buttonState);
|
|
}
|
|
|
|
void ControlInteractivityTests::CreateSubsequentSelectionWithDragging()
|
|
{
|
|
BEGIN_TEST_METHOD_PROPERTIES()
|
|
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
|
END_TEST_METHOD_PROPERTIES()
|
|
|
|
// This is a test for GH#9725
|
|
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
|
|
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
|
|
_standardInit(core, interactivity);
|
|
|
|
// For this test, don't use any modifiers
|
|
const auto modifiers = ControlKeyStates();
|
|
const auto leftMouseDown{ Control::MouseButtonState::IsLeftButtonDown };
|
|
const Control::MouseButtonState noMouseDown{};
|
|
|
|
const til::size fontSize{ 9, 21 };
|
|
|
|
Log::Comment(L"Click on the terminal");
|
|
const til::point terminalPosition0{ 0, 0 };
|
|
const auto cursorPosition0 = terminalPosition0 * fontSize;
|
|
interactivity->PointerPressed(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
0, // timestamp
|
|
modifiers,
|
|
cursorPosition0.to_core_point());
|
|
Log::Comment(L"Verify that there's not yet a selection");
|
|
|
|
VERIFY_IS_FALSE(core->HasSelection());
|
|
|
|
Log::Comment(L"Drag the mouse just a little");
|
|
// move not quite a whole cell, but enough to start a selection
|
|
const til::point terminalPosition1{ 0, 0 };
|
|
const til::point cursorPosition1{ 6, 0 };
|
|
interactivity->PointerMoved(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
modifiers,
|
|
true, // focused,
|
|
cursorPosition1.to_core_point(),
|
|
true);
|
|
Log::Comment(L"Verify that there's one selection");
|
|
VERIFY_IS_TRUE(core->HasSelection());
|
|
|
|
Log::Comment(L"Drag the mouse down a whole row");
|
|
const til::point terminalPosition2{ 1, 1 };
|
|
const auto cursorPosition2 = terminalPosition2 * fontSize;
|
|
interactivity->PointerMoved(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
modifiers,
|
|
true, // focused,
|
|
cursorPosition2.to_core_point(),
|
|
true);
|
|
Log::Comment(L"Verify that there's now two selections (one on each row)");
|
|
VERIFY_IS_TRUE(core->HasSelection());
|
|
|
|
Log::Comment(L"Release the mouse");
|
|
interactivity->PointerReleased(noMouseDown,
|
|
WM_LBUTTONUP, //pointerUpdateKind
|
|
modifiers,
|
|
cursorPosition2.to_core_point());
|
|
Log::Comment(L"Verify that there's still two selections");
|
|
VERIFY_IS_TRUE(core->HasSelection());
|
|
|
|
Log::Comment(L"click outside the current selection");
|
|
const til::point terminalPosition3{ 2, 2 };
|
|
const auto cursorPosition3 = terminalPosition3 * fontSize;
|
|
interactivity->PointerPressed(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
0, // timestamp
|
|
modifiers,
|
|
cursorPosition3.to_core_point());
|
|
Log::Comment(L"Verify that there's now no selection");
|
|
VERIFY_IS_FALSE(core->HasSelection());
|
|
|
|
Log::Comment(L"Drag the mouse");
|
|
const til::point terminalPosition4{ 3, 2 };
|
|
const auto cursorPosition4 = terminalPosition4 * fontSize;
|
|
interactivity->PointerMoved(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
modifiers,
|
|
true, // focused,
|
|
cursorPosition4.to_core_point(),
|
|
true);
|
|
Log::Comment(L"Verify that there's now one selection");
|
|
VERIFY_IS_TRUE(core->HasSelection());
|
|
}
|
|
|
|
void ControlInteractivityTests::ScrollWithSelection()
|
|
{
|
|
// This is a test for GH#9955.a
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
|
|
_standardInit(core, interactivity);
|
|
// For the sake of this test, scroll one line at a time
|
|
interactivity->_rowsToScroll = 1;
|
|
|
|
Log::Comment(L"Add some test to the terminal so we can scroll");
|
|
for (auto i = 0; i < 40; ++i)
|
|
{
|
|
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
|
|
}
|
|
// We printed that 40 times, but the final \r\n bumped the view down one MORE row.
|
|
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
VERIFY_ARE_EQUAL(20, core->ViewHeight());
|
|
VERIFY_ARE_EQUAL(41, core->BufferHeight());
|
|
|
|
// For this test, don't use any modifiers
|
|
const auto modifiers = ControlKeyStates();
|
|
const auto leftMouseDown{ Control::MouseButtonState::IsLeftButtonDown };
|
|
|
|
const til::size fontSize{ 9, 21 };
|
|
|
|
Log::Comment(L"Click on the terminal");
|
|
const til::point terminalPosition0{ 5, 5 };
|
|
const auto cursorPosition0{ terminalPosition0 * fontSize };
|
|
interactivity->PointerPressed(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
0, // timestamp
|
|
modifiers,
|
|
cursorPosition0.to_core_point());
|
|
|
|
Log::Comment(L"Verify that there's not yet a selection");
|
|
VERIFY_IS_FALSE(core->HasSelection());
|
|
|
|
VERIFY_IS_TRUE(interactivity->_singleClickTouchdownPos.has_value());
|
|
VERIFY_ARE_EQUAL(cursorPosition0.to_core_point(), interactivity->_singleClickTouchdownPos.value());
|
|
|
|
Log::Comment(L"Drag the mouse just a little");
|
|
// move not quite a whole cell, but enough to start a selection
|
|
const auto cursorPosition1{ cursorPosition0 + til::point{ 6, 0 } };
|
|
interactivity->PointerMoved(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
modifiers,
|
|
true, // focused,
|
|
cursorPosition1.to_core_point(),
|
|
true);
|
|
Log::Comment(L"Verify that there's one selection");
|
|
VERIFY_IS_TRUE(core->HasSelection());
|
|
|
|
Log::Comment(L"Verify the location of the selection");
|
|
// The viewport is on row 21, so the selection will be on:
|
|
// {(5, 5)+(0, 21)} to {(5, 5)+(0, 21)}
|
|
til::point expectedAnchor{ 5, 26 };
|
|
til::point expectedEnd{ 6, 26 }; // add 1 to x-coordinate because end is exclusive
|
|
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionAnchor());
|
|
VERIFY_ARE_EQUAL(expectedEnd, core->_terminal->GetSelectionEnd());
|
|
|
|
Log::Comment(L"Scroll up a line, with the left mouse button selected");
|
|
interactivity->MouseWheel(modifiers,
|
|
WHEEL_DELTA,
|
|
cursorPosition1.to_core_point(),
|
|
leftMouseDown);
|
|
|
|
Log::Comment(L"Verify the location of the selection");
|
|
// The viewport is now on row 20, so the selection will be on:
|
|
// {(5 + 1, 5)+(0, 20)} to {(5, 5)+(0, 21)}
|
|
// NOTE: newExpectedAnchor should be expectedEnd moved up one row
|
|
til::point newExpectedAnchor{ 6, 25 };
|
|
// Remember, the anchor is always before the end in the buffer. So yes,
|
|
// we started the selection on 5,26, but now that's the end.
|
|
VERIFY_ARE_EQUAL(newExpectedAnchor, core->_terminal->GetSelectionAnchor());
|
|
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionEnd());
|
|
}
|
|
|
|
void ControlInteractivityTests::TestScrollWithTrackpad()
|
|
{
|
|
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
|
|
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
|
|
_standardInit(core, interactivity);
|
|
// For the sake of this test, scroll one line at a time
|
|
interactivity->_rowsToScroll = 1;
|
|
|
|
for (auto i = 0; i < 40; ++i)
|
|
{
|
|
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
|
|
}
|
|
// We printed that 40 times, but the final \r\n bumped the view down one MORE row.
|
|
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
VERIFY_ARE_EQUAL(20, core->ViewHeight());
|
|
VERIFY_ARE_EQUAL(41, core->BufferHeight());
|
|
|
|
Log::Comment(L"Scroll up a line");
|
|
const auto modifiers = ControlKeyStates();
|
|
|
|
// Deltas that I saw while scrolling with the surface laptop trackpad
|
|
// were on the range [-22, 7], though I'm sure they could be greater in
|
|
// magnitude.
|
|
//
|
|
// WHEEL_DELTA is 120, so we'll use 24 for now as the delta, just so the tests don't take forever.
|
|
|
|
const auto delta = WHEEL_DELTA / 5;
|
|
const Core::Point mousePos{ 0, 0 };
|
|
Control::MouseButtonState state{};
|
|
|
|
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
|
|
Log::Comment(L"Scroll up 4 more times. Once we're at 3/5 scrolls, "
|
|
L"we'll round the internal scrollbar position to scrolling to the next row.");
|
|
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 3/5
|
|
VERIFY_ARE_EQUAL(20, core->ScrollOffset());
|
|
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 4/5
|
|
VERIFY_ARE_EQUAL(20, core->ScrollOffset());
|
|
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 5/5
|
|
VERIFY_ARE_EQUAL(20, core->ScrollOffset());
|
|
|
|
Log::Comment(L"Jump to line 5, so we can scroll down from there.");
|
|
interactivity->UpdateScrollbar(5);
|
|
VERIFY_ARE_EQUAL(5, core->ScrollOffset());
|
|
Log::Comment(L"Scroll down 5 times, at which point we should accumulate a whole row of delta.");
|
|
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 1/5
|
|
VERIFY_ARE_EQUAL(5, core->ScrollOffset());
|
|
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 2/5
|
|
VERIFY_ARE_EQUAL(5, core->ScrollOffset());
|
|
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 3/5
|
|
VERIFY_ARE_EQUAL(6, core->ScrollOffset());
|
|
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 4/5
|
|
VERIFY_ARE_EQUAL(6, core->ScrollOffset());
|
|
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 5/5
|
|
VERIFY_ARE_EQUAL(6, core->ScrollOffset());
|
|
|
|
Log::Comment(L"Jump to the bottom.");
|
|
interactivity->UpdateScrollbar(21);
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
Log::Comment(L"Scroll a bit, then emit a line of text. We should reset our internal scroll position.");
|
|
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
|
|
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
|
|
VERIFY_ARE_EQUAL(22, core->ScrollOffset());
|
|
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5
|
|
VERIFY_ARE_EQUAL(22, core->ScrollOffset());
|
|
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5
|
|
VERIFY_ARE_EQUAL(22, core->ScrollOffset());
|
|
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 3/5
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 4/5
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 5/5
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
}
|
|
|
|
void ControlInteractivityTests::TestQuickDragOnSelect()
|
|
{
|
|
// This is a test for GH#9955.c
|
|
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
|
|
_standardInit(core, interactivity);
|
|
|
|
// For this test, don't use any modifiers
|
|
const auto modifiers = ControlKeyStates();
|
|
const auto leftMouseDown{ Control::MouseButtonState::IsLeftButtonDown };
|
|
|
|
const til::size fontSize{ 9, 21 };
|
|
|
|
Log::Comment(L"Click on the terminal");
|
|
const til::point cursorPosition0{ 6, 0 };
|
|
interactivity->PointerPressed(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
0, // timestamp
|
|
modifiers,
|
|
cursorPosition0.to_core_point());
|
|
|
|
Log::Comment(L"Verify that there's not yet a selection");
|
|
VERIFY_IS_FALSE(core->HasSelection());
|
|
|
|
VERIFY_IS_TRUE(interactivity->_singleClickTouchdownPos.has_value());
|
|
VERIFY_ARE_EQUAL(cursorPosition0.to_core_point(), interactivity->_singleClickTouchdownPos.value());
|
|
|
|
Log::Comment(L"Drag the mouse a lot. This simulates dragging the mouse real fast.");
|
|
const til::point cursorPosition1{ 6 + fontSize.width * 2, 0 };
|
|
interactivity->PointerMoved(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
modifiers,
|
|
true, // focused,
|
|
cursorPosition1.to_core_point(),
|
|
true);
|
|
Log::Comment(L"Verify that there's one selection");
|
|
VERIFY_IS_TRUE(core->HasSelection());
|
|
|
|
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 };
|
|
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionAnchor());
|
|
}
|
|
|
|
void ControlInteractivityTests::TestDragSelectOutsideBounds()
|
|
{
|
|
// This is a test for GH#4603
|
|
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
|
|
_standardInit(core, interactivity);
|
|
|
|
// For this test, don't use any modifiers
|
|
const auto modifiers = ControlKeyStates();
|
|
const auto leftMouseDown{ Control::MouseButtonState::IsLeftButtonDown };
|
|
const Control::MouseButtonState noMouseDown{};
|
|
|
|
const til::size fontSize{ 9, 21 };
|
|
Log::Comment(L"Click on the terminal");
|
|
const til::point cursorPosition0{ 6, 0 };
|
|
interactivity->PointerPressed(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
0, // timestamp
|
|
modifiers,
|
|
cursorPosition0.to_core_point());
|
|
|
|
Log::Comment(L"Verify that there's not yet a selection");
|
|
VERIFY_IS_FALSE(core->HasSelection());
|
|
|
|
VERIFY_IS_TRUE(interactivity->_singleClickTouchdownPos.has_value());
|
|
VERIFY_ARE_EQUAL(cursorPosition0.to_core_point(), interactivity->_singleClickTouchdownPos.value());
|
|
|
|
Log::Comment(L"Drag the mouse a lot. This simulates dragging the mouse real fast.");
|
|
const til::point cursorPosition1{ 6 + fontSize.width * 2, 0 };
|
|
interactivity->PointerMoved(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
modifiers,
|
|
true, // focused,
|
|
cursorPosition1.to_core_point(),
|
|
true);
|
|
Log::Comment(L"Verify that there's one selection");
|
|
VERIFY_IS_TRUE(core->HasSelection());
|
|
|
|
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 };
|
|
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionAnchor());
|
|
til::point expectedEnd{ 3, 0 }; // add 1 to x-coordinate because end is exclusive
|
|
VERIFY_ARE_EQUAL(expectedEnd, core->_terminal->GetSelectionEnd());
|
|
|
|
interactivity->PointerReleased(noMouseDown,
|
|
WM_LBUTTONUP,
|
|
modifiers,
|
|
cursorPosition1.to_core_point());
|
|
|
|
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionAnchor());
|
|
VERIFY_ARE_EQUAL(expectedEnd, core->_terminal->GetSelectionEnd());
|
|
|
|
Log::Comment(L"Simulate dragging the mouse into the control, without first clicking into the control");
|
|
const til::point cursorPosition2{ fontSize.width * 10, 0 };
|
|
interactivity->PointerMoved(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
modifiers,
|
|
true, // focused,
|
|
cursorPosition2.to_core_point(),
|
|
false);
|
|
|
|
Log::Comment(L"The selection should be unchanged.");
|
|
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionAnchor());
|
|
VERIFY_ARE_EQUAL(expectedEnd, core->_terminal->GetSelectionEnd());
|
|
}
|
|
|
|
void ControlInteractivityTests::PointerClickOutsideActiveRegion()
|
|
{
|
|
// This is a test for GH#10642
|
|
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
|
|
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
|
|
_standardInit(core, interactivity);
|
|
|
|
// For this test, don't use any modifiers
|
|
const auto modifiers = ControlKeyStates();
|
|
const auto leftMouseDown{ Control::MouseButtonState::IsLeftButtonDown };
|
|
const Control::MouseButtonState noMouseDown{};
|
|
|
|
const til::size fontSize{ 9, 21 };
|
|
interactivity->_rowsToScroll = 1;
|
|
auto expectedTop = 0;
|
|
auto expectedViewHeight = 20;
|
|
auto expectedBufferHeight = 20;
|
|
|
|
auto scrollChangedHandler = [&](auto&&, const Control::ScrollPositionChangedArgs& args) mutable {
|
|
VERIFY_ARE_EQUAL(expectedTop, args.ViewTop());
|
|
VERIFY_ARE_EQUAL(expectedViewHeight, args.ViewHeight());
|
|
VERIFY_ARE_EQUAL(expectedBufferHeight, args.BufferSize());
|
|
};
|
|
core->ScrollPositionChanged(scrollChangedHandler);
|
|
interactivity->ScrollPositionChanged(scrollChangedHandler);
|
|
|
|
for (auto i = 0; i < 40; ++i)
|
|
{
|
|
Log::Comment(NoThrowString().Format(L"Writing line #%d", i));
|
|
// The \r\n in the 19th loop will cause the view to start moving
|
|
if (i >= 19)
|
|
{
|
|
expectedTop++;
|
|
expectedBufferHeight++;
|
|
}
|
|
|
|
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
|
|
}
|
|
// We printed that 40 times, but the final \r\n bumped the view down one MORE row.
|
|
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
VERIFY_ARE_EQUAL(20, core->ViewHeight());
|
|
VERIFY_ARE_EQUAL(41, core->BufferHeight());
|
|
|
|
expectedBufferHeight = 41;
|
|
expectedTop = 21;
|
|
|
|
Log::Comment(L"Scroll up 10 times");
|
|
for (auto i = 0; i < 11; ++i)
|
|
{
|
|
expectedTop--;
|
|
interactivity->MouseWheel(modifiers,
|
|
WHEEL_DELTA,
|
|
Core::Point{ 0, 0 },
|
|
noMouseDown);
|
|
}
|
|
|
|
// Enable VT mouse event tracking
|
|
conn->WriteInput(winrt_wstring_to_array_view(L"\x1b[?1003;1006h"));
|
|
|
|
// Mouse clicks in the inactive region (i.e. the top 10 rows in this case) should not register
|
|
Log::Comment(L"Click on the terminal");
|
|
const til::point terminalPosition0{ 4, 4 };
|
|
const auto cursorPosition0 = terminalPosition0 * fontSize;
|
|
interactivity->PointerPressed(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
0, // timestamp
|
|
modifiers,
|
|
cursorPosition0.to_core_point());
|
|
Log::Comment(L"Verify that there's not yet a selection");
|
|
|
|
VERIFY_IS_FALSE(core->HasSelection());
|
|
|
|
Log::Comment(L"Drag the mouse");
|
|
// move the mouse as if to make a selection
|
|
const til::point terminalPosition1{ 10, 4 };
|
|
const auto cursorPosition1 = terminalPosition1 * fontSize;
|
|
interactivity->PointerMoved(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
modifiers,
|
|
true, // focused,
|
|
cursorPosition1.to_core_point(),
|
|
true);
|
|
Log::Comment(L"Verify that there's still no selection");
|
|
VERIFY_IS_FALSE(core->HasSelection());
|
|
}
|
|
|
|
void ControlInteractivityTests::IncrementCircularBufferWithSelection()
|
|
{
|
|
// This is a test for GH#10749
|
|
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
|
|
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
|
|
_standardInit(core, interactivity);
|
|
|
|
Log::Comment(L"Fill up the history buffer");
|
|
const auto scrollbackLength = settings->HistorySize();
|
|
// Output lines equal to history size + viewport height to make sure we're
|
|
// at the point where outputting more lines causes circular incrementing
|
|
for (auto i = 0; i < settings->HistorySize() + core->ViewHeight(); ++i)
|
|
{
|
|
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
|
|
}
|
|
VERIFY_ARE_EQUAL(scrollbackLength, core->_terminal->GetScrollOffset());
|
|
|
|
// For this test, don't use any modifiers
|
|
const auto modifiers = ControlKeyStates();
|
|
const auto leftMouseDown{ Control::MouseButtonState::IsLeftButtonDown };
|
|
|
|
const til::size fontSize{ 9, 21 };
|
|
|
|
Log::Comment(L"Click on the terminal");
|
|
const til::point terminalPosition0{ 5, 5 };
|
|
const auto cursorPosition0{ terminalPosition0 * fontSize };
|
|
interactivity->PointerPressed(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
0, // timestamp
|
|
modifiers,
|
|
cursorPosition0.to_core_point());
|
|
|
|
Log::Comment(L"Verify that there's not yet a selection");
|
|
VERIFY_IS_FALSE(core->HasSelection());
|
|
|
|
VERIFY_IS_TRUE(interactivity->_singleClickTouchdownPos.has_value());
|
|
VERIFY_ARE_EQUAL(cursorPosition0.to_core_point(), interactivity->_singleClickTouchdownPos.value());
|
|
|
|
Log::Comment(L"Drag the mouse just a little");
|
|
// move not quite a whole cell, but enough to start a selection
|
|
const auto cursorPosition1{ cursorPosition0 + til::point{ 6, 0 } };
|
|
interactivity->PointerMoved(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
modifiers,
|
|
true, // focused,
|
|
cursorPosition1.to_core_point(),
|
|
true);
|
|
Log::Comment(L"Verify that there's one selection");
|
|
VERIFY_IS_TRUE(core->HasSelection());
|
|
|
|
Log::Comment(L"Verify the location of the selection");
|
|
// The viewport is on row (historySize + 5), so the selection will be on:
|
|
// {(5, (historySize+5))+(0, 21)} to {(5, (historySize+5))+(0, 21)}
|
|
til::point expectedAnchor{ 5, settings->HistorySize() + 5 };
|
|
til::point expectedEnd{ 6, expectedAnchor.y }; // add 1 to x-coordinate because end is exclusive
|
|
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionAnchor());
|
|
VERIFY_ARE_EQUAL(expectedEnd, core->_terminal->GetSelectionEnd());
|
|
|
|
Log::Comment(L"Output a line of text");
|
|
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
|
|
|
|
Log::Comment(L"Verify the location of the selection");
|
|
// The selection should now be 1 row lower
|
|
expectedAnchor.y -= 1;
|
|
expectedEnd.y -= 1;
|
|
{
|
|
const auto anchor{ core->_terminal->GetSelectionAnchor() };
|
|
const auto end{ core->_terminal->GetSelectionEnd() };
|
|
Log::Comment(fmt::format(L"expectedAnchor:({},{})", expectedAnchor.x, expectedAnchor.y).c_str());
|
|
Log::Comment(fmt::format(L"anchor:({},{})", anchor.x, anchor.y).c_str());
|
|
Log::Comment(fmt::format(L"end:({},{})", end.x, end.y).c_str());
|
|
|
|
VERIFY_ARE_EQUAL(expectedAnchor, anchor);
|
|
VERIFY_ARE_EQUAL(expectedEnd, end);
|
|
}
|
|
VERIFY_ARE_EQUAL(scrollbackLength - 1, core->_terminal->GetScrollOffset());
|
|
|
|
Log::Comment(L"Output a line of text");
|
|
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
|
|
|
|
Log::Comment(L"Verify the location of the selection");
|
|
// The selection should now be 1 row lower
|
|
expectedAnchor.y -= 1;
|
|
expectedEnd.y -= 1;
|
|
{
|
|
const auto anchor{ core->_terminal->GetSelectionAnchor() };
|
|
const auto end{ core->_terminal->GetSelectionEnd() };
|
|
Log::Comment(fmt::format(L"expectedAnchor:({},{})", expectedAnchor.x, expectedAnchor.y).c_str());
|
|
Log::Comment(fmt::format(L"anchor:({},{})", anchor.x, anchor.y).c_str());
|
|
Log::Comment(fmt::format(L"end:({},{})", end.x, end.y).c_str());
|
|
|
|
VERIFY_ARE_EQUAL(expectedAnchor, anchor);
|
|
VERIFY_ARE_EQUAL(expectedEnd, end);
|
|
}
|
|
VERIFY_ARE_EQUAL(scrollbackLength - 2, core->_terminal->GetScrollOffset());
|
|
|
|
Log::Comment(L"Move the mouse a little, to update the selection");
|
|
// At this point, there should only be one selection region! The
|
|
// viewport moved up to keep the selection at the same relative spot. So
|
|
// wiggling the cursor should continue to select only the same
|
|
// character in the buffer (if, albeit in a new location).
|
|
//
|
|
// This helps test GH #14462, a regression from #10749.
|
|
interactivity->PointerMoved(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
modifiers,
|
|
true, // focused,
|
|
cursorPosition0.to_core_point(),
|
|
true);
|
|
VERIFY_IS_TRUE(core->HasSelection());
|
|
{
|
|
const auto anchor{ core->_terminal->GetSelectionAnchor() };
|
|
const auto end{ core->_terminal->GetSelectionEnd() };
|
|
Log::Comment(fmt::format(L"expectedAnchor:({},{})", expectedAnchor.x, expectedAnchor.y).c_str());
|
|
Log::Comment(fmt::format(L"anchor:({},{})", anchor.x, anchor.y).c_str());
|
|
Log::Comment(fmt::format(L"end:({},{})", end.x, end.y).c_str());
|
|
|
|
// Selection was updated, but we didn't highlight a full cell
|
|
VERIFY_ARE_EQUAL(expectedAnchor, anchor);
|
|
VERIFY_ARE_EQUAL(expectedAnchor, end);
|
|
}
|
|
|
|
Log::Comment(L"Output a line and move the mouse a little to update the selection, all at once");
|
|
// Same as above. The viewport has moved, so the mouse is still over the
|
|
// same character, even though it's at a new offset.
|
|
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
|
|
expectedAnchor.y -= 1;
|
|
expectedEnd.y -= 1;
|
|
VERIFY_ARE_EQUAL(scrollbackLength - 3, core->_terminal->GetScrollOffset());
|
|
interactivity->PointerMoved(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
modifiers,
|
|
true, // focused,
|
|
cursorPosition1.to_core_point(),
|
|
true);
|
|
VERIFY_IS_TRUE(core->HasSelection());
|
|
{
|
|
const auto anchor{ core->_terminal->GetSelectionAnchor() };
|
|
const auto end{ core->_terminal->GetSelectionEnd() };
|
|
Log::Comment(fmt::format(L"expectedAnchor:({},{})", expectedAnchor.x, expectedAnchor.y).c_str());
|
|
Log::Comment(fmt::format(L"anchor:({},{})", anchor.x, anchor.y).c_str());
|
|
Log::Comment(fmt::format(L"end:({},{})", end.x, end.y).c_str());
|
|
|
|
VERIFY_ARE_EQUAL(expectedAnchor, anchor);
|
|
VERIFY_ARE_EQUAL(expectedEnd, end);
|
|
}
|
|
|
|
// Output enough text for the selection to get pushed off the buffer
|
|
for (auto i = 0; i < settings->HistorySize() + core->ViewHeight(); ++i)
|
|
{
|
|
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
|
|
}
|
|
// Verify that the selection got reset
|
|
VERIFY_IS_FALSE(core->HasSelection());
|
|
}
|
|
|
|
void ControlInteractivityTests::GetMouseEventsInTest()
|
|
{
|
|
// This is just a simple case that proves you can test mouse events
|
|
// generated by the terminal
|
|
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
|
|
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
|
|
_standardInit(core, interactivity);
|
|
|
|
std::deque<std::wstring> expectedOutput{};
|
|
auto validateDrained = _addInputCallback(conn, expectedOutput);
|
|
|
|
Log::Comment(L"Enable mouse mode");
|
|
auto& term{ *core->_terminal };
|
|
term.Write(L"\x1b[?1000h");
|
|
|
|
Log::Comment(L"Click on the terminal");
|
|
|
|
expectedOutput.push_back(L"\x1b[M &&");
|
|
// For this test, don't use any modifiers
|
|
const auto modifiers = ControlKeyStates();
|
|
const auto leftMouseDown{ Control::MouseButtonState::IsLeftButtonDown };
|
|
const til::size fontSize{ 9, 21 };
|
|
const til::point terminalPosition0{ 5, 5 };
|
|
const auto cursorPosition0{ terminalPosition0 * fontSize };
|
|
interactivity->PointerPressed(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
0, // timestamp
|
|
modifiers,
|
|
cursorPosition0.to_core_point());
|
|
}
|
|
|
|
void ControlInteractivityTests::AltBufferClampMouse()
|
|
{
|
|
// This is a test for
|
|
// * GH#10642
|
|
// * a comment in GH#12719
|
|
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
|
|
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
|
|
_standardInit(core, interactivity);
|
|
auto& term{ *core->_terminal };
|
|
|
|
// Output enough text for view to start scrolling
|
|
for (auto i = 0; i < core->ViewHeight() * 2; ++i)
|
|
{
|
|
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
|
|
}
|
|
|
|
// Start checking output
|
|
std::deque<std::wstring> expectedOutput{};
|
|
auto validateDrained = _addInputCallback(conn, expectedOutput);
|
|
|
|
const auto originalViewport{ term.GetViewport() };
|
|
VERIFY_ARE_EQUAL(originalViewport.Width(), 30);
|
|
|
|
Log::Comment(L" --- Enable mouse mode ---");
|
|
term.Write(L"\x1b[?1000h");
|
|
|
|
Log::Comment(L" --- Click on the terminal ---");
|
|
// Recall:
|
|
//
|
|
// > ! specifies the value 1. The upper left character position on
|
|
// > the terminal is denoted as 1,1
|
|
//
|
|
// So 5 in our buffer is 32+5+1 = '&'
|
|
expectedOutput.push_back(L"\x1b[M &&");
|
|
// For this test, don't use any modifiers
|
|
const auto modifiers = ControlKeyStates();
|
|
const auto leftMouseDown{ Control::MouseButtonState::IsLeftButtonDown };
|
|
const til::size fontSize{ 9, 21 };
|
|
const til::point terminalPosition0{ 5, 5 };
|
|
const auto cursorPosition0{ terminalPosition0 * fontSize };
|
|
interactivity->PointerPressed(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
0, // timestamp
|
|
modifiers,
|
|
cursorPosition0.to_core_point());
|
|
VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Validate we drained all the expected output");
|
|
|
|
// These first two bits are a test for GH#10642
|
|
Log::Comment(L" --- Click on the terminal outside the width of the mutable viewport, see that it's clamped to the viewport ---");
|
|
// Not actually possible, but for validation.
|
|
const til::point terminalPosition1{ originalViewport.Width() + 5, 5 };
|
|
const auto cursorPosition1{ terminalPosition1 * fontSize };
|
|
|
|
// The viewport is only 30 wide, so clamping 35 to the buffer size gets
|
|
// us 29, which converted is (32 + 29 + 1) = 62 = '>'
|
|
expectedOutput.push_back(L"\x1b[M >&");
|
|
interactivity->PointerPressed(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
0, // timestamp
|
|
modifiers,
|
|
cursorPosition1.to_core_point());
|
|
VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Validate we drained all the expected output");
|
|
|
|
Log::Comment(L" --- Scroll up, click the terminal. We shouldn't get any event. ---");
|
|
core->UserScrollViewport(10);
|
|
VERIFY_IS_GREATER_THAN(core->ScrollOffset(), 0);
|
|
|
|
// Viewport is now above the mutable viewport, so the mouse event
|
|
// will be clamped to the top line.
|
|
|
|
expectedOutput.push_back(L"\x1b[M &!"); // 5, 1
|
|
interactivity->PointerPressed(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
0, // timestamp
|
|
modifiers,
|
|
cursorPosition0.to_core_point());
|
|
// Flush it out.
|
|
VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Validate we drained all the expected output");
|
|
|
|
// This is the part as mentioned in GH#12719
|
|
Log::Comment(L" --- Switch to alt buffer ---");
|
|
term.Write(L"\x1b[?1049h");
|
|
auto returnToMain = wil::scope_exit([&]() { term.Write(L"\x1b[?1049h"); });
|
|
|
|
VERIFY_ARE_EQUAL(0, core->ScrollOffset());
|
|
Log::Comment(L" --- Click on a spot that's still outside the buffer ---");
|
|
expectedOutput.push_back(L"\x1b[M >&");
|
|
interactivity->PointerPressed(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
0, // timestamp
|
|
modifiers,
|
|
cursorPosition1.to_core_point());
|
|
|
|
Log::Comment(L" --- Resize the terminal to be 10 columns wider ---");
|
|
const auto newWidth = 40.0f * fontSize.width;
|
|
const auto newHeight = 20.0f * fontSize.height;
|
|
core->SizeChanged(newWidth, newHeight);
|
|
|
|
Log::Comment(L" --- Click on a spot that's NOW INSIDE the buffer ---");
|
|
// (32 + 35 + 1) = 68 = 'D'
|
|
expectedOutput.push_back(L"\x1b[M D&");
|
|
interactivity->PointerPressed(leftMouseDown,
|
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
|
0, // timestamp
|
|
modifiers,
|
|
cursorPosition1.to_core_point());
|
|
VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Validate we drained all the expected output");
|
|
}
|
|
}
|