diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 482892e419d..9816926f289 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -275,6 +275,7 @@ ROW& TextBuffer::GetScratchpadRow(const TextAttribute& attributes) void TextBuffer::CopyProperties(const TextBuffer& OtherBuffer) noexcept { GetCursor().CopyProperties(OtherBuffer.GetCursor()); + _scrollMargins = OtherBuffer._scrollMargins; } // Routine Description: @@ -906,6 +907,16 @@ void TextBuffer::SetCurrentAttributes(const TextAttribute& currentAttributes) no _currentAttributes = currentAttributes; } +til::inclusive_rect TextBuffer::GetScrollMargins() const noexcept +{ + return _scrollMargins; +} + +void TextBuffer::SetScrollMargins(const til::inclusive_rect& scrollMargins) noexcept +{ + _scrollMargins = scrollMargins; +} + void TextBuffer::SetWrapForced(const til::CoordType y, bool wrap) { GetMutableRowByOffset(y).SetWrapForced(wrap); diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 9628fb3ab08..7e2d2b9f2a2 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -143,6 +143,9 @@ class TextBuffer final void SetCurrentAttributes(const TextAttribute& currentAttributes) noexcept; + til::inclusive_rect GetScrollMargins() const noexcept; + void SetScrollMargins(const til::inclusive_rect& scrollMargins) noexcept; + void SetWrapForced(til::CoordType y, bool wrap); void SetCurrentLineRendition(const LineRendition lineRendition, const TextAttribute& fillAttributes); void ResetLineRenditionRange(const til::CoordType startRow, const til::CoordType endRow); @@ -405,6 +408,7 @@ class TextBuffer final uint16_t _height = 0; TextAttribute _currentAttributes; + til::inclusive_rect _scrollMargins; til::CoordType _firstRow = 0; // indexes top row (not necessarily 0) uint64_t _lastMutationId = 0; diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index fa3fbe5412a..d0c1d06622c 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -124,6 +124,7 @@ class ScreenBufferTests TEST_METHOD(VtSoftResetCursorPosition); TEST_METHOD(VtSoftResetAltBufferCursorState); + TEST_METHOD(VtScrollMarginsAltBufferInheritance); TEST_METHOD(VtScrollMarginsNewlineColor); @@ -1535,6 +1536,25 @@ void ScreenBufferTests::VtSoftResetAltBufferCursorState() VERIFY_ARE_EQUAL(til::point(6, 3), gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor().GetPosition()); } +void ScreenBufferTests::VtScrollMarginsAltBufferInheritance() +{ + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + gci.LockConsole(); + auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); + + auto& stateMachine = gci.GetActiveOutputBuffer().GetStateMachine(); + + Log::Comment(L"Set margins on the main buffer and enter the alternate buffer."); + stateMachine.ProcessString(L"\x1b[1;15r\x1b[?1049h\x1b[999B"); + VERIFY_IS_TRUE(gci.GetActiveOutputBuffer()._IsAltBuffer()); + VERIFY_ARE_EQUAL(til::point(0, 14), gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor().GetPosition()); + + Log::Comment(L"Change margins on the alternate buffer and return to the main buffer."); + stateMachine.ProcessString(L"\x1b[1;10r\x1b[?1049l\x1b[999B"); + VERIFY_IS_FALSE(gci.GetActiveOutputBuffer()._IsAltBuffer()); + VERIFY_ARE_EQUAL(til::point(0, 9), gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor().GetPosition()); +} + void ScreenBufferTests::VtScrollMarginsNewlineColor() { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index 0c48af7c257..84a261094a3 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -369,12 +369,16 @@ void TextBufferTests::TestCopyProperties() testTextBuffer->GetCursor().SetIsDouble(false); otherTbi.GetCursor().SetIsDouble(true); + testTextBuffer->SetScrollMargins({}); + otherTbi.SetScrollMargins({ 5, 3, 70, 20 }); + // run copy testTextBuffer->CopyProperties(otherTbi); // test that new now contains values from other VERIFY_IS_TRUE(testTextBuffer->GetCursor().IsVisible()); VERIFY_IS_TRUE(testTextBuffer->GetCursor().IsDouble()); + VERIFY_ARE_EQUAL(otherTbi.GetScrollMargins(), testTextBuffer->GetScrollMargins()); } void TextBufferTests::TestLastNonSpace(const til::CoordType cursorPosY) diff --git a/src/terminal/adapter/PageManager.cpp b/src/terminal/adapter/PageManager.cpp index d061bab96e9..fd6809a449a 100644 --- a/src/terminal/adapter/PageManager.cpp +++ b/src/terminal/adapter/PageManager.cpp @@ -8,6 +8,24 @@ using namespace Microsoft::Console::VirtualTerminal; +namespace +{ + til::inclusive_rect getEffectiveMargins(const TextBuffer& buffer, const til::size pageSize) noexcept + { + const auto scrollMargins = buffer.GetScrollMargins(); + const auto rightmostColumn = buffer.GetSize().Width() - 1; + const auto bottommostRow = pageSize.height - 1; + const auto horizontalMarginsSet = scrollMargins.left < scrollMargins.right && scrollMargins.left < rightmostColumn; + const auto verticalMarginsSet = scrollMargins.top < scrollMargins.bottom && scrollMargins.top < bottommostRow; + return { + horizontalMarginsSet ? scrollMargins.left : 0, + verticalMarginsSet ? scrollMargins.top : 0, + horizontalMarginsSet ? std::min(scrollMargins.right, rightmostColumn) : rightmostColumn, + verticalMarginsSet ? std::min(scrollMargins.bottom, bottommostRow) : bottommostRow, + }; + } +} + Page::Page(TextBuffer& buffer, const til::rect& viewport, const til::CoordType number) noexcept : _buffer{ buffer }, _viewport{ viewport }, @@ -149,7 +167,7 @@ Page PageManager::VisiblePage() const return Get(_visiblePageNumber); } -void PageManager::MoveTo(const til::CoordType pageNumber, const bool makeVisible) +void PageManager::MoveTo(const til::CoordType pageNumber, const bool makeVisible, const bool originMode) { auto [visibleBuffer, visibleViewport, isMainBuffer] = _api.GetBufferAndViewport(); if (!isMainBuffer) @@ -161,6 +179,8 @@ void PageManager::MoveTo(const til::CoordType pageNumber, const bool makeVisible const auto visibleTop = visibleViewport.top; const auto wasVisible = _activePageNumber == _visiblePageNumber; const auto newPageNumber = std::min(std::max(pageNumber, 1), MAX_PAGES); + // This must be captured before a visible page swap replaces the margins in visibleBuffer. + const auto oldMargins = getEffectiveMargins(wasVisible ? visibleBuffer : _getBuffer(_activePageNumber, pageSize), pageSize); auto redrawRequired = false; // If we're changing the visible page, what we do is swap out the current @@ -179,6 +199,8 @@ void PageManager::MoveTo(const til::CoordType pageNumber, const bool makeVisible { newBuffer.CopyRow(i, visibleTop + i, visibleBuffer); } + saveBuffer.SetScrollMargins(visibleBuffer.GetScrollMargins()); + visibleBuffer.SetScrollMargins(newBuffer.GetScrollMargins()); _visiblePageNumber = newPageNumber; redrawRequired = true; } @@ -187,24 +209,17 @@ void PageManager::MoveTo(const til::CoordType pageNumber, const bool makeVisible // there is no need to update any buffer properties, because we'll have // been using the main buffer in both cases. const auto isVisible = newPageNumber == _visiblePageNumber; + auto& oldBuffer = wasVisible ? visibleBuffer : _getBuffer(_activePageNumber, pageSize); + auto& newBuffer = isVisible ? visibleBuffer : _getBuffer(newPageNumber, pageSize); if (!wasVisible || !isVisible) { // Otherwise we need to copy the properties from the old buffer to the // new, so we retain the current attributes and cursor position. This // is only needed if they are actually different. - auto& oldBuffer = wasVisible ? visibleBuffer : _getBuffer(_activePageNumber, pageSize); - auto& newBuffer = isVisible ? visibleBuffer : _getBuffer(newPageNumber, pageSize); if (&oldBuffer != &newBuffer) { - // When copying the cursor position, we need to adjust the y - // coordinate to account for scrollback in the visible buffer. - const auto oldTop = wasVisible ? visibleTop : 0; - const auto newTop = isVisible ? visibleTop : 0; - auto position = oldBuffer.GetCursor().GetPosition(); - position.y = position.y - oldTop + newTop; newBuffer.SetCurrentAttributes(oldBuffer.GetCurrentAttributes()); - newBuffer.CopyProperties(oldBuffer); - newBuffer.GetCursor().SetPosition(position); + newBuffer.GetCursor().CopyProperties(oldBuffer.GetCursor()); } // If we moved from the visible buffer to a background buffer we need // to hide the cursor in the visible buffer. This is because the page @@ -217,6 +232,25 @@ void PageManager::MoveTo(const til::CoordType pageNumber, const bool makeVisible } } + // Adjust the cursor when changing the active page, or when transferring an + // unchanged active page from its backing buffer into the visible buffer. + if (_activePageNumber != newPageNumber || (makeVisible && !wasVisible)) + { + const auto oldTop = wasVisible ? visibleTop : 0; + const auto newTop = isVisible ? visibleTop : 0; + auto position = oldBuffer.GetCursor().GetPosition(); + position.y = position.y - oldTop + newTop; + if (originMode) + { + const auto newMargins = getEffectiveMargins(newBuffer, pageSize); + position.x += newMargins.left - oldMargins.left; + position.y += newMargins.top - oldMargins.top; + position.x = std::min(position.x, newMargins.right); + position.y = std::min(position.y, newTop + newMargins.bottom); + } + newBuffer.GetCursor().SetPosition(position); + } + _activePageNumber = newPageNumber; if (redrawRequired && _renderer) { @@ -224,16 +258,16 @@ void PageManager::MoveTo(const til::CoordType pageNumber, const bool makeVisible } } -void PageManager::MoveRelative(const til::CoordType pageCount, const bool makeVisible) +void PageManager::MoveRelative(const til::CoordType pageCount, const bool makeVisible, const bool originMode) { - MoveTo(_activePageNumber + pageCount, makeVisible); + MoveTo(_activePageNumber + pageCount, makeVisible, originMode); } -void PageManager::MakeActivePageVisible() +void PageManager::MakeActivePageVisible(const bool originMode) { if (_activePageNumber != _visiblePageNumber) { - MoveTo(_activePageNumber, true); + MoveTo(_activePageNumber, true, originMode); } } diff --git a/src/terminal/adapter/PageManager.hpp b/src/terminal/adapter/PageManager.hpp index 4c244d71bda..c269cc76cc4 100644 --- a/src/terminal/adapter/PageManager.hpp +++ b/src/terminal/adapter/PageManager.hpp @@ -52,9 +52,9 @@ namespace Microsoft::Console::VirtualTerminal Page Get(const til::CoordType pageNumber) const; Page ActivePage() const; Page VisiblePage() const; - void MoveTo(const til::CoordType pageNumber, const bool makeVisible); - void MoveRelative(const til::CoordType pageCount, const bool makeVisible); - void MakeActivePageVisible(); + void MoveTo(const til::CoordType pageNumber, const bool makeVisible, const bool originMode); + void MoveRelative(const til::CoordType pageCount, const bool makeVisible, const bool originMode); + void MakeActivePageVisible(const bool originMode); private: TextBuffer& _getBuffer(const til::CoordType pageNumber, const til::size pageSize) const; diff --git a/src/terminal/adapter/SixelParser.cpp b/src/terminal/adapter/SixelParser.cpp index 1ffb48e3908..feee3870c48 100644 --- a/src/terminal/adapter/SixelParser.cpp +++ b/src/terminal/adapter/SixelParser.cpp @@ -284,7 +284,7 @@ bool SixelParser::_initTextBufferBoundaries() // be inside the horizontal margins and above the bottom margin, else // nothing will be rendered. const auto [topMargin, bottomMargin] = _dispatcher._GetVerticalMargins(page, true); - const auto [leftMargin, rightMargin] = _dispatcher._GetHorizontalMargins(page.Width()); + const auto [leftMargin, rightMargin] = _dispatcher._GetHorizontalMargins(page, page.Width()); _textMargins = til::rect{ leftMargin, topMargin, rightMargin + 1, bottomMargin + 1 }; _textCursor = page.Cursor().GetPosition(); _availablePixelWidth = (_textMargins.right - _textCursor.x) * _cellSize.width; diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index f0fb5136d39..d9dee0b82d4 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -107,7 +107,7 @@ void AdaptDispatch::_WriteToBuffer(const std::wstring_view string) const auto& attributes = page.Attributes(); auto [topMargin, bottomMargin] = _GetVerticalMargins(page, true); - const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page.Width()); + const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page, page.Width()); auto lineWidth = textBuffer.GetLineWidth(cursorPosition.y); if (cursorPosition.x <= rightMargin && cursorPosition.y >= topMargin && cursorPosition.y <= bottomMargin) @@ -275,16 +275,18 @@ void AdaptDispatch::CursorPrevLine(const VTInt distance) // - A std::pair containing the top and bottom coordinates (inclusive). std::pair AdaptDispatch::_GetVerticalMargins(const Page& page, const bool absolute) noexcept { + auto scrollMargins = page.Buffer().GetScrollMargins(); // If the top is out of range, reset the margins completely. const auto bottommostRow = page.Height() - 1; - if (_scrollMargins.top >= bottommostRow) + if (scrollMargins.top >= bottommostRow) { - _scrollMargins.top = _scrollMargins.bottom = 0; + scrollMargins.top = scrollMargins.bottom = 0; + page.Buffer().SetScrollMargins(scrollMargins); } // If margins aren't set, use the full extent of the page. - const auto marginsSet = _scrollMargins.top < _scrollMargins.bottom; - auto topMargin = marginsSet ? _scrollMargins.top : 0; - auto bottomMargin = marginsSet ? _scrollMargins.bottom : bottommostRow; + const auto marginsSet = scrollMargins.top < scrollMargins.bottom; + auto topMargin = marginsSet ? scrollMargins.top : 0; + auto bottomMargin = marginsSet ? scrollMargins.bottom : bottommostRow; // If the bottom is out of range, clamp it to the bottommost row. bottomMargin = std::min(bottomMargin, bottommostRow); if (absolute) @@ -298,21 +300,24 @@ std::pair AdaptDispatch::_GetVerticalMargins(const Page& page, const b // Routine Description: // - Returns the coordinates of the horizontal scroll margins. // Arguments: -// - pageWidth - The width of the page +// - page - The page that the margins will apply to. +// - pageWidth - The width of the page. // Return Value: // - A std::pair containing the left and right coordinates (inclusive). -std::pair AdaptDispatch::_GetHorizontalMargins(const til::CoordType pageWidth) noexcept +std::pair AdaptDispatch::_GetHorizontalMargins(const Page& page, const til::CoordType pageWidth) noexcept { + auto scrollMargins = page.Buffer().GetScrollMargins(); // If the left is out of range, reset the margins completely. const auto rightmostColumn = pageWidth - 1; - if (_scrollMargins.left >= rightmostColumn) + if (scrollMargins.left >= rightmostColumn) { - _scrollMargins.left = _scrollMargins.right = 0; + scrollMargins.left = scrollMargins.right = 0; + page.Buffer().SetScrollMargins(scrollMargins); } // If margins aren't set, use the full extent of the buffer. - const auto marginsSet = _scrollMargins.left < _scrollMargins.right; - auto leftMargin = marginsSet ? _scrollMargins.left : 0; - auto rightMargin = marginsSet ? _scrollMargins.right : rightmostColumn; + const auto marginsSet = scrollMargins.left < scrollMargins.right; + auto leftMargin = marginsSet ? scrollMargins.left : 0; + auto rightMargin = marginsSet ? scrollMargins.right : rightmostColumn; // If the right is out of range, clamp it to the rightmost column. rightMargin = std::min(rightMargin, rightmostColumn); return { leftMargin, rightMargin }; @@ -332,7 +337,7 @@ void AdaptDispatch::_CursorMovePosition(const Offset rowOffset, const Offset col const auto pageWidth = page.Width(); const auto cursorPosition = cursor.GetPosition(); const auto [topMargin, bottomMargin] = _GetVerticalMargins(page, true); - const auto [leftMargin, rightMargin] = _GetHorizontalMargins(pageWidth); + const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page, pageWidth); // For relative movement, the given offsets will be relative to // the current cursor position. @@ -468,7 +473,7 @@ void AdaptDispatch::CursorSaveState() // Although if origin mode is set, the cursor is relative to the margin origin. if (_modes.test(Mode::Origin)) { - cursorPosition.x -= _GetHorizontalMargins(page.Width()).first; + cursorPosition.x -= _GetHorizontalMargins(page, page.Width()).first; cursorPosition.y -= _GetVerticalMargins(page, false).first; } @@ -659,7 +664,7 @@ void AdaptDispatch::_InsertDeleteCharacterHelper(const VTInt delta) const auto lineWidth = page.Buffer().GetLineWidth(row); const auto [topMargin, bottomMargin] = _GetVerticalMargins(page, true); const auto [leftMargin, rightMargin] = (row >= topMargin && row <= bottomMargin) ? - _GetHorizontalMargins(lineWidth) : + _GetHorizontalMargins(page, lineWidth) : std::make_pair(0, lineWidth - 1); if (col >= leftMargin && col <= rightMargin) { @@ -1010,7 +1015,7 @@ til::rect AdaptDispatch::_CalculateRectArea(const Page& page, const VTInt top, c // We start by calculating the margin offsets and maximum dimensions. // If the origin mode isn't set, we use the page extent. const auto [topMargin, bottomMargin] = _GetVerticalMargins(page, false); - const auto [leftMargin, rightMargin] = _GetHorizontalMargins(pageWidth); + const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page, pageWidth); const auto yOffset = _modes.test(Mode::Origin) ? topMargin : 0; const auto yMaximum = _modes.test(Mode::Origin) ? bottomMargin + 1 : pageHeight; const auto xOffset = _modes.test(Mode::Origin) ? leftMargin : 0; @@ -1563,7 +1568,7 @@ void AdaptDispatch::_CursorPositionReport(const bool extendedReport) // If the origin mode is set, the cursor is relative to the margin origin. if (_modes.test(Mode::Origin)) { - cursorPosition.x -= _GetHorizontalMargins(page.Width()).first; + cursorPosition.x -= _GetHorizontalMargins(page, page.Width()).first; cursorPosition.y -= _GetVerticalMargins(page, false).first; } @@ -1617,7 +1622,7 @@ void AdaptDispatch::_ScrollMovement(const VTInt delta) { const auto page = _pages.ActivePage(); const auto [topMargin, bottomMargin] = _GetVerticalMargins(page, true); - const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page.Width()); + const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page, page.Width()); _ScrollRectVertically(page, { leftMargin, topMargin, rightMargin + 1, bottomMargin + 1 }, delta); } @@ -1668,7 +1673,7 @@ void AdaptDispatch::PrecedingPage(const VTInt pageCount) // - page - Destination page void AdaptDispatch::PagePositionAbsolute(const VTInt page) { - _pages.MoveTo(page, _modes.test(Mode::PageCursorCoupling)); + _pages.MoveTo(page, _modes.test(Mode::PageCursorCoupling), _modes.test(Mode::Origin)); } // Routine Description: @@ -1678,7 +1683,7 @@ void AdaptDispatch::PagePositionAbsolute(const VTInt page) // - pageCount - Number of pages to move void AdaptDispatch::PagePositionRelative(const VTInt pageCount) { - _pages.MoveRelative(pageCount, _modes.test(Mode::PageCursorCoupling)); + _pages.MoveRelative(pageCount, _modes.test(Mode::PageCursorCoupling), _modes.test(Mode::Origin)); } // Routine Description: @@ -1688,7 +1693,7 @@ void AdaptDispatch::PagePositionRelative(const VTInt pageCount) // - pageCount - Number of pages to move void AdaptDispatch::PagePositionBack(const VTInt pageCount) { - _pages.MoveRelative(-pageCount, _modes.test(Mode::PageCursorCoupling)); + _pages.MoveRelative(-pageCount, _modes.test(Mode::PageCursorCoupling), _modes.test(Mode::Origin)); } // Routine Description: @@ -1740,17 +1745,21 @@ void AdaptDispatch::_SetColumnMode(const bool enable) // - void AdaptDispatch::_SetAlternateScreenBufferMode(const bool enable) { + const auto scrollMargins = _pages.ActivePage().Buffer().GetScrollMargins(); + if (enable) { CursorSaveState(); const auto page = _pages.ActivePage(); _api.UseAlternateScreenBuffer(_GetEraseAttributes(page)); _usingAltBuffer = true; + _pages.ActivePage().Buffer().SetScrollMargins(scrollMargins); } else { _api.UseMainScreenBuffer(); _usingAltBuffer = false; + _pages.ActivePage().Buffer().SetScrollMargins(scrollMargins); CursorRestoreState(); } } @@ -1820,7 +1829,7 @@ void AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con _modes.set(Mode::PageCursorCoupling, enable); if (enable) { - _pages.MakeActivePageVisible(); + _pages.MakeActivePageVisible(_modes.test(Mode::Origin)); } break; case DispatchTypes::ModeParams::DECNKM_NumericKeypadMode: @@ -2141,7 +2150,7 @@ void AdaptDispatch::_InsertDeleteLineHelper(const VTInt delta) const auto row = cursor.GetPosition().y; const auto [topMargin, bottomMargin] = _GetVerticalMargins(page, true); - const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page.Width()); + const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page, page.Width()); if (row >= topMargin && row <= bottomMargin && col >= leftMargin && col <= rightMargin) { // We emulate inserting and deleting by scrolling the area between the cursor and the bottom margin. @@ -2192,7 +2201,7 @@ void AdaptDispatch::_InsertDeleteColumnHelper(const VTInt delta) const auto row = cursor.GetPosition().y; const auto [topMargin, bottomMargin] = _GetVerticalMargins(page, true); - const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page.Width()); + const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page, page.Width()); if (row >= topMargin && row <= bottomMargin && col >= leftMargin && col <= rightMargin) { // We emulate inserting and deleting by scrolling the area between the cursor and the right margin. @@ -2274,8 +2283,10 @@ void AdaptDispatch::_DoSetTopBottomScrollingMargins(const VTInt topMargin, actualTop -= 1; actualBottom -= 1; } - _scrollMargins.top = actualTop; - _scrollMargins.bottom = actualBottom; + auto scrollMargins = page.Buffer().GetScrollMargins(); + scrollMargins.top = actualTop; + scrollMargins.bottom = actualBottom; + page.Buffer().SetScrollMargins(scrollMargins); // If requested, we may also need to move the cursor to the home // position, but only if the requested margins were valid. if (homeCursor) @@ -2346,8 +2357,10 @@ void AdaptDispatch::_DoSetLeftRightScrollingMargins(const VTInt leftMargin, actualLeft -= 1; actualRight -= 1; } - _scrollMargins.left = actualLeft; - _scrollMargins.right = actualRight; + auto scrollMargins = page.Buffer().GetScrollMargins(); + scrollMargins.left = actualLeft; + scrollMargins.right = actualRight; + page.Buffer().SetScrollMargins(scrollMargins); // If requested, we may also need to move the cursor to the home // position, but only if the requested margins were valid. if (homeCursor) @@ -2422,7 +2435,7 @@ bool AdaptDispatch::_DoLineFeed(const Page& page, const bool withReturn, const b const auto pageWidth = page.Width(); const auto bufferHeight = page.BufferHeight(); const auto [topMargin, bottomMargin] = _GetVerticalMargins(page, true); - const auto [leftMargin, rightMargin] = _GetHorizontalMargins(pageWidth); + const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page, pageWidth); auto viewportMoved = false; auto& cursor = page.Cursor(); @@ -2540,7 +2553,7 @@ void AdaptDispatch::ReverseLineFeed() const auto& textBuffer = page.Buffer(); auto& cursor = page.Cursor(); const auto cursorPosition = cursor.GetPosition(); - const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page.Width()); + const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page, page.Width()); const auto [topMargin, bottomMargin] = _GetVerticalMargins(page, true); // If the cursor is at the top of the margin area, we shift the buffer @@ -2566,7 +2579,7 @@ void AdaptDispatch::BackIndex() const auto page = _pages.ActivePage(); auto& cursor = page.Cursor(); const auto cursorPosition = cursor.GetPosition(); - const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page.Width()); + const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page, page.Width()); const auto [topMargin, bottomMargin] = _GetVerticalMargins(page, true); // If the cursor is at the left of the margin area, we shift the buffer right. @@ -2591,7 +2604,7 @@ void AdaptDispatch::ForwardIndex() const auto page = _pages.ActivePage(); auto& cursor = page.Cursor(); const auto cursorPosition = cursor.GetPosition(); - const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page.Width()); + const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page, page.Width()); const auto [topMargin, bottomMargin] = _GetVerticalMargins(page, true); // If the cursor is at the right of the margin area, we shift the buffer left. @@ -2666,7 +2679,7 @@ void AdaptDispatch::ForwardTab(const VTInt numTabs) auto tabsPerformed = 0; const auto [topMargin, bottomMargin] = _GetVerticalMargins(page, true); - const auto [leftMargin, rightMargin] = _GetHorizontalMargins(width); + const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page, width); const auto clampToMargin = row >= topMargin && row <= bottomMargin && column <= rightMargin; const auto maxColumn = clampToMargin ? rightMargin : width - 1; @@ -2709,7 +2722,7 @@ void AdaptDispatch::BackwardsTab(const VTInt numTabs) auto tabsPerformed = 0; const auto [topMargin, bottomMargin] = _GetVerticalMargins(page, true); - const auto [leftMargin, rightMargin] = _GetHorizontalMargins(width); + const auto [leftMargin, rightMargin] = _GetHorizontalMargins(page, width); const auto clampToMargin = row >= topMargin && row <= bottomMargin && column >= leftMargin; const auto minColumn = clampToMargin ? leftMargin : 0; @@ -4406,8 +4419,8 @@ void AdaptDispatch::_ReportDECSTBMSetting() // - None void AdaptDispatch::_ReportDECSLRMSetting() { - const auto pageWidth = _pages.ActivePage().Width(); - const auto [marginLeft, marginRight] = _GetHorizontalMargins(pageWidth); + const auto page = _pages.ActivePage(); + const auto [marginLeft, marginRight] = _GetHorizontalMargins(page, page.Width()); // A valid response always starts with 1 $ r and the 's' indicates this is a DECSLRM response. // VT origin is at 1,1 so we need to add 1 to these margins. _ReturnDcsResponse(fmt::format(FMT_COMPILE(L"1$r{};{}s"), marginLeft + 1, marginRight + 1)); @@ -4571,7 +4584,7 @@ void AdaptDispatch::_ReportCursorInformation() // If the origin mode is set, the cursor is relative to the margin origin. if (_modes.test(Mode::Origin)) { - cursorPosition.x -= _GetHorizontalMargins(page.Width()).first; + cursorPosition.x -= _GetHorizontalMargins(page, page.Width()).first; cursorPosition.y -= _GetVerticalMargins(page, false).first; } diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 1c1c714ee02..93dbd0ec34d 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -247,7 +247,7 @@ namespace Microsoft::Console::VirtualTerminal void _WriteToBuffer(const std::wstring_view string); std::pair _GetVerticalMargins(const Page& page, const bool absolute) noexcept; - std::pair _GetHorizontalMargins(const til::CoordType bufferWidth) noexcept; + std::pair _GetHorizontalMargins(const Page& page, const til::CoordType bufferWidth) noexcept; void _CursorMovePosition(const Offset rowOffset, const Offset colOffset, const bool clampInMargins); void _FillRect(const Page& page, const til::rect& fillRect, const std::wstring_view& fillChar, const TextAttribute& fillAttrs) const; void _SelectiveEraseRect(const Page& page, const til::rect& eraseRect); @@ -330,8 +330,6 @@ namespace Microsoft::Console::VirtualTerminal std::array _savedCursorState; bool _usingAltBuffer; - til::inclusive_rect _scrollMargins; - til::enumset _modes{ Mode::PageCursorCoupling }; SgrStack _sgrStack; diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index dcbe580b247..70d90a396ec 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -2935,66 +2935,68 @@ class AdapterTest _testGetSet->_viewport.right = 8; _testGetSet->_viewport.bottom = 8; auto sScreenHeight = _testGetSet->_viewport.bottom - _testGetSet->_viewport.top; + const auto getMargins = [&]() { return _pDispatch->_pages.ActivePage().Buffer().GetScrollMargins(); }; + const auto clearMargins = [&]() { _pDispatch->_pages.ActivePage().Buffer().SetScrollMargins({}); }; Log::Comment(L"Test 1: Verify having both values is valid."); _pDispatch->SetTopBottomScrollingMargins(2, 6); - VERIFY_ARE_EQUAL(2, _pDispatch->_scrollMargins.top + 1); - VERIFY_ARE_EQUAL(6, _pDispatch->_scrollMargins.bottom + 1); + VERIFY_ARE_EQUAL(2, getMargins().top + 1); + VERIFY_ARE_EQUAL(6, getMargins().bottom + 1); Log::Comment(L"Test 2: Verify having only top is valid."); _pDispatch->SetTopBottomScrollingMargins(7, 0); - VERIFY_ARE_EQUAL(7, _pDispatch->_scrollMargins.top + 1); - VERIFY_ARE_EQUAL(sScreenHeight, _pDispatch->_scrollMargins.bottom + 1); + VERIFY_ARE_EQUAL(7, getMargins().top + 1); + VERIFY_ARE_EQUAL(sScreenHeight, getMargins().bottom + 1); Log::Comment(L"Test 3: Verify having only bottom is valid."); _pDispatch->SetTopBottomScrollingMargins(0, 7); - VERIFY_ARE_EQUAL(1, _pDispatch->_scrollMargins.top + 1); - VERIFY_ARE_EQUAL(7, _pDispatch->_scrollMargins.bottom + 1); + VERIFY_ARE_EQUAL(1, getMargins().top + 1); + VERIFY_ARE_EQUAL(7, getMargins().bottom + 1); Log::Comment(L"Test 4: Verify having no values is valid."); _pDispatch->SetTopBottomScrollingMargins(0, 0); - VERIFY_ARE_EQUAL(til::inclusive_rect{}, _pDispatch->_scrollMargins); + VERIFY_ARE_EQUAL(til::inclusive_rect{}, getMargins()); Log::Comment(L"Test 5: Verify having both values, but bad bounds has no effect."); - _pDispatch->_scrollMargins = {}; + clearMargins(); _pDispatch->SetTopBottomScrollingMargins(7, 3); - VERIFY_ARE_EQUAL(til::inclusive_rect{}, _pDispatch->_scrollMargins); + VERIFY_ARE_EQUAL(til::inclusive_rect{}, getMargins()); Log::Comment(L"Test 6: Verify setting margins to (0, height) clears them"); // First set, _pDispatch->SetTopBottomScrollingMargins(2, 6); // Then clear _pDispatch->SetTopBottomScrollingMargins(0, sScreenHeight); - VERIFY_ARE_EQUAL(til::inclusive_rect{}, _pDispatch->_scrollMargins); + VERIFY_ARE_EQUAL(til::inclusive_rect{}, getMargins()); Log::Comment(L"Test 7: Verify setting margins to (1, height) clears them"); // First set, _pDispatch->SetTopBottomScrollingMargins(2, 6); // Then clear _pDispatch->SetTopBottomScrollingMargins(1, sScreenHeight); - VERIFY_ARE_EQUAL(til::inclusive_rect{}, _pDispatch->_scrollMargins); + VERIFY_ARE_EQUAL(til::inclusive_rect{}, getMargins()); Log::Comment(L"Test 8: Verify setting margins to (1, 0) clears them"); // First set, _pDispatch->SetTopBottomScrollingMargins(2, 6); // Then clear _pDispatch->SetTopBottomScrollingMargins(1, 0); - VERIFY_ARE_EQUAL(til::inclusive_rect{}, _pDispatch->_scrollMargins); + VERIFY_ARE_EQUAL(til::inclusive_rect{}, getMargins()); Log::Comment(L"Test 9: Verify having top and bottom margin the same has no effect."); - _pDispatch->_scrollMargins = {}; + clearMargins(); _pDispatch->SetTopBottomScrollingMargins(4, 4); - VERIFY_ARE_EQUAL(til::inclusive_rect{}, _pDispatch->_scrollMargins); + VERIFY_ARE_EQUAL(til::inclusive_rect{}, getMargins()); Log::Comment(L"Test 10: Verify having top margin out of bounds has no effect."); - _pDispatch->_scrollMargins = {}; + clearMargins(); _pDispatch->SetTopBottomScrollingMargins(sScreenHeight + 1, sScreenHeight + 10); - VERIFY_ARE_EQUAL(til::inclusive_rect{}, _pDispatch->_scrollMargins); + VERIFY_ARE_EQUAL(til::inclusive_rect{}, getMargins()); Log::Comment(L"Test 11: Verify having bottom margin out of bounds has no effect."); - _pDispatch->_scrollMargins = {}; + clearMargins(); _pDispatch->SetTopBottomScrollingMargins(1, sScreenHeight + 1); - VERIFY_ARE_EQUAL(til::inclusive_rect{}, _pDispatch->_scrollMargins); + VERIFY_ARE_EQUAL(til::inclusive_rect{}, getMargins()); } TEST_METHOD(LineFeedTest) @@ -3998,6 +4000,81 @@ class AdapterTest _pDispatch->PagePositionAbsolute(1); } + TEST_METHOD(PageMovementWithOriginModeTest) + { + _testGetSet->PrepData(); + _testGetSet->_textBuffer = std::make_unique(til::size{ 80, 24 }, TextAttribute{}, 0, false, &_testGetSet->_renderer); + _testGetSet->_viewport = { 0, 0, 80, 24 }; + + _pDispatch->SetMode(DispatchTypes::DECLRMM_LeftRightMarginMode); + _pDispatch->SetTopBottomScrollingMargins(2, 20); + _pDispatch->SetLeftRightScrollingMargins(3, 60); + _pDispatch->PagePositionAbsolute(2); + _pDispatch->SetTopBottomScrollingMargins(5, 12); + _pDispatch->SetLeftRightScrollingMargins(10, 20); + _pDispatch->PagePositionAbsolute(1); + _pDispatch->SetMode(DispatchTypes::DECOM_OriginMode); + _pDispatch->ResetMode(DispatchTypes::ModeParams::DECPCCM_PageCursorCouplingMode); + + const auto getCursorPosition = [&]() { return _pDispatch->_pages.ActivePage().Cursor().GetPosition(); }; + _pDispatch->CursorPosition(3, 4); + _pDispatch->PagePositionAbsolute(2); + VERIFY_ARE_EQUAL(til::point(12, 6), getCursorPosition(), L"PPA preserves margin-relative coordinates"); + _pDispatch->PagePositionAbsolute(1); + VERIFY_ARE_EQUAL(til::point(5, 3), getCursorPosition(), L"PPA preserves coordinates in both directions"); + + _pDispatch->CursorPosition(10, 40); + _pDispatch->PagePositionAbsolute(2); + VERIFY_ARE_EQUAL(til::point(19, 11), getCursorPosition(), L"PPA clamps to smaller margins"); + _pDispatch->PagePositionAbsolute(1); + + _pDispatch->SetMode(DispatchTypes::DECPCCM_PageCursorCouplingMode); + _pDispatch->CursorPosition(3, 4); + _pDispatch->PagePositionAbsolute(2); + VERIFY_ARE_EQUAL(til::point(12, 6), getCursorPosition(), L"Coupled PPA preserves margin-relative coordinates"); + _pDispatch->PagePositionAbsolute(1); + _pDispatch->CursorPosition(10, 40); + _pDispatch->PagePositionAbsolute(2); + VERIFY_ARE_EQUAL(til::point(19, 11), getCursorPosition(), L"Coupled PPA clamps to smaller margins"); + } + + TEST_METHOD(PageMarginsAreIndependentTest) + { + _testGetSet->PrepData(); + _testGetSet->_textBuffer = std::make_unique(til::size{ 80, 24 }, TextAttribute{}, 0, false, &_testGetSet->_renderer); + _testGetSet->_viewport = { 0, 0, 80, 24 }; + + _stateMachine->ProcessString(L"\033#8\033[2 P\033[9;15r\033[1 P\033[999B\n\n\n\n\n\n\n"); + + const auto bottomText = _testGetSet->_textBuffer->GetRowByOffset(23).GetText().substr(0, 80); + const std::wstring expected(80, L' '); + VERIFY_ARE_EQUAL(String(expected.c_str()), String(bottomText.data(), gsl::narrow(bottomText.size()))); + + const auto requestSetting = [=](const std::wstring_view settingId) { + const auto stringHandler = _pDispatch->RequestSetting(); + for (const auto ch : settingId) + { + stringHandler(ch); + } + stringHandler(L'\033'); + }; + + requestSetting(L"r"); + _testGetSet->ValidateInputEvent(L"\033P1$r1;24r\033\\"); + _pDispatch->PagePositionAbsolute(2); + requestSetting(L"r"); + _testGetSet->ValidateInputEvent(L"\033P1$r9;15r\033\\"); + + _pDispatch->SetMode(DispatchTypes::DECLRMM_LeftRightMarginMode); + _pDispatch->SetLeftRightScrollingMargins(10, 20); + _pDispatch->PagePositionAbsolute(1); + requestSetting(L"s"); + _testGetSet->ValidateInputEvent(L"\033P1$r1;80s\033\\"); + _pDispatch->PagePositionAbsolute(2); + requestSetting(L"s"); + _testGetSet->ValidateInputEvent(L"\033P1$r10;20s\033\\"); + } + TEST_METHOD(SendC1ControlTest) { const auto S7C1T = L"\033 F";