Skip to content
30 changes: 3 additions & 27 deletions src/host/_stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,11 @@ using Microsoft::Console::VirtualTerminal::StateMachine;
const COORD newPostMarginsOrigin = { 0, moveToYPosition };
const COORD newViewOrigin = { 0, newViewTop };

// Unset the margins to scroll the content below the margins,
// then restore them after.
screenInfo.SetScrollMargins(Viewport::FromInclusive({ 0 }));
try
{
ScrollRegion(screenInfo, scrollRect, std::nullopt, newPostMarginsOrigin, UNICODE_SPACE, bufferAttributes);
}
CATCH_LOG();
screenInfo.SetScrollMargins(relativeMargins);

// Move the viewport down
auto hr = screenInfo.SetViewportOrigin(true, newViewOrigin, true);
Expand Down Expand Up @@ -186,39 +182,19 @@ using Microsoft::Console::VirtualTerminal::StateMachine;
SMALL_RECT scrollRect = { 0 };
scrollRect.Top = srMargins.Top;
scrollRect.Bottom = srMargins.Bottom;
scrollRect.Left = screenInfo.GetViewport().Left(); // NOTE: Left/Right Scroll margins don't do anything currently.
scrollRect.Right = screenInfo.GetViewport().RightInclusive();
scrollRect.Left = 0; // NOTE: Left/Right Scroll margins don't do anything currently.
scrollRect.Right = bufferSize.X - 1; // -1, otherwise this would be an exclusive rect.

COORD dest;
dest.X = scrollRect.Left;
dest.Y = scrollRect.Top - diff;

SMALL_RECT clipRect = scrollRect;
// Typically ScrollRegion() clips by the scroll margins. However, if
// we're scrolling down at the top of the viewport, we'll need to
// not clip at the margins, instead move the contents of the margins
// up above the viewport. So we'll clear out the current margins, and
// set them to the viewport+(#diff rows above the viewport).
if (scrollDownAtTop)
{
clipRect.Top -= diff;
auto fakeMargins = srMargins;
fakeMargins.Top -= diff;
auto fakeRelative = viewport.ConvertToOrigin(Viewport::FromInclusive(fakeMargins));
screenInfo.SetScrollMargins(fakeRelative);
}

try
{
ScrollRegion(screenInfo, scrollRect, clipRect, dest, UNICODE_SPACE, bufferAttributes);
ScrollRegion(screenInfo, scrollRect, scrollRect, dest, UNICODE_SPACE, bufferAttributes);
}
CATCH_LOG();

if (scrollDownAtTop)
{
// Undo the fake margins we set above
screenInfo.SetScrollMargins(relativeMargins);
}
coordCursor.Y -= diff;
}

Expand Down
10 changes: 10 additions & 0 deletions src/host/getset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1368,6 +1368,11 @@ void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool
srScroll.Right = SHORT_MAX;
srScroll.Top = viewport.Top;
srScroll.Bottom = viewport.Bottom;
// Clip to the DECSTBM margin boundary
if (screenInfo.AreMarginsSet())
{
srScroll.Bottom = screenInfo.GetAbsoluteScrollMargins().BottomInclusive();
}
// Paste coordinate for cut text above
COORD coordDestination;
coordDestination.X = 0;
Expand Down Expand Up @@ -2044,6 +2049,11 @@ void DoSrvPrivateModifyLinesImpl(const unsigned int count, const bool insert)
srScroll.Right = SHORT_MAX;
srScroll.Top = cursorPosition.Y;
srScroll.Bottom = screenInfo.GetViewport().BottomInclusive();
// Clip to the DECSTBM margin boundary
if (screenInfo.AreMarginsSet())
{
srScroll.Bottom = screenInfo.GetAbsoluteScrollMargins().BottomInclusive();
}
// Paste coordinate for cut text above
COORD coordDestination;
coordDestination.X = 0;
Expand Down
25 changes: 0 additions & 25 deletions src/host/output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -361,19 +361,6 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
// If there was no clip rect, we'll clip to the entire buffer size.
auto clip = Viewport::FromInclusive(clipRectGiven.value_or(buffer.ToInclusive()));

// Account for the scroll margins set by DECSTBM
// DECSTBM command can sometimes apply a clipping behavior as well. Check if we have any
// margins defined by DECSTBM and further restrict the clipping area here.
if (screenInfo.AreMarginsSet())
{
const auto margin = screenInfo.GetScrollingRegion();

// Update the clip rectangle to only include the area that is also in the margin.
clip = Viewport::Intersect(clip, margin);

// We'll also need to update the source rectangle, but we need to do that later.
}

// OK, make sure that the clip rectangle also fits inside the buffer
clip = Viewport::Intersect(buffer, clip);

Expand Down Expand Up @@ -416,18 +403,6 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
targetOrigin.Y += currentSourceOrigin.Y - originalSourceOrigin.Y;
}

// See MSFT:20204600 - Update the source rectangle to only include the region
// inside the scroll margins. We need to do this AFTER we calculate the
// delta between the currentSourceOrigin and the originalSourceOrigin.
// Don't combine this with the above block, because if there are margins set
// and the source rectangle was clipped by the buffer, we still want to
// adjust the target origin point based on the clipping of the buffer.
if (screenInfo.AreMarginsSet())
{
const auto margin = screenInfo.GetScrollingRegion();
source = Viewport::Intersect(source, margin);
}

// And now the target viewport is the same size as the source viewport but at the different position.
auto target = Viewport::FromDimensions(targetOrigin, source.Dimensions());

Expand Down
10 changes: 10 additions & 0 deletions src/host/ut_host/ApiRoutinesTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -594,9 +594,12 @@ class ApiRoutinesTests
TEST_METHOD(ApiScrollConsoleScreenBufferW)
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"data:setMargins", L"{false, true}")
TEST_METHOD_PROPERTY(L"data:checkClipped", L"{false, true}")
END_TEST_METHOD_PROPERTIES();

bool setMargins;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"setMargins", setMargins), L"Get whether or not we should set the DECSTBM margins.");
bool checkClipped;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"checkClipped", checkClipped), L"Get whether or not we should check all the options using a clipping rectangle.");

Expand All @@ -605,6 +608,13 @@ class ApiRoutinesTests

VERIFY_SUCCEEDED(si.GetTextBuffer().ResizeTraditional({ 5, 5 }), L"Make the buffer small so this doesn't take forever.");

// Tests are run both with and without the DECSTBM margins set. This should not alter
// the results, since ScrollConsoleScreenBuffer should not be affected by VT margins.
auto& stateMachine = si.GetStateMachine();
stateMachine.ProcessString(setMargins ? L"\x1b[2;4r" : L"\x1b[r");
// Make sure we clear the margins on exit so they can't break other tests.
auto clearMargins = wil::scope_exit([&] { stateMachine.ProcessString(L"\x1b[r"); });

gci.LockConsole();
auto Unlock = wil::scope_exit([&] { gci.UnlockConsole(); });

Expand Down
26 changes: 26 additions & 0 deletions src/host/ut_host/ScreenBufferTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3280,6 +3280,13 @@ void ScreenBufferTests::ScrollOperations()

void ScreenBufferTests::InsertChars()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:setMargins", L"{false, true}")
END_TEST_METHOD_PROPERTIES();

bool setMargins;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"setMargins", setMargins));

auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer();
auto& stateMachine = si.GetStateMachine();
Expand All @@ -3293,6 +3300,12 @@ void ScreenBufferTests::InsertChars()
VERIFY_SUCCEEDED(si.ResizeScreenBuffer({ bufferWidth, bufferHeight }, false));
si.SetViewport(Viewport::FromExclusive({ viewportStart, 0, viewportEnd, 25 }), true);

// Tests are run both with and without the DECSTBM margins set. This should not alter
// the results, since the ICH operation is not affected by vertical margins.
stateMachine.ProcessString(setMargins ? L"\x1b[15;20r" : L"\x1b[r");
// Make sure we clear the margins on exit so they can't break other tests.
auto clearMargins = wil::scope_exit([&] { stateMachine.ProcessString(L"\x1b[r"); });

Log::Comment(
L"Test 1: Fill the line with Qs. Write some text within the viewport boundaries. "
L"Then insert 5 spaces at the cursor. Watch spaces get inserted, text slides right "
Expand Down Expand Up @@ -3423,6 +3436,13 @@ void ScreenBufferTests::InsertChars()

void ScreenBufferTests::DeleteChars()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:setMargins", L"{false, true}")
END_TEST_METHOD_PROPERTIES();

bool setMargins;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"setMargins", setMargins));

auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer();
auto& stateMachine = si.GetStateMachine();
Expand All @@ -3436,6 +3456,12 @@ void ScreenBufferTests::DeleteChars()
VERIFY_SUCCEEDED(si.ResizeScreenBuffer({ bufferWidth, bufferHeight }, false));
si.SetViewport(Viewport::FromExclusive({ viewportStart, 0, viewportEnd, 25 }), true);

// Tests are run both with and without the DECSTBM margins set. This should not alter
// the results, since the DCH operation is not affected by vertical margins.
stateMachine.ProcessString(setMargins ? L"\x1b[15;20r" : L"\x1b[r");
// Make sure we clear the margins on exit so they can't break other tests.
auto clearMargins = wil::scope_exit([&] { stateMachine.ProcessString(L"\x1b[r"); });

Log::Comment(
L"Test 1: Fill the line with Qs. Write some text within the viewport boundaries. "
L"Then delete 5 characters at the cursor. Watch the rest of the line slide left, "
Expand Down
10 changes: 7 additions & 3 deletions src/terminal/adapter/adaptDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -963,13 +963,17 @@ bool AdaptDispatch::_ScrollMovement(const ScrollDirection sdDirection, _In_ unsi
srScreen.Right = SHORT_MAX;
srScreen.Top = csbiex.srWindow.Top;
srScreen.Bottom = csbiex.srWindow.Bottom - 1; // srWindow is exclusive, hence the - 1
// Clip to the DECSTBM margin boundaries
if (_srScrollMargins.Top < _srScrollMargins.Bottom)
{
srScreen.Top = csbiex.srWindow.Top + _srScrollMargins.Top;
srScreen.Bottom = csbiex.srWindow.Top + _srScrollMargins.Bottom;
}

// Paste coordinate for cut text above
COORD coordDestination;
coordDestination.X = srScreen.Left;
// Scroll starting from the top of the scroll margins.
coordDestination.Y = (_srScrollMargins.Top + srScreen.Top) + sDistance * (sdDirection == ScrollDirection::Up ? -1 : 1);
// We don't need to worry about clipping the margins at all, ScrollRegion inside conhost will do that correctly for us
coordDestination.Y = srScreen.Top + sDistance * (sdDirection == ScrollDirection::Up ? -1 : 1);

// Fill character for remaining space left behind by "cut" operation (or for fill if we "cut" the entire line)
CHAR_INFO ciFill;
Expand Down