72cbe59078
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
115 lines
5.6 KiB
C++
115 lines
5.6 KiB
C++
/*++
|
|
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;
|
|
};
|
|
}
|