Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove unwanted DECSTBM clipping #2764

Merged
merged 8 commits into from
Sep 23, 2019
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