-
Notifications
You must be signed in to change notification settings - Fork 8.4k
/
sgrStack.hpp
114 lines (100 loc) · 5.58 KB
/
sgrStack.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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;
};
}