diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index b893c1962cd..dcf2a461426 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1665,6 +1665,12 @@ void SCREEN_INFORMATION::SetCursorDBMode(const bool DoubleCursor) cursor.SetPosition(Position); + // If the cursor has moved below the virtual bottom, the bottom should be updated. + if (Position.Y > _virtualBottom) + { + _virtualBottom = Position.Y; + } + // if we have the focus, adjust the cursor state if (gci.Flags & CONSOLE_HAS_FOCUS) { diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index 7322e896a6b..57be6a3e99c 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -210,6 +210,8 @@ class ScreenBufferTests TEST_METHOD(ScreenAlignmentPattern); TEST_METHOD(TestCursorIsOn); + + TEST_METHOD(UpdateVirtualBottomWhenCursorMovesBelowIt); }; void ScreenBufferTests::SingleAlternateBufferCreationTest() @@ -5870,3 +5872,50 @@ void ScreenBufferTests::TestCursorIsOn() VERIFY_IS_FALSE(cursor.IsBlinkingAllowed()); VERIFY_IS_FALSE(cursor.IsVisible()); } + +void ScreenBufferTests::UpdateVirtualBottomWhenCursorMovesBelowIt() +{ + auto& g = ServiceLocator::LocateGlobals(); + auto& gci = g.getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer(); + auto& cursor = si.GetTextBuffer().GetCursor(); + + Log::Comment(L"Make sure the initial virtual bottom is at the bottom of the viewport"); + si.UpdateBottom(); + const auto initialVirtualBottom = si.GetViewport().BottomInclusive(); + VERIFY_ARE_EQUAL(initialVirtualBottom, si._virtualBottom); + + Log::Comment(L"Set the initial cursor position on that virtual bottom line"); + const auto initialCursorPos = COORD{ 0, initialVirtualBottom }; + cursor.SetPosition(initialCursorPos); + VERIFY_ARE_EQUAL(initialCursorPos, cursor.GetPosition()); + + Log::Comment(L"Pan down so the initial viewport has the cursor in the middle"); + const auto initialOrigin = COORD{ 0, si.GetViewport().Top() + si.GetViewport().Height() / 2 }; + gci.SetTerminalScrolling(false); + VERIFY_SUCCEEDED(si.SetViewportOrigin(false, initialOrigin, false)); + VERIFY_ARE_EQUAL(initialOrigin, si.GetViewport().Origin()); + + Log::Comment(L"Confirm that the virtual bottom has not changed"); + VERIFY_ARE_EQUAL(initialVirtualBottom, si._virtualBottom); + + Log::Comment(L"Now write several lines of content using WriteCharsLegacy"); + const auto content = L"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"; + auto numBytes = wcslen(content) * sizeof(wchar_t); + VERIFY_SUCCESS_NTSTATUS(WriteCharsLegacy(si, content, content, content, &numBytes, nullptr, 0, 0, nullptr)); + + Log::Comment(L"Confirm that the cursor position has moved down 10 lines"); + const auto newCursorPos = COORD{ initialCursorPos.X, initialCursorPos.Y + 10 }; + VERIFY_ARE_EQUAL(newCursorPos, cursor.GetPosition()); + + Log::Comment(L"Confirm that the virtual bottom matches that new cursor position"); + const auto newVirtualBottom = newCursorPos.Y; + VERIFY_ARE_EQUAL(newVirtualBottom, si._virtualBottom); + + Log::Comment(L"The viewport itself should not have changed at this point"); + VERIFY_ARE_EQUAL(initialOrigin, si.GetViewport().Origin()); + + Log::Comment(L"But after MoveToBottom, the viewport should align with the new virtual bottom"); + si.MoveToBottom(); + VERIFY_ARE_EQUAL(newVirtualBottom, si.GetViewport().BottomInclusive()); +}