mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
Add support for VT paging operations (#16615)
This PR adds support for multiples pages in the VT architecture, along with new operations for moving between those pages: `NP` (Next Page), `PP` (Preceding Page), `PPA` (Page Position Absolute), `PPR` (Page Position Relative), and `PPB` (Page Position Back). There's also a new mode, `DECPCCM` (Page Cursor Coupling Mode), which determines whether or not the active page is also the visible page, and a new query sequence, `DECRQDE` (Request Displayed Extent), which can be used to query the visible page. ## References and Relevant Issues When combined with `DECCRA` (Copy Rectangular Area), which can copy between pages, you can layer content on top of existing output, and still restore the original data afterwards. So this could serve as an alternative solution to #10810. ## Detailed Description of the Pull Request / Additional comments On the original DEC terminals that supported paging, you couldn't have both paging and scrollback at the same time - only the one or the other. But modern terminals typically allow both, so we support that too. The way it works, the currently visible page will be attached to the scrollback, and any content that scrolls off the top will thus be saved. But the background pages will not have scrollback, so their content is lost if it scrolls off the top. And when the screen is resized, only the visible page will be reflowed. Background pages are not affected by a resize until they become active. At that point they just receive the traditional style of resize, where the content is clipped or padded to match the new dimensions. I'm not sure this is the best way to handle resizing, but we can always consider other approaches once people have had a chance to try it out. ## Validation Steps Performed I've added some unit tests covering the new operations, and also done a lot of manual testing. Closes #13892 Tests added/passed
This commit is contained in:
parent
097a2c1136
commit
4a243f0445
4
.github/actions/spelling/expect/expect.txt
vendored
4
.github/actions/spelling/expect/expect.txt
vendored
@ -406,6 +406,7 @@ DECNKM
|
||||
DECNRCM
|
||||
DECOM
|
||||
decommit
|
||||
DECPCCM
|
||||
DECPCTERM
|
||||
DECPS
|
||||
DECRARA
|
||||
@ -414,6 +415,7 @@ DECREQTPARM
|
||||
DECRLM
|
||||
DECRPM
|
||||
DECRQCRA
|
||||
DECRQDE
|
||||
DECRQM
|
||||
DECRQPSR
|
||||
DECRQSS
|
||||
@ -2123,6 +2125,7 @@ XIn
|
||||
XManifest
|
||||
XMath
|
||||
xorg
|
||||
XPan
|
||||
XResource
|
||||
xsi
|
||||
xstyler
|
||||
@ -2142,6 +2145,7 @@ YCast
|
||||
YCENTER
|
||||
YCount
|
||||
YLimit
|
||||
YPan
|
||||
YSubstantial
|
||||
YVIRTUALSCREEN
|
||||
YWalk
|
||||
|
||||
@ -131,8 +131,7 @@ public:
|
||||
// These methods are defined in TerminalApi.cpp
|
||||
void ReturnResponse(const std::wstring_view response) override;
|
||||
Microsoft::Console::VirtualTerminal::StateMachine& GetStateMachine() noexcept override;
|
||||
TextBuffer& GetTextBuffer() noexcept override;
|
||||
til::rect GetViewport() const noexcept override;
|
||||
BufferState GetBufferAndViewport() noexcept override;
|
||||
void SetViewportPosition(const til::point position) noexcept override;
|
||||
void SetTextAttributes(const TextAttribute& attrs) noexcept override;
|
||||
void SetSystemMode(const Mode mode, const bool enabled) noexcept override;
|
||||
|
||||
@ -34,14 +34,9 @@ Microsoft::Console::VirtualTerminal::StateMachine& Terminal::GetStateMachine() n
|
||||
return *_stateMachine;
|
||||
}
|
||||
|
||||
TextBuffer& Terminal::GetTextBuffer() noexcept
|
||||
ITerminalApi::BufferState Terminal::GetBufferAndViewport() noexcept
|
||||
{
|
||||
return _activeBuffer();
|
||||
}
|
||||
|
||||
til::rect Terminal::GetViewport() const noexcept
|
||||
{
|
||||
return til::rect{ _GetMutableViewport().ToInclusive() };
|
||||
return { _activeBuffer(), til::rect{ _GetMutableViewport().ToInclusive() }, !_inAltBuffer() };
|
||||
}
|
||||
|
||||
void Terminal::SetViewportPosition(const til::point position) noexcept
|
||||
|
||||
@ -44,6 +44,11 @@ namespace TerminalCoreUnitTests
|
||||
VERIFY_ARE_EQUAL(selection, expected);
|
||||
}
|
||||
|
||||
TextBuffer& GetTextBuffer(Terminal& term)
|
||||
{
|
||||
return term.GetBufferAndViewport().buffer;
|
||||
}
|
||||
|
||||
TEST_METHOD(SelectUnit)
|
||||
{
|
||||
Terminal term{ Terminal::TestDummyMarker{} };
|
||||
@ -394,7 +399,7 @@ namespace TerminalCoreUnitTests
|
||||
const auto burrito = L"\xD83C\xDF2F";
|
||||
|
||||
// Insert wide glyph at position (4,10)
|
||||
term.GetTextBuffer().GetCursor().SetPosition({ 4, 10 });
|
||||
GetTextBuffer(term).GetCursor().SetPosition({ 4, 10 });
|
||||
term.Write(burrito);
|
||||
|
||||
// Simulate click at (x,y) = (5,10)
|
||||
@ -417,7 +422,7 @@ namespace TerminalCoreUnitTests
|
||||
const auto burrito = L"\xD83C\xDF2F";
|
||||
|
||||
// Insert wide glyph at position (4,10)
|
||||
term.GetTextBuffer().GetCursor().SetPosition({ 4, 10 });
|
||||
GetTextBuffer(term).GetCursor().SetPosition({ 4, 10 });
|
||||
term.Write(burrito);
|
||||
|
||||
// Simulate click at (x,y) = (5,10)
|
||||
@ -440,11 +445,11 @@ namespace TerminalCoreUnitTests
|
||||
const auto burrito = L"\xD83C\xDF2F";
|
||||
|
||||
// Insert wide glyph at position (4,10)
|
||||
term.GetTextBuffer().GetCursor().SetPosition({ 4, 10 });
|
||||
GetTextBuffer(term).GetCursor().SetPosition({ 4, 10 });
|
||||
term.Write(burrito);
|
||||
|
||||
// Insert wide glyph at position (7,11)
|
||||
term.GetTextBuffer().GetCursor().SetPosition({ 7, 11 });
|
||||
GetTextBuffer(term).GetCursor().SetPosition({ 7, 11 });
|
||||
term.Write(burrito);
|
||||
|
||||
// Simulate ALT + click at (x,y) = (5,8)
|
||||
@ -496,7 +501,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Insert text at position (4,10)
|
||||
const std::wstring_view text = L"doubleClickMe";
|
||||
term.GetTextBuffer().GetCursor().SetPosition({ 4, 10 });
|
||||
GetTextBuffer(term).GetCursor().SetPosition({ 4, 10 });
|
||||
term.Write(text);
|
||||
|
||||
// Simulate double click at (x,y) = (5,10)
|
||||
@ -540,7 +545,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Insert text at position (4,10)
|
||||
const std::wstring_view text = L"C:\\Terminal>";
|
||||
term.GetTextBuffer().GetCursor().SetPosition({ 4, 10 });
|
||||
GetTextBuffer(term).GetCursor().SetPosition({ 4, 10 });
|
||||
term.Write(text);
|
||||
|
||||
// Simulate click at (x,y) = (15,10)
|
||||
@ -568,7 +573,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Insert text at position (4,10)
|
||||
const std::wstring_view text = L"doubleClickMe dragThroughHere";
|
||||
term.GetTextBuffer().GetCursor().SetPosition({ 4, 10 });
|
||||
GetTextBuffer(term).GetCursor().SetPosition({ 4, 10 });
|
||||
term.Write(text);
|
||||
|
||||
// Simulate double click at (x,y) = (5,10)
|
||||
@ -597,7 +602,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Insert text at position (21,10)
|
||||
const std::wstring_view text = L"doubleClickMe dragThroughHere";
|
||||
term.GetTextBuffer().GetCursor().SetPosition({ 4, 10 });
|
||||
GetTextBuffer(term).GetCursor().SetPosition({ 4, 10 });
|
||||
term.Write(text);
|
||||
|
||||
// Simulate double click at (x,y) = (21,10)
|
||||
@ -685,7 +690,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Insert text at position (4,10)
|
||||
const std::wstring_view text = L"doubleClickMe dragThroughHere";
|
||||
term.GetTextBuffer().GetCursor().SetPosition({ 4, 10 });
|
||||
GetTextBuffer(term).GetCursor().SetPosition({ 4, 10 });
|
||||
term.Write(text);
|
||||
|
||||
// Step 1: Create a selection on "doubleClickMe"
|
||||
|
||||
@ -152,7 +152,8 @@ void TerminalApiTest::CursorVisibility()
|
||||
VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsOn());
|
||||
VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsBlinkingAllowed());
|
||||
|
||||
term.GetTextBuffer().GetCursor().SetIsVisible(false);
|
||||
auto& textBuffer = term.GetBufferAndViewport().buffer;
|
||||
textBuffer.GetCursor().SetIsVisible(false);
|
||||
VERIFY_IS_FALSE(term._mainBuffer->GetCursor().IsVisible());
|
||||
VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsOn());
|
||||
VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsBlinkingAllowed());
|
||||
|
||||
@ -52,25 +52,16 @@ StateMachine& ConhostInternalGetSet::GetStateMachine()
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves the text buffer for the active output buffer.
|
||||
// - Retrieves the text buffer and virtual viewport for the active output
|
||||
// buffer. Also returns a flag indicating whether it's the main buffer.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - a reference to the TextBuffer instance.
|
||||
TextBuffer& ConhostInternalGetSet::GetTextBuffer()
|
||||
// - a tuple with the buffer reference, viewport, and main buffer flag.
|
||||
ITerminalApi::BufferState ConhostInternalGetSet::GetBufferAndViewport()
|
||||
{
|
||||
return _io.GetActiveOutputBuffer().GetTextBuffer();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves the virtual viewport of the active output buffer.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the exclusive coordinates of the viewport.
|
||||
til::rect ConhostInternalGetSet::GetViewport() const
|
||||
{
|
||||
return _io.GetActiveOutputBuffer().GetVirtualViewport().ToExclusive();
|
||||
auto& info = _io.GetActiveOutputBuffer();
|
||||
return { info.GetTextBuffer(), info.GetVirtualViewport().ToExclusive(), info.Next == nullptr };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
||||
@ -32,8 +32,7 @@ public:
|
||||
void ReturnResponse(const std::wstring_view response) override;
|
||||
|
||||
Microsoft::Console::VirtualTerminal::StateMachine& GetStateMachine() override;
|
||||
TextBuffer& GetTextBuffer() override;
|
||||
til::rect GetViewport() const override;
|
||||
BufferState GetBufferAndViewport() override;
|
||||
void SetViewportPosition(const til::point position) override;
|
||||
|
||||
void SetTextAttributes(const TextAttribute& attrs) override;
|
||||
|
||||
@ -531,6 +531,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
|
||||
ATT610_StartCursorBlink = DECPrivateMode(12),
|
||||
DECTCEM_TextCursorEnableMode = DECPrivateMode(25),
|
||||
XTERM_EnableDECCOLMSupport = DECPrivateMode(40),
|
||||
DECPCCM_PageCursorCouplingMode = DECPrivateMode(64),
|
||||
DECNKM_NumericKeypadMode = DECPrivateMode(66),
|
||||
DECBKM_BackarrowKeyMode = DECPrivateMode(67),
|
||||
DECLRMM_LeftRightMarginMode = DECPrivateMode(69),
|
||||
|
||||
@ -49,6 +49,12 @@ public:
|
||||
virtual bool DeleteCharacter(const VTInt count) = 0; // DCH
|
||||
virtual bool ScrollUp(const VTInt distance) = 0; // SU
|
||||
virtual bool ScrollDown(const VTInt distance) = 0; // SD
|
||||
virtual bool NextPage(const VTInt pageCount) = 0; // NP
|
||||
virtual bool PrecedingPage(const VTInt pageCount) = 0; // PP
|
||||
virtual bool PagePositionAbsolute(const VTInt page) = 0; // PPA
|
||||
virtual bool PagePositionRelative(const VTInt pageCount) = 0; // PPR
|
||||
virtual bool PagePositionBack(const VTInt pageCount) = 0; // PPB
|
||||
virtual bool RequestDisplayedExtent() = 0; // DECRQDE
|
||||
virtual bool InsertLine(const VTInt distance) = 0; // IL
|
||||
virtual bool DeleteLine(const VTInt distance) = 0; // DL
|
||||
virtual bool InsertColumn(const VTInt distance) = 0; // DECIC
|
||||
|
||||
@ -39,9 +39,15 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
virtual void ReturnResponse(const std::wstring_view response) = 0;
|
||||
|
||||
struct BufferState
|
||||
{
|
||||
TextBuffer& buffer;
|
||||
til::rect viewport;
|
||||
bool isMainBuffer;
|
||||
};
|
||||
|
||||
virtual StateMachine& GetStateMachine() = 0;
|
||||
virtual TextBuffer& GetTextBuffer() = 0;
|
||||
virtual til::rect GetViewport() const = 0;
|
||||
virtual BufferState GetBufferAndViewport() = 0;
|
||||
virtual void SetViewportPosition(const til::point position) = 0;
|
||||
|
||||
virtual bool IsVtInputEnabled() const = 0;
|
||||
|
||||
@ -108,7 +108,7 @@ bool InteractDispatch::WindowManipulation(const DispatchTypes::WindowManipulatio
|
||||
_api.ShowWindow(false);
|
||||
return true;
|
||||
case DispatchTypes::WindowManipulationType::RefreshWindow:
|
||||
_api.GetTextBuffer().TriggerRedrawAll();
|
||||
_api.GetBufferAndViewport().buffer.TriggerRedrawAll();
|
||||
return true;
|
||||
case DispatchTypes::WindowManipulationType::ResizeWindowInCharacters:
|
||||
// TODO:GH#1765 We should introduce a better `ResizeConpty` function to
|
||||
@ -135,7 +135,7 @@ bool InteractDispatch::WindowManipulation(const DispatchTypes::WindowManipulatio
|
||||
bool InteractDispatch::MoveCursor(const VTInt row, const VTInt col)
|
||||
{
|
||||
// First retrieve some information about the buffer
|
||||
const auto viewport = _api.GetViewport();
|
||||
const auto viewport = _api.GetBufferAndViewport().viewport;
|
||||
|
||||
// In VT, the origin is 1,1. For our array, it's 0,0. So subtract 1.
|
||||
// Apply boundary tests to ensure the cursor isn't outside the viewport rectangle.
|
||||
|
||||
254
src/terminal/adapter/PageManager.cpp
Normal file
254
src/terminal/adapter/PageManager.cpp
Normal file
@ -0,0 +1,254 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "PageManager.hpp"
|
||||
#include "../../renderer/base/renderer.hpp"
|
||||
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
|
||||
Page::Page(TextBuffer& buffer, const til::rect& viewport, const til::CoordType number) noexcept :
|
||||
_buffer{ buffer },
|
||||
_viewport{ viewport },
|
||||
_number(number)
|
||||
{
|
||||
}
|
||||
|
||||
TextBuffer& Page::Buffer() const noexcept
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
til::rect Page::Viewport() const noexcept
|
||||
{
|
||||
return _viewport;
|
||||
}
|
||||
|
||||
til::CoordType Page::Number() const noexcept
|
||||
{
|
||||
return _number;
|
||||
}
|
||||
|
||||
Cursor& Page::Cursor() const noexcept
|
||||
{
|
||||
return _buffer.GetCursor();
|
||||
}
|
||||
|
||||
const TextAttribute& Page::Attributes() const noexcept
|
||||
{
|
||||
return _buffer.GetCurrentAttributes();
|
||||
}
|
||||
|
||||
void Page::SetAttributes(const TextAttribute& attr, ITerminalApi* api) const
|
||||
{
|
||||
_buffer.SetCurrentAttributes(attr);
|
||||
// If the api parameter was specified, we need to pass the new attributes
|
||||
// through to the api. This occurs when there's a potential for the colors
|
||||
// to be changed, which may require some legacy remapping in conhost.
|
||||
if (api)
|
||||
{
|
||||
api->SetTextAttributes(attr);
|
||||
}
|
||||
}
|
||||
|
||||
til::CoordType Page::Top() const noexcept
|
||||
{
|
||||
// If we ever support vertical window panning, the page top won't
|
||||
// necessarily align with the viewport top, so it's best we always
|
||||
// treat them as distinct properties.
|
||||
return _viewport.top;
|
||||
}
|
||||
|
||||
til::CoordType Page::Bottom() const noexcept
|
||||
{
|
||||
// Similarly, the page bottom won't always match the viewport bottom.
|
||||
return _viewport.bottom;
|
||||
}
|
||||
|
||||
til::CoordType Page::Width() const noexcept
|
||||
{
|
||||
// The page width could also one day be different from the buffer width,
|
||||
// so again it's best treated as a distinct property.
|
||||
return _buffer.GetSize().Width();
|
||||
}
|
||||
|
||||
til::CoordType Page::Height() const noexcept
|
||||
{
|
||||
return Bottom() - Top();
|
||||
}
|
||||
|
||||
til::CoordType Page::BufferHeight() const noexcept
|
||||
{
|
||||
return _buffer.GetSize().Height();
|
||||
}
|
||||
|
||||
til::CoordType Page::XPanOffset() const noexcept
|
||||
{
|
||||
return _viewport.left;
|
||||
}
|
||||
|
||||
til::CoordType Page::YPanOffset() const noexcept
|
||||
{
|
||||
return 0; // Vertical panning is not yet supported
|
||||
}
|
||||
|
||||
PageManager::PageManager(ITerminalApi& api, Renderer& renderer) noexcept :
|
||||
_api{ api },
|
||||
_renderer{ renderer }
|
||||
{
|
||||
}
|
||||
|
||||
void PageManager::Reset()
|
||||
{
|
||||
_activePageNumber = 1;
|
||||
_visiblePageNumber = 1;
|
||||
_buffers = {};
|
||||
}
|
||||
|
||||
Page PageManager::Get(const til::CoordType pageNumber) const
|
||||
{
|
||||
const auto requestedPageNumber = std::min(std::max(pageNumber, 1), MAX_PAGES);
|
||||
auto [visibleBuffer, visibleViewport, isMainBuffer] = _api.GetBufferAndViewport();
|
||||
|
||||
// If we're not in the main buffer (either because an app has enabled the
|
||||
// alternate buffer mode, or switched the conhost screen buffer), then VT
|
||||
// paging doesn't apply, so we disregard the requested page number and just
|
||||
// use the visible buffer (with a fixed page number of 1).
|
||||
if (!isMainBuffer)
|
||||
{
|
||||
return { visibleBuffer, visibleViewport, 1 };
|
||||
}
|
||||
|
||||
// If the requested page number happens to be the visible page, then we
|
||||
// can also just use the visible buffer as is.
|
||||
if (requestedPageNumber == _visiblePageNumber)
|
||||
{
|
||||
return { visibleBuffer, visibleViewport, _visiblePageNumber };
|
||||
}
|
||||
|
||||
// Otherwise we're working with a background buffer, so we need to
|
||||
// retrieve that from the buffer array, and resize it to match the
|
||||
// active page size.
|
||||
const auto pageSize = visibleViewport.size();
|
||||
auto& pageBuffer = _getBuffer(requestedPageNumber, pageSize);
|
||||
return { pageBuffer, til::rect{ pageSize }, requestedPageNumber };
|
||||
}
|
||||
|
||||
Page PageManager::ActivePage() const
|
||||
{
|
||||
return Get(_activePageNumber);
|
||||
}
|
||||
|
||||
Page PageManager::VisiblePage() const
|
||||
{
|
||||
return Get(_visiblePageNumber);
|
||||
}
|
||||
|
||||
void PageManager::MoveTo(const til::CoordType pageNumber, const bool makeVisible)
|
||||
{
|
||||
auto [visibleBuffer, visibleViewport, isMainBuffer] = _api.GetBufferAndViewport();
|
||||
if (!isMainBuffer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto pageSize = visibleViewport.size();
|
||||
const auto visibleTop = visibleViewport.top;
|
||||
const auto wasVisible = _activePageNumber == _visiblePageNumber;
|
||||
const auto newPageNumber = std::min(std::max(pageNumber, 1), MAX_PAGES);
|
||||
auto redrawRequired = false;
|
||||
|
||||
// If we're changing the visible page, what we do is swap out the current
|
||||
// visible page into its backing buffer, and swap in the new page from the
|
||||
// backing buffer to the main buffer. That way the rest of the system only
|
||||
// ever has to deal with the main buffer.
|
||||
if (makeVisible && _visiblePageNumber != newPageNumber)
|
||||
{
|
||||
const auto& newBuffer = _getBuffer(newPageNumber, pageSize);
|
||||
auto& saveBuffer = _getBuffer(_visiblePageNumber, pageSize);
|
||||
for (auto i = 0; i < pageSize.height; i++)
|
||||
{
|
||||
saveBuffer.GetMutableRowByOffset(i).CopyFrom(visibleBuffer.GetRowByOffset(visibleTop + i));
|
||||
}
|
||||
for (auto i = 0; i < pageSize.height; i++)
|
||||
{
|
||||
visibleBuffer.GetMutableRowByOffset(visibleTop + i).CopyFrom(newBuffer.GetRowByOffset(i));
|
||||
}
|
||||
_visiblePageNumber = newPageNumber;
|
||||
redrawRequired = true;
|
||||
}
|
||||
|
||||
// If the active page was previously visible, and is now still visible,
|
||||
// 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;
|
||||
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);
|
||||
}
|
||||
// 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
|
||||
// number is like a third dimension in the cursor coordinate system.
|
||||
// If the cursor isn't on the visible page, it's the same as if its
|
||||
// x/y coordinates are outside the visible viewport.
|
||||
if (wasVisible && !isVisible)
|
||||
{
|
||||
visibleBuffer.GetCursor().SetIsVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
_activePageNumber = newPageNumber;
|
||||
if (redrawRequired)
|
||||
{
|
||||
_renderer.TriggerRedrawAll();
|
||||
}
|
||||
}
|
||||
|
||||
void PageManager::MoveRelative(const til::CoordType pageCount, const bool makeVisible)
|
||||
{
|
||||
MoveTo(_activePageNumber + pageCount, makeVisible);
|
||||
}
|
||||
|
||||
void PageManager::MakeActivePageVisible()
|
||||
{
|
||||
if (_activePageNumber != _visiblePageNumber)
|
||||
{
|
||||
MoveTo(_activePageNumber, true);
|
||||
}
|
||||
}
|
||||
|
||||
TextBuffer& PageManager::_getBuffer(const til::CoordType pageNumber, const til::size pageSize) const
|
||||
{
|
||||
auto& buffer = til::at(_buffers, pageNumber - 1);
|
||||
if (buffer == nullptr)
|
||||
{
|
||||
// Page buffers are created on demand, and are sized to match the active
|
||||
// page dimensions without any scrollback rows.
|
||||
buffer = std::make_unique<TextBuffer>(pageSize, TextAttribute{}, 0, false, _renderer);
|
||||
}
|
||||
else if (buffer->GetSize().Dimensions() != pageSize)
|
||||
{
|
||||
// If a buffer already exists for the page, and the page dimensions have
|
||||
// changed while it was inactive, it will need to be resized.
|
||||
// TODO: We don't currently reflow the existing content in this case, but
|
||||
// that may be something we want to reconsider.
|
||||
buffer->ResizeTraditional(pageSize);
|
||||
}
|
||||
return *buffer;
|
||||
}
|
||||
67
src/terminal/adapter/PageManager.hpp
Normal file
67
src/terminal/adapter/PageManager.hpp
Normal file
@ -0,0 +1,67 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- PageManager.hpp
|
||||
|
||||
Abstract:
|
||||
- This manages the text buffers required by the VT paging operations.
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ITerminalApi.hpp"
|
||||
#include "til.h"
|
||||
|
||||
namespace Microsoft::Console::VirtualTerminal
|
||||
{
|
||||
class Page
|
||||
{
|
||||
public:
|
||||
Page(TextBuffer& buffer, const til::rect& viewport, const til::CoordType number) noexcept;
|
||||
TextBuffer& Buffer() const noexcept;
|
||||
til::rect Viewport() const noexcept;
|
||||
til::CoordType Number() const noexcept;
|
||||
Cursor& Cursor() const noexcept;
|
||||
const TextAttribute& Attributes() const noexcept;
|
||||
void SetAttributes(const TextAttribute& attr, ITerminalApi* api = nullptr) const;
|
||||
til::CoordType Top() const noexcept;
|
||||
til::CoordType Bottom() const noexcept;
|
||||
til::CoordType Width() const noexcept;
|
||||
til::CoordType Height() const noexcept;
|
||||
til::CoordType BufferHeight() const noexcept;
|
||||
til::CoordType XPanOffset() const noexcept;
|
||||
til::CoordType YPanOffset() const noexcept;
|
||||
|
||||
private:
|
||||
TextBuffer& _buffer;
|
||||
til::rect _viewport;
|
||||
til::CoordType _number;
|
||||
};
|
||||
|
||||
class PageManager
|
||||
{
|
||||
using Renderer = Microsoft::Console::Render::Renderer;
|
||||
|
||||
public:
|
||||
PageManager(ITerminalApi& api, Renderer& renderer) noexcept;
|
||||
void Reset();
|
||||
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();
|
||||
|
||||
private:
|
||||
TextBuffer& _getBuffer(const til::CoordType pageNumber, const til::size pageSize) const;
|
||||
|
||||
ITerminalApi& _api;
|
||||
Renderer& _renderer;
|
||||
til::CoordType _activePageNumber = 1;
|
||||
til::CoordType _visiblePageNumber = 1;
|
||||
static constexpr til::CoordType MAX_PAGES = 6;
|
||||
mutable std::array<std::unique_ptr<TextBuffer>, MAX_PAGES> _buffers;
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -18,6 +18,7 @@ Author(s):
|
||||
#include "ITerminalApi.hpp"
|
||||
#include "FontBuffer.hpp"
|
||||
#include "MacroBuffer.hpp"
|
||||
#include "PageManager.hpp"
|
||||
#include "terminalOutput.hpp"
|
||||
#include "../input/terminalInput.hpp"
|
||||
#include "../../types/inc/sgrStack.hpp"
|
||||
@ -81,6 +82,12 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
bool RequestTerminalParameters(const DispatchTypes::ReportingPermission permission) override; // DECREQTPARM
|
||||
bool ScrollUp(const VTInt distance) override; // SU
|
||||
bool ScrollDown(const VTInt distance) override; // SD
|
||||
bool NextPage(const VTInt pageCount) override; // NP
|
||||
bool PrecedingPage(const VTInt pageCount) override; // PP
|
||||
bool PagePositionAbsolute(const VTInt page) override; // PPA
|
||||
bool PagePositionRelative(const VTInt pageCount) override; // PPR
|
||||
bool PagePositionBack(const VTInt pageCount) override; // PPB
|
||||
bool RequestDisplayedExtent() override; // DECRQDE
|
||||
bool InsertLine(const VTInt distance) override; // IL
|
||||
bool DeleteLine(const VTInt distance) override; // DL
|
||||
bool InsertColumn(const VTInt distance) override; // DECIC
|
||||
@ -178,7 +185,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
AllowDECCOLM,
|
||||
AllowDECSLRM,
|
||||
EraseColor,
|
||||
RectangularChangeExtent
|
||||
RectangularChangeExtent,
|
||||
PageCursorCoupling
|
||||
};
|
||||
enum class ScrollDirection
|
||||
{
|
||||
@ -189,6 +197,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
{
|
||||
VTInt Row = 1;
|
||||
VTInt Column = 1;
|
||||
VTInt Page = 1;
|
||||
bool IsDelayedEOLWrap = false;
|
||||
bool IsOriginModeRelative = false;
|
||||
TextAttribute Attributes = {};
|
||||
@ -214,20 +223,20 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
};
|
||||
|
||||
void _WriteToBuffer(const std::wstring_view string);
|
||||
std::pair<int, int> _GetVerticalMargins(const til::rect& viewport, const bool absolute) noexcept;
|
||||
std::pair<int, int> _GetVerticalMargins(const Page& page, const bool absolute) noexcept;
|
||||
std::pair<int, int> _GetHorizontalMargins(const til::CoordType bufferWidth) noexcept;
|
||||
bool _CursorMovePosition(const Offset rowOffset, const Offset colOffset, const bool clampInMargins);
|
||||
void _ApplyCursorMovementFlags(Cursor& cursor) noexcept;
|
||||
void _FillRect(TextBuffer& textBuffer, const til::rect& fillRect, const std::wstring_view& fillChar, const TextAttribute& fillAttrs) const;
|
||||
void _SelectiveEraseRect(TextBuffer& textBuffer, const til::rect& eraseRect);
|
||||
void _ChangeRectAttributes(TextBuffer& textBuffer, const til::rect& changeRect, const ChangeOps& changeOps);
|
||||
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);
|
||||
void _ChangeRectAttributes(const Page& page, const til::rect& changeRect, const ChangeOps& changeOps);
|
||||
void _ChangeRectOrStreamAttributes(const til::rect& changeArea, const ChangeOps& changeOps);
|
||||
til::rect _CalculateRectArea(const VTInt top, const VTInt left, const VTInt bottom, const VTInt right, const til::size bufferSize);
|
||||
til::rect _CalculateRectArea(const Page& page, const VTInt top, const VTInt left, const VTInt bottom, const VTInt right);
|
||||
bool _EraseScrollback();
|
||||
bool _EraseAll();
|
||||
TextAttribute _GetEraseAttributes(const TextBuffer& textBuffer) const noexcept;
|
||||
void _ScrollRectVertically(TextBuffer& textBuffer, const til::rect& scrollRect, const VTInt delta);
|
||||
void _ScrollRectHorizontally(TextBuffer& textBuffer, const til::rect& scrollRect, const VTInt delta);
|
||||
TextAttribute _GetEraseAttributes(const Page& page) const noexcept;
|
||||
void _ScrollRectVertically(const Page& page, const til::rect& scrollRect, const VTInt delta);
|
||||
void _ScrollRectHorizontally(const Page& page, const til::rect& scrollRect, const VTInt delta);
|
||||
void _InsertDeleteCharacterHelper(const VTInt delta);
|
||||
void _InsertDeleteLineHelper(const VTInt delta);
|
||||
void _InsertDeleteColumnHelper(const VTInt delta);
|
||||
@ -240,7 +249,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
const VTInt rightMargin,
|
||||
const bool homeCursor = false);
|
||||
|
||||
void _DoLineFeed(TextBuffer& textBuffer, const bool withReturn, const bool wrapForced);
|
||||
void _DoLineFeed(const Page& page, const bool withReturn, const bool wrapForced);
|
||||
|
||||
void _DeviceStatusReport(const wchar_t* parameters) const;
|
||||
void _CursorPositionReport(const bool extendedReport);
|
||||
@ -281,6 +290,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
RenderSettings& _renderSettings;
|
||||
TerminalInput& _terminalInput;
|
||||
TerminalOutput _termOutput;
|
||||
PageManager _pages;
|
||||
std::unique_ptr<FontBuffer> _fontBuffer;
|
||||
std::shared_ptr<MacroBuffer> _macroBuffer;
|
||||
std::optional<unsigned int> _initialCodePage;
|
||||
@ -295,7 +305,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
til::inclusive_rect _scrollMargins;
|
||||
|
||||
til::enumset<Mode> _modes;
|
||||
til::enumset<Mode> _modes{ Mode::PageCursorCoupling };
|
||||
|
||||
SgrStack _sgrStack;
|
||||
|
||||
|
||||
@ -422,9 +422,10 @@ void AdaptDispatch::_ApplyGraphicsOptions(const VTParameters options,
|
||||
// - True.
|
||||
bool AdaptDispatch::SetGraphicsRendition(const VTParameters options)
|
||||
{
|
||||
auto attr = _api.GetTextBuffer().GetCurrentAttributes();
|
||||
const auto page = _pages.ActivePage();
|
||||
auto attr = page.Attributes();
|
||||
_ApplyGraphicsOptions(options, attr);
|
||||
_api.SetTextAttributes(attr);
|
||||
page.SetAttributes(attr, &_api);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -438,8 +439,8 @@ bool AdaptDispatch::SetGraphicsRendition(const VTParameters options)
|
||||
// - True.
|
||||
bool AdaptDispatch::SetCharacterProtectionAttribute(const VTParameters options)
|
||||
{
|
||||
auto& textBuffer = _api.GetTextBuffer();
|
||||
auto attr = textBuffer.GetCurrentAttributes();
|
||||
const auto page = _pages.ActivePage();
|
||||
auto attr = page.Attributes();
|
||||
for (size_t i = 0; i < options.size(); i++)
|
||||
{
|
||||
const LogicalAttributeOptions opt = options.at(i);
|
||||
@ -456,7 +457,7 @@ bool AdaptDispatch::SetCharacterProtectionAttribute(const VTParameters options)
|
||||
break;
|
||||
}
|
||||
}
|
||||
textBuffer.SetCurrentAttributes(attr);
|
||||
page.SetAttributes(attr);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -470,7 +471,7 @@ bool AdaptDispatch::SetCharacterProtectionAttribute(const VTParameters options)
|
||||
// - True.
|
||||
bool AdaptDispatch::PushGraphicsRendition(const VTParameters options)
|
||||
{
|
||||
const auto& currentAttributes = _api.GetTextBuffer().GetCurrentAttributes();
|
||||
const auto& currentAttributes = _pages.ActivePage().Attributes();
|
||||
_sgrStack.Push(currentAttributes, options);
|
||||
return true;
|
||||
}
|
||||
@ -484,7 +485,8 @@ bool AdaptDispatch::PushGraphicsRendition(const VTParameters options)
|
||||
// - True.
|
||||
bool AdaptDispatch::PopGraphicsRendition()
|
||||
{
|
||||
const auto& currentAttributes = _api.GetTextBuffer().GetCurrentAttributes();
|
||||
_api.SetTextAttributes(_sgrStack.Pop(currentAttributes));
|
||||
const auto page = _pages.ActivePage();
|
||||
const auto& currentAttributes = page.Attributes();
|
||||
page.SetAttributes(_sgrStack.Pop(currentAttributes), &_api);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
<ClCompile Include="..\FontBuffer.cpp" />
|
||||
<ClCompile Include="..\InteractDispatch.cpp" />
|
||||
<ClCompile Include="..\MacroBuffer.cpp" />
|
||||
<ClCompile Include="..\PageManager.cpp" />
|
||||
<ClCompile Include="..\adaptDispatchGraphics.cpp" />
|
||||
<ClCompile Include="..\terminalOutput.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
@ -29,6 +30,7 @@
|
||||
<ClInclude Include="..\InteractDispatch.hpp" />
|
||||
<ClInclude Include="..\ITerminalApi.hpp" />
|
||||
<ClInclude Include="..\MacroBuffer.hpp" />
|
||||
<ClInclude Include="..\PageManager.hpp" />
|
||||
<ClInclude Include="..\precomp.h" />
|
||||
<ClInclude Include="..\terminalOutput.hpp" />
|
||||
<ClInclude Include="..\ITermDispatch.hpp" />
|
||||
|
||||
@ -36,6 +36,9 @@
|
||||
<ClCompile Include="..\MacroBuffer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\PageManager.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\adaptDispatch.hpp">
|
||||
@ -74,6 +77,9 @@
|
||||
<ClInclude Include="..\MacroBuffer.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\PageManager.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
|
||||
|
||||
@ -34,6 +34,7 @@ SOURCES= \
|
||||
..\FontBuffer.cpp \
|
||||
..\InteractDispatch.cpp \
|
||||
..\MacroBuffer.cpp \
|
||||
..\PageManager.cpp \
|
||||
..\adaptDispatchGraphics.cpp \
|
||||
..\terminalOutput.cpp \
|
||||
|
||||
|
||||
@ -42,6 +42,12 @@ public:
|
||||
bool DeleteCharacter(const VTInt /*count*/) override { return false; } // DCH
|
||||
bool ScrollUp(const VTInt /*distance*/) override { return false; } // SU
|
||||
bool ScrollDown(const VTInt /*distance*/) override { return false; } // SD
|
||||
bool NextPage(const VTInt /*pageCount*/) override { return false; } // NP
|
||||
bool PrecedingPage(const VTInt /*pageCount*/) override { return false; } // PP
|
||||
bool PagePositionAbsolute(const VTInt /*page*/) override { return false; } // PPA
|
||||
bool PagePositionRelative(const VTInt /*pageCount*/) override { return false; } // PPR
|
||||
bool PagePositionBack(const VTInt /*pageCount*/) override { return false; } // PPB
|
||||
bool RequestDisplayedExtent() override { return false; } // DECRQDE
|
||||
bool InsertLine(const VTInt /*distance*/) override { return false; } // IL
|
||||
bool DeleteLine(const VTInt /*distance*/) override { return false; } // DL
|
||||
bool InsertColumn(const VTInt /*distance*/) override { return false; } // DECIC
|
||||
|
||||
@ -81,14 +81,10 @@ public:
|
||||
return *_stateMachine;
|
||||
}
|
||||
|
||||
TextBuffer& GetTextBuffer() override
|
||||
BufferState GetBufferAndViewport() override
|
||||
{
|
||||
return *_textBuffer.get();
|
||||
}
|
||||
|
||||
til::rect GetViewport() const override
|
||||
{
|
||||
return { _viewport.left, _viewport.top, _viewport.right, _viewport.bottom };
|
||||
const auto viewport = til::rect{ _viewport.left, _viewport.top, _viewport.right, _viewport.bottom };
|
||||
return { *_textBuffer.get(), viewport, true };
|
||||
}
|
||||
|
||||
void SetViewportPosition(const til::point /*position*/) override
|
||||
@ -1575,14 +1571,23 @@ public:
|
||||
coordCursorExpected.x++;
|
||||
coordCursorExpected.y++;
|
||||
|
||||
// Until we support paging (GH#13892) the reported page number should always be 1.
|
||||
const auto pageExpected = 1;
|
||||
// By default, the initial page number should be 1.
|
||||
auto pageExpected = 1;
|
||||
|
||||
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::ExtendedCursorPositionReport, {}));
|
||||
|
||||
wchar_t pwszBuffer[50];
|
||||
swprintf_s(pwszBuffer, ARRAYSIZE(pwszBuffer), L"\x1b[?%d;%d;%dR", coordCursorExpected.y, coordCursorExpected.x, pageExpected);
|
||||
_testGetSet->ValidateInputEvent(pwszBuffer);
|
||||
|
||||
// Now test with the page number set to 3.
|
||||
pageExpected = 3;
|
||||
_pDispatch->PagePositionAbsolute(pageExpected);
|
||||
|
||||
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::ExtendedCursorPositionReport, {}));
|
||||
|
||||
swprintf_s(pwszBuffer, ARRAYSIZE(pwszBuffer), L"\x1b[?%d;%d;%dR", coordCursorExpected.y, coordCursorExpected.x, pageExpected);
|
||||
_testGetSet->ValidateInputEvent(pwszBuffer);
|
||||
}
|
||||
|
||||
TEST_METHOD(DeviceStatus_MacroSpaceReportTest)
|
||||
@ -1746,6 +1751,42 @@ public:
|
||||
VERIFY_THROWS(_pDispatch->TertiaryDeviceAttributes(), std::exception);
|
||||
}
|
||||
|
||||
TEST_METHOD(RequestDisplayedExtentTests)
|
||||
{
|
||||
Log::Comment(L"Starting test...");
|
||||
|
||||
Log::Comment(L"Test 1: Verify DECRQDE response in home position");
|
||||
_testGetSet->PrepData();
|
||||
_testGetSet->_viewport.left = 0;
|
||||
_testGetSet->_viewport.right = 80;
|
||||
_testGetSet->_viewport.top = 0;
|
||||
_testGetSet->_viewport.bottom = 24;
|
||||
VERIFY_IS_TRUE(_pDispatch->RequestDisplayedExtent());
|
||||
_testGetSet->ValidateInputEvent(L"\x1b[24;80;1;1;1\"w");
|
||||
|
||||
Log::Comment(L"Test 2: Verify DECRQDE response when panned horizontally");
|
||||
_testGetSet->_viewport.left += 5;
|
||||
_testGetSet->_viewport.right += 5;
|
||||
VERIFY_IS_TRUE(_pDispatch->RequestDisplayedExtent());
|
||||
_testGetSet->ValidateInputEvent(L"\x1b[24;80;6;1;1\"w");
|
||||
|
||||
Log::Comment(L"Test 3: Verify DECRQDE response on page 3");
|
||||
_pDispatch->PagePositionAbsolute(3);
|
||||
VERIFY_IS_TRUE(_pDispatch->RequestDisplayedExtent());
|
||||
_testGetSet->ValidateInputEvent(L"\x1b[24;80;6;1;3\"w");
|
||||
|
||||
Log::Comment(L"Test 3: Verify DECRQDE response when active page not visible");
|
||||
_pDispatch->ResetMode(DispatchTypes::ModeParams::DECPCCM_PageCursorCouplingMode);
|
||||
_pDispatch->PagePositionAbsolute(1);
|
||||
VERIFY_IS_TRUE(_pDispatch->RequestDisplayedExtent());
|
||||
_testGetSet->ValidateInputEvent(L"\x1b[24;80;6;1;3\"w");
|
||||
|
||||
Log::Comment(L"Test 4: Verify DECRQDE response when page 1 visible again");
|
||||
_pDispatch->SetMode(DispatchTypes::ModeParams::DECPCCM_PageCursorCouplingMode);
|
||||
VERIFY_IS_TRUE(_pDispatch->RequestDisplayedExtent());
|
||||
_testGetSet->ValidateInputEvent(L"\x1b[24;80;6;1;1\"w");
|
||||
}
|
||||
|
||||
TEST_METHOD(RequestTerminalParametersTests)
|
||||
{
|
||||
Log::Comment(L"Starting test...");
|
||||
@ -3263,7 +3304,7 @@ public:
|
||||
setMacroText(63, L"Macro 63");
|
||||
|
||||
const auto getBufferOutput = [&]() {
|
||||
const auto& textBuffer = _testGetSet->GetTextBuffer();
|
||||
const auto& textBuffer = _testGetSet->GetBufferAndViewport().buffer;
|
||||
const auto cursorPos = textBuffer.GetCursor().GetPosition();
|
||||
return textBuffer.GetRowByOffset(cursorPos.y).GetText().substr(0, cursorPos.x);
|
||||
};
|
||||
@ -3314,7 +3355,8 @@ public:
|
||||
{
|
||||
_testGetSet->PrepData();
|
||||
_pDispatch->WindowManipulation(DispatchTypes::WindowManipulationType::ReportTextSizeInCharacters, NULL, NULL);
|
||||
const std::wstring expectedResponse = fmt::format(L"\033[8;{};{}t", _testGetSet->GetViewport().height(), _testGetSet->GetTextBuffer().GetSize().Width());
|
||||
const auto [textBuffer, viewport, _] = _testGetSet->GetBufferAndViewport();
|
||||
const std::wstring expectedResponse = fmt::format(L"\033[8;{};{}t", viewport.height(), textBuffer.GetSize().Width());
|
||||
_testGetSet->ValidateInputEvent(expectedResponse.c_str());
|
||||
}
|
||||
|
||||
@ -3345,6 +3387,89 @@ public:
|
||||
VERIFY_IS_TRUE(_pDispatch->DoVsCodeAction(LR"(Completions;10;20;30;{ "foo": "what;ever", "bar": 2 })"));
|
||||
}
|
||||
|
||||
TEST_METHOD(PageMovementTests)
|
||||
{
|
||||
_testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER);
|
||||
auto& pages = _pDispatch->_pages;
|
||||
const auto startPos = pages.ActivePage().Cursor().GetPosition();
|
||||
const auto homePos = til::point{ 0, pages.ActivePage().Top() };
|
||||
|
||||
// Testing PPA (page position absolute)
|
||||
VERIFY_ARE_EQUAL(1, pages.ActivePage().Number(), L"Initial page is 1");
|
||||
_pDispatch->PagePositionAbsolute(3);
|
||||
VERIFY_ARE_EQUAL(3, pages.ActivePage().Number(), L"PPA 3 moves to page 3");
|
||||
_pDispatch->PagePositionAbsolute(VTParameter{});
|
||||
VERIFY_ARE_EQUAL(1, pages.ActivePage().Number(), L"PPA with omitted page moves to 1");
|
||||
_pDispatch->PagePositionAbsolute(9999);
|
||||
VERIFY_ARE_EQUAL(6, pages.ActivePage().Number(), L"PPA is clamped at page 6");
|
||||
VERIFY_ARE_EQUAL(startPos, pages.ActivePage().Cursor().GetPosition(), L"Cursor position never changes");
|
||||
|
||||
_testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER);
|
||||
_pDispatch->PagePositionAbsolute(1); // Reset to page 1
|
||||
|
||||
// Testing PPR (page position relative)
|
||||
VERIFY_ARE_EQUAL(1, pages.ActivePage().Number(), L"Initial page is 1");
|
||||
_pDispatch->PagePositionRelative(2);
|
||||
VERIFY_ARE_EQUAL(3, pages.ActivePage().Number(), L"PPR 2 moves forward 2 pages");
|
||||
_pDispatch->PagePositionRelative(VTParameter{});
|
||||
VERIFY_ARE_EQUAL(4, pages.ActivePage().Number(), L"PPR with omitted count moves forward 1");
|
||||
_pDispatch->PagePositionRelative(9999);
|
||||
VERIFY_ARE_EQUAL(6, pages.ActivePage().Number(), L"PPR is clamped at page 6");
|
||||
VERIFY_ARE_EQUAL(startPos, pages.ActivePage().Cursor().GetPosition(), L"Cursor position never changes");
|
||||
|
||||
_testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER);
|
||||
|
||||
// Testing PPB (page position back)
|
||||
VERIFY_ARE_EQUAL(6, pages.ActivePage().Number(), L"Initial page is 6");
|
||||
_pDispatch->PagePositionBack(2);
|
||||
VERIFY_ARE_EQUAL(4, pages.ActivePage().Number(), L"PPB 2 moves back 2 pages");
|
||||
_pDispatch->PagePositionBack(VTParameter{});
|
||||
VERIFY_ARE_EQUAL(3, pages.ActivePage().Number(), L"PPB with omitted count moves back 1");
|
||||
_pDispatch->PagePositionBack(9999);
|
||||
VERIFY_ARE_EQUAL(1, pages.ActivePage().Number(), L"PPB is clamped at page 1");
|
||||
VERIFY_ARE_EQUAL(startPos, pages.ActivePage().Cursor().GetPosition(), L"Cursor position never changes");
|
||||
|
||||
_testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER);
|
||||
|
||||
// Testing NP (next page)
|
||||
VERIFY_ARE_EQUAL(1, pages.ActivePage().Number(), L"Initial page is 1");
|
||||
_pDispatch->NextPage(2);
|
||||
VERIFY_ARE_EQUAL(3, pages.ActivePage().Number(), L"NP 2 moves forward 2 pages");
|
||||
_pDispatch->NextPage(VTParameter{});
|
||||
VERIFY_ARE_EQUAL(4, pages.ActivePage().Number(), L"NP with omitted count moves forward 1");
|
||||
_pDispatch->NextPage(9999);
|
||||
VERIFY_ARE_EQUAL(6, pages.ActivePage().Number(), L"NP is clamped at page 6");
|
||||
VERIFY_ARE_EQUAL(homePos, pages.ActivePage().Cursor().GetPosition(), L"Cursor position is reset to home");
|
||||
|
||||
_testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER);
|
||||
|
||||
// Testing PP (preceding page)
|
||||
VERIFY_ARE_EQUAL(6, pages.ActivePage().Number(), L"Initial page is 6");
|
||||
_pDispatch->PrecedingPage(2);
|
||||
VERIFY_ARE_EQUAL(4, pages.ActivePage().Number(), L"PP 2 moves back 2 pages");
|
||||
_pDispatch->PrecedingPage(VTParameter{});
|
||||
VERIFY_ARE_EQUAL(3, pages.ActivePage().Number(), L"PP with omitted count moves back 1");
|
||||
_pDispatch->PrecedingPage(9999);
|
||||
VERIFY_ARE_EQUAL(1, pages.ActivePage().Number(), L"PP is clamped at page 1");
|
||||
VERIFY_ARE_EQUAL(homePos, pages.ActivePage().Cursor().GetPosition(), L"Cursor position is reset to home");
|
||||
|
||||
// Testing DECPCCM (page cursor coupling mode)
|
||||
_pDispatch->SetMode(DispatchTypes::ModeParams::DECPCCM_PageCursorCouplingMode);
|
||||
_pDispatch->PagePositionAbsolute(2);
|
||||
VERIFY_ARE_EQUAL(2, pages.ActivePage().Number());
|
||||
VERIFY_ARE_EQUAL(2, pages.VisiblePage().Number(), L"Visible page should follow active if DECPCCM set");
|
||||
_pDispatch->ResetMode(DispatchTypes::ModeParams::DECPCCM_PageCursorCouplingMode);
|
||||
_pDispatch->PagePositionAbsolute(4);
|
||||
VERIFY_ARE_EQUAL(4, pages.ActivePage().Number());
|
||||
VERIFY_ARE_EQUAL(2, pages.VisiblePage().Number(), L"Visible page should not change if DECPCCM reset");
|
||||
_pDispatch->SetMode(DispatchTypes::ModeParams::DECPCCM_PageCursorCouplingMode);
|
||||
VERIFY_ARE_EQUAL(4, pages.ActivePage().Number());
|
||||
VERIFY_ARE_EQUAL(4, pages.VisiblePage().Number(), L"Active page should become visible when DECPCCM set");
|
||||
|
||||
// Reset to page 1
|
||||
_pDispatch->PagePositionAbsolute(1);
|
||||
}
|
||||
|
||||
private:
|
||||
TerminalInput _terminalInput;
|
||||
std::unique_ptr<TestGetSet> _testGetSet;
|
||||
|
||||
@ -556,6 +556,12 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete
|
||||
case CsiActionCodes::SD_ScrollDown:
|
||||
success = _dispatch->ScrollDown(parameters.at(0));
|
||||
break;
|
||||
case CsiActionCodes::NP_NextPage:
|
||||
success = _dispatch->NextPage(parameters.at(0));
|
||||
break;
|
||||
case CsiActionCodes::PP_PrecedingPage:
|
||||
success = _dispatch->PrecedingPage(parameters.at(0));
|
||||
break;
|
||||
case CsiActionCodes::ANSISYSRC_CursorRestore:
|
||||
success = _dispatch->CursorRestoreState();
|
||||
break;
|
||||
@ -601,6 +607,15 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete
|
||||
}
|
||||
success = true;
|
||||
break;
|
||||
case CsiActionCodes::PPA_PagePositionAbsolute:
|
||||
success = _dispatch->PagePositionAbsolute(parameters.at(0));
|
||||
break;
|
||||
case CsiActionCodes::PPR_PagePositionRelative:
|
||||
success = _dispatch->PagePositionRelative(parameters.at(0));
|
||||
break;
|
||||
case CsiActionCodes::PPB_PagePositionBack:
|
||||
success = _dispatch->PagePositionBack(parameters.at(0));
|
||||
break;
|
||||
case CsiActionCodes::DECSCUSR_SetCursorStyle:
|
||||
success = _dispatch->SetCursorStyle(parameters.at(0));
|
||||
break;
|
||||
@ -610,6 +625,9 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete
|
||||
case CsiActionCodes::DECSCA_SetCharacterProtectionAttribute:
|
||||
success = _dispatch->SetCharacterProtectionAttribute(parameters);
|
||||
break;
|
||||
case CsiActionCodes::DECRQDE_RequestDisplayedExtent:
|
||||
success = _dispatch->RequestDisplayedExtent();
|
||||
break;
|
||||
case CsiActionCodes::XT_PushSgr:
|
||||
case CsiActionCodes::XT_PushSgrAlias:
|
||||
success = _dispatch->PushGraphicsRendition(parameters);
|
||||
|
||||
@ -122,6 +122,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
DCH_DeleteCharacter = VTID("P"),
|
||||
SU_ScrollUp = VTID("S"),
|
||||
SD_ScrollDown = VTID("T"),
|
||||
NP_NextPage = VTID("U"),
|
||||
PP_PrecedingPage = VTID("V"),
|
||||
DECST8C_SetTabEvery8Columns = VTID("?W"),
|
||||
ECH_EraseCharacters = VTID("X"),
|
||||
CBT_CursorBackTab = VTID("Z"),
|
||||
@ -147,9 +149,13 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
DTTERM_WindowManipulation = VTID("t"), // NOTE: Overlaps with DECSLPP. Fix when/if implemented.
|
||||
ANSISYSRC_CursorRestore = VTID("u"),
|
||||
DECREQTPARM_RequestTerminalParameters = VTID("x"),
|
||||
PPA_PagePositionAbsolute = VTID(" P"),
|
||||
PPR_PagePositionRelative = VTID(" Q"),
|
||||
PPB_PagePositionBack = VTID(" R"),
|
||||
DECSCUSR_SetCursorStyle = VTID(" q"),
|
||||
DECSTR_SoftReset = VTID("!p"),
|
||||
DECSCA_SetCharacterProtectionAttribute = VTID("\"q"),
|
||||
DECRQDE_RequestDisplayedExtent = VTID("\"v"),
|
||||
XT_PushSgrAlias = VTID("#p"),
|
||||
XT_PopSgrAlias = VTID("#q"),
|
||||
XT_PushSgr = VTID("#{"),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user