Skip to content

Commit

Permalink
Add support for XTPUSHSGR / XTPOPSGR (#1978)
Browse files Browse the repository at this point in the history
Implement the `XTPUSHSGR` and `XTPOPSGR` control sequences (see #1796).

This change adds a new pair of methods to `ITermDispatch`:
`PushGraphicsRendition` and `PopGraphicsRendition`, and then plumbs the
change through `AdaptDispatch`, `TerminalDispatch`, `ITerminalApi` and
`TerminalApi`.

The stack logic is encapsulated in the `SgrStack` class, to allow it to
be reused between the two APIs (`AdaptDispatch` and `TerminalDispatch`).

Like xterm, only ten levels of nesting are supported.

The stack is implemented as a "ring stack": if you push when the stack
is full, the bottom of the stack will be dropped to make room.

Partial pushes (see the description of `XTPUSHSGR` in Issue #1796) are
implemented per xterm spec.

## Validation Steps Performed
Tests added, plus manual verification of the feature.

Closes #1796
  • Loading branch information
jazzdelightsme authored Feb 18, 2021
1 parent 847749f commit 72cbe59
Show file tree
Hide file tree
Showing 22 changed files with 641 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .github/actions/spelling/expect/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2843,6 +2843,8 @@ XSubstantial
xtended
xterm
XTest
XTPUSHSGR
XTPOPSGR
xunit
xutr
xvalue
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalCore/ITerminalApi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ namespace Microsoft::Terminal::Core
virtual bool SetWorkingDirectory(std::wstring_view uri) noexcept = 0;
virtual std::wstring_view GetWorkingDirectory() noexcept = 0;

virtual bool PushGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::VTParameters options) noexcept = 0;
virtual bool PopGraphicsRendition() noexcept = 0;

protected:
ITerminalApi() = default;
};
Expand Down
7 changes: 7 additions & 0 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <conattrs.hpp>

#include "../../buffer/out/textBuffer.hpp"
#include "../../types/inc/sgrStack.hpp"
#include "../../renderer/inc/BlinkingState.hpp"
#include "../../terminal/parser/StateMachine.hpp"
#include "../../terminal/input/terminalInput.hpp"
Expand Down Expand Up @@ -124,6 +125,10 @@ class Microsoft::Terminal::Core::Terminal final :
bool SetTaskbarProgress(const size_t state, const size_t progress) noexcept override;
bool SetWorkingDirectory(std::wstring_view uri) noexcept override;
std::wstring_view GetWorkingDirectory() noexcept override;

bool PushGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::VTParameters options) noexcept override;
bool PopGraphicsRendition() noexcept override;

#pragma endregion

#pragma region ITerminalInput
Expand Down Expand Up @@ -347,6 +352,8 @@ class Microsoft::Terminal::Core::Terminal final :
COORD _ConvertToBufferCell(const COORD viewportPos) const;
#pragma endregion

Microsoft::Console::VirtualTerminal::SgrStack _sgrStack;

#ifdef UNIT_TESTING
friend class TerminalCoreUnitTests::TerminalBufferTests;
friend class TerminalCoreUnitTests::TerminalApiTest;
Expand Down
28 changes: 28 additions & 0 deletions src/cascadia/TerminalCore/TerminalApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -640,3 +640,31 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept
{
return _workingDirectory;
}

// Method Description:
// - Saves the current text attributes to an internal stack.
// Arguments:
// - options, cOptions: if present, specify which portions of the current text attributes
// should be saved. Only a small subset of GraphicsOptions are actually supported;
// others are ignored. If no options are specified, all attributes are stored.
// Return Value:
// - true
bool Terminal::PushGraphicsRendition(const VTParameters options) noexcept
{
_sgrStack.Push(_buffer->GetCurrentAttributes(), options);
return true;
}

// Method Description:
// - Restores text attributes from the internal stack. If only portions of text attributes
// were saved, combines those with the current attributes.
// Arguments:
// - <none>
// Return Value:
// - true
bool Terminal::PopGraphicsRendition() noexcept
{
const TextAttribute current = _buffer->GetCurrentAttributes();
_buffer->SetCurrentAttributes(_sgrStack.Pop(current));
return true;
}
3 changes: 3 additions & 0 deletions src/cascadia/TerminalCore/TerminalDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class TerminalDispatch : public Microsoft::Console::VirtualTerminal::TermDispatc

bool SetGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::VTParameters options) noexcept override;

bool PushGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::VTParameters options) noexcept override;
bool PopGraphicsRendition() noexcept override;

bool CursorPosition(const size_t line,
const size_t column) noexcept override; // CUP

Expand Down
10 changes: 10 additions & 0 deletions src/cascadia/TerminalCore/TerminalDispatchGraphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,13 @@ bool TerminalDispatch::SetGraphicsRendition(const VTParameters options) noexcept
_terminalApi.SetTextAttributes(attr);
return true;
}

bool TerminalDispatch::PushGraphicsRendition(const VTParameters options) noexcept
{
return _terminalApi.PushGraphicsRendition(options);
}

bool TerminalDispatch::PopGraphicsRendition() noexcept
{
return _terminalApi.PopGraphicsRendition();
}
34 changes: 34 additions & 0 deletions src/terminal/adapter/DispatchTypes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,40 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
BrightBackgroundWhite = 107,
};

// Many of these correspond directly to SGR parameters (the GraphicsOptions enum), but
// these are distinct (notably 10 and 11, which as SGR parameters would select fonts,
// are used here to indicate that the foreground/background colors should be saved).
// From xterm's ctlseqs doc for XTPUSHSGR:
//
// Ps = 1 => Bold.
// Ps = 2 => Faint.
// Ps = 3 => Italicized.
// Ps = 4 => Underlined.
// Ps = 5 => Blink.
// Ps = 7 => Inverse.
// Ps = 8 => Invisible.
// Ps = 9 => Crossed-out characters.
// Ps = 2 1 => Doubly-underlined.
// Ps = 3 0 => Foreground color.
// Ps = 3 1 => Background color.
//
enum class SgrSaveRestoreStackOptions : size_t
{
All = 0,
Boldness = 1,
Faintness = 2,
Italics = 3,
Underline = 4,
Blink = 5,
Negative = 7,
Invisible = 8,
CrossedOut = 9,
DoublyUnderlined = 21,
SaveForegroundColor = 30,
SaveBackgroundColor = 31,
Max = SaveBackgroundColor
};

enum class AnsiStatusType : size_t
{
OS_OperatingStatus = 5,
Expand Down
3 changes: 3 additions & 0 deletions src/terminal/adapter/ITermDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch

virtual bool SetGraphicsRendition(const VTParameters options) = 0; // SGR

virtual bool PushGraphicsRendition(const VTParameters options) = 0; // XTPUSHSGR
virtual bool PopGraphicsRendition() = 0; // XTPOPSGR

virtual bool SetMode(const DispatchTypes::ModeParams param) = 0; // DECSET

virtual bool ResetMode(const DispatchTypes::ModeParams param) = 0; // DECRST
Expand Down
5 changes: 5 additions & 0 deletions src/terminal/adapter/adaptDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Author(s):
#include "conGetSet.hpp"
#include "adaptDefaults.hpp"
#include "terminalOutput.hpp"
#include "..\..\types\inc\sgrStack.hpp"

namespace Microsoft::Console::VirtualTerminal
{
Expand Down Expand Up @@ -56,6 +57,8 @@ namespace Microsoft::Console::VirtualTerminal
bool InsertCharacter(const size_t count) override; // ICH
bool DeleteCharacter(const size_t count) override; // DCH
bool SetGraphicsRendition(const VTParameters options) override; // SGR
bool PushGraphicsRendition(const VTParameters options) override; // XTPUSHSGR
bool PopGraphicsRendition() override; // XTPOPSGR
bool DeviceStatusReport(const DispatchTypes::AnsiStatusType statusType) override; // DSR, DSR-OS, DSR-CPR
bool DeviceAttributes() override; // DA1
bool SecondaryDeviceAttributes() override; // DA2
Expand Down Expand Up @@ -199,6 +202,8 @@ namespace Microsoft::Console::VirtualTerminal

bool _isDECCOLMAllowed;

SgrStack _sgrStack;

size_t _SetRgbColorsHelper(const VTParameters options,
TextAttribute& attr,
const bool isForeground) noexcept;
Expand Down
45 changes: 45 additions & 0 deletions src/terminal/adapter/adaptDispatchGraphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,48 @@ bool AdaptDispatch::SetGraphicsRendition(const VTParameters options)

return success;
}

// Method Description:
// - Saves the current text attributes to an internal stack.
// Arguments:
// - options: if not empty, specify which portions of the current text attributes should
// be saved. Options that are not supported are ignored. If no options are specified,
// all attributes are stored.
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::PushGraphicsRendition(const VTParameters options)
{
bool success = true;
TextAttribute currentAttributes;

success = _pConApi->PrivateGetTextAttributes(currentAttributes);

if (success)
{
_sgrStack.Push(currentAttributes, options);
}

return success;
}

// Method Description:
// - Restores text attributes from the internal stack. If only portions of text attributes
// were saved, combines those with the current attributes.
// Arguments:
// - <none>
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::PopGraphicsRendition()
{
bool success = true;
TextAttribute currentAttributes;

success = _pConApi->PrivateGetTextAttributes(currentAttributes);

if (success)
{
success = _pConApi->PrivateSetTextAttributes(_sgrStack.Pop(currentAttributes));
}

return success;
}
3 changes: 3 additions & 0 deletions src/terminal/adapter/termDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons

bool SetGraphicsRendition(const VTParameters /*options*/) noexcept override { return false; } // SGR

bool PushGraphicsRendition(const VTParameters /*options*/) noexcept override { return false; } // XTPUSHSGR
bool PopGraphicsRendition() noexcept override { return false; } // XTPOPSGR

bool SetMode(const DispatchTypes::ModeParams /*param*/) noexcept override { return false; } // DECSET

bool ResetMode(const DispatchTypes::ModeParams /*param*/) noexcept override { return false; } // DECRST
Expand Down
3 changes: 3 additions & 0 deletions src/terminal/adapter/ut_adapter/Adapter.UnitTests.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
<ProjectReference Include="..\lib\adapter.vcxproj">
<Project>{dcf55140-ef6a-4736-a403-957e4f7430bb}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\buffer\out\lib\bufferout.vcxproj">
<Project>{0cf235bd-2da0-407e-90ee-c467e8bbc714}</Project>
</ProjectReference>
</ItemGroup>
<ItemDefinitionGroup>
<ClCompile>
Expand Down
138 changes: 138 additions & 0 deletions src/terminal/adapter/ut_adapter/adapterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1599,6 +1599,144 @@ class AdapterTest
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
}

TEST_METHOD(GraphicsPushPopTests)
{
Log::Comment(L"Starting test...");

_testGetSet->PrepData(); // default color from here is gray on black, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED

VTParameter rgOptions[16];
VTParameter rgStackOptions[16];
size_t cOptions = 1;

Log::Comment(L"Test 1: Basic push and pop");

rgOptions[0] = DispatchTypes::GraphicsOptions::Off;
_testGetSet->_expectedAttribute = {};
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));

cOptions = 0;
VERIFY_IS_TRUE(_pDispatch->PushGraphicsRendition({ rgStackOptions, cOptions }));

VERIFY_IS_TRUE(_pDispatch->PopGraphicsRendition());

Log::Comment(L"Test 2: Push, change color, pop");

VERIFY_IS_TRUE(_pDispatch->PushGraphicsRendition({ rgStackOptions, cOptions }));

cOptions = 1;
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundCyan;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(3);
_testGetSet->_expectedAttribute.SetDefaultBackground();
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));

cOptions = 0;
_testGetSet->_expectedAttribute = {};
VERIFY_IS_TRUE(_pDispatch->PopGraphicsRendition());

Log::Comment(L"Test 3: two pushes (nested) and pops");

// First push:
VERIFY_IS_TRUE(_pDispatch->PushGraphicsRendition({ rgStackOptions, cOptions }));

cOptions = 1;
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundRed;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(FOREGROUND_RED);
_testGetSet->_expectedAttribute.SetDefaultBackground();
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));

// Second push:
cOptions = 0;
VERIFY_IS_TRUE(_pDispatch->PushGraphicsRendition({ rgStackOptions, cOptions }));

cOptions = 1;
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundGreen;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(FOREGROUND_GREEN);
_testGetSet->_expectedAttribute.SetDefaultBackground();
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));

// First pop:
cOptions = 0;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(FOREGROUND_RED);
_testGetSet->_expectedAttribute.SetDefaultBackground();
VERIFY_IS_TRUE(_pDispatch->PopGraphicsRendition());

// Second pop:
cOptions = 0;
_testGetSet->_expectedAttribute = {};
VERIFY_IS_TRUE(_pDispatch->PopGraphicsRendition());

Log::Comment(L"Test 4: Save and restore partial attributes");

cOptions = 1;
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundGreen;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(FOREGROUND_GREEN);
_testGetSet->_expectedAttribute.SetDefaultBackground();
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));

cOptions = 1;
rgOptions[0] = DispatchTypes::GraphicsOptions::BoldBright;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(FOREGROUND_GREEN);
_testGetSet->_expectedAttribute.SetBold(true);
_testGetSet->_expectedAttribute.SetDefaultBackground();
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));

rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundBlue;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(FOREGROUND_GREEN);
_testGetSet->_expectedAttribute.SetIndexedBackground(BACKGROUND_BLUE >> 4);
_testGetSet->_expectedAttribute.SetBold(true);
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));

// Push, specifying that we only want to save the background, the boldness, and double-underline-ness:
cOptions = 3;
rgStackOptions[0] = (size_t)DispatchTypes::SgrSaveRestoreStackOptions::Boldness;
rgStackOptions[1] = (size_t)DispatchTypes::SgrSaveRestoreStackOptions::SaveBackgroundColor;
rgStackOptions[2] = (size_t)DispatchTypes::SgrSaveRestoreStackOptions::DoublyUnderlined;
VERIFY_IS_TRUE(_pDispatch->PushGraphicsRendition({ rgStackOptions, cOptions }));

// Now change everything...
cOptions = 2;
rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundGreen;
rgOptions[1] = DispatchTypes::GraphicsOptions::DoublyUnderlined;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(FOREGROUND_GREEN);
_testGetSet->_expectedAttribute.SetIndexedBackground(BACKGROUND_GREEN >> 4);
_testGetSet->_expectedAttribute.SetBold(true);
_testGetSet->_expectedAttribute.SetDoublyUnderlined(true);
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));

cOptions = 1;
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundRed;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(FOREGROUND_RED);
_testGetSet->_expectedAttribute.SetIndexedBackground(BACKGROUND_GREEN >> 4);
_testGetSet->_expectedAttribute.SetBold(true);
_testGetSet->_expectedAttribute.SetDoublyUnderlined(true);
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));

rgOptions[0] = DispatchTypes::GraphicsOptions::NotBoldOrFaint;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(FOREGROUND_RED);
_testGetSet->_expectedAttribute.SetIndexedBackground(BACKGROUND_GREEN >> 4);
_testGetSet->_expectedAttribute.SetDoublyUnderlined(true);
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));

// And then restore...
cOptions = 0;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(FOREGROUND_RED);
_testGetSet->_expectedAttribute.SetIndexedBackground(BACKGROUND_BLUE >> 4);
_testGetSet->_expectedAttribute.SetBold(true);
VERIFY_IS_TRUE(_pDispatch->PopGraphicsRendition());
}

TEST_METHOD(GraphicsPersistBrightnessTests)
{
Log::Comment(L"Starting test...");
Expand Down
Loading

0 comments on commit 72cbe59

Please sign in to comment.