master
h 395 lines 12.9 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 VTSupport.h
8
9 Abstract:
10
11 This file contains VT (Virtual Terminal) sequence constants, construction
12 helpers, and console mode RAII wrappers for use in Windows WSL components.
13
14 --*/
15
16 #pragma once
17
18 #include <cstdint>
19 #include <format>
20 #include <initializer_list>
21 #include <iosfwd>
22 #include <optional>
23 #include <string>
24 #include <string_view>
25 #include <type_traits>
26 #include "wslutil.h"
27
28 namespace wsl::windows::common::vt {
29
30 // Get the integral value for an enum.
31 template <typename E>
32 constexpr inline std::enable_if_t<std::is_enum_v<E>, std::underlying_type_t<E>> ToIntegral(E e)
33 {
34 return static_cast<std::underlying_type_t<E>>(e);
35 }
36
37 // Get the enum value for an integral.
38 template <typename E>
39 constexpr inline std::enable_if_t<std::is_enum_v<E>, E> ToEnum(std::underlying_type_t<E> ut)
40 {
41 return static_cast<E>(ut);
42 }
43
44 // RAII helper that changes cursor visibility on a console handle and restores
45 // the original cursor info on destruction. No-op if the handle is not a console.
46 class ChangeTerminalMode
47 {
48 public:
49 NON_COPYABLE(ChangeTerminalMode);
50 NON_MOVABLE(ChangeTerminalMode);
51
52 ChangeTerminalMode(HANDLE console, bool cursorVisible);
53 ~ChangeTerminalMode();
54
55 bool IsConsole() const;
56
57 private:
58 HANDLE m_console{};
59 CONSOLE_CURSOR_INFO m_originalCursorInfo{};
60 };
61
62 // RAII helper that enables VT processing on a console handle and restores the
63 // original mode on destruction. No-op if the handle is not a console.
64 //
65 // Output mode (STD_OUTPUT_HANDLE): sets ENABLE_VIRTUAL_TERMINAL_PROCESSING,
66 // optionally DISABLE_NEWLINE_AUTO_RETURN (best-effort, falls back without it).
67 //
68 // Input mode (STD_INPUT_HANDLE): sets ENABLE_VIRTUAL_TERMINAL_INPUT and
69 // ENABLE_EXTENDED_FLAGS, clears ENABLE_LINE_INPUT and ENABLE_ECHO_INPUT.
70 class EnableVirtualTerminal
71 {
72 public:
73 NON_COPYABLE(EnableVirtualTerminal);
74 NON_MOVABLE(EnableVirtualTerminal);
75
76 enum class Mode
77 {
78 Output,
79 Input,
80 };
81
82 explicit EnableVirtualTerminal(HANDLE console, Mode mode = Mode::Output, bool disableNewlineAutoReturn = false);
83 ~EnableVirtualTerminal();
84
85 // Returns true if VT processing is currently enabled on the console handle,
86 // whether this instance enabled it or it was already enabled at construction.
87 // This is the correct gate for "should we emit VT escape sequences?".
88 // It is independent of whether the destructor will restore the prior mode
89 // (that ownership is tracked separately by m_console).
90 bool IsVTEnabled() const;
91
92 private:
93 HANDLE m_console = nullptr; // non-null only when this instance must restore on destruction
94 DWORD m_originalMode = 0;
95 bool m_vtEnabled = false; // true when VT processing is active on the console handle
96 };
97
98 // VT escape sequences are pure ASCII byte sequences (0x00-0x7F), but the
99 // Windows WSL components are wide-string throughout (WriteConsoleW, wostream,
100 // std::wstring buffers). Sequences are therefore stored as std::wstring /
101 // std::wstring_view so they compose directly with the surrounding wide-string
102 // code with no per-call widening. Use the std::formatter specialization below
103 // for std::wformat output.
104
105 // The base for all VT sequences.
106 struct Sequence
107 {
108 constexpr Sequence() = default;
109 explicit constexpr Sequence(std::wstring_view c) : m_chars(c)
110 {
111 }
112
113 // Prevent construction from a std::wstring (lvalue or rvalue): std::wstring is
114 // implicitly convertible to std::wstring_view, so without this guard
115 // Sequence(someString) would compile but leave m_chars dangling once the string
116 // is destroyed. Use ConstructedSequence for runtime / owned sequences.
117 // A constrained template (rather than named overloads) avoids making wchar_t[]
118 // literals ambiguous between the deleted and wstring_view constructors.
119 template <typename T>
120 requires std::is_same_v<std::remove_cvref_t<T>, std::wstring>
121 explicit Sequence(T&&) = delete;
122
123 std::wstring_view Get() const
124 {
125 return m_chars;
126 }
127
128 // Returns true if this is a color or formatting sequence (SGR or OSC 8 hyperlink)
129 // that should be suppressed when --no-color is set.
130 bool IsColor() const;
131
132 protected:
133 void Set(const std::wstring& s)
134 {
135 m_chars = s;
136 }
137
138 private:
139 std::wstring_view m_chars;
140 };
141
142 // A VT sequence that is constructed at runtime.
143 struct ConstructedSequence : public Sequence
144 {
145 ConstructedSequence();
146 explicit ConstructedSequence(std::wstring s);
147
148 ConstructedSequence(const ConstructedSequence& other);
149 ConstructedSequence& operator=(const ConstructedSequence& other);
150
151 ConstructedSequence(ConstructedSequence&& other) noexcept;
152 ConstructedSequence& operator=(ConstructedSequence&& other) noexcept;
153
154 void Append(const Sequence& sequence);
155 void Clear();
156
157 private:
158 std::wstring m_str;
159 };
160
161 // Constructs a single SGR (Select Graphic Rendition) sequence with one or more
162 // semicolon-separated parameters. e.g. Sgr({1, 31}) produces "\x1b[1;31m".
163 // Prefer named constants in the Format namespace for single-parameter sequences;
164 // use this only when a multi-parameter form is required to match specific terminal
165 // output exactly (e.g. a shell PS1 that emits combined bold+color in one sequence).
166 ConstructedSequence Sgr(std::initializer_list<int> params);
167
168 // Below are mapped to the sequences described here:
169 // https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
170
171 // Contains the response to a DA1 (Primary Device Attributes) request.
172 struct PrimaryDeviceAttributes
173 {
174 // Queries the device attributes on creation.
175 // Both streams must be opened in _O_U8TEXT mode (or equivalent wide mode).
176 // outStream receives the DA1 request; inStream provides the terminal's response.
177 PrimaryDeviceAttributes(std::wostream& outStream, std::wistream& inStream);
178
179 // The extensions that a device may support.
180 enum class Extension
181 {
182 Columns132 = 1,
183 PrinterPort = 2,
184 Sixel = 4,
185 SelectiveErase = 6,
186 SoftCharacterSet = 7,
187 UserDefinedKeys = 8,
188 NationalReplacementCharacterSets = 9,
189 SoftCharacterSet2 = 12,
190 EightBitInterface = 14,
191 TechnicalCharacterSet = 15,
192 WindowingCapability = 18,
193 HorizontalScrolling = 21,
194 ColorText = 22,
195 Greek = 23,
196 Turkish = 24,
197 RectangularAreaOperations = 28,
198 TextMacros = 32,
199 ISO_Latin2CharacterSet = 42,
200 PC_Term = 44,
201 SoftKeyMap = 45,
202 ASCII_Emulation = 46,
203 };
204
205 // Determines if the given extension is supported.
206 bool Supports(Extension extension) const;
207
208 private:
209 uint32_t m_conformanceLevel = 0;
210 uint64_t m_extensions = 0;
211 };
212
213 // Cursor movement, visibility, and input mode sequences.
214 namespace Cursor {
215 // Move cursor N cells in the given direction.
216 ConstructedSequence Up(int cells);
217 ConstructedSequence Down(int cells);
218 ConstructedSequence Forward(int cells);
219 ConstructedSequence Backward(int cells);
220
221 // Move cursor to an absolute position (1-based row and column).
222 ConstructedSequence MoveTo(int row, int col);
223
224 // Move cursor to the top-left corner of the screen.
225 extern const Sequence Home;
226
227 // Cursor visibility.
228 extern const Sequence EnableBlink;
229 extern const Sequence DisableBlink;
230 extern const Sequence Show;
231 extern const Sequence Hide;
232
233 // Bracketed paste mode causes the terminal to wrap pasted text in escape sequences
234 // so the application can distinguish typed input from pasted input.
235 // See https://cirw.in/blog/bracketed-paste
236 extern const Sequence BracketedPasteOn;
237 extern const Sequence BracketedPasteOff;
238 } // namespace Cursor
239
240 // Text formatting (color, weight, style) sequences.
241 namespace Format {
242 extern const Sequence Default;
243 extern const Sequence Negative;
244
245 // Intensity attributes. Normal cancels both Bright and Dim (SGR 22).
246 extern const Sequence Bright;
247 extern const Sequence Dim;
248 extern const Sequence Normal;
249
250 extern const Sequence Italic;
251 extern const Sequence NoItalic;
252 extern const Sequence Underline;
253 extern const Sequence NoUnderline;
254
255 // A color, used in constructed sequences.
256 struct Color
257 {
258 uint8_t R;
259 uint8_t G;
260 uint8_t B;
261 };
262
263 namespace Fg {
264 // Standard foreground colors using SGR 30-37.
265 extern const Sequence Black;
266 extern const Sequence Red;
267 extern const Sequence Green;
268 extern const Sequence Yellow;
269 extern const Sequence Blue;
270 extern const Sequence Magenta;
271 extern const Sequence Cyan;
272 extern const Sequence White;
273
274 // High-intensity ("bright") foreground colors using SGR 90-97.
275 // These are distinct from SGR 1;3x (bold + standard color), which is
276 // a different byte sequence even though terminals often render them identically.
277 extern const Sequence BrightBlack; // Typically rendered as dark gray.
278 extern const Sequence BrightRed;
279 extern const Sequence BrightGreen;
280 extern const Sequence BrightYellow;
281 extern const Sequence BrightBlue;
282 extern const Sequence BrightMagenta;
283 extern const Sequence BrightCyan;
284 extern const Sequence BrightWhite;
285
286 ConstructedSequence Extended(const Color& color);
287 } // namespace Fg
288
289 namespace Bg {
290 // Standard background colors using SGR 40-47.
291 extern const Sequence Black;
292 extern const Sequence Red;
293 extern const Sequence Green;
294 extern const Sequence Yellow;
295 extern const Sequence Blue;
296 extern const Sequence Magenta;
297 extern const Sequence Cyan;
298 extern const Sequence White;
299
300 // High-intensity ("bright") background colors using SGR 100-107.
301 extern const Sequence BrightBlack; // Typically rendered as dark gray.
302 extern const Sequence BrightRed;
303 extern const Sequence BrightGreen;
304 extern const Sequence BrightYellow;
305 extern const Sequence BrightBlue;
306 extern const Sequence BrightMagenta;
307 extern const Sequence BrightCyan;
308 extern const Sequence BrightWhite;
309
310 ConstructedSequence Extended(const Color& color);
311 } // namespace Bg
312
313 ConstructedSequence Hyperlink(const std::wstring& text, const std::wstring& ref);
314 ConstructedSequence LinkOpen(const std::wstring& url);
315 extern const Sequence LinkClose;
316 } // namespace Format
317
318 // Line and screen erasure sequences.
319 namespace Erase {
320 extern const Sequence LineForward;
321 extern const Sequence LineBackward;
322 extern const Sequence LineEntirely;
323 extern const Sequence ScreenForward;
324 extern const Sequence ScreenBackward;
325 extern const Sequence ScreenEntirely;
326 } // namespace Erase
327
328 namespace Progress {
329 enum class State
330 {
331 None,
332 Indeterminate,
333 Normal,
334 Paused,
335 Error
336 };
337
338 ConstructedSequence Construct(State state, std::optional<uint32_t> percentage = std::nullopt);
339 } // namespace Progress
340
341 // operator+ overloads for combining sequences with wide strings.
342 std::wstring operator+(const Sequence& lhs, const Sequence& rhs);
343 std::wstring operator+(const Sequence& lhs, const std::wstring& rhs);
344 std::wstring operator+(const std::wstring& lhs, const Sequence& rhs);
345 std::wstring operator+(const Sequence& lhs, const wchar_t* rhs);
346 std::wstring operator+(const wchar_t* lhs, const Sequence& rhs);
347
348 // operator== overloads for comparing sequences against string literals.
349 template <typename T, typename = std::enable_if_t<std::is_base_of<Sequence, T>::value>>
350 inline bool operator==(const T& lhs, std::wstring_view rhs)
351 {
352 return lhs.Get() == rhs;
353 }
354
355 template <typename T, typename = std::enable_if_t<std::is_base_of<Sequence, T>::value>>
356 inline bool operator==(std::wstring_view lhs, const T& rhs)
357 {
358 return lhs == rhs.Get();
359 }
360
361 template <typename T, typename = std::enable_if_t<std::is_base_of<Sequence, T>::value>>
362 inline bool operator==(const T& lhs, const wchar_t* rhs)
363 {
364 return lhs.Get() == rhs;
365 }
366
367 template <typename T, typename = std::enable_if_t<std::is_base_of<Sequence, T>::value>>
368 inline bool operator==(const wchar_t* lhs, const T& rhs)
369 {
370 return lhs == rhs.Get();
371 }
372
373 // In-place wide string append.
374 std::wstring& operator+=(std::wstring& lhs, const Sequence& rhs);
375
376 } // namespace wsl::windows::common::vt
377
378 // std::formatter specializations, must be outside namespace.
379 template <>
380 struct std::formatter<wsl::windows::common::vt::Sequence, wchar_t> : std::formatter<std::wstring_view, wchar_t>
381 {
382 auto format(const wsl::windows::common::vt::Sequence& s, std::wformat_context& ctx) const
383 {
384 return std::formatter<std::wstring_view, wchar_t>::format(s.Get(), ctx);
385 }
386 };
387
388 template <>
389 struct std::formatter<wsl::windows::common::vt::ConstructedSequence, wchar_t> : std::formatter<wsl::windows::common::vt::Sequence, wchar_t>
390 {
391 auto format(const wsl::windows::common::vt::ConstructedSequence& s, std::wformat_context& ctx) const
392 {
393 return std::formatter<wsl::windows::common::vt::Sequence, wchar_t>::format(s, ctx);
394 }
395 };