From 7fcff4d33ad5595192a57b92c5506e6b745071b7 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Tue, 18 Aug 2020 19:57:52 +0100 Subject: [PATCH] Refactor VT control sequence identification (#7304) This PR changes the way VT control sequences are identified and dispatched, to be more efficient and easier to extend. Instead of parsing the intermediate characters into a vector, and then having to identify a sequence using both that vector and the final char, we now use just a single `uint64_t` value as the identifier. The way the identifier is constructed is by taking the private parameter prefix, each of the intermediate characters, and then the final character, and shifting them into a 64-bit integer one byte at a time, in reverse order. For example, the `DECTLTC` control has a private parameter prefix of `?`, one intermediate of `'`, and a final character of `s`. The ASCII values of those characters are `0x3F`, `0x27`, and `0x73` respectively, and reversing them gets you 0x73273F, so that would then be the identifier for the control. The reason for storing them in reverse order, is because sometimes we need to look at the first intermediate to determine the operation, and treat the rest of the sequence as a kind of sub-identifier (the character set designation sequences are one example of this). When in reverse order, this can easily be achieved by masking off the low byte to get the first intermediate, and then shifting the value right by 8 bits to get a new identifier with the rest of the sequence. With 64 bits we have enough space for a private prefix, six intermediates, and the final char, which is way more than we should ever need (the _DEC STD 070_ specification recommends supporting at least three intermediates, but in practice we're unlikely to see more than two). With this new way of identifying controls, it should now be possible for every action code to be unique (for the most part). So I've also used this PR to clean up the action codes a bit, splitting the codes for the escape sequences from the control sequences, and sorting them into alphabetical order (which also does a reasonable job of clustering associated controls). ## Validation Steps Performed I think the existing unit tests should be good enough to confirm that all sequences are still being dispatched correctly. However, I've also manually tested a number of sequences to make sure they were still working as expected, in particular those that used intermediates, since they were the most affected by the dispatch code refactoring. Since these changes also affected the input state machine, I've done some manual testing of the conpty keyboard handling (both with and without the new Win32 input mode enabled) to make sure the keyboard VT sequences were processed correctly. I've also manually tested the various VT mouse modes in Vttest to confirm that they were still working correctly too. Closes #7276 --- .github/actions/spell-check/expect/expect.txt | 2 + src/terminal/adapter/DispatchTypes.hpp | 102 +- src/terminal/adapter/ITermDispatch.hpp | 6 +- src/terminal/adapter/adaptDispatch.cpp | 10 +- src/terminal/adapter/adaptDispatch.hpp | 6 +- src/terminal/adapter/termDispatch.hpp | 6 +- src/terminal/adapter/terminalOutput.cpp | 124 +- src/terminal/adapter/terminalOutput.hpp | 4 +- src/terminal/parser/IStateMachineEngine.hpp | 14 +- .../parser/InputStateMachineEngine.cpp | 89 +- .../parser/InputStateMachineEngine.hpp | 58 +- .../parser/OutputStateMachineEngine.cpp | 1011 +++++++---------- .../parser/OutputStateMachineEngine.hpp | 174 ++- src/terminal/parser/stateMachine.cpp | 15 +- src/terminal/parser/stateMachine.hpp | 4 +- .../parser/ut_parser/InputEngineTest.cpp | 10 +- .../parser/ut_parser/StateMachineTest.cpp | 11 +- 17 files changed, 733 insertions(+), 913 deletions(-) diff --git a/.github/actions/spell-check/expect/expect.txt b/.github/actions/spell-check/expect/expect.txt index ba5e05b86b..ddb33c771f 100644 --- a/.github/actions/spell-check/expect/expect.txt +++ b/.github/actions/spell-check/expect/expect.txt @@ -548,6 +548,7 @@ DECSCUSR DECSED DECSEL DECSET +DECSLPP DECSLRM DECSMBV DECSMKR @@ -2579,6 +2580,7 @@ vstudio vswhere vtapp VTE +VTID vtio vtmode vtpipeterm diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index 9de95ca152..1454ed279e 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -3,6 +3,94 @@ #pragma once +namespace Microsoft::Console::VirtualTerminal +{ + class VTID + { + public: + template + constexpr VTID(const char (&s)[Length]) : + _value{ _FromString(s) } + { + } + + constexpr VTID(const uint64_t value) : + _value{ value } + { + } + + constexpr operator uint64_t() const + { + return _value; + } + + constexpr char operator[](const size_t offset) const + { + return SubSequence(offset)._value & 0xFF; + } + + constexpr VTID SubSequence(const size_t offset) const + { + return _value >> (CHAR_BIT * offset); + } + + private: + template + static constexpr uint64_t _FromString(const char (&s)[Length]) + { + static_assert(Length - 1 <= sizeof(_value)); + uint64_t value = 0; + for (auto i = Length - 1; i-- > 0;) + { + value = (value << CHAR_BIT) + gsl::at(s, i); + } + return value; + } + + uint64_t _value; + }; + + class VTIDBuilder + { + public: + void Clear() noexcept + { + _idAccumulator = 0; + _idShift = 0; + } + + void AddIntermediate(const wchar_t intermediateChar) noexcept + { + if (_idShift + CHAR_BIT >= sizeof(_idAccumulator) * CHAR_BIT) + { + // If there is not enough space in the accumulator to add + // the intermediate and still have room left for the final, + // then we reset the accumulator to zero. This will result + // in an id with all zero intermediates, which shouldn't + // match anything. + _idAccumulator = 0; + } + else + { + // Otherwise we shift the intermediate so as to add it to the + // accumulator in the next available space, and then increment + // the shift by 8 bits in preparation for the next character. + _idAccumulator += (static_cast(intermediateChar) << _idShift); + _idShift += CHAR_BIT; + } + } + + VTID Finalize(const wchar_t finalChar) noexcept + { + return _idAccumulator + (static_cast(finalChar) << _idShift); + } + + private: + uint64_t _idAccumulator = 0; + size_t _idShift = 0; + }; +} + namespace Microsoft::Console::VirtualTerminal::DispatchTypes { enum class EraseType : unsigned int @@ -101,16 +189,16 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes W32IM_Win32InputMode = 9001 }; - namespace CharacterSets + enum CharacterSets : uint64_t { - constexpr auto DecSpecialGraphics = std::make_pair(L'0', L'\0'); - constexpr auto ASCII = std::make_pair(L'B', L'\0'); - } + DecSpecialGraphics = VTID("0"), + ASCII = VTID("B") + }; - enum CodingSystem : wchar_t + enum CodingSystem : uint64_t { - ISO2022 = L'@', - UTF8 = L'G' + ISO2022 = VTID("@"), + UTF8 = VTID("G") }; enum TabClearType : unsigned short diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index 93f1dffecc..b90705ac4a 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -98,9 +98,9 @@ public: virtual bool TertiaryDeviceAttributes() = 0; // DA3 virtual bool Vt52DeviceAttributes() = 0; // VT52 Identify - virtual bool DesignateCodingSystem(const wchar_t codingSystem) = 0; // DOCS - virtual bool Designate94Charset(const size_t gsetNumber, const std::pair charset) = 0; // SCS - virtual bool Designate96Charset(const size_t gsetNumber, const std::pair charset) = 0; // SCS + virtual bool DesignateCodingSystem(const VTID codingSystem) = 0; // DOCS + virtual bool Designate94Charset(const size_t gsetNumber, const VTID charset) = 0; // SCS + virtual bool Designate96Charset(const size_t gsetNumber, const VTID charset) = 0; // SCS virtual bool LockingShift(const size_t gsetNumber) = 0; // LS0, LS1, LS2, LS3 virtual bool LockingShiftRight(const size_t gsetNumber) = 0; // LS1R, LS2R, LS3R virtual bool SingleShift(const size_t gsetNumber) = 0; // SS2, SS3 diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 8d656e572d..d435498c32 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1670,7 +1670,7 @@ void AdaptDispatch::_InitTabStopsForWidth(const size_t width) // - codingSystem - The coding system that will be selected. // Return value: // True if handled successfully. False otherwise. -bool AdaptDispatch::DesignateCodingSystem(const wchar_t codingSystem) +bool AdaptDispatch::DesignateCodingSystem(const VTID codingSystem) { // If we haven't previously saved the initial code page, do so now. // This will be used to restore the code page in response to a reset. @@ -1712,10 +1712,10 @@ bool AdaptDispatch::DesignateCodingSystem(const wchar_t codingSystem) // If the specified charset is unsupported, we do nothing (remain on the current one) //Arguments: // - gsetNumber - The G-set into which the charset will be selected. -// - charset - The characters indicating the charset that will be used. +// - charset - The identifier indicating the charset that will be used. // Return value: // True if handled successfully. False otherwise. -bool AdaptDispatch::Designate94Charset(const size_t gsetNumber, const std::pair charset) +bool AdaptDispatch::Designate94Charset(const size_t gsetNumber, const VTID charset) { return _termOutput.Designate94Charset(gsetNumber, charset); } @@ -1727,10 +1727,10 @@ bool AdaptDispatch::Designate94Charset(const size_t gsetNumber, const std::pair< // If the specified charset is unsupported, we do nothing (remain on the current one) //Arguments: // - gsetNumber - The G-set into which the charset will be selected. -// - charset - The characters indicating the charset that will be used. +// - charset - The identifier indicating the charset that will be used. // Return value: // True if handled successfully. False otherwise. -bool AdaptDispatch::Designate96Charset(const size_t gsetNumber, const std::pair charset) +bool AdaptDispatch::Designate96Charset(const size_t gsetNumber, const VTID charset) { return _termOutput.Designate96Charset(gsetNumber, charset); } diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 12a0c90210..2e5f27a782 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -89,9 +89,9 @@ namespace Microsoft::Console::VirtualTerminal bool ForwardTab(const size_t numTabs) override; // CHT, HT bool BackwardsTab(const size_t numTabs) override; // CBT bool TabClear(const size_t clearType) override; // TBC - bool DesignateCodingSystem(const wchar_t codingSystem) override; // DOCS - bool Designate94Charset(const size_t gsetNumber, const std::pair charset) override; // SCS - bool Designate96Charset(const size_t gsetNumber, const std::pair charset) override; // SCS + bool DesignateCodingSystem(const VTID codingSystem) override; // DOCS + bool Designate94Charset(const size_t gsetNumber, const VTID charset) override; // SCS + bool Designate96Charset(const size_t gsetNumber, const VTID charset) override; // SCS bool LockingShift(const size_t gsetNumber) override; // LS0, LS1, LS2, LS3 bool LockingShiftRight(const size_t gsetNumber) override; // LS1R, LS2R, LS3R bool SingleShift(const size_t gsetNumber) override; // SS2, SS3 diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index e13579c688..47ecd99078 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -92,9 +92,9 @@ public: bool TertiaryDeviceAttributes() noexcept override { return false; } // DA3 bool Vt52DeviceAttributes() noexcept override { return false; } // VT52 Identify - bool DesignateCodingSystem(const wchar_t /*codingSystem*/) noexcept override { return false; } // DOCS - bool Designate94Charset(const size_t /*gsetNumber*/, const std::pair /*charset*/) noexcept override { return false; } // SCS - bool Designate96Charset(const size_t /*gsetNumber*/, const std::pair /*charset*/) noexcept override { return false; } // SCS + bool DesignateCodingSystem(const VTID /*codingSystem*/) noexcept override { return false; } // DOCS + bool Designate94Charset(const size_t /*gsetNumber*/, const VTID /*charset*/) noexcept override { return false; } // SCS + bool Designate96Charset(const size_t /*gsetNumber*/, const VTID /*charset*/) noexcept override { return false; } // SCS bool LockingShift(const size_t /*gsetNumber*/) noexcept override { return false; } // LS0, LS1, LS2, LS3 bool LockingShiftRight(const size_t /*gsetNumber*/) noexcept override { return false; } // LS1R, LS2R, LS3R bool SingleShift(const size_t /*gsetNumber*/) noexcept override { return false; } // SS2, SS3 diff --git a/src/terminal/adapter/terminalOutput.cpp b/src/terminal/adapter/terminalOutput.cpp index f320839ac5..14de84e139 100644 --- a/src/terminal/adapter/terminalOutput.cpp +++ b/src/terminal/adapter/terminalOutput.cpp @@ -17,107 +17,89 @@ TerminalOutput::TerminalOutput() noexcept _gsetTranslationTables.at(3) = Latin1; } -bool TerminalOutput::Designate94Charset(size_t gsetNumber, const std::pair charset) +bool TerminalOutput::Designate94Charset(size_t gsetNumber, const VTID charset) { - switch (charset.first) + switch (charset) { - case L'B': // US ASCII - case L'1': // Alternate Character ROM + case VTID("B"): // US ASCII + case VTID("1"): // Alternate Character ROM return _SetTranslationTable(gsetNumber, Ascii); - case L'0': // DEC Special Graphics - case L'2': // Alternate Character ROM Special Graphics + case VTID("0"): // DEC Special Graphics + case VTID("2"): // Alternate Character ROM Special Graphics return _SetTranslationTable(gsetNumber, DecSpecialGraphics); - case L'<': // DEC Supplemental + case VTID("<"): // DEC Supplemental return _SetTranslationTable(gsetNumber, DecSupplemental); - case L'A': // British NRCS + case VTID("A"): // British NRCS return _SetTranslationTable(gsetNumber, BritishNrcs); - case L'4': // Dutch NRCS + case VTID("4"): // Dutch NRCS return _SetTranslationTable(gsetNumber, DutchNrcs); - case L'5': // Finnish NRCS - case L'C': // (fallback) + case VTID("5"): // Finnish NRCS + case VTID("C"): // (fallback) return _SetTranslationTable(gsetNumber, FinnishNrcs); - case L'R': // French NRCS + case VTID("R"): // French NRCS return _SetTranslationTable(gsetNumber, FrenchNrcs); - case L'f': // French NRCS (ISO update) + case VTID("f"): // French NRCS (ISO update) return _SetTranslationTable(gsetNumber, FrenchNrcsIso); - case L'9': // French Canadian NRCS - case L'Q': // (fallback) + case VTID("9"): // French Canadian NRCS + case VTID("Q"): // (fallback) return _SetTranslationTable(gsetNumber, FrenchCanadianNrcs); - case L'K': // German NRCS + case VTID("K"): // German NRCS return _SetTranslationTable(gsetNumber, GermanNrcs); - case L'Y': // Italian NRCS + case VTID("Y"): // Italian NRCS return _SetTranslationTable(gsetNumber, ItalianNrcs); - case L'6': // Norwegian/Danish NRCS - case L'E': // (fallback) + case VTID("6"): // Norwegian/Danish NRCS + case VTID("E"): // (fallback) return _SetTranslationTable(gsetNumber, NorwegianDanishNrcs); - case L'`': // Norwegian/Danish NRCS (ISO standard) + case VTID("`"): // Norwegian/Danish NRCS (ISO standard) return _SetTranslationTable(gsetNumber, NorwegianDanishNrcsIso); - case L'Z': // Spanish NRCS + case VTID("Z"): // Spanish NRCS return _SetTranslationTable(gsetNumber, SpanishNrcs); - case L'7': // Swedish NRCS - case L'H': // (fallback) + case VTID("7"): // Swedish NRCS + case VTID("H"): // (fallback) return _SetTranslationTable(gsetNumber, SwedishNrcs); - case L'=': // Swiss NRCS + case VTID("="): // Swiss NRCS return _SetTranslationTable(gsetNumber, SwissNrcs); - case L'&': - switch (charset.second) - { - case L'4': // DEC Cyrillic - return _SetTranslationTable(gsetNumber, DecCyrillic); - case L'5': // Russian NRCS - return _SetTranslationTable(gsetNumber, RussianNrcs); - default: - return false; - } - case L'"': - switch (charset.second) - { - case L'?': // DEC Greek - return _SetTranslationTable(gsetNumber, DecGreek); - case L'>': // Greek NRCS - return _SetTranslationTable(gsetNumber, GreekNrcs); - case L'4': // DEC Hebrew - return _SetTranslationTable(gsetNumber, DecHebrew); - default: - return false; - } - case L'%': - switch (charset.second) - { - case L'=': // Hebrew NRCS - return _SetTranslationTable(gsetNumber, HebrewNrcs); - case L'0': // DEC Turkish - return _SetTranslationTable(gsetNumber, DecTurkish); - case L'2': // Turkish NRCS - return _SetTranslationTable(gsetNumber, TurkishNrcs); - case L'5': // DEC Supplemental - return _SetTranslationTable(gsetNumber, DecSupplemental); - case L'6': // Portuguese NRCS - return _SetTranslationTable(gsetNumber, PortugueseNrcs); - default: - return false; - } + case VTID("&4"): // DEC Cyrillic + return _SetTranslationTable(gsetNumber, DecCyrillic); + case VTID("&5"): // Russian NRCS + return _SetTranslationTable(gsetNumber, RussianNrcs); + case VTID("\"?"): // DEC Greek + return _SetTranslationTable(gsetNumber, DecGreek); + case VTID("\">"): // Greek NRCS + return _SetTranslationTable(gsetNumber, GreekNrcs); + case VTID("\"4"): // DEC Hebrew + return _SetTranslationTable(gsetNumber, DecHebrew); + case VTID("%="): // Hebrew NRCS + return _SetTranslationTable(gsetNumber, HebrewNrcs); + case VTID("%0"): // DEC Turkish + return _SetTranslationTable(gsetNumber, DecTurkish); + case VTID("%2"): // Turkish NRCS + return _SetTranslationTable(gsetNumber, TurkishNrcs); + case VTID("%5"): // DEC Supplemental + return _SetTranslationTable(gsetNumber, DecSupplemental); + case VTID("%6"): // Portuguese NRCS + return _SetTranslationTable(gsetNumber, PortugueseNrcs); default: return false; } } -bool TerminalOutput::Designate96Charset(size_t gsetNumber, const std::pair charset) +bool TerminalOutput::Designate96Charset(size_t gsetNumber, const VTID charset) { - switch (charset.first) + switch (charset) { - case L'A': // ISO Latin-1 Supplemental - case L'<': // (UPSS when assigned to Latin-1) + case VTID("A"): // ISO Latin-1 Supplemental + case VTID("<"): // (UPSS when assigned to Latin-1) return _SetTranslationTable(gsetNumber, Latin1); - case L'B': // ISO Latin-2 Supplemental + case VTID("B"): // ISO Latin-2 Supplemental return _SetTranslationTable(gsetNumber, Latin2); - case L'L': // ISO Latin-Cyrillic Supplemental + case VTID("L"): // ISO Latin-Cyrillic Supplemental return _SetTranslationTable(gsetNumber, LatinCyrillic); - case L'F': // ISO Latin-Greek Supplemental + case VTID("F"): // ISO Latin-Greek Supplemental return _SetTranslationTable(gsetNumber, LatinGreek); - case L'H': // ISO Latin-Hebrew Supplemental + case VTID("H"): // ISO Latin-Hebrew Supplemental return _SetTranslationTable(gsetNumber, LatinHebrew); - case L'M': // ISO Latin-5 Supplemental + case VTID("M"): // ISO Latin-5 Supplemental return _SetTranslationTable(gsetNumber, Latin5); default: return false; diff --git a/src/terminal/adapter/terminalOutput.hpp b/src/terminal/adapter/terminalOutput.hpp index a26f6d85a6..e82e067a7c 100644 --- a/src/terminal/adapter/terminalOutput.hpp +++ b/src/terminal/adapter/terminalOutput.hpp @@ -26,8 +26,8 @@ namespace Microsoft::Console::VirtualTerminal TerminalOutput() noexcept; wchar_t TranslateKey(const wchar_t wch) const noexcept; - bool Designate94Charset(const size_t gsetNumber, const std::pair charset); - bool Designate96Charset(const size_t gsetNumber, const std::pair charset); + bool Designate94Charset(const size_t gsetNumber, const VTID charset); + bool Designate96Charset(const size_t gsetNumber, const VTID charset); bool LockingShift(const size_t gsetNumber); bool LockingShiftRight(const size_t gsetNumber); bool SingleShift(const size_t gsetNumber); diff --git a/src/terminal/parser/IStateMachineEngine.hpp b/src/terminal/parser/IStateMachineEngine.hpp index c1a668fb72..169cf8ac02 100644 --- a/src/terminal/parser/IStateMachineEngine.hpp +++ b/src/terminal/parser/IStateMachineEngine.hpp @@ -12,6 +12,9 @@ Abstract: the existing VT parsing. */ #pragma once + +#include "../adapter/DispatchTypes.hpp" + namespace Microsoft::Console::VirtualTerminal { class IStateMachineEngine @@ -30,14 +33,9 @@ namespace Microsoft::Console::VirtualTerminal virtual bool ActionPassThroughString(const std::wstring_view string) = 0; - virtual bool ActionEscDispatch(const wchar_t wch, - const gsl::span intermediates) = 0; - virtual bool ActionVt52EscDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) = 0; - virtual bool ActionCsiDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) = 0; + virtual bool ActionEscDispatch(const VTID id) = 0; + virtual bool ActionVt52EscDispatch(const VTID id, const gsl::span parameters) = 0; + virtual bool ActionCsiDispatch(const VTID id, const gsl::span parameters) = 0; virtual bool ActionClear() = 0; diff --git a/src/terminal/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp index a4d903f564..c795243bf0 100644 --- a/src/terminal/parser/InputStateMachineEngine.cpp +++ b/src/terminal/parser/InputStateMachineEngine.cpp @@ -34,9 +34,9 @@ static constexpr std::array s_csiMap = { CsiToVkey{ CsiActionCodes::CSI_F4, VK_F4 } }; -static bool operator==(const CsiToVkey& pair, const CsiActionCodes code) noexcept +static bool operator==(const CsiToVkey& pair, const VTID id) noexcept { - return pair.action == code; + return pair.action == id; } struct GenericToVkey @@ -298,12 +298,10 @@ bool InputStateMachineEngine::ActionPassThroughString(const std::wstring_view st // a simple escape sequence. These sequences traditionally start with ESC // and a simple letter. No complicated parameters. // Arguments: -// - wch - Character to dispatch. -// - intermediates - Intermediate characters in the sequence +// - id - Identifier of the escape sequence to dispatch. // Return Value: // - true iff we successfully dispatched the sequence. -bool InputStateMachineEngine::ActionEscDispatch(const wchar_t wch, - const gsl::span /*intermediates*/) +bool InputStateMachineEngine::ActionEscDispatch(const VTID id) { if (_pDispatch->IsVtInputEnabled() && _pfnFlushToInputQueue) { @@ -312,6 +310,9 @@ bool InputStateMachineEngine::ActionEscDispatch(const wchar_t wch, bool success = false; + // There are no intermediates, so the id is effectively the final char. + const wchar_t wch = gsl::narrow_cast(id); + // 0x7f is DEL, which we treat effectively the same as a ctrl character. if (wch == 0x7f) { @@ -339,14 +340,11 @@ bool InputStateMachineEngine::ActionEscDispatch(const wchar_t wch, // a VT52 escape sequence. These sequences start with ESC and a single letter, // sometimes followed by parameters. // Arguments: -// - wch - Character to dispatch. -// - intermediates - Intermediate characters in the sequence. +// - id - Identifier of the VT52 sequence to dispatch. // - parameters - Set of parameters collected while parsing the sequence. // Return Value: // - true iff we successfully dispatched the sequence. -bool InputStateMachineEngine::ActionVt52EscDispatch(const wchar_t /*wch*/, - const gsl::span /*intermediates*/, - const gsl::span /*parameters*/) noexcept +bool InputStateMachineEngine::ActionVt52EscDispatch(const VTID /*id*/, const gsl::span /*parameters*/) noexcept { // VT52 escape sequences are not used in the input state machine. return false; @@ -357,17 +355,12 @@ bool InputStateMachineEngine::ActionVt52EscDispatch(const wchar_t /*wch*/, // a control sequence. These sequences perform various API-type commands // that can include many parameters. // Arguments: -// - wch - Character to dispatch. -// - intermediates - Intermediate characters in the sequence +// - id - Identifier of the control sequence to dispatch. // - parameters - set of numeric parameters collected while parsing the sequence. // Return Value: // - true iff we successfully dispatched the sequence. -bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) +bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const gsl::span parameters) { - const auto actionCode = static_cast(wch); - // GH#4999 - If the client was in VT input mode, but we received a // win32-input-mode sequence, then _don't_ passthrough the sequence to the // client. It's impossibly unlikely that the client actually wanted @@ -376,7 +369,7 @@ bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, // client reads it. if (_pDispatch->IsVtInputEnabled() && _pfnFlushToInputQueue && - actionCode != CsiActionCodes::Win32KeyboardInput) + id != CsiActionCodes::Win32KeyboardInput) { return _pfnFlushToInputQueue(); } @@ -392,32 +385,22 @@ bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, const auto remainingArgs = parameters.size() > 1 ? parameters.subspan(1) : gsl::span{}; bool success = false; - // Handle intermediate characters, if any - if (!intermediates.empty()) + switch (id) { - switch (static_cast(til::at(intermediates, 0))) - { - case CsiIntermediateCodes::MOUSE_SGR: - { - DWORD buttonState = 0; - DWORD eventFlags = 0; - modifierState = _GetSGRMouseModifierState(parameters); - success = _GetSGRXYPosition(parameters, row, col); + case CsiActionCodes::MouseDown: + case CsiActionCodes::MouseUp: + { + DWORD buttonState = 0; + DWORD eventFlags = 0; + modifierState = _GetSGRMouseModifierState(parameters); + success = _GetSGRXYPosition(parameters, row, col); - // we need _UpdateSGRMouseButtonState() on the left side here because we _always_ should be updating our state - // even if we failed to parse a portion of this sequence. - success = _UpdateSGRMouseButtonState(wch, parameters, buttonState, eventFlags) && success; - success = success && _WriteMouseEvent(col, row, buttonState, modifierState, eventFlags); - break; - } - default: - success = false; - break; - } + // we need _UpdateSGRMouseButtonState() on the left side here because we _always_ should be updating our state + // even if we failed to parse a portion of this sequence. + success = _UpdateSGRMouseButtonState(id, parameters, buttonState, eventFlags) && success; + success = success && _WriteMouseEvent(col, row, buttonState, modifierState, eventFlags); return success; } - switch (actionCode) - { case CsiActionCodes::Generic: modifierState = _GetGenericKeysModifierState(parameters); success = _GetGenericVkey(parameters, vkey); @@ -443,8 +426,8 @@ bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, case CsiActionCodes::CSI_F1: case CsiActionCodes::CSI_F2: case CsiActionCodes::CSI_F4: - success = _GetCursorKeysVkey(wch, vkey); - modifierState = _GetCursorKeysModifierState(parameters, static_cast(wch)); + success = _GetCursorKeysVkey(id, vkey); + modifierState = _GetCursorKeysModifierState(parameters, id); break; case CsiActionCodes::CursorBackTab: modifierState = SHIFT_PRESSED; @@ -464,7 +447,7 @@ bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, if (success) { - switch (static_cast(wch)) + switch (id) { // case CsiActionCodes::DSR_DeviceStatusReportResponse: case CsiActionCodes::CSI_F3: @@ -799,10 +782,10 @@ bool InputStateMachineEngine::_WriteMouseEvent(const size_t column, const size_t // sequence. This is for Arrow keys, Home, End, etc. // Arguments: // - parameters - the set of parameters to get the modifier state from. -// - actionCode - the actionCode for the sequence we're operating on. +// - id - the identifier for the sequence we're operating on. // Return Value: // - the INPUT_RECORD compatible modifier state. -DWORD InputStateMachineEngine::_GetCursorKeysModifierState(const gsl::span parameters, const CsiActionCodes actionCode) noexcept +DWORD InputStateMachineEngine::_GetCursorKeysModifierState(const gsl::span parameters, const VTID id) noexcept { DWORD modifiers = 0; if (_IsModified(parameters.size()) && parameters.size() >= 2) @@ -816,7 +799,7 @@ DWORD InputStateMachineEngine::_GetCursorKeysModifierState(const gsl::span CsiActionCodes::CSI_F4) + if (id < CsiActionCodes::CSI_F1 || id > CsiActionCodes::CSI_F4) { WI_SetFlag(modifiers, ENHANCED_KEY); } @@ -917,13 +900,13 @@ DWORD InputStateMachineEngine::_GetModifier(const size_t modifierParam) noexcept // - Here, we refer to and maintain the global state of our mouse. // - Mouse wheel events are added at the end to keep them out of the global state // Arguments: -// - wch: the wchar_t representing whether the button was pressed or released +// - id: the sequence identifier representing whether the button was pressed or released // - parameters: the wchar_t to get the mapped vkey of. Represents the direction of the button (down vs up) // - buttonState: Receives the button state for the record // - eventFlags: Receives the special mouse events for the record // Return Value: // true iff we were able to synthesize buttonState -bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const wchar_t wch, +bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const VTID id, const gsl::span parameters, DWORD& buttonState, DWORD& eventFlags) noexcept @@ -987,7 +970,7 @@ bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const wchar_t wch, // Step 2: Decide whether to set or clear that button's bit // NOTE: WI_SetFlag/WI_ClearFlag can't be used here because buttonFlag would have to be a compile-time constant - switch (static_cast(wch)) + switch (id) { case CsiActionCodes::MouseDown: // set flag @@ -1049,15 +1032,15 @@ bool InputStateMachineEngine::_GetGenericVkey(const gsl::span para // Method Description: // - Gets the Vkey from the CSI codes table associated with a particular character. // Arguments: -// - wch: the wchar_t to get the mapped vkey of. +// - id: the sequence identifier to get the mapped vkey of. // - vkey: Receives the vkey // Return Value: // true iff we found the key -bool InputStateMachineEngine::_GetCursorKeysVkey(const wchar_t wch, short& vkey) const +bool InputStateMachineEngine::_GetCursorKeysVkey(const VTID id, short& vkey) const { vkey = 0; - const auto mapping = std::find(s_csiMap.cbegin(), s_csiMap.cend(), (CsiActionCodes)wch); + const auto mapping = std::find(s_csiMap.cbegin(), s_csiMap.cend(), id); if (mapping != s_csiMap.end()) { vkey = mapping->vkey; diff --git a/src/terminal/parser/InputStateMachineEngine.hpp b/src/terminal/parser/InputStateMachineEngine.hpp index 14f6cedc18..a14aa87f63 100644 --- a/src/terminal/parser/InputStateMachineEngine.hpp +++ b/src/terminal/parser/InputStateMachineEngine.hpp @@ -50,30 +50,25 @@ namespace Microsoft::Console::VirtualTerminal // CAPSLOCK_ON 0x0080 // ENHANCED_KEY 0x0100 - enum CsiIntermediateCodes : wchar_t + enum CsiActionCodes : uint64_t { - MOUSE_SGR = L'<', - }; - - enum class CsiActionCodes : wchar_t - { - ArrowUp = L'A', - ArrowDown = L'B', - ArrowRight = L'C', - ArrowLeft = L'D', - Home = L'H', - End = L'F', - MouseDown = L'M', - MouseUp = L'm', - Generic = L'~', // Used for a whole bunch of possible keys - CSI_F1 = L'P', - CSI_F2 = L'Q', - CSI_F3 = L'R', // Both F3 and DSR are on R. - // DSR_DeviceStatusReportResponse = L'R', - CSI_F4 = L'S', - DTTERM_WindowManipulation = L't', - CursorBackTab = L'Z', - Win32KeyboardInput = L'_' + ArrowUp = VTID("A"), + ArrowDown = VTID("B"), + ArrowRight = VTID("C"), + ArrowLeft = VTID("D"), + Home = VTID("H"), + End = VTID("F"), + MouseDown = VTID(" intermediates) override; + bool ActionEscDispatch(const VTID id) override; - bool ActionVt52EscDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) noexcept override; + bool ActionVt52EscDispatch(const VTID id, const gsl::span parameters) noexcept override; - bool ActionCsiDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) override; + bool ActionCsiDispatch(const VTID id, const gsl::span parameters) override; bool ActionClear() noexcept override; @@ -180,7 +170,7 @@ namespace Microsoft::Console::VirtualTerminal bool _lookingForDSR; DWORD _mouseButtonState = 0; - DWORD _GetCursorKeysModifierState(const gsl::span parameters, const CsiActionCodes actionCode) noexcept; + DWORD _GetCursorKeysModifierState(const gsl::span parameters, const VTID id) noexcept; DWORD _GetGenericKeysModifierState(const gsl::span parameters) noexcept; DWORD _GetSGRMouseModifierState(const gsl::span parameters) noexcept; bool _GenerateKeyFromChar(const wchar_t wch, short& vkey, DWORD& modifierState) noexcept; @@ -188,13 +178,13 @@ namespace Microsoft::Console::VirtualTerminal bool _IsModified(const size_t paramCount) noexcept; DWORD _GetModifier(const size_t parameter) noexcept; - bool _UpdateSGRMouseButtonState(const wchar_t wch, + bool _UpdateSGRMouseButtonState(const VTID id, const gsl::span parameters, DWORD& buttonState, DWORD& eventFlags) noexcept; bool _GetGenericVkey(const gsl::span parameters, short& vkey) const; - bool _GetCursorKeysVkey(const wchar_t wch, short& vkey) const; + bool _GetCursorKeysVkey(const VTID id, short& vkey) const; bool _GetSs3KeysVkey(const wchar_t wch, short& vkey) const; bool _WriteSingleKey(const short vkey, const DWORD modifierState); diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 86a2023af9..cdecc28bbd 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -179,89 +179,123 @@ bool OutputStateMachineEngine::ActionPassThroughString(const std::wstring_view s // a simple escape sequence. These sequences traditionally start with ESC // and a simple letter. No complicated parameters. // Arguments: -// - wch - Character to dispatch. -// - intermediates - Intermediate characters in the sequence +// - id - Identifier of the escape sequence to dispatch. // Return Value: // - true iff we successfully dispatched the sequence. -bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch, - const gsl::span intermediates) +bool OutputStateMachineEngine::ActionEscDispatch(const VTID id) { - if (wch == L'\\' && intermediates.empty()) - { - // This is presumably the 7-bit string terminator, which is essentially a no-op. - return true; - } - bool success = false; - // no intermediates. - if (intermediates.empty()) + switch (id) { - switch (wch) + case EscActionCodes::ST_StringTerminator: + // This is the 7-bit string terminator, which is essentially a no-op. + success = true; + break; + case EscActionCodes::DECSC_CursorSave: + success = _dispatch->CursorSaveState(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSC); + break; + case EscActionCodes::DECRC_CursorRestore: + success = _dispatch->CursorRestoreState(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECRC); + break; + case EscActionCodes::DECKPAM_KeypadApplicationMode: + success = _dispatch->SetKeypadMode(true); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECKPAM); + break; + case EscActionCodes::DECKPNM_KeypadNumericMode: + success = _dispatch->SetKeypadMode(false); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECKPNM); + break; + case EscActionCodes::NEL_NextLine: + success = _dispatch->LineFeed(DispatchTypes::LineFeedType::WithReturn); + TermTelemetry::Instance().Log(TermTelemetry::Codes::NEL); + break; + case EscActionCodes::IND_Index: + success = _dispatch->LineFeed(DispatchTypes::LineFeedType::WithoutReturn); + TermTelemetry::Instance().Log(TermTelemetry::Codes::IND); + break; + case EscActionCodes::RI_ReverseLineFeed: + success = _dispatch->ReverseLineFeed(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::RI); + break; + case EscActionCodes::HTS_HorizontalTabSet: + success = _dispatch->HorizontalTabSet(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::HTS); + break; + case EscActionCodes::RIS_ResetToInitialState: + success = _dispatch->HardReset(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::RIS); + break; + case EscActionCodes::SS2_SingleShift: + success = _dispatch->SingleShift(2); + TermTelemetry::Instance().Log(TermTelemetry::Codes::SS2); + break; + case EscActionCodes::SS3_SingleShift: + success = _dispatch->SingleShift(3); + TermTelemetry::Instance().Log(TermTelemetry::Codes::SS3); + break; + case EscActionCodes::LS2_LockingShift: + success = _dispatch->LockingShift(2); + TermTelemetry::Instance().Log(TermTelemetry::Codes::LS2); + break; + case EscActionCodes::LS3_LockingShift: + success = _dispatch->LockingShift(3); + TermTelemetry::Instance().Log(TermTelemetry::Codes::LS3); + break; + case EscActionCodes::LS1R_LockingShift: + success = _dispatch->LockingShiftRight(1); + TermTelemetry::Instance().Log(TermTelemetry::Codes::LS1R); + break; + case EscActionCodes::LS2R_LockingShift: + success = _dispatch->LockingShiftRight(2); + TermTelemetry::Instance().Log(TermTelemetry::Codes::LS2R); + break; + case EscActionCodes::LS3R_LockingShift: + success = _dispatch->LockingShiftRight(3); + TermTelemetry::Instance().Log(TermTelemetry::Codes::LS3R); + break; + case EscActionCodes::DECALN_ScreenAlignmentPattern: + success = _dispatch->ScreenAlignmentPattern(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECALN); + break; + default: + const auto commandChar = id[0]; + const auto commandParameter = id.SubSequence(1); + switch (commandChar) { - case VTActionCodes::DECSC_CursorSave: - success = _dispatch->CursorSaveState(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSC); + case '%': + success = _dispatch->DesignateCodingSystem(commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DOCS); break; - case VTActionCodes::DECRC_CursorRestore: - success = _dispatch->CursorRestoreState(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECRC); + case '(': + success = _dispatch->Designate94Charset(0, commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG0); break; - case VTActionCodes::DECKPAM_KeypadApplicationMode: - success = _dispatch->SetKeypadMode(true); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECKPAM); + case ')': + success = _dispatch->Designate94Charset(1, commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG1); break; - case VTActionCodes::DECKPNM_KeypadNumericMode: - success = _dispatch->SetKeypadMode(false); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECKPNM); + case '*': + success = _dispatch->Designate94Charset(2, commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG2); break; - case VTActionCodes::NEL_NextLine: - success = _dispatch->LineFeed(DispatchTypes::LineFeedType::WithReturn); - TermTelemetry::Instance().Log(TermTelemetry::Codes::NEL); + case '+': + success = _dispatch->Designate94Charset(3, commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG3); break; - case VTActionCodes::IND_Index: - success = _dispatch->LineFeed(DispatchTypes::LineFeedType::WithoutReturn); - TermTelemetry::Instance().Log(TermTelemetry::Codes::IND); + case '-': + success = _dispatch->Designate96Charset(1, commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG1); break; - case VTActionCodes::RI_ReverseLineFeed: - success = _dispatch->ReverseLineFeed(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::RI); + case '.': + success = _dispatch->Designate96Charset(2, commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG2); break; - case VTActionCodes::HTS_HorizontalTabSet: - success = _dispatch->HorizontalTabSet(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::HTS); - break; - case VTActionCodes::RIS_ResetToInitialState: - success = _dispatch->HardReset(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::RIS); - break; - case VTActionCodes::SS2_SingleShift: - success = _dispatch->SingleShift(2); - TermTelemetry::Instance().Log(TermTelemetry::Codes::SS2); - break; - case VTActionCodes::SS3_SingleShift: - success = _dispatch->SingleShift(3); - TermTelemetry::Instance().Log(TermTelemetry::Codes::SS3); - break; - case VTActionCodes::LS2_LockingShift: - success = _dispatch->LockingShift(2); - TermTelemetry::Instance().Log(TermTelemetry::Codes::LS2); - break; - case VTActionCodes::LS3_LockingShift: - success = _dispatch->LockingShift(3); - TermTelemetry::Instance().Log(TermTelemetry::Codes::LS3); - break; - case VTActionCodes::LS1R_LockingShift: - success = _dispatch->LockingShiftRight(1); - TermTelemetry::Instance().Log(TermTelemetry::Codes::LS1R); - break; - case VTActionCodes::LS2R_LockingShift: - success = _dispatch->LockingShiftRight(2); - TermTelemetry::Instance().Log(TermTelemetry::Codes::LS2R); - break; - case VTActionCodes::LS3R_LockingShift: - success = _dispatch->LockingShiftRight(3); - TermTelemetry::Instance().Log(TermTelemetry::Codes::LS3R); + case '/': + success = _dispatch->Designate96Charset(3, commandParameter); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG3); break; default: // If no functions to call, overall dispatch was a failure. @@ -269,36 +303,6 @@ bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch, break; } } - else if (intermediates.size() == 1) - { - switch (til::at(intermediates, 0)) - { - case L'%': - success = _dispatch->DesignateCodingSystem(wch); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DOCS); - break; - case L'#': - switch (wch) - { - case VTActionCodes::DECALN_ScreenAlignmentPattern: - success = _dispatch->ScreenAlignmentPattern(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECALN); - break; - default: - // If no functions to call, overall dispatch was a failure. - success = false; - break; - } - break; - default: - success = _IntermediateScsDispatch(wch, intermediates); - break; - } - } - else if (intermediates.size() == 2) - { - success = _IntermediateScsDispatch(wch, intermediates); - } // If we were unable to process the string, and there's a TTY attached to us, // trigger the state machine to flush the string to the terminal. @@ -317,135 +321,74 @@ bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch, // a VT52 escape sequence. These sequences start with ESC and a single letter, // sometimes followed by parameters. // Arguments: -// - wch - Character to dispatch. -// - intermediates - Intermediate characters in the sequence. +// - id - Identifier of the VT52 sequence to dispatch. // - parameters - Set of parameters collected while parsing the sequence. // Return Value: // - true iff we successfully dispatched the sequence. -bool OutputStateMachineEngine::ActionVt52EscDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) +bool OutputStateMachineEngine::ActionVt52EscDispatch(const VTID id, const gsl::span parameters) { bool success = false; - // no intermediates. - if (intermediates.empty()) + switch (id) { - switch (wch) - { - case Vt52ActionCodes::CursorUp: - success = _dispatch->CursorUp(1); - break; - case Vt52ActionCodes::CursorDown: - success = _dispatch->CursorDown(1); - break; - case Vt52ActionCodes::CursorRight: - success = _dispatch->CursorForward(1); - break; - case Vt52ActionCodes::CursorLeft: - success = _dispatch->CursorBackward(1); - break; - case Vt52ActionCodes::EnterGraphicsMode: - success = _dispatch->Designate94Charset(0, DispatchTypes::CharacterSets::DecSpecialGraphics); - break; - case Vt52ActionCodes::ExitGraphicsMode: - success = _dispatch->Designate94Charset(0, DispatchTypes::CharacterSets::ASCII); - break; - case Vt52ActionCodes::CursorToHome: - success = _dispatch->CursorPosition(1, 1); - break; - case Vt52ActionCodes::ReverseLineFeed: - success = _dispatch->ReverseLineFeed(); - break; - case Vt52ActionCodes::EraseToEndOfScreen: - success = _dispatch->EraseInDisplay(DispatchTypes::EraseType::ToEnd); - break; - case Vt52ActionCodes::EraseToEndOfLine: - success = _dispatch->EraseInLine(DispatchTypes::EraseType::ToEnd); - break; - case Vt52ActionCodes::DirectCursorAddress: - // VT52 cursor addresses are provided as ASCII characters, with - // the lowest value being a space, representing an address of 1. - success = _dispatch->CursorPosition(gsl::at(parameters, 0) - ' ' + 1, gsl::at(parameters, 1) - ' ' + 1); - break; - case Vt52ActionCodes::Identify: - success = _dispatch->Vt52DeviceAttributes(); - break; - case Vt52ActionCodes::EnterAlternateKeypadMode: - success = _dispatch->SetKeypadMode(true); - break; - case Vt52ActionCodes::ExitAlternateKeypadMode: - success = _dispatch->SetKeypadMode(false); - break; - case Vt52ActionCodes::ExitVt52Mode: - { - const DispatchTypes::PrivateModeParams mode[] = { DispatchTypes::PrivateModeParams::DECANM_AnsiMode }; - success = _dispatch->SetPrivateModes(mode); - break; - } - default: - // If no functions to call, overall dispatch was a failure. - success = false; - break; - } + case Vt52ActionCodes::CursorUp: + success = _dispatch->CursorUp(1); + break; + case Vt52ActionCodes::CursorDown: + success = _dispatch->CursorDown(1); + break; + case Vt52ActionCodes::CursorRight: + success = _dispatch->CursorForward(1); + break; + case Vt52ActionCodes::CursorLeft: + success = _dispatch->CursorBackward(1); + break; + case Vt52ActionCodes::EnterGraphicsMode: + success = _dispatch->Designate94Charset(0, DispatchTypes::CharacterSets::DecSpecialGraphics); + break; + case Vt52ActionCodes::ExitGraphicsMode: + success = _dispatch->Designate94Charset(0, DispatchTypes::CharacterSets::ASCII); + break; + case Vt52ActionCodes::CursorToHome: + success = _dispatch->CursorPosition(1, 1); + break; + case Vt52ActionCodes::ReverseLineFeed: + success = _dispatch->ReverseLineFeed(); + break; + case Vt52ActionCodes::EraseToEndOfScreen: + success = _dispatch->EraseInDisplay(DispatchTypes::EraseType::ToEnd); + break; + case Vt52ActionCodes::EraseToEndOfLine: + success = _dispatch->EraseInLine(DispatchTypes::EraseType::ToEnd); + break; + case Vt52ActionCodes::DirectCursorAddress: + // VT52 cursor addresses are provided as ASCII characters, with + // the lowest value being a space, representing an address of 1. + success = _dispatch->CursorPosition(gsl::at(parameters, 0) - ' ' + 1, gsl::at(parameters, 1) - ' ' + 1); + break; + case Vt52ActionCodes::Identify: + success = _dispatch->Vt52DeviceAttributes(); + break; + case Vt52ActionCodes::EnterAlternateKeypadMode: + success = _dispatch->SetKeypadMode(true); + break; + case Vt52ActionCodes::ExitAlternateKeypadMode: + success = _dispatch->SetKeypadMode(false); + break; + case Vt52ActionCodes::ExitVt52Mode: + { + const DispatchTypes::PrivateModeParams mode[] = { DispatchTypes::PrivateModeParams::DECANM_AnsiMode }; + success = _dispatch->SetPrivateModes(mode); + break; } - - _ClearLastChar(); - - return success; -} - -// Routine Description: -// - Handles SCS charset designation actions that can have one or two possible intermediates. -// Arguments: -// - wch - Character to dispatch. -// - intermediates - Intermediate characters in the sequence -// Return Value: -// - True if handled successfully. False otherwise. -bool OutputStateMachineEngine::_IntermediateScsDispatch(const wchar_t wch, - const gsl::span intermediates) -{ - bool success = false; - - // If we have more than one intermediate, the second intermediate forms part of - // the charset identifier. Otherwise it's identified by just the final character. - const auto charset = intermediates.size() > 1 ? std::make_pair(til::at(intermediates, 1), wch) : std::make_pair(wch, L'\0'); - - switch (til::at(intermediates, 0)) - { - case L'(': - success = _dispatch->Designate94Charset(0, charset); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG0); - break; - case L')': - success = _dispatch->Designate94Charset(1, charset); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG1); - break; - case L'*': - success = _dispatch->Designate94Charset(2, charset); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG2); - break; - case L'+': - success = _dispatch->Designate94Charset(3, charset); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG3); - break; - case L'-': - success = _dispatch->Designate96Charset(1, charset); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG1); - break; - case L'.': - success = _dispatch->Designate96Charset(2, charset); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG2); - break; - case L'/': - success = _dispatch->Designate96Charset(3, charset); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG3); - break; default: + // If no functions to call, overall dispatch was a failure. success = false; break; } + _ClearLastChar(); + return success; } @@ -454,14 +397,11 @@ bool OutputStateMachineEngine::_IntermediateScsDispatch(const wchar_t wch, // a control sequence. These sequences perform various API-type commands // that can include many parameters. // Arguments: -// - wch - Character to dispatch. -// - intermediates - Intermediate characters in the sequence +// - id - Identifier of the control sequence to dispatch. // - parameters - set of numeric parameters collected while parsing the sequence. // Return Value: // - true iff we successfully dispatched the sequence. -bool OutputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, - const gsl::span intermediates, - gsl::span parameters) +bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, gsl::span parameters) { bool success = false; size_t distance = 0; @@ -473,252 +413,261 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, size_t clearType = 0; unsigned int function = 0; DispatchTypes::EraseType eraseType = DispatchTypes::EraseType::ToEnd; + std::vector privateModeParams; // We hold the vector in the class because client applications that do a lot of color work // would spend a lot of time reallocating/resizing the vector. _graphicsOptions.clear(); DispatchTypes::AnsiStatusType deviceStatusType = static_cast(0); // there is no default status type. size_t repeatCount = 0; + DispatchTypes::CursorStyle cursorStyle = DefaultCursorStyle; // This is all the args after the first arg, and the count of args not including the first one. const auto remainingParams = parameters.size() > 1 ? parameters.subspan(1) : gsl::span{}; - if (intermediates.empty()) + // fill params + switch (id) { - // fill params - switch (wch) - { - case VTActionCodes::CUU_CursorUp: - case VTActionCodes::CUD_CursorDown: - case VTActionCodes::CUF_CursorForward: - case VTActionCodes::CUB_CursorBackward: - case VTActionCodes::CNL_CursorNextLine: - case VTActionCodes::CPL_CursorPrevLine: - case VTActionCodes::CHA_CursorHorizontalAbsolute: - case VTActionCodes::HPA_HorizontalPositionAbsolute: - case VTActionCodes::VPA_VerticalLinePositionAbsolute: - case VTActionCodes::HPR_HorizontalPositionRelative: - case VTActionCodes::VPR_VerticalPositionRelative: - case VTActionCodes::ICH_InsertCharacter: - case VTActionCodes::DCH_DeleteCharacter: - case VTActionCodes::ECH_EraseCharacters: - success = _GetCursorDistance(parameters, distance); - break; - case VTActionCodes::HVP_HorizontalVerticalPosition: - case VTActionCodes::CUP_CursorPosition: - success = _GetXYPosition(parameters, line, column); - break; - case VTActionCodes::DECSTBM_SetScrollingRegion: - success = _GetTopBottomMargins(parameters, topMargin, bottomMargin); - break; - case VTActionCodes::ED_EraseDisplay: - case VTActionCodes::EL_EraseLine: - success = _GetEraseOperation(parameters, eraseType); - break; - case VTActionCodes::SGR_SetGraphicsRendition: - success = _GetGraphicsOptions(parameters, _graphicsOptions); - break; - case VTActionCodes::DSR_DeviceStatusReport: - success = _GetDeviceStatusOperation(parameters, deviceStatusType); - break; - case VTActionCodes::DA_DeviceAttributes: - success = _VerifyDeviceAttributesParams(parameters); - break; - case VTActionCodes::SU_ScrollUp: - case VTActionCodes::SD_ScrollDown: - success = _GetScrollDistance(parameters, distance); - break; - case VTActionCodes::ANSISYSSC_CursorSave: - case VTActionCodes::ANSISYSRC_CursorRestore: - success = _VerifyHasNoParameters(parameters); - break; - case VTActionCodes::IL_InsertLine: - case VTActionCodes::DL_DeleteLine: - success = _GetScrollDistance(parameters, distance); - break; - case VTActionCodes::CHT_CursorForwardTab: - case VTActionCodes::CBT_CursorBackTab: - success = _GetTabDistance(parameters, numTabs); - break; - case VTActionCodes::TBC_TabClear: - success = _GetTabClearType(parameters, clearType); - break; - case VTActionCodes::DTTERM_WindowManipulation: - success = _GetWindowManipulationType(parameters, function); - break; - case VTActionCodes::REP_RepeatCharacter: - success = _GetRepeatCount(parameters, repeatCount); - break; - default: - // If no params to fill, param filling was successful. - success = true; - break; - } - - // if param filling successful, try to dispatch - if (success) - { - switch (wch) - { - case VTActionCodes::CUU_CursorUp: - success = _dispatch->CursorUp(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CUU); - break; - case VTActionCodes::CUD_CursorDown: - success = _dispatch->CursorDown(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CUD); - break; - case VTActionCodes::CUF_CursorForward: - success = _dispatch->CursorForward(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CUF); - break; - case VTActionCodes::CUB_CursorBackward: - success = _dispatch->CursorBackward(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CUB); - break; - case VTActionCodes::CNL_CursorNextLine: - success = _dispatch->CursorNextLine(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CNL); - break; - case VTActionCodes::CPL_CursorPrevLine: - success = _dispatch->CursorPrevLine(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CPL); - break; - case VTActionCodes::CHA_CursorHorizontalAbsolute: - case VTActionCodes::HPA_HorizontalPositionAbsolute: - success = _dispatch->CursorHorizontalPositionAbsolute(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CHA); - break; - case VTActionCodes::VPA_VerticalLinePositionAbsolute: - success = _dispatch->VerticalLinePositionAbsolute(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::VPA); - break; - case VTActionCodes::HPR_HorizontalPositionRelative: - success = _dispatch->HorizontalPositionRelative(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::HPR); - break; - case VTActionCodes::VPR_VerticalPositionRelative: - success = _dispatch->VerticalPositionRelative(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::VPR); - break; - case VTActionCodes::CUP_CursorPosition: - case VTActionCodes::HVP_HorizontalVerticalPosition: - success = _dispatch->CursorPosition(line, column); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CUP); - break; - case VTActionCodes::DECSTBM_SetScrollingRegion: - success = _dispatch->SetTopBottomScrollingMargins(topMargin, bottomMargin); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTBM); - break; - case VTActionCodes::ICH_InsertCharacter: - success = _dispatch->InsertCharacter(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::ICH); - break; - case VTActionCodes::DCH_DeleteCharacter: - success = _dispatch->DeleteCharacter(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DCH); - break; - case VTActionCodes::ED_EraseDisplay: - success = _dispatch->EraseInDisplay(eraseType); - TermTelemetry::Instance().Log(TermTelemetry::Codes::ED); - break; - case VTActionCodes::EL_EraseLine: - success = _dispatch->EraseInLine(eraseType); - TermTelemetry::Instance().Log(TermTelemetry::Codes::EL); - break; - case VTActionCodes::SGR_SetGraphicsRendition: - success = _dispatch->SetGraphicsRendition({ _graphicsOptions.data(), _graphicsOptions.size() }); - TermTelemetry::Instance().Log(TermTelemetry::Codes::SGR); - break; - case VTActionCodes::DSR_DeviceStatusReport: - success = _dispatch->DeviceStatusReport(deviceStatusType); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DSR); - break; - case VTActionCodes::DA_DeviceAttributes: - success = _dispatch->DeviceAttributes(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DA); - break; - case VTActionCodes::SU_ScrollUp: - success = _dispatch->ScrollUp(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::SU); - break; - case VTActionCodes::SD_ScrollDown: - success = _dispatch->ScrollDown(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::SD); - break; - case VTActionCodes::ANSISYSSC_CursorSave: - success = _dispatch->CursorSaveState(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::ANSISYSSC); - break; - case VTActionCodes::ANSISYSRC_CursorRestore: - success = _dispatch->CursorRestoreState(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::ANSISYSRC); - break; - case VTActionCodes::IL_InsertLine: - success = _dispatch->InsertLine(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::IL); - break; - case VTActionCodes::DL_DeleteLine: - success = _dispatch->DeleteLine(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DL); - break; - case VTActionCodes::CHT_CursorForwardTab: - success = _dispatch->ForwardTab(numTabs); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CHT); - break; - case VTActionCodes::CBT_CursorBackTab: - success = _dispatch->BackwardsTab(numTabs); - TermTelemetry::Instance().Log(TermTelemetry::Codes::CBT); - break; - case VTActionCodes::TBC_TabClear: - success = _dispatch->TabClear(clearType); - TermTelemetry::Instance().Log(TermTelemetry::Codes::TBC); - break; - case VTActionCodes::ECH_EraseCharacters: - success = _dispatch->EraseCharacters(distance); - TermTelemetry::Instance().Log(TermTelemetry::Codes::ECH); - break; - case VTActionCodes::DTTERM_WindowManipulation: - success = _dispatch->WindowManipulation(static_cast(function), - remainingParams); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DTTERM_WM); - break; - case VTActionCodes::REP_RepeatCharacter: - // Handled w/o the dispatch. This function is unique in that way - // If this were in the ITerminalDispatch, then each - // implementation would effectively be the same, calling only - // functions that are already part of the interface. - // Print the last graphical character a number of times. - if (_lastPrintedChar != AsciiChars::NUL) - { - std::wstring wstr(repeatCount, _lastPrintedChar); - _dispatch->PrintString(wstr); - } - success = true; - TermTelemetry::Instance().Log(TermTelemetry::Codes::REP); - break; - default: - // If no functions to call, overall dispatch was a failure. - success = false; - break; - } - } + case CsiActionCodes::CUU_CursorUp: + case CsiActionCodes::CUD_CursorDown: + case CsiActionCodes::CUF_CursorForward: + case CsiActionCodes::CUB_CursorBackward: + case CsiActionCodes::CNL_CursorNextLine: + case CsiActionCodes::CPL_CursorPrevLine: + case CsiActionCodes::CHA_CursorHorizontalAbsolute: + case CsiActionCodes::HPA_HorizontalPositionAbsolute: + case CsiActionCodes::VPA_VerticalLinePositionAbsolute: + case CsiActionCodes::HPR_HorizontalPositionRelative: + case CsiActionCodes::VPR_VerticalPositionRelative: + case CsiActionCodes::ICH_InsertCharacter: + case CsiActionCodes::DCH_DeleteCharacter: + case CsiActionCodes::ECH_EraseCharacters: + success = _GetCursorDistance(parameters, distance); + break; + case CsiActionCodes::HVP_HorizontalVerticalPosition: + case CsiActionCodes::CUP_CursorPosition: + success = _GetXYPosition(parameters, line, column); + break; + case CsiActionCodes::DECSTBM_SetScrollingRegion: + success = _GetTopBottomMargins(parameters, topMargin, bottomMargin); + break; + case CsiActionCodes::ED_EraseDisplay: + case CsiActionCodes::EL_EraseLine: + success = _GetEraseOperation(parameters, eraseType); + break; + case CsiActionCodes::DECSET_PrivateModeSet: + case CsiActionCodes::DECRST_PrivateModeReset: + success = _GetPrivateModeParams(parameters, privateModeParams); + break; + case CsiActionCodes::SGR_SetGraphicsRendition: + success = _GetGraphicsOptions(parameters, _graphicsOptions); + break; + case CsiActionCodes::DSR_DeviceStatusReport: + success = _GetDeviceStatusOperation(parameters, deviceStatusType); + break; + case CsiActionCodes::DA_DeviceAttributes: + case CsiActionCodes::DA2_SecondaryDeviceAttributes: + case CsiActionCodes::DA3_TertiaryDeviceAttributes: + success = _VerifyDeviceAttributesParams(parameters); + break; + case CsiActionCodes::SU_ScrollUp: + case CsiActionCodes::SD_ScrollDown: + success = _GetScrollDistance(parameters, distance); + break; + case CsiActionCodes::ANSISYSSC_CursorSave: + case CsiActionCodes::ANSISYSRC_CursorRestore: + success = _VerifyHasNoParameters(parameters); + break; + case CsiActionCodes::IL_InsertLine: + case CsiActionCodes::DL_DeleteLine: + success = _GetScrollDistance(parameters, distance); + break; + case CsiActionCodes::CHT_CursorForwardTab: + case CsiActionCodes::CBT_CursorBackTab: + success = _GetTabDistance(parameters, numTabs); + break; + case CsiActionCodes::TBC_TabClear: + success = _GetTabClearType(parameters, clearType); + break; + case CsiActionCodes::DTTERM_WindowManipulation: + success = _GetWindowManipulationType(parameters, function); + break; + case CsiActionCodes::REP_RepeatCharacter: + success = _GetRepeatCount(parameters, repeatCount); + break; + case CsiActionCodes::DECSCUSR_SetCursorStyle: + success = _GetCursorStyle(parameters, cursorStyle); + break; + default: + // If no params to fill, param filling was successful. + success = true; + break; } - else if (intermediates.size() == 1) + + // if param filling successful, try to dispatch + if (success) { - const auto value = til::at(intermediates, 0); - switch (value) + switch (id) { - case L'?': - success = _IntermediateQuestionMarkDispatch(wch, parameters); + case CsiActionCodes::CUU_CursorUp: + success = _dispatch->CursorUp(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CUU); break; - case L'>': - case L'=': - success = _IntermediateGreaterThanOrEqualDispatch(wch, value, parameters); + case CsiActionCodes::CUD_CursorDown: + success = _dispatch->CursorDown(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CUD); break; - case L'!': - success = _IntermediateExclamationDispatch(wch); + case CsiActionCodes::CUF_CursorForward: + success = _dispatch->CursorForward(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CUF); break; - case L' ': - success = _IntermediateSpaceDispatch(wch, parameters); + case CsiActionCodes::CUB_CursorBackward: + success = _dispatch->CursorBackward(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CUB); + break; + case CsiActionCodes::CNL_CursorNextLine: + success = _dispatch->CursorNextLine(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CNL); + break; + case CsiActionCodes::CPL_CursorPrevLine: + success = _dispatch->CursorPrevLine(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CPL); + break; + case CsiActionCodes::CHA_CursorHorizontalAbsolute: + case CsiActionCodes::HPA_HorizontalPositionAbsolute: + success = _dispatch->CursorHorizontalPositionAbsolute(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CHA); + break; + case CsiActionCodes::VPA_VerticalLinePositionAbsolute: + success = _dispatch->VerticalLinePositionAbsolute(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::VPA); + break; + case CsiActionCodes::HPR_HorizontalPositionRelative: + success = _dispatch->HorizontalPositionRelative(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::HPR); + break; + case CsiActionCodes::VPR_VerticalPositionRelative: + success = _dispatch->VerticalPositionRelative(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::VPR); + break; + case CsiActionCodes::CUP_CursorPosition: + case CsiActionCodes::HVP_HorizontalVerticalPosition: + success = _dispatch->CursorPosition(line, column); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CUP); + break; + case CsiActionCodes::DECSTBM_SetScrollingRegion: + success = _dispatch->SetTopBottomScrollingMargins(topMargin, bottomMargin); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTBM); + break; + case CsiActionCodes::ICH_InsertCharacter: + success = _dispatch->InsertCharacter(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::ICH); + break; + case CsiActionCodes::DCH_DeleteCharacter: + success = _dispatch->DeleteCharacter(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DCH); + break; + case CsiActionCodes::ED_EraseDisplay: + success = _dispatch->EraseInDisplay(eraseType); + TermTelemetry::Instance().Log(TermTelemetry::Codes::ED); + break; + case CsiActionCodes::EL_EraseLine: + success = _dispatch->EraseInLine(eraseType); + TermTelemetry::Instance().Log(TermTelemetry::Codes::EL); + break; + case CsiActionCodes::DECSET_PrivateModeSet: + success = _dispatch->SetPrivateModes({ privateModeParams.data(), privateModeParams.size() }); + //TODO: MSFT:6367459 Add specific logging for each of the DECSET/DECRST codes + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSET); + break; + case CsiActionCodes::DECRST_PrivateModeReset: + success = _dispatch->ResetPrivateModes({ privateModeParams.data(), privateModeParams.size() }); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECRST); + break; + case CsiActionCodes::SGR_SetGraphicsRendition: + success = _dispatch->SetGraphicsRendition({ _graphicsOptions.data(), _graphicsOptions.size() }); + TermTelemetry::Instance().Log(TermTelemetry::Codes::SGR); + break; + case CsiActionCodes::DSR_DeviceStatusReport: + success = _dispatch->DeviceStatusReport(deviceStatusType); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DSR); + break; + case CsiActionCodes::DA_DeviceAttributes: + success = _dispatch->DeviceAttributes(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DA); + break; + case CsiActionCodes::DA2_SecondaryDeviceAttributes: + success = _dispatch->SecondaryDeviceAttributes(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DA2); + break; + case CsiActionCodes::DA3_TertiaryDeviceAttributes: + success = _dispatch->TertiaryDeviceAttributes(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DA3); + break; + case CsiActionCodes::SU_ScrollUp: + success = _dispatch->ScrollUp(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::SU); + break; + case CsiActionCodes::SD_ScrollDown: + success = _dispatch->ScrollDown(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::SD); + break; + case CsiActionCodes::ANSISYSSC_CursorSave: + success = _dispatch->CursorSaveState(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::ANSISYSSC); + break; + case CsiActionCodes::ANSISYSRC_CursorRestore: + success = _dispatch->CursorRestoreState(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::ANSISYSRC); + break; + case CsiActionCodes::IL_InsertLine: + success = _dispatch->InsertLine(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::IL); + break; + case CsiActionCodes::DL_DeleteLine: + success = _dispatch->DeleteLine(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DL); + break; + case CsiActionCodes::CHT_CursorForwardTab: + success = _dispatch->ForwardTab(numTabs); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CHT); + break; + case CsiActionCodes::CBT_CursorBackTab: + success = _dispatch->BackwardsTab(numTabs); + TermTelemetry::Instance().Log(TermTelemetry::Codes::CBT); + break; + case CsiActionCodes::TBC_TabClear: + success = _dispatch->TabClear(clearType); + TermTelemetry::Instance().Log(TermTelemetry::Codes::TBC); + break; + case CsiActionCodes::ECH_EraseCharacters: + success = _dispatch->EraseCharacters(distance); + TermTelemetry::Instance().Log(TermTelemetry::Codes::ECH); + break; + case CsiActionCodes::DTTERM_WindowManipulation: + success = _dispatch->WindowManipulation(static_cast(function), + remainingParams); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DTTERM_WM); + break; + case CsiActionCodes::REP_RepeatCharacter: + // Handled w/o the dispatch. This function is unique in that way + // If this were in the ITerminalDispatch, then each + // implementation would effectively be the same, calling only + // functions that are already part of the interface. + // Print the last graphical character a number of times. + if (_lastPrintedChar != AsciiChars::NUL) + { + std::wstring wstr(repeatCount, _lastPrintedChar); + _dispatch->PrintString(wstr); + } + success = true; + TermTelemetry::Instance().Log(TermTelemetry::Codes::REP); + break; + case CsiActionCodes::DECSCUSR_SetCursorStyle: + success = _dispatch->SetCursorStyle(cursorStyle); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSCUSR); + break; + case CsiActionCodes::DECSTR_SoftReset: + success = _dispatch->SoftReset(); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTR); break; default: // If no functions to call, overall dispatch was a failure. @@ -726,6 +675,7 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, break; } } + // If we were unable to process the string, and there's a TTY attached to us, // trigger the state machine to flush the string to the terminal. if (_pfnFlushToTerminal != nullptr && !success) @@ -738,165 +688,6 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const wchar_t wch, return success; } -// Routine Description: -// - Handles actions that have postfix params on an intermediate '?', such as DECTCEM, DECCOLM, ATT610 -// Arguments: -// - wch - Character to dispatch. -// - parameters - set of numeric parameters collected while parsing the sequence. -// Return Value: -// - True if handled successfully. False otherwise. -bool OutputStateMachineEngine::_IntermediateQuestionMarkDispatch(const wchar_t wchAction, - const gsl::span parameters) -{ - bool success = false; - - std::vector privateModeParams; - // Ensure that there was the right number of params - switch (wchAction) - { - case VTActionCodes::DECSET_PrivateModeSet: - case VTActionCodes::DECRST_PrivateModeReset: - success = _GetPrivateModeParams(parameters, privateModeParams); - break; - - default: - // If no params to fill, param filling was successful. - success = true; - break; - } - if (success) - { - switch (wchAction) - { - case VTActionCodes::DECSET_PrivateModeSet: - success = _dispatch->SetPrivateModes({ privateModeParams.data(), privateModeParams.size() }); - //TODO: MSFT:6367459 Add specific logging for each of the DECSET/DECRST codes - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSET); - break; - case VTActionCodes::DECRST_PrivateModeReset: - success = _dispatch->ResetPrivateModes({ privateModeParams.data(), privateModeParams.size() }); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECRST); - break; - default: - // If no functions to call, overall dispatch was a failure. - success = false; - break; - } - } - return success; -} - -// Routine Description: -// - Handles actions that have postfix params on an intermediate '>' or '='. -// Arguments: -// - wch - Character to dispatch. -// - intermediate - The intermediate character. -// - parameters - Set of numeric parameters collected while parsing the sequence. -// Return Value: -// - True if handled successfully. False otherwise. -bool OutputStateMachineEngine::_IntermediateGreaterThanOrEqualDispatch(const wchar_t wch, - const wchar_t intermediate, - const gsl::span parameters) -{ - bool success = false; - - switch (wch) - { - case VTActionCodes::DA_DeviceAttributes: - if (_VerifyDeviceAttributesParams(parameters)) - { - switch (intermediate) - { - case L'>': - success = _dispatch->SecondaryDeviceAttributes(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DA2); - break; - case L'=': - success = _dispatch->TertiaryDeviceAttributes(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DA3); - break; - default: - success = false; - break; - } - } - break; - default: - success = false; - break; - } - - return success; -} - -// Routine Description: -// - Handles actions that have an intermediate '!', such as DECSTR -// Arguments: -// - wch - Character to dispatch. -// Return Value: -// - True if handled successfully. False otherwise. -bool OutputStateMachineEngine::_IntermediateExclamationDispatch(const wchar_t wchAction) -{ - bool success = false; - - switch (wchAction) - { - case VTActionCodes::DECSTR_SoftReset: - success = _dispatch->SoftReset(); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTR); - break; - default: - // If no functions to call, overall dispatch was a failure. - success = false; - break; - } - return success; -} - -// Routine Description: -// - Handles actions that have an intermediate ' ' (0x20), such as DECSCUSR -// Arguments: -// - wch - Character to dispatch. -// - parameters - set of numeric parameters collected while parsing the sequence. -// Return Value: -// - True if handled successfully. False otherwise. -bool OutputStateMachineEngine::_IntermediateSpaceDispatch(const wchar_t wchAction, - const gsl::span parameters) -{ - bool success = false; - DispatchTypes::CursorStyle cursorStyle = DefaultCursorStyle; - - // Parse params - switch (wchAction) - { - case VTActionCodes::DECSCUSR_SetCursorStyle: - success = _GetCursorStyle(parameters, cursorStyle); - break; - default: - // If no functions to call, overall dispatch was a failure. - success = false; - break; - } - - // if param filling successful, try to dispatch - if (success) - { - switch (wchAction) - { - case VTActionCodes::DECSCUSR_SetCursorStyle: - success = _dispatch->SetCursorStyle(cursorStyle); - TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSCUSR); - break; - default: - // If no functions to call, overall dispatch was a failure. - success = false; - break; - } - } - - return success; -} - // Routine Description: // - Triggers the Clear action to indicate that the state machine should erase // all internal state. diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index bce98255e0..e6332a076e 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -33,16 +33,11 @@ namespace Microsoft::Console::VirtualTerminal bool ActionPassThroughString(const std::wstring_view string) override; - bool ActionEscDispatch(const wchar_t wch, - const gsl::span intermediates) override; + bool ActionEscDispatch(const VTID id) override; - bool ActionVt52EscDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) override; + bool ActionVt52EscDispatch(const VTID id, const gsl::span parameters) override; - bool ActionCsiDispatch(const wchar_t wch, - const gsl::span intermediates, - const gsl::span parameters) override; + bool ActionCsiDispatch(const VTID id, const gsl::span parameters) override; bool ActionClear() noexcept override; @@ -73,94 +68,89 @@ namespace Microsoft::Console::VirtualTerminal wchar_t _lastPrintedChar; std::vector _graphicsOptions; - bool _IntermediateScsDispatch(const wchar_t wch, - const gsl::span intermediates); - bool _IntermediateQuestionMarkDispatch(const wchar_t wchAction, - const gsl::span parameters); - bool _IntermediateGreaterThanOrEqualDispatch(const wchar_t wch, - const wchar_t intermediate, - const gsl::span parameters); - bool _IntermediateExclamationDispatch(const wchar_t wch); - bool _IntermediateSpaceDispatch(const wchar_t wchAction, - const gsl::span parameters); - - enum VTActionCodes : wchar_t + enum EscActionCodes : uint64_t { - CUU_CursorUp = L'A', - CUD_CursorDown = L'B', - CUF_CursorForward = L'C', - CUB_CursorBackward = L'D', - CNL_CursorNextLine = L'E', - CPL_CursorPrevLine = L'F', - CHA_CursorHorizontalAbsolute = L'G', - CUP_CursorPosition = L'H', - ED_EraseDisplay = L'J', - EL_EraseLine = L'K', - SU_ScrollUp = L'S', - SD_ScrollDown = L'T', - ICH_InsertCharacter = L'@', - DCH_DeleteCharacter = L'P', - SGR_SetGraphicsRendition = L'm', - DECSC_CursorSave = L'7', - DECRC_CursorRestore = L'8', - DECSET_PrivateModeSet = L'h', - DECRST_PrivateModeReset = L'l', - ANSISYSSC_CursorSave = L's', // NOTE: Overlaps with DECLRMM/DECSLRM. Fix when/if implemented. - ANSISYSRC_CursorRestore = L'u', // NOTE: Overlaps with DECSMBV. Fix when/if implemented. - DECKPAM_KeypadApplicationMode = L'=', - DECKPNM_KeypadNumericMode = L'>', - DSR_DeviceStatusReport = L'n', - DA_DeviceAttributes = L'c', - DECSCPP_SetColumnsPerPage = L'|', - IL_InsertLine = L'L', - DL_DeleteLine = L'M', // Yes, this is the same as RI, however, RI is not preceded by a CSI, and DL is. - HPA_HorizontalPositionAbsolute = L'`', - VPA_VerticalLinePositionAbsolute = L'd', - HPR_HorizontalPositionRelative = L'a', - VPR_VerticalPositionRelative = L'e', - DECSTBM_SetScrollingRegion = L'r', - NEL_NextLine = L'E', // Not a CSI, so doesn't overlap with CNL - IND_Index = L'D', // Not a CSI, so doesn't overlap with CUB - RI_ReverseLineFeed = L'M', - HTS_HorizontalTabSet = L'H', // Not a CSI, so doesn't overlap with CUP - CHT_CursorForwardTab = L'I', - CBT_CursorBackTab = L'Z', - TBC_TabClear = L'g', - ECH_EraseCharacters = L'X', - HVP_HorizontalVerticalPosition = L'f', - DECSTR_SoftReset = L'p', - RIS_ResetToInitialState = L'c', // DA is prefaced by CSI, RIS by ESC - // 'q' is overloaded - no postfix is DECLL, ' ' postfix is DECSCUSR, and '"' is DECSCA - DECSCUSR_SetCursorStyle = L'q', // I believe we'll only ever implement DECSCUSR - DTTERM_WindowManipulation = L't', - REP_RepeatCharacter = L'b', - SS2_SingleShift = L'N', - SS3_SingleShift = L'O', - LS2_LockingShift = L'n', - LS3_LockingShift = L'o', - LS1R_LockingShift = L'~', - LS2R_LockingShift = L'}', - LS3R_LockingShift = L'|', - DECALN_ScreenAlignmentPattern = L'8' + DECSC_CursorSave = VTID("7"), + DECRC_CursorRestore = VTID("8"), + DECKPAM_KeypadApplicationMode = VTID("="), + DECKPNM_KeypadNumericMode = VTID(">"), + IND_Index = VTID("D"), + NEL_NextLine = VTID("E"), + HTS_HorizontalTabSet = VTID("H"), + RI_ReverseLineFeed = VTID("M"), + SS2_SingleShift = VTID("N"), + SS3_SingleShift = VTID("O"), + ST_StringTerminator = VTID("\\"), + RIS_ResetToInitialState = VTID("c"), + LS2_LockingShift = VTID("n"), + LS3_LockingShift = VTID("o"), + LS1R_LockingShift = VTID("~"), + LS2R_LockingShift = VTID("}"), + LS3R_LockingShift = VTID("|"), + DECALN_ScreenAlignmentPattern = VTID("#8") }; - enum Vt52ActionCodes : wchar_t + enum CsiActionCodes : uint64_t { - CursorUp = L'A', - CursorDown = L'B', - CursorRight = L'C', - CursorLeft = L'D', - EnterGraphicsMode = L'F', - ExitGraphicsMode = L'G', - CursorToHome = L'H', - ReverseLineFeed = L'I', - EraseToEndOfScreen = L'J', - EraseToEndOfLine = L'K', - DirectCursorAddress = L'Y', - Identify = L'Z', - EnterAlternateKeypadMode = L'=', - ExitAlternateKeypadMode = L'>', - ExitVt52Mode = L'<' + ICH_InsertCharacter = VTID("@"), + CUU_CursorUp = VTID("A"), + CUD_CursorDown = VTID("B"), + CUF_CursorForward = VTID("C"), + CUB_CursorBackward = VTID("D"), + CNL_CursorNextLine = VTID("E"), + CPL_CursorPrevLine = VTID("F"), + CHA_CursorHorizontalAbsolute = VTID("G"), + CUP_CursorPosition = VTID("H"), + CHT_CursorForwardTab = VTID("I"), + ED_EraseDisplay = VTID("J"), + EL_EraseLine = VTID("K"), + IL_InsertLine = VTID("L"), + DL_DeleteLine = VTID("M"), + DCH_DeleteCharacter = VTID("P"), + SU_ScrollUp = VTID("S"), + SD_ScrollDown = VTID("T"), + ECH_EraseCharacters = VTID("X"), + CBT_CursorBackTab = VTID("Z"), + HPA_HorizontalPositionAbsolute = VTID("`"), + HPR_HorizontalPositionRelative = VTID("a"), + REP_RepeatCharacter = VTID("b"), + DA_DeviceAttributes = VTID("c"), + DA2_SecondaryDeviceAttributes = VTID(">c"), + DA3_TertiaryDeviceAttributes = VTID("=c"), + VPA_VerticalLinePositionAbsolute = VTID("d"), + VPR_VerticalPositionRelative = VTID("e"), + HVP_HorizontalVerticalPosition = VTID("f"), + TBC_TabClear = VTID("g"), + DECSET_PrivateModeSet = VTID("?h"), + DECRST_PrivateModeReset = VTID("?l"), + SGR_SetGraphicsRendition = VTID("m"), + DSR_DeviceStatusReport = VTID("n"), + DECSTBM_SetScrollingRegion = VTID("r"), + ANSISYSSC_CursorSave = VTID("s"), // NOTE: Overlaps with DECLRMM/DECSLRM. Fix when/if implemented. + DTTERM_WindowManipulation = VTID("t"), // NOTE: Overlaps with DECSLPP. Fix when/if implemented. + ANSISYSRC_CursorRestore = VTID("u"), + DECSCUSR_SetCursorStyle = VTID(" q"), + DECSTR_SoftReset = VTID("!p"), + DECSCPP_SetColumnsPerPage = VTID("$|") + }; + + enum Vt52ActionCodes : uint64_t + { + CursorUp = VTID("A"), + CursorDown = VTID("B"), + CursorRight = VTID("C"), + CursorLeft = VTID("D"), + EnterGraphicsMode = VTID("F"), + ExitGraphicsMode = VTID("G"), + CursorToHome = VTID("H"), + ReverseLineFeed = VTID("I"), + EraseToEndOfScreen = VTID("J"), + EraseToEndOfLine = VTID("K"), + DirectCursorAddress = VTID("Y"), + Identify = VTID("Z"), + EnterAlternateKeypadMode = VTID("="), + ExitAlternateKeypadMode = VTID(">"), + ExitVt52Mode = VTID("<") }; enum OscActionCodes : unsigned int diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 7e60032c5e..536bc591c0 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -15,7 +15,6 @@ StateMachine::StateMachine(std::unique_ptr engine) : _state(VTStates::Ground), _trace(Microsoft::Console::VirtualTerminal::ParserTracing()), _isInAnsiMode(true), - _intermediates{}, _parameters{}, _oscString{}, _cachedSequence{ std::nullopt }, @@ -422,7 +421,7 @@ void StateMachine::_ActionEscDispatch(const wchar_t wch) { _trace.TraceOnAction(L"EscDispatch"); - const bool success = _engine->ActionEscDispatch(wch, { _intermediates.data(), _intermediates.size() }); + const bool success = _engine->ActionEscDispatch(_identifier.Finalize(wch)); // Trace the result. _trace.DispatchSequenceTrace(success); @@ -445,8 +444,7 @@ void StateMachine::_ActionVt52EscDispatch(const wchar_t wch) { _trace.TraceOnAction(L"Vt52EscDispatch"); - const bool success = _engine->ActionVt52EscDispatch(wch, - { _intermediates.data(), _intermediates.size() }, + const bool success = _engine->ActionVt52EscDispatch(_identifier.Finalize(wch), { _parameters.data(), _parameters.size() }); // Trace the result. @@ -470,8 +468,7 @@ void StateMachine::_ActionCsiDispatch(const wchar_t wch) { _trace.TraceOnAction(L"CsiDispatch"); - const bool success = _engine->ActionCsiDispatch(wch, - { _intermediates.data(), _intermediates.size() }, + const bool success = _engine->ActionCsiDispatch(_identifier.Finalize(wch), { _parameters.data(), _parameters.size() }); // Trace the result. @@ -490,12 +487,12 @@ void StateMachine::_ActionCsiDispatch(const wchar_t wch) // - wch - Character to dispatch. // Return Value: // - -void StateMachine::_ActionCollect(const wchar_t wch) +void StateMachine::_ActionCollect(const wchar_t wch) noexcept { _trace.TraceOnAction(L"Collect"); // store collect data - _intermediates.push_back(wch); + _identifier.AddIntermediate(wch); } // Routine Description: @@ -541,7 +538,7 @@ void StateMachine::_ActionClear() _trace.TraceOnAction(L"Clear"); // clear all internal stored state. - _intermediates.clear(); + _identifier.Clear(); _parameters.clear(); diff --git a/src/terminal/parser/stateMachine.hpp b/src/terminal/parser/stateMachine.hpp index bcbf3f95b9..03d134dfc8 100644 --- a/src/terminal/parser/stateMachine.hpp +++ b/src/terminal/parser/stateMachine.hpp @@ -55,7 +55,7 @@ namespace Microsoft::Console::VirtualTerminal void _ActionPrint(const wchar_t wch); void _ActionEscDispatch(const wchar_t wch); void _ActionVt52EscDispatch(const wchar_t wch); - void _ActionCollect(const wchar_t wch); + void _ActionCollect(const wchar_t wch) noexcept; void _ActionParam(const wchar_t wch); void _ActionCsiDispatch(const wchar_t wch); void _ActionOscParam(const wchar_t wch) noexcept; @@ -142,7 +142,7 @@ namespace Microsoft::Console::VirtualTerminal std::wstring_view _run; - std::vector _intermediates; + VTIDBuilder _identifier; std::vector _parameters; std::wstring _oscString; diff --git a/src/terminal/parser/ut_parser/InputEngineTest.cpp b/src/terminal/parser/ut_parser/InputEngineTest.cpp index b97fa263c1..c9d97b8680 100644 --- a/src/terminal/parser/ut_parser/InputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/InputEngineTest.cpp @@ -222,7 +222,7 @@ class Microsoft::Console::VirtualTerminal::InputEngineTest std::wstring GenerateSgrMouseSequence(const CsiMouseButtonCodes button, const unsigned short modifiers, const COORD position, - const CsiActionCodes direction); + const VTID direction); // SGR_PARAMS serves as test input // - the state of the buttons (constructed via InputStateMachineEngine::CsiActionMouseCodes) @@ -1089,7 +1089,7 @@ void InputEngineTest::AltBackspaceEnterTest() std::wstring InputEngineTest::GenerateSgrMouseSequence(const CsiMouseButtonCodes button, const unsigned short modifiers, const COORD position, - const CsiActionCodes direction) + const VTID direction) { // we first need to convert "button" and "modifiers" into an 8 bit sequence unsigned int actionCode = 0; @@ -1102,7 +1102,11 @@ std::wstring InputEngineTest::GenerateSgrMouseSequence(const CsiMouseButtonCodes // modifiers represents the middle 4 bits actionCode |= modifiers; - return wil::str_printf_failfast(L"\x1b[<%d;%d;%d%c", static_cast(actionCode), position.X, position.Y, direction); + // mouse sequence identifiers consist of a private parameter prefix and a final character + const wchar_t prefixChar = direction[0]; + const wchar_t finalChar = direction[1]; + + return wil::str_printf_failfast(L"\x1b[%c%d;%d;%d%c", prefixChar, static_cast(actionCode), position.X, position.Y, finalChar); } void InputEngineTest::VerifySGRMouseData(const std::vector> testData) diff --git a/src/terminal/parser/ut_parser/StateMachineTest.cpp b/src/terminal/parser/ut_parser/StateMachineTest.cpp index 74e29ea72a..1b4e2f057d 100644 --- a/src/terminal/parser/ut_parser/StateMachineTest.cpp +++ b/src/terminal/parser/ut_parser/StateMachineTest.cpp @@ -50,12 +50,9 @@ public: return true; }; - bool ActionEscDispatch(const wchar_t /* wch */, - const gsl::span /* intermediates */) override { return true; }; + bool ActionEscDispatch(const VTID /* id */) override { return true; }; - bool ActionVt52EscDispatch(const wchar_t /*wch*/, - const gsl::span /*intermediates*/, - const gsl::span /*parameters*/) override { return true; }; + bool ActionVt52EscDispatch(const VTID /*id*/, const gsl::span /*parameters*/) override { return true; }; bool ActionClear() override { return true; }; @@ -82,9 +79,7 @@ public: bool DispatchIntermediatesFromEscape() const override { return false; }; // ActionCsiDispatch is the only method that's actually implemented. - bool ActionCsiDispatch(const wchar_t /*wch*/, - const gsl::span /*intermediates*/, - const gsl::span parameters) override + bool ActionCsiDispatch(const VTID /*id*/, const gsl::span parameters) override { // If flush to terminal is registered for a test, then use it. if (pfnFlushToTerminal)