Fix snapping to the cursor in "Terminal Scrolling" mode (#2705)
fixes #1222 PSReadline calls SetConsoleCursorPosition on each character they emit (go figure). When that function is called, and we set the cursor position, we'll try and "snap" the viewport to the location of the cursor, so that the cursor remains visible. However, we'd only ever do this with the visible viewport, the viewport defined by `SCREEN_INFORMATION::_viewport`. When there's a virtual viewport in Terminal Scrolling mode, we actually need to snap the virtual viewport, so that this behavior looks more regular.
This commit is contained in:
parent
eafa884fc4
commit
6f4b98acb4
|
@ -649,31 +649,42 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
|||
|
||||
LOG_IF_FAILED(ConsoleImeResizeCompStrView());
|
||||
|
||||
COORD WindowOrigin;
|
||||
WindowOrigin.X = 0;
|
||||
WindowOrigin.Y = 0;
|
||||
// Attempt to "snap" the viewport to the cursor position. If the cursor
|
||||
// is not in the current viewport, we'll try and move the viewport so
|
||||
// that the cursor is visible.
|
||||
// microsoft/terminal#1222 - Use the "virtual" viewport here, so that
|
||||
// when the console is in terminal-scrolling mode, the viewport snaps
|
||||
// back to the virtual viewport's location.
|
||||
const SMALL_RECT currentViewport = gci.IsTerminalScrolling() ?
|
||||
buffer.GetVirtualViewport().ToInclusive() :
|
||||
buffer.GetViewport().ToInclusive();
|
||||
COORD delta{ 0 };
|
||||
{
|
||||
const SMALL_RECT currentViewport = buffer.GetViewport().ToInclusive();
|
||||
if (currentViewport.Left > position.X)
|
||||
{
|
||||
WindowOrigin.X = position.X - currentViewport.Left;
|
||||
delta.X = position.X - currentViewport.Left;
|
||||
}
|
||||
else if (currentViewport.Right < position.X)
|
||||
{
|
||||
WindowOrigin.X = position.X - currentViewport.Right;
|
||||
delta.X = position.X - currentViewport.Right;
|
||||
}
|
||||
|
||||
if (currentViewport.Top > position.Y)
|
||||
{
|
||||
WindowOrigin.Y = position.Y - currentViewport.Top;
|
||||
delta.Y = position.Y - currentViewport.Top;
|
||||
}
|
||||
else if (currentViewport.Bottom < position.Y)
|
||||
{
|
||||
WindowOrigin.Y = position.Y - currentViewport.Bottom;
|
||||
delta.Y = position.Y - currentViewport.Bottom;
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_IF_NTSTATUS_FAILED(buffer.SetViewportOrigin(false, WindowOrigin, true));
|
||||
COORD newWindowOrigin{ 0 };
|
||||
newWindowOrigin.X = currentViewport.Left + delta.X;
|
||||
newWindowOrigin.Y = currentViewport.Top + delta.Y;
|
||||
// SetViewportOrigin will worry about clamping these values to the
|
||||
// buffer for us.
|
||||
RETURN_IF_NTSTATUS_FAILED(buffer.SetViewportOrigin(true, newWindowOrigin, true));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
|
|
@ -179,6 +179,8 @@ class ScreenBufferTests
|
|||
|
||||
TEST_METHOD(RestoreDownAltBufferWithTerminalScrolling);
|
||||
|
||||
TEST_METHOD(SnapCursorWithTerminalScrolling);
|
||||
|
||||
TEST_METHOD(ClearAlternateBuffer);
|
||||
|
||||
TEST_METHOD(InitializeTabStopsInVTMode);
|
||||
|
@ -4288,6 +4290,81 @@ void ScreenBufferTests::RestoreDownAltBufferWithTerminalScrolling()
|
|||
}
|
||||
}
|
||||
|
||||
void ScreenBufferTests::SnapCursorWithTerminalScrolling()
|
||||
{
|
||||
// This is a test for microsoft/terminal#1222. Refer to that issue for more
|
||||
// context
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
CONSOLE_INFORMATION& gci = g.getConsoleInformation();
|
||||
gci.SetTerminalScrolling(true);
|
||||
gci.LockConsole(); // Lock must be taken to manipulate buffer.
|
||||
auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
|
||||
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& cursor = si.GetTextBuffer().GetCursor();
|
||||
const auto originalView = si._viewport;
|
||||
si._virtualBottom = originalView.BottomInclusive();
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"cursor=%s", VerifyOutputTraits<COORD>::ToString(cursor.GetPosition()).GetBuffer()));
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"originalView=%s", VerifyOutputTraits<SMALL_RECT>::ToString(originalView.ToInclusive()).GetBuffer()));
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"First set the viewport somewhere lower in the buffer, as if the text "
|
||||
L"was output there. Manually move the cursor there as well, so the "
|
||||
L"cursor is within that viewport."));
|
||||
const COORD secondWindowOrigin{ 0, 10 };
|
||||
VERIFY_SUCCEEDED(si.SetViewportOrigin(true, secondWindowOrigin, true));
|
||||
si.GetTextBuffer().GetCursor().SetPosition(secondWindowOrigin);
|
||||
|
||||
const auto secondView = si._viewport;
|
||||
const auto secondVirtualBottom = si._virtualBottom;
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"cursor=%s", VerifyOutputTraits<COORD>::ToString(cursor.GetPosition()).GetBuffer()));
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"secondView=%s", VerifyOutputTraits<SMALL_RECT>::ToString(secondView.ToInclusive()).GetBuffer()));
|
||||
|
||||
VERIFY_ARE_EQUAL(10, secondView.Top());
|
||||
VERIFY_ARE_EQUAL(originalView.Height() + 10, secondView.BottomExclusive());
|
||||
VERIFY_ARE_EQUAL(originalView.Height() + 10 - 1, secondVirtualBottom);
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Emulate scrolling upwards with the mouse (not moving the virtual view)"));
|
||||
|
||||
const COORD thirdWindowOrigin{ 0, 2 };
|
||||
VERIFY_SUCCEEDED(si.SetViewportOrigin(true, thirdWindowOrigin, false));
|
||||
|
||||
const auto thirdView = si._viewport;
|
||||
const auto thirdVirtualBottom = si._virtualBottom;
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"cursor=%s", VerifyOutputTraits<COORD>::ToString(cursor.GetPosition()).GetBuffer()));
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"thirdView=%s", VerifyOutputTraits<SMALL_RECT>::ToString(thirdView.ToInclusive()).GetBuffer()));
|
||||
|
||||
VERIFY_ARE_EQUAL(2, thirdView.Top());
|
||||
VERIFY_ARE_EQUAL(originalView.Height() + 2, thirdView.BottomExclusive());
|
||||
VERIFY_ARE_EQUAL(secondVirtualBottom, thirdVirtualBottom);
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Call SetConsoleCursorPosition to snap to the cursor"));
|
||||
VERIFY_SUCCEEDED(g.api.SetConsoleCursorPositionImpl(si, secondWindowOrigin));
|
||||
|
||||
const auto fourthView = si._viewport;
|
||||
const auto fourthVirtualBottom = si._virtualBottom;
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"cursor=%s", VerifyOutputTraits<COORD>::ToString(cursor.GetPosition()).GetBuffer()));
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"thirdView=%s", VerifyOutputTraits<SMALL_RECT>::ToString(fourthView.ToInclusive()).GetBuffer()));
|
||||
|
||||
VERIFY_ARE_EQUAL(10, fourthView.Top());
|
||||
VERIFY_ARE_EQUAL(originalView.Height() + 10, fourthView.BottomExclusive());
|
||||
VERIFY_ARE_EQUAL(secondVirtualBottom, fourthVirtualBottom);
|
||||
}
|
||||
|
||||
void ScreenBufferTests::ClearAlternateBuffer()
|
||||
{
|
||||
// This is a test for microsoft/terminal#1189. Refer to that issue for more
|
||||
|
|
Loading…
Reference in a new issue