Skip to content

Commit

Permalink
Fix snapping to the cursor in "Terminal Scrolling" mode (#2705)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
zadjii-msft committed Sep 27, 2019
1 parent eafa884 commit 6f4b98a
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 9 deletions.
29 changes: 20 additions & 9 deletions src/host/getset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
77 changes: 77 additions & 0 deletions src/host/ut_host/ScreenBufferTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ class ScreenBufferTests

TEST_METHOD(RestoreDownAltBufferWithTerminalScrolling);

TEST_METHOD(SnapCursorWithTerminalScrolling);

TEST_METHOD(ClearAlternateBuffer);

TEST_METHOD(InitializeTabStopsInVTMode);
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 6f4b98a

Please sign in to comment.