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:
Mike Griese 2019-09-27 11:21:37 -05:00 committed by GitHub
parent eafa884fc4
commit 6f4b98acb4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 97 additions and 9 deletions

View file

@ -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;
}

View file

@ -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