Add support for XTPUSHSGR / XTPOPSGR (#1978)
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
This commit is contained in:
parent
847749f19e
commit
72cbe59078
2
.github/actions/spelling/expect/expect.txt
vendored
2
.github/actions/spelling/expect/expect.txt
vendored
|
@ -2843,6 +2843,8 @@ XSubstantial
|
||||||
xtended
|
xtended
|
||||||
xterm
|
xterm
|
||||||
XTest
|
XTest
|
||||||
|
XTPUSHSGR
|
||||||
|
XTPOPSGR
|
||||||
xunit
|
xunit
|
||||||
xutr
|
xutr
|
||||||
xvalue
|
xvalue
|
||||||
|
|
|
@ -70,6 +70,9 @@ namespace Microsoft::Terminal::Core
|
||||||
virtual bool SetWorkingDirectory(std::wstring_view uri) noexcept = 0;
|
virtual bool SetWorkingDirectory(std::wstring_view uri) noexcept = 0;
|
||||||
virtual std::wstring_view GetWorkingDirectory() 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:
|
protected:
|
||||||
ITerminalApi() = default;
|
ITerminalApi() = default;
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <conattrs.hpp>
|
#include <conattrs.hpp>
|
||||||
|
|
||||||
#include "../../buffer/out/textBuffer.hpp"
|
#include "../../buffer/out/textBuffer.hpp"
|
||||||
|
#include "../../types/inc/sgrStack.hpp"
|
||||||
#include "../../renderer/inc/BlinkingState.hpp"
|
#include "../../renderer/inc/BlinkingState.hpp"
|
||||||
#include "../../terminal/parser/StateMachine.hpp"
|
#include "../../terminal/parser/StateMachine.hpp"
|
||||||
#include "../../terminal/input/terminalInput.hpp"
|
#include "../../terminal/input/terminalInput.hpp"
|
||||||
|
@ -124,6 +125,10 @@ public:
|
||||||
bool SetTaskbarProgress(const size_t state, const size_t progress) noexcept override;
|
bool SetTaskbarProgress(const size_t state, const size_t progress) noexcept override;
|
||||||
bool SetWorkingDirectory(std::wstring_view uri) noexcept override;
|
bool SetWorkingDirectory(std::wstring_view uri) noexcept override;
|
||||||
std::wstring_view GetWorkingDirectory() 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 endregion
|
||||||
|
|
||||||
#pragma region ITerminalInput
|
#pragma region ITerminalInput
|
||||||
|
@ -347,6 +352,8 @@ private:
|
||||||
COORD _ConvertToBufferCell(const COORD viewportPos) const;
|
COORD _ConvertToBufferCell(const COORD viewportPos) const;
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
|
Microsoft::Console::VirtualTerminal::SgrStack _sgrStack;
|
||||||
|
|
||||||
#ifdef UNIT_TESTING
|
#ifdef UNIT_TESTING
|
||||||
friend class TerminalCoreUnitTests::TerminalBufferTests;
|
friend class TerminalCoreUnitTests::TerminalBufferTests;
|
||||||
friend class TerminalCoreUnitTests::TerminalApiTest;
|
friend class TerminalCoreUnitTests::TerminalApiTest;
|
||||||
|
|
|
@ -640,3 +640,31 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept
|
||||||
{
|
{
|
||||||
return _workingDirectory;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@ public:
|
||||||
|
|
||||||
bool SetGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::VTParameters options) noexcept override;
|
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,
|
bool CursorPosition(const size_t line,
|
||||||
const size_t column) noexcept override; // CUP
|
const size_t column) noexcept override; // CUP
|
||||||
|
|
||||||
|
|
|
@ -276,3 +276,13 @@ bool TerminalDispatch::SetGraphicsRendition(const VTParameters options) noexcept
|
||||||
_terminalApi.SetTextAttributes(attr);
|
_terminalApi.SetTextAttributes(attr);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TerminalDispatch::PushGraphicsRendition(const VTParameters options) noexcept
|
||||||
|
{
|
||||||
|
return _terminalApi.PushGraphicsRendition(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TerminalDispatch::PopGraphicsRendition() noexcept
|
||||||
|
{
|
||||||
|
return _terminalApi.PopGraphicsRendition();
|
||||||
|
}
|
||||||
|
|
|
@ -311,6 +311,40 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
|
||||||
BrightBackgroundWhite = 107,
|
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
|
enum class AnsiStatusType : size_t
|
||||||
{
|
{
|
||||||
OS_OperatingStatus = 5,
|
OS_OperatingStatus = 5,
|
||||||
|
|
|
@ -89,6 +89,9 @@ public:
|
||||||
|
|
||||||
virtual bool SetGraphicsRendition(const VTParameters options) = 0; // SGR
|
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 SetMode(const DispatchTypes::ModeParams param) = 0; // DECSET
|
||||||
|
|
||||||
virtual bool ResetMode(const DispatchTypes::ModeParams param) = 0; // DECRST
|
virtual bool ResetMode(const DispatchTypes::ModeParams param) = 0; // DECRST
|
||||||
|
|
|
@ -19,6 +19,7 @@ Author(s):
|
||||||
#include "conGetSet.hpp"
|
#include "conGetSet.hpp"
|
||||||
#include "adaptDefaults.hpp"
|
#include "adaptDefaults.hpp"
|
||||||
#include "terminalOutput.hpp"
|
#include "terminalOutput.hpp"
|
||||||
|
#include "..\..\types\inc\sgrStack.hpp"
|
||||||
|
|
||||||
namespace Microsoft::Console::VirtualTerminal
|
namespace Microsoft::Console::VirtualTerminal
|
||||||
{
|
{
|
||||||
|
@ -56,6 +57,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||||
bool InsertCharacter(const size_t count) override; // ICH
|
bool InsertCharacter(const size_t count) override; // ICH
|
||||||
bool DeleteCharacter(const size_t count) override; // DCH
|
bool DeleteCharacter(const size_t count) override; // DCH
|
||||||
bool SetGraphicsRendition(const VTParameters options) override; // SGR
|
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 DeviceStatusReport(const DispatchTypes::AnsiStatusType statusType) override; // DSR, DSR-OS, DSR-CPR
|
||||||
bool DeviceAttributes() override; // DA1
|
bool DeviceAttributes() override; // DA1
|
||||||
bool SecondaryDeviceAttributes() override; // DA2
|
bool SecondaryDeviceAttributes() override; // DA2
|
||||||
|
@ -199,6 +202,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||||
|
|
||||||
bool _isDECCOLMAllowed;
|
bool _isDECCOLMAllowed;
|
||||||
|
|
||||||
|
SgrStack _sgrStack;
|
||||||
|
|
||||||
size_t _SetRgbColorsHelper(const VTParameters options,
|
size_t _SetRgbColorsHelper(const VTParameters options,
|
||||||
TextAttribute& attr,
|
TextAttribute& attr,
|
||||||
const bool isForeground) noexcept;
|
const bool isForeground) noexcept;
|
||||||
|
|
|
@ -286,3 +286,48 @@ bool AdaptDispatch::SetGraphicsRendition(const VTParameters options)
|
||||||
|
|
||||||
return success;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -83,6 +83,9 @@ public:
|
||||||
|
|
||||||
bool SetGraphicsRendition(const VTParameters /*options*/) noexcept override { return false; } // SGR
|
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 SetMode(const DispatchTypes::ModeParams /*param*/) noexcept override { return false; } // DECSET
|
||||||
|
|
||||||
bool ResetMode(const DispatchTypes::ModeParams /*param*/) noexcept override { return false; } // DECRST
|
bool ResetMode(const DispatchTypes::ModeParams /*param*/) noexcept override { return false; } // DECRST
|
||||||
|
|
|
@ -57,6 +57,9 @@
|
||||||
<ProjectReference Include="..\lib\adapter.vcxproj">
|
<ProjectReference Include="..\lib\adapter.vcxproj">
|
||||||
<Project>{dcf55140-ef6a-4736-a403-957e4f7430bb}</Project>
|
<Project>{dcf55140-ef6a-4736-a403-957e4f7430bb}</Project>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\..\..\buffer\out\lib\bufferout.vcxproj">
|
||||||
|
<Project>{0cf235bd-2da0-407e-90ee-c467e8bbc714}</Project>
|
||||||
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemDefinitionGroup>
|
<ItemDefinitionGroup>
|
||||||
<ClCompile>
|
<ClCompile>
|
||||||
|
|
|
@ -1599,6 +1599,144 @@ public:
|
||||||
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
|
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)
|
TEST_METHOD(GraphicsPersistBrightnessTests)
|
||||||
{
|
{
|
||||||
Log::Comment(L"Starting test...");
|
Log::Comment(L"Starting test...");
|
||||||
|
|
|
@ -589,6 +589,19 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete
|
||||||
success = _dispatch->SoftReset();
|
success = _dispatch->SoftReset();
|
||||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTR);
|
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTR);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CsiActionCodes::XT_PushSgr:
|
||||||
|
case CsiActionCodes::XT_PushSgrAlias:
|
||||||
|
success = _dispatch->PushGraphicsRendition(parameters);
|
||||||
|
TermTelemetry::Instance().Log(TermTelemetry::Codes::XTPUSHSGR);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CsiActionCodes::XT_PopSgr:
|
||||||
|
case CsiActionCodes::XT_PopSgrAlias:
|
||||||
|
success = _dispatch->PopGraphicsRendition();
|
||||||
|
TermTelemetry::Instance().Log(TermTelemetry::Codes::XTPOPSGR);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// If no functions to call, overall dispatch was a failure.
|
// If no functions to call, overall dispatch was a failure.
|
||||||
success = false;
|
success = false;
|
||||||
|
|
|
@ -131,7 +131,11 @@ namespace Microsoft::Console::VirtualTerminal
|
||||||
DECREQTPARM_RequestTerminalParameters = VTID("x"),
|
DECREQTPARM_RequestTerminalParameters = VTID("x"),
|
||||||
DECSCUSR_SetCursorStyle = VTID(" q"),
|
DECSCUSR_SetCursorStyle = VTID(" q"),
|
||||||
DECSTR_SoftReset = VTID("!p"),
|
DECSTR_SoftReset = VTID("!p"),
|
||||||
DECSCPP_SetColumnsPerPage = VTID("$|")
|
XT_PushSgrAlias = VTID("#p"),
|
||||||
|
XT_PopSgrAlias = VTID("#q"),
|
||||||
|
XT_PushSgr = VTID("#{"),
|
||||||
|
XT_PopSgr = VTID("#}"),
|
||||||
|
DECSCPP_SetColumnsPerPage = VTID("$|"),
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Vt52ActionCodes : uint64_t
|
enum Vt52ActionCodes : uint64_t
|
||||||
|
|
|
@ -274,6 +274,8 @@ void TermTelemetry::WriteFinalTraceLog() const
|
||||||
TraceLoggingUInt32(_uiTimesUsed[OSCSCB], "OscSetClipboard"),
|
TraceLoggingUInt32(_uiTimesUsed[OSCSCB], "OscSetClipboard"),
|
||||||
TraceLoggingUInt32(_uiTimesUsed[REP], "REP"),
|
TraceLoggingUInt32(_uiTimesUsed[REP], "REP"),
|
||||||
TraceLoggingUInt32(_uiTimesUsed[DECALN], "DECALN"),
|
TraceLoggingUInt32(_uiTimesUsed[DECALN], "DECALN"),
|
||||||
|
TraceLoggingUInt32(_uiTimesUsed[XTPUSHSGR], "XTPUSHSGR"),
|
||||||
|
TraceLoggingUInt32(_uiTimesUsed[XTPOPSGR], "XTPOPSGR"),
|
||||||
TraceLoggingUInt32Array(_uiTimesFailed, ARRAYSIZE(_uiTimesFailed), "Failed"),
|
TraceLoggingUInt32Array(_uiTimesFailed, ARRAYSIZE(_uiTimesFailed), "Failed"),
|
||||||
TraceLoggingUInt32(_uiTimesFailedOutsideRange, "FailedOutsideRange"));
|
TraceLoggingUInt32(_uiTimesFailedOutsideRange, "FailedOutsideRange"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,6 +101,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||||
OSCBG,
|
OSCBG,
|
||||||
DECALN,
|
DECALN,
|
||||||
OSCSCB,
|
OSCSCB,
|
||||||
|
XTPUSHSGR,
|
||||||
|
XTPOPSGR,
|
||||||
// Only use this last enum as a count of the number of codes.
|
// Only use this last enum as a count of the number of codes.
|
||||||
NUMBER_OF_CODES
|
NUMBER_OF_CODES
|
||||||
};
|
};
|
||||||
|
|
114
src/types/inc/sgrStack.hpp
Normal file
114
src/types/inc/sgrStack.hpp
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
/*++
|
||||||
|
Copyright (c) Microsoft Corporation
|
||||||
|
Licensed under the MIT license.
|
||||||
|
|
||||||
|
Module Name:
|
||||||
|
- sgrStack.hpp
|
||||||
|
|
||||||
|
Abstract:
|
||||||
|
- Encapsulates logic for the XTPUSHSGR / XTPOPSGR VT control sequences, which save and
|
||||||
|
restore text attributes on a stack.
|
||||||
|
|
||||||
|
--*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "..\..\buffer\out\TextAttribute.hpp"
|
||||||
|
#include "..\..\terminal\adapter\DispatchTypes.hpp"
|
||||||
|
#include <bitset>
|
||||||
|
|
||||||
|
namespace Microsoft::Console::VirtualTerminal
|
||||||
|
{
|
||||||
|
class SgrStack
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SgrStack() noexcept;
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Saves the specified text attributes onto an internal stack.
|
||||||
|
// Arguments:
|
||||||
|
// - currentAttributes - The attributes to save onto the stack.
|
||||||
|
// - options - If none supplied, the full attributes are saved. Else only the
|
||||||
|
// specified parts of currentAttributes are saved.
|
||||||
|
// Return Value:
|
||||||
|
// - <none>
|
||||||
|
void Push(const TextAttribute& currentAttributes,
|
||||||
|
const VTParameters options) noexcept;
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Restores text attributes by removing from the top of the internal stack,
|
||||||
|
// combining them with the supplied currentAttributes, if appropriate.
|
||||||
|
// Arguments:
|
||||||
|
// - currentAttributes - The current text attributes. If only a portion of
|
||||||
|
// attributes were saved on the internal stack, then those attributes will be
|
||||||
|
// combined with the currentAttributes passed in to form the return value.
|
||||||
|
// Return Value:
|
||||||
|
// - The TextAttribute that has been removed from the top of the stack, possibly
|
||||||
|
// combined with currentAttributes.
|
||||||
|
const TextAttribute Pop(const TextAttribute& currentAttributes) noexcept;
|
||||||
|
|
||||||
|
// Xterm allows the save stack to go ten deep, so we'll follow suit.
|
||||||
|
static constexpr int c_MaxStoredSgrPushes = 10;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Note the +1 in the size of the bitset: this is because we use the
|
||||||
|
// SgrSaveRestoreStackOptions enum values as bitset flags, so they are naturally
|
||||||
|
// one-based.
|
||||||
|
typedef std::bitset<static_cast<size_t>(DispatchTypes::SgrSaveRestoreStackOptions::Max) + 1> AttrBitset;
|
||||||
|
|
||||||
|
TextAttribute _CombineWithCurrentAttributes(const TextAttribute& currentAttributes,
|
||||||
|
const TextAttribute& savedAttribute,
|
||||||
|
const AttrBitset validParts); // valid parts of savedAttribute
|
||||||
|
|
||||||
|
struct SavedSgrAttributes
|
||||||
|
{
|
||||||
|
TextAttribute TextAttributes;
|
||||||
|
AttrBitset ValidParts; // flags that indicate which parts of TextAttributes are meaningful
|
||||||
|
};
|
||||||
|
|
||||||
|
// The number of "save slots" on the stack is limited (let's say there are N). So
|
||||||
|
// there are a couple of problems to think about: what to do about apps that try
|
||||||
|
// to do more pushes than will fit, and how to recover from garbage (such as
|
||||||
|
// accidentally running "cat" on a binary file that looks like lots of pushes).
|
||||||
|
//
|
||||||
|
// Dealing with more pops than pushes is simple: just ignore pops when the stack
|
||||||
|
// is empty.
|
||||||
|
//
|
||||||
|
// But how should we handle doing more pushes than are supported by the storage?
|
||||||
|
//
|
||||||
|
// One approach might be to ignore pushes once the stack is full. Things won't
|
||||||
|
// look right while the number of outstanding pushes is above the stack, but once
|
||||||
|
// it gets popped back down into range, things start working again. Put another
|
||||||
|
// way: with a traditional stack, the first N pushes work, and the last N pops
|
||||||
|
// work. But that introduces a burden: you have to do something (lots of pops) in
|
||||||
|
// order to recover from garbage. (There are strategies that could be employed to
|
||||||
|
// place an upper bound on how many pops are required (say K), but it's still
|
||||||
|
// something that /must/ be done to recover from a blown stack.)
|
||||||
|
//
|
||||||
|
// An alternative approach is a "ring stack": if you do another push when the
|
||||||
|
// stack is already full, it just drops the bottom of the stack. With this
|
||||||
|
// strategy, the last N pushes work, and the first N pops work. And the advantage
|
||||||
|
// of this approach is that there is no "recovery procedure" necessary: if you
|
||||||
|
// want a clean slate, you can just declare a clean slate--you will always have N
|
||||||
|
// slots for pushes and pops in front of you.
|
||||||
|
//
|
||||||
|
// A ring stack will also lead to apps that are friendlier to cross-app
|
||||||
|
// pushes/pops.
|
||||||
|
//
|
||||||
|
// Consider using a traditional stack. In that case, an app might be tempted to
|
||||||
|
// always begin by issuing a bunch of pops (K), in order to ensure they have a
|
||||||
|
// clean state. However, apps that behave that way would not work well with
|
||||||
|
// cross-app push/pops (e.g. I push before I ssh to my remote system, and will pop
|
||||||
|
// when after closing the connection, and during the connection I'll run apps on
|
||||||
|
// the remote host which might also do pushes and pops). By using a ring stack, an
|
||||||
|
// app does not need to do /anything/ to start in a "clean state"--an app can
|
||||||
|
// ALWAYS consider its initial state to be clean.
|
||||||
|
//
|
||||||
|
// So we've chosen to use a "ring stack", because it is simplest for apps to deal
|
||||||
|
// with.
|
||||||
|
|
||||||
|
int _nextPushIndex; // will wrap around once the stack is full
|
||||||
|
int _numSavedAttrs; // how much of _storedSgrAttributes is actually in use
|
||||||
|
std::array<SavedSgrAttributes, c_MaxStoredSgrPushes> _storedSgrAttributes;
|
||||||
|
};
|
||||||
|
}
|
|
@ -22,6 +22,7 @@
|
||||||
<ClCompile Include="..\MenuEvent.cpp" />
|
<ClCompile Include="..\MenuEvent.cpp" />
|
||||||
<ClCompile Include="..\ModifierKeyState.cpp" />
|
<ClCompile Include="..\ModifierKeyState.cpp" />
|
||||||
<ClCompile Include="..\ScreenInfoUiaProviderBase.cpp" />
|
<ClCompile Include="..\ScreenInfoUiaProviderBase.cpp" />
|
||||||
|
<ClCompile Include="..\sgrStack.cpp" />
|
||||||
<ClCompile Include="..\ThemeUtils.cpp" />
|
<ClCompile Include="..\ThemeUtils.cpp" />
|
||||||
<ClCompile Include="..\UiaTextRangeBase.cpp" />
|
<ClCompile Include="..\UiaTextRangeBase.cpp" />
|
||||||
<ClCompile Include="..\UiaTracing.cpp" />
|
<ClCompile Include="..\UiaTracing.cpp" />
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
<ClInclude Include="..\inc\Environment.hpp" />
|
<ClInclude Include="..\inc\Environment.hpp" />
|
||||||
<ClInclude Include="..\inc\GlyphWidth.hpp" />
|
<ClInclude Include="..\inc\GlyphWidth.hpp" />
|
||||||
<ClInclude Include="..\inc\IInputEvent.hpp" />
|
<ClInclude Include="..\inc\IInputEvent.hpp" />
|
||||||
|
<ClInclude Include="..\inc\sgrStack.hpp" />
|
||||||
<ClInclude Include="..\inc\ThemeUtils.h" />
|
<ClInclude Include="..\inc\ThemeUtils.h" />
|
||||||
<ClInclude Include="..\inc\utils.hpp" />
|
<ClInclude Include="..\inc\utils.hpp" />
|
||||||
<ClInclude Include="..\inc\Viewport.hpp" />
|
<ClInclude Include="..\inc\Viewport.hpp" />
|
||||||
|
|
|
@ -72,6 +72,9 @@
|
||||||
<ClCompile Include="..\Environment.cpp">
|
<ClCompile Include="..\Environment.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\sgrStack.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\UiaTracing.cpp">
|
<ClCompile Include="..\UiaTracing.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -161,6 +164,9 @@
|
||||||
<ClInclude Include="..\inc\Environment.hpp">
|
<ClInclude Include="..\inc\Environment.hpp">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\inc\sgrStack.hpp">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="..\UiaTracing.h">
|
<ClInclude Include="..\UiaTracing.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
|
212
src/types/sgrStack.cpp
Normal file
212
src/types/sgrStack.cpp
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
#include "precomp.h"
|
||||||
|
#include "inc/sgrStack.hpp"
|
||||||
|
|
||||||
|
using namespace Microsoft::Console::VirtualTerminal::DispatchTypes;
|
||||||
|
|
||||||
|
namespace Microsoft::Console::VirtualTerminal
|
||||||
|
{
|
||||||
|
SgrStack::SgrStack() noexcept :
|
||||||
|
_nextPushIndex{ 0 },
|
||||||
|
_numSavedAttrs{ 0 }
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SgrStack::Push(const TextAttribute& currentAttributes,
|
||||||
|
const VTParameters options) noexcept
|
||||||
|
{
|
||||||
|
AttrBitset validParts;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (options.empty())
|
||||||
|
{
|
||||||
|
// We save all current attributes.
|
||||||
|
validParts.set(static_cast<size_t>(SgrSaveRestoreStackOptions::All));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Each option is encoded as a bit in validParts. All options (that fit) are
|
||||||
|
// encoded; options that aren't supported are ignored when read back (popped).
|
||||||
|
// So if you try to save only unsupported aspects of the current text
|
||||||
|
// attributes, you'll do what is effectively an "empty" push (the subsequent
|
||||||
|
// pop will not change the current attributes), which is the correct behavior.
|
||||||
|
|
||||||
|
for (size_t i = 0; i < options.size(); i++)
|
||||||
|
{
|
||||||
|
const size_t optionAsIndex = options.at(i).value_or(0);
|
||||||
|
|
||||||
|
// Options must be specified singly; not in combination. Values that are
|
||||||
|
// out of range will be ignored.
|
||||||
|
if (optionAsIndex < validParts.size())
|
||||||
|
{
|
||||||
|
validParts.set(optionAsIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
// The static analyzer knows that the bitset operations can throw
|
||||||
|
// std::out_of_range. However, we know that won't happen, because we pre-check
|
||||||
|
// that everything should be in range. So we plan to never execute this
|
||||||
|
// failfast:
|
||||||
|
FAIL_FAST_CAUGHT_EXCEPTION();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_numSavedAttrs < gsl::narrow<int>(_storedSgrAttributes.size()))
|
||||||
|
{
|
||||||
|
_numSavedAttrs++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_storedSgrAttributes.at(_nextPushIndex) = { currentAttributes, validParts };
|
||||||
|
_nextPushIndex = (_nextPushIndex + 1) % gsl::narrow<int>(_storedSgrAttributes.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextAttribute SgrStack::Pop(const TextAttribute& currentAttributes) noexcept
|
||||||
|
{
|
||||||
|
if (_numSavedAttrs > 0)
|
||||||
|
{
|
||||||
|
_numSavedAttrs--;
|
||||||
|
|
||||||
|
if (_nextPushIndex == 0)
|
||||||
|
{
|
||||||
|
_nextPushIndex = gsl::narrow<int>(_storedSgrAttributes.size() - 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_nextPushIndex--;
|
||||||
|
}
|
||||||
|
|
||||||
|
SavedSgrAttributes& restoreMe = _storedSgrAttributes.at(_nextPushIndex);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (restoreMe.ValidParts.test(static_cast<size_t>(SgrSaveRestoreStackOptions::All)))
|
||||||
|
{
|
||||||
|
return restoreMe.TextAttributes;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _CombineWithCurrentAttributes(currentAttributes,
|
||||||
|
restoreMe.TextAttributes,
|
||||||
|
restoreMe.ValidParts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
// The static analyzer knows that the bitset operations can throw
|
||||||
|
// std::out_of_range. However, we know that won't happen, because we
|
||||||
|
// pre-check that everything should be in range. So we plan to never
|
||||||
|
// execute this failfast:
|
||||||
|
FAIL_FAST_CAUGHT_EXCEPTION();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextAttribute SgrStack::_CombineWithCurrentAttributes(const TextAttribute& currentAttributes,
|
||||||
|
const TextAttribute& savedAttribute,
|
||||||
|
const AttrBitset validParts) // of savedAttribute
|
||||||
|
{
|
||||||
|
// If we are restoring all attributes, we should have just taken savedAttribute
|
||||||
|
// before we even got here.
|
||||||
|
FAIL_FAST_IF(validParts.test(static_cast<size_t>(SgrSaveRestoreStackOptions::All)));
|
||||||
|
|
||||||
|
TextAttribute result = currentAttributes;
|
||||||
|
|
||||||
|
// From xterm documentation:
|
||||||
|
//
|
||||||
|
// CSI # {
|
||||||
|
// CSI Ps ; Ps # {
|
||||||
|
// Push video attributes onto stack (XTPUSHSGR), xterm. The
|
||||||
|
// optional parameters correspond to the SGR encoding for video
|
||||||
|
// attributes, except for colors (which do not have a unique SGR
|
||||||
|
// code):
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// (some closing braces for people with editors that get thrown off without them: }})
|
||||||
|
|
||||||
|
// Boldness = 1,
|
||||||
|
if (validParts.test(static_cast<size_t>(SgrSaveRestoreStackOptions::Boldness)))
|
||||||
|
{
|
||||||
|
result.SetBold(savedAttribute.IsBold());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Faintness = 2,
|
||||||
|
if (validParts.test(static_cast<size_t>(SgrSaveRestoreStackOptions::Faintness)))
|
||||||
|
{
|
||||||
|
result.SetFaint(savedAttribute.IsFaint());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Italics = 3,
|
||||||
|
if (validParts.test(static_cast<size_t>(SgrSaveRestoreStackOptions::Italics)))
|
||||||
|
{
|
||||||
|
result.SetItalic(savedAttribute.IsItalic());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Underline = 4,
|
||||||
|
if (validParts.test(static_cast<size_t>(SgrSaveRestoreStackOptions::Underline)))
|
||||||
|
{
|
||||||
|
result.SetUnderlined(savedAttribute.IsUnderlined());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blink = 5,
|
||||||
|
if (validParts.test(static_cast<size_t>(SgrSaveRestoreStackOptions::Blink)))
|
||||||
|
{
|
||||||
|
result.SetBlinking(savedAttribute.IsBlinking());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negative = 7,
|
||||||
|
if (validParts.test(static_cast<size_t>(SgrSaveRestoreStackOptions::Negative)))
|
||||||
|
{
|
||||||
|
result.SetReverseVideo(savedAttribute.IsReverseVideo());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invisible = 8,
|
||||||
|
if (validParts.test(static_cast<size_t>(SgrSaveRestoreStackOptions::Invisible)))
|
||||||
|
{
|
||||||
|
result.SetInvisible(savedAttribute.IsInvisible());
|
||||||
|
}
|
||||||
|
|
||||||
|
// CrossedOut = 9,
|
||||||
|
if (validParts.test(static_cast<size_t>(SgrSaveRestoreStackOptions::CrossedOut)))
|
||||||
|
{
|
||||||
|
result.SetCrossedOut(savedAttribute.IsCrossedOut());
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoublyUnderlined = 21,
|
||||||
|
if (validParts.test(static_cast<size_t>(SgrSaveRestoreStackOptions::DoublyUnderlined)))
|
||||||
|
{
|
||||||
|
result.SetDoublyUnderlined(savedAttribute.IsDoublyUnderlined());
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveForegroundColor = 30,
|
||||||
|
if (validParts.test(static_cast<size_t>(SgrSaveRestoreStackOptions::SaveForegroundColor)))
|
||||||
|
{
|
||||||
|
result.SetForeground(savedAttribute.GetForeground());
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveBackgroundColor = 31,
|
||||||
|
if (validParts.test(static_cast<size_t>(SgrSaveRestoreStackOptions::SaveBackgroundColor)))
|
||||||
|
{
|
||||||
|
result.SetBackground(savedAttribute.GetBackground());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -44,6 +44,7 @@ SOURCES= \
|
||||||
..\utils.cpp \
|
..\utils.cpp \
|
||||||
..\ThemeUtils.cpp \
|
..\ThemeUtils.cpp \
|
||||||
..\ScreenInfoUiaProviderBase.cpp \
|
..\ScreenInfoUiaProviderBase.cpp \
|
||||||
|
..\sgrStack.cpp \
|
||||||
..\UiaTextRangeBase.cpp \
|
..\UiaTextRangeBase.cpp \
|
||||||
..\UiaTracing.cpp \
|
..\UiaTracing.cpp \
|
||||||
..\TermControlUiaProvider.cpp \
|
..\TermControlUiaProvider.cpp \
|
||||||
|
|
Loading…
Reference in a new issue