Skip to content

Commit

Permalink
Implement XTPUSHSGR, XTPOPSGR
Browse files Browse the repository at this point in the history
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.

Pushes beyond ten will remain balanced (an equal number of pops will
take you back down to zero), up to 100 pushes. Beyond 100 pushes, pushes
will become unbalanced (the internal counter will no longer be
incremented). This bound gives the terminal a deterministic way to
recover from garbage--do 101 pops and you know you've cleared the stack
back down to zero.

For partial pushes (see the description of XTPUSHSGR in Issue microsoft#1796),
only attributes that are supported by terminal are saved; others are
ignored (this change does not including adding general support for
double underlines, for example). A partial push of unsupported
parameters results in an "empty" push--the subsequent pop will not
change the current text attributes.
  • Loading branch information
jazzdelightsme committed Oct 16, 2019
1 parent 019cf56 commit 98dc153
Show file tree
Hide file tree
Showing 16 changed files with 465 additions and 26 deletions.
6 changes: 3 additions & 3 deletions src/buffer/out/TextAttribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,17 +221,17 @@ bool TextAttribute::IsRightVerticalDisplayed() const noexcept
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_GRID_RVERTICAL);
}

void TextAttribute::SetLeftVerticalDisplayed(bool isDisplayed) noexcept
void TextAttribute::SetLeftVerticalDisplayed(const bool isDisplayed) noexcept
{
WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_GRID_LVERTICAL, isDisplayed);
}

void TextAttribute::SetRightVerticalDisplayed(bool isDisplayed) noexcept
void TextAttribute::SetRightVerticalDisplayed(const bool isDisplayed) noexcept
{
WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_GRID_RVERTICAL, isDisplayed);
}

void TextAttribute::SetBottomHorizontalDisplayed(bool isDisplayed) noexcept
void TextAttribute::SetBottomHorizontalDisplayed(const bool isDisplayed) noexcept
{
WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_UNDERSCORE, isDisplayed);
}
Expand Down
6 changes: 3 additions & 3 deletions src/buffer/out/TextAttribute.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ class TextAttribute final
bool IsLeftVerticalDisplayed() const noexcept;
bool IsRightVerticalDisplayed() const noexcept;

void SetLeftVerticalDisplayed(bool isDisplayed) noexcept;
void SetRightVerticalDisplayed(bool isDisplayed) noexcept;
void SetBottomHorizontalDisplayed(bool isDisplayed) noexcept;
void SetLeftVerticalDisplayed(const bool isDisplayed) noexcept;
void SetRightVerticalDisplayed(const bool isDisplayed) noexcept;
void SetBottomHorizontalDisplayed(const bool isDisplayed) noexcept;

void SetFromLegacy(const WORD wLegacy) noexcept;

Expand Down
238 changes: 238 additions & 0 deletions src/buffer/out/sgrStack.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
/*++
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 "TextAttribute.hpp"
#include "..\..\terminal\adapter\DispatchTypes.hpp"

namespace Microsoft::Console::VirtualTerminal
{
class SgrStack
{
public:
SgrStack() noexcept :
_numSgrPushes{ 0 },
_validAttributes{ 0 },
_storedSgrAttributes{ 0 }
{
}

// 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 gsl::span<const DispatchTypes::GraphicsOptions> options) noexcept
{
uint32_t validParts = 0;

if (options.size() == 0)
{
// We save all current attributes.
validParts = UINT32_MAX;
}
else
{
// Each option is encoded as a bit in validParts. Options that aren't
// supported are ignored. So if you try to save only unsuppported aspects
// of the current text attributes, validParts will be zero, and you'll do
// what is effectively an "empty" push (the subsequent pop will not change
// the current attributes).
for (auto option : options)
{
validParts |= _GraphicsOptionToFlag(option);
}
}

if (_numSgrPushes < _countof(_storedSgrAttributes))
{
// Must disable 26482 "Only index into arrays using constant expressions" because we are
// implementing a stack, and that's the whole point.
// We also disable the warning for using gsl::at, because doing that yields another: "No
// array to pointer decay".
#pragma warning(push)
#pragma warning(disable : 26482, 26446)
_storedSgrAttributes[_numSgrPushes] = currentAttributes;
_validAttributes[_numSgrPushes] = validParts;
#pragma warning(pop)
}

if (_numSgrPushes < c_MaxBalancedPushes)
{
_numSgrPushes++;
}
}

// 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
{
if (_numSgrPushes > 0)
{
_numSgrPushes--;

if (_numSgrPushes < _countof(_storedSgrAttributes))
{
// Must disable 26482 "Only index into arrays using constant expressions" because we are
// implementing a stack, and that's the whole point.
// We also disable the warning for using gsl::at, because doing that yields another: "No
// array to pointer decay".
#pragma warning(push)
#pragma warning(disable : 26482, 26446)
const uint32_t validParts = _validAttributes[_numSgrPushes];

if (validParts == UINT32_MAX)
{
return _storedSgrAttributes[_numSgrPushes];
}
else
{
return _CombineWithCurrentAttributes(currentAttributes,
_storedSgrAttributes[_numSgrPushes],
validParts);
}
#pragma warning(pop)
}
}

return currentAttributes;
}

// Xterm allows the save stack to go ten deep, so we'll follow suit. Pushes after
// ten deep will still remain "balanced"--once you pop back down below ten, you'll
// restore the appropriate text attributes. However, if you get more than a
// hundred pushes deep, we'll stop counting. Why unbalance somebody doing so many
// pushes? Putting a bound on it allows us to provide "reset" functionality: at
// any given point, you can execute 101 pops and know that you've taken the stack
// (push count) to zero. (Then you reset text attributes, and your state is
// clean.)
static constexpr int c_MaxStoredSgrPushes = 10;
static constexpr int c_MaxBalancedPushes = 100;

private:
static constexpr uint32_t _GraphicsOptionToFlag(DispatchTypes::GraphicsOptions option)
{
int iOption = static_cast<int>(option);

if (iOption < (sizeof(uint32_t) * 8))
{
iOption = 1 << iOption;
}
// else it's a bad parameter; we'll just ignore it

return iOption;
}

TextAttribute _CombineWithCurrentAttributes(const TextAttribute& currentAttributes,
const TextAttribute& savedAttribute,
uint32_t validParts) // of savedAttribute
{
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 = 1 0 -> Foreground color.
// Ps = 1 1 -> Background color.
// Ps = 2 1 -> Doubly-underlined.
//
// (some closing braces for people with editors that get thrown off without them: }})
//
// Attributes that are not currently supported are simply ignored.

if (_GraphicsOptionToFlag(DispatchTypes::GraphicsOptions::BoldBright) & validParts)
{
if (savedAttribute.IsBold())
{
result.Embolden();
}
else
{
result.Debolden();
}
}

if (_GraphicsOptionToFlag(DispatchTypes::GraphicsOptions::Underline) & validParts)
{
if (savedAttribute.IsUnderline())
{
result.EnableUnderline();
}
else
{
result.DisableUnderline();
}
}

if (_GraphicsOptionToFlag(DispatchTypes::GraphicsOptions::Negative) & validParts)
{
if (savedAttribute.IsReverseVideo())
{
if (!result.IsReverseVideo())
{
result.Invert();
}
}
else
{
if (result.IsReverseVideo())
{
result.Invert();
}
}
}

if (_GraphicsOptionToFlag(DispatchTypes::GraphicsOptions::SaveForegroundColor) & validParts)
{
result.SetForegroundFrom(savedAttribute);
}

if (_GraphicsOptionToFlag(DispatchTypes::GraphicsOptions::SaveBackgroundColor) & validParts)
{
result.SetBackgroundFrom(savedAttribute);
}

return result;
}

int _numSgrPushes; // used as an index into the following arrays
TextAttribute _storedSgrAttributes[c_MaxStoredSgrPushes];
uint32_t _validAttributes[c_MaxStoredSgrPushes]; // flags that indicate which portions of the attributes are valid
};
}
3 changes: 3 additions & 0 deletions src/cascadia/TerminalCore/ITerminalApi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ namespace Microsoft::Terminal::Core
virtual bool SetDefaultForeground(const DWORD dwColor) = 0;
virtual bool SetDefaultBackground(const DWORD dwColor) = 0;

virtual bool PushGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions* options, size_t cOptions) = 0;
virtual bool PopGraphicsRendition() = 0;

protected:
ITerminalApi() = default;
};
Expand Down
5 changes: 5 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 "../../buffer/out/sgrStack.hpp"
#include "../../renderer/inc/IRenderData.hpp"
#include "../../terminal/parser/StateMachine.hpp"
#include "../../terminal/input/terminalInput.hpp"
Expand Down Expand Up @@ -83,6 +84,8 @@ class Microsoft::Terminal::Core::Terminal final :
bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) override;
bool SetDefaultForeground(const COLORREF dwColor) override;
bool SetDefaultBackground(const COLORREF dwColor) override;
bool PushGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions* options, size_t cOptions) override;
bool PopGraphicsRendition() override;
#pragma endregion

#pragma region ITerminalInput
Expand Down Expand Up @@ -243,4 +246,6 @@ class Microsoft::Terminal::Core::Terminal final :
SMALL_RECT _GetSelectionRow(const SHORT row, const COORD higherCoord, const COORD lowerCoord) const;
void _ExpandSelectionRow(SMALL_RECT& selectionRow) const;
#pragma endregion

Microsoft::Console::VirtualTerminal::SgrStack _sgrStack;
};
28 changes: 28 additions & 0 deletions src/cascadia/TerminalCore/TerminalApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,3 +474,31 @@ bool Terminal::SetDefaultBackground(const COLORREF dwColor)
_buffer->GetRenderTarget().TriggerRedrawAll();
return true;
}

// 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 ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions* options, size_t cOptions)
{
_sgrStack.Push(_buffer->GetCurrentAttributes(), { options, (int) cOptions });
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()
{
TextAttribute current = _buffer->GetCurrentAttributes();
_buffer->SetCurrentAttributes(_sgrStack.Pop(current));
return true;
}
4 changes: 4 additions & 0 deletions src/cascadia/TerminalCore/TerminalDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class TerminalDispatch : public Microsoft::Console::VirtualTerminal::TermDispatc
bool SetGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions* const rgOptions,
const size_t cOptions) override;

bool PushGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions* const rgOptions,
const size_t cOptions) override;
bool PopGraphicsRendition() override;

virtual bool CursorPosition(const unsigned int uiLine,
const unsigned int uiColumn) override; // CUP

Expand Down
11 changes: 11 additions & 0 deletions src/cascadia/TerminalCore/TerminalDispatchGraphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,14 @@ bool TerminalDispatch::SetGraphicsRendition(const DispatchTypes::GraphicsOptions
}
return fSuccess;
}

bool TerminalDispatch::PushGraphicsRendition(const DispatchTypes::GraphicsOptions* const rgOptions,
const size_t cOptions)
{
return _terminalApi.PushGraphicsRendition(rgOptions, cOptions);
}

bool TerminalDispatch::PopGraphicsRendition()
{
return _terminalApi.PopGraphicsRendition();
}
8 changes: 7 additions & 1 deletion src/host/outputStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ BOOL ConhostInternalGetSet::PrivateSetDefaultAttributes(const bool fForeground,
// - fBackground - The new attributes contain an update to the background attributes
// - fMeta - The new attributes contain an update to the meta attributes
// Return Value:
// - TRUE if successful (see DoSrvVtSetLegacyAttributes). FALSE otherwise.
// - TRUE.
BOOL ConhostInternalGetSet::PrivateSetLegacyAttributes(const WORD wAttr,
const bool fForeground,
const bool fBackground,
Expand All @@ -239,6 +239,12 @@ BOOL ConhostInternalGetSet::PrivateSetLegacyAttributes(const WORD wAttr,
return TRUE;
}

// Routine Description:
// - Similar to PrivateSetLegacyAttributes, but sets the full fidelity TextAttribute.
// Arguments:
// - attributes - new text attributes to apply as default within the console text buffer
// Return Value:
// - TRUE.
BOOL ConhostInternalGetSet::PrivateSetAttributes(const TextAttribute& attributes)
{
DoSrvPrivateSetAttributes(_io.GetActiveOutputBuffer(), attributes);
Expand Down
4 changes: 4 additions & 0 deletions src/terminal/adapter/DispatchTypes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
Negative = 7,
Invisible = 8,
CrossedOut = 9,
// 10 - 19 are actually for selecting fonts, but rarely implemented. 10 and 11
// have been repurposed by xterm for XTPUSHGR.
SaveForegroundColor = 10,
SaveBackgroundColor = 11,
DoublyUnderlined = 21,
UnBold = 22,
NotItalics = 23,
Expand Down
Loading

0 comments on commit 98dc153

Please sign in to comment.