diff --git a/.github/actions/spell-check/dictionary/dictionary.txt b/.github/actions/spell-check/dictionary/dictionary.txt index c01a75b1927..e1b7da0f86a 100644 --- a/.github/actions/spell-check/dictionary/dictionary.txt +++ b/.github/actions/spell-check/dictionary/dictionary.txt @@ -99401,6 +99401,8 @@ DCP DCPR DCPSK DCS +Dcs +dcs DCT DCTN DCTS diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 4d5b4d0c8ac..85a219db8f3 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -186,6 +186,12 @@ bool OutputStateMachineEngine::ActionPassThroughString(const std::wstring_view s bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch, const gsl::span intermediates) { + 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. diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index c49e52e3c78..7e60032c5e0 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -45,7 +45,7 @@ IStateMachineEngine& StateMachine::Engine() noexcept // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. -static constexpr bool _isNumber(const wchar_t wch) noexcept +static constexpr bool _isNumericParamValue(const wchar_t wch) noexcept { return wch >= L'0' && wch <= L'9'; // 0x30 - 0x39 } @@ -92,6 +92,21 @@ static constexpr bool _isC1Csi(const wchar_t wch) noexcept return wch == L'\x9b'; } +// Routine Description: +// - Determines if a character is a C1 DCS (Device Control Strings) +// This is a single-character way to start a control sequence, as opposed to "ESC P". +// +// See the comment above _isC1Csi for more information on how this is impacted by codepages. +// +// Arguments: +// - wch - Character to check. +// Return Value: +// - True if it is. False if it isn't. +static constexpr bool _isC1Dcs(const wchar_t wch) noexcept +{ + return wch == L'\x90'; +} + // Routine Description: // - Determines if a character is a valid intermediate in an VT escape sequence. // Intermediates are punctuation type characters that are generally vendor specific and @@ -129,6 +144,17 @@ static constexpr bool _isEscape(const wchar_t wch) noexcept return wch == AsciiChars::ESC; } +// Routine Description: +// - Determines if a character is a delimiter between two parameters in a "control sequence". +// Arguments: +// - wch - Character to check. +// Return Value: +// - True if it is. False if it isn't. +static constexpr bool _isParameterDelimiter(const wchar_t wch) noexcept +{ + return wch == L';'; // 0x3B +} + // Routine Description: // - Determines if a character is "control sequence" beginning indicator. // This immediately follows an escape and signifies a varying length control sequence. @@ -142,51 +168,72 @@ static constexpr bool _isCsiIndicator(const wchar_t wch) noexcept } // Routine Description: -// - Determines if a character is a delimiter between two parameters in a "control sequence" -// This occurs in the middle of a control sequence after escape and CsiIndicator have been recognized -// between a series of parameters. +// - Determines if a character is a private range marker for a control sequence. +// Private range markers indicate vendor-specific behavior. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. -static constexpr bool _isCsiDelimiter(const wchar_t wch) noexcept +static constexpr bool _isCsiPrivateMarker(const wchar_t wch) noexcept { - return wch == L';'; // 0x3B + return wch == L'<' || wch == L'=' || wch == L'>' || wch == L'?'; // 0x3C - 0x3F } // Routine Description: -// - Determines if a character is a valid parameter value -// Parameters must be numerical digits. +// - Determines if a character is invalid in a control sequence // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. -static constexpr bool _isCsiParamValue(const wchar_t wch) noexcept +static constexpr bool _isCsiInvalid(const wchar_t wch) noexcept { - return wch >= L'0' && wch <= L'9'; // 0x30 - 0x39 + return wch == L':'; // 0x3A } // Routine Description: -// - Determines if a character is a private range marker for a control sequence. -// Private range markers indicate vendor-specific behavior. +// - Determines if a character is an invalid intermediate. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. -static constexpr bool _isCsiPrivateMarker(const wchar_t wch) noexcept +static constexpr bool _isIntermediateInvalid(const wchar_t wch) noexcept { - return wch == L'<' || wch == L'=' || wch == L'>' || wch == L'?'; // 0x3C - 0x3F + // 0x30 - 0x3F + return _isNumericParamValue(wch) || _isCsiInvalid(wch) || _isParameterDelimiter(wch) || _isCsiPrivateMarker(wch); } // Routine Description: -// - Determines if a character is invalid in a control sequence +// - Determines if a character is an invalid parameter. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. -static constexpr bool _isCsiInvalid(const wchar_t wch) noexcept +static constexpr bool _isParameterInvalid(const wchar_t wch) noexcept { - return wch == L':'; // 0x3A + // 0x3A, 0x3C - 0x3F + return _isCsiInvalid(wch) || _isCsiPrivateMarker(wch); +} + +// Routine Description: +// - Determines if a character is a string terminator. +// Arguments: +// - wch - Character to check. +// Return Value: +// - True if it is. False if it isn't. +static constexpr bool _isStringTerminator(const wchar_t wch) noexcept +{ + return wch == L'\x9C'; +} + +// Routine Description: +// - Determines if a character is a string terminator indicator. +// Arguments: +// - wch - Character to check. +// Return Value: +// - True if it is. False if it isn't. +static constexpr bool _isStringTerminatorIndicator(const wchar_t wch) noexcept +{ + return wch == L'\\'; // 0x5c } // Routine Description: @@ -241,19 +288,6 @@ static constexpr bool _isOscDelimiter(const wchar_t wch) noexcept return wch == L';'; // 0x3B } -// Routine Description: -// - Determines if a character is a valid parameter value for an OSC String, -// that is, the indicator of which OSC action to take. -// Parameters must be numerical digits. -// Arguments: -// - wch - Character to check. -// Return Value: -// - True if it is. False if it isn't. -static constexpr bool _isOscParamValue(const wchar_t wch) noexcept -{ - return _isNumber(wch); // 0x30 - 0x39 -} - // Routine Description: // - Determines if a character should be initiate the end of an OSC sequence. // Arguments: @@ -287,7 +321,42 @@ static constexpr bool _isOscInvalid(const wchar_t wch) noexcept // - True if it is. False if it isn't. static constexpr bool _isOscTerminator(const wchar_t wch) noexcept { - return wch == L'\x7' || wch == L'\x9C'; // Bell character or C1 terminator + return wch == AsciiChars::BEL || _isStringTerminator(wch); // Bell character or C1 terminator +} + +// Routine Description: +// - Determines if a character is "device control string" beginning +// indicator. +// Arguments: +// - wch - Character to check. +// Return Value: +// - True if it is. False if it isn't. +static constexpr bool _isDcsIndicator(const wchar_t wch) noexcept +{ + return wch == L'P'; // 0x50 +} + +// Routine Description: +// - Determines if a character should initiate the end of a DCS sequence. +// Arguments: +// - wch - Character to check. +// Return Value: +// - True if it is. False if it isn't. +static constexpr bool _isDcsTerminationInitiator(const wchar_t wch) noexcept +{ + return wch == AsciiChars::ESC; +} + +// Routine Description: +// - Determines if a character is valid for a DCS pass through sequence. +// Arguments: +// - wch - Character to check. +// Return Value: +// - True if it is. False if it isn't. +static constexpr bool _isDcsPassThroughValid(const wchar_t wch) noexcept +{ + // 0x20 - 0x7E + return wch >= AsciiChars::SPC && wch < AsciiChars::DEL; } // Routine Description: @@ -299,7 +368,7 @@ static constexpr bool _isOscTerminator(const wchar_t wch) noexcept // - True if it is. False if it isn't. static constexpr bool _isActionableFromGround(const wchar_t wch) noexcept { - return (wch <= AsciiChars::US) || _isC1Csi(wch) || _isDelete(wch); + return (wch <= AsciiChars::US) || _isC1Csi(wch) || _isC1Dcs(wch) || _isDelete(wch); } #pragma warning(pop) @@ -566,6 +635,19 @@ void StateMachine::_ActionSs3Dispatch(const wchar_t wch) } } +// Routine Description: +// - Triggers the DcsPassThrough action to indicate that the listener should handle a DCS data string character. +// Arguments: +// - wch - Character to dispatch. +// Return Value: +// - +void StateMachine::_ActionDcsPassThrough(const wchar_t wch) +{ + _trace.TraceOnAction(L"DcsPassThrough"); + _trace.TraceOnExecute(wch); + // TODO:GH#7316: Send the DCS passthrough sequence to the engine +} + // Routine Description: // - Moves the state machine into the Ground state. // This state is entered: @@ -757,6 +839,96 @@ void StateMachine::_EnterVt52Param() noexcept _trace.TraceStateChange(L"Vt52Param"); } +// Routine Description: +// - Moves the state machine into the DcsEntry state. +// This state is entered: +// 1. When the DcsEntry character is seen after an Escape entry (only from the Escape state) +// Arguments: +// - +// Return Value: +// - +void StateMachine::_EnterDcsEntry() +{ + _state = VTStates::DcsEntry; + _trace.TraceStateChange(L"DcsEntry"); + _ActionClear(); +} + +// Routine Description: +// - Moves the state machine into the DcsParam state. +// This state is entered: +// 1. When valid parameter characters are detected on entering a DCS (from DcsEntry state) +// Arguments: +// - +// Return Value: +// - +void StateMachine::_EnterDcsParam() noexcept +{ + _state = VTStates::DcsParam; + _trace.TraceStateChange(L"DcsParam"); +} + +// Routine Description: +// - Moves the state machine into the DcsIgnore state. +// This state is entered: +// 1. When an invalid character is detected during a DCS sequence indicating we should ignore the whole sequence. +// (From DcsEntry, DcsParam, DcsPassThrough, or DcsIntermediate states.) +// Arguments: +// - +// Return Value: +// - +void StateMachine::_EnterDcsIgnore() noexcept +{ + _state = VTStates::DcsIgnore; + _trace.TraceStateChange(L"DcsIgnore"); +} + +// Routine Description: +// - Moves the state machine into the DcsIntermediate state. +// This state is entered: +// 1. When an intermediate character is seen immediately after entering a control sequence (from DcsEntry) +// 2. When an intermediate character is seen while collecting parameter data (from DcsParam) +// Arguments: +// - +// Return Value: +// - +void StateMachine::_EnterDcsIntermediate() noexcept +{ + _state = VTStates::DcsIntermediate; + _trace.TraceStateChange(L"DcsIntermediate"); +} + +// Routine Description: +// - Moves the state machine into the DcsPassThrough state. +// This state is entered: +// 1. When a data string character is seen immediately after entering a control sequence (from DcsEntry) +// 2. When a data string character is seen while collecting parameter data (from DcsParam) +// 3. When a data string character is seen while collecting intermediate data (from DcsIntermediate) +// Arguments: +// - +// Return Value: +// - +void StateMachine::_EnterDcsPassThrough() noexcept +{ + _state = VTStates::DcsPassThrough; + _trace.TraceStateChange(L"DcsPassThrough"); +} + +// Routine Description: +// - Moves the state machine into the DcsTermination state. +// This state is entered: +// 1. When an ESC is seen in a DCS string. This escape will be followed by a +// '\', as to encode a 0x9C as a 7-bit ASCII char stream. +// Arguments: +// - +// Return Value: +// - +void StateMachine::_EnterDcsTermination() noexcept +{ + _state = VTStates::DcsTermination; + _trace.TraceStateChange(L"DcsTermination"); +} + // Routine Description: // - Processes a character event into an Action that occurs while in the Ground state. // Events in this state will: @@ -778,6 +950,10 @@ void StateMachine::_EventGround(const wchar_t wch) { _EnterCsiEntry(); } + else if (_isC1Dcs(wch) && _isInAnsiMode) + { + _EnterDcsEntry(); + } else { _ActionPrint(wch); @@ -842,6 +1018,10 @@ void StateMachine::_EventEscape(const wchar_t wch) { _EnterSs3Entry(); } + else if (_isDcsIndicator(wch)) + { + _EnterDcsEntry(); + } else { _ActionEscDispatch(wch); @@ -935,7 +1115,7 @@ void StateMachine::_EventCsiEntry(const wchar_t wch) { _EnterCsiIgnore(); } - else if (_isCsiParamValue(wch) || _isCsiDelimiter(wch)) + else if (_isNumericParamValue(wch) || _isParameterDelimiter(wch)) { _ActionParam(wch); _EnterCsiParam(); @@ -979,7 +1159,7 @@ void StateMachine::_EventCsiIntermediate(const wchar_t wch) { _ActionIgnore(); } - else if (_isCsiParamValue(wch) || _isCsiInvalid(wch) || _isCsiDelimiter(wch) || _isCsiPrivateMarker(wch)) + else if (_isIntermediateInvalid(wch)) { _EnterCsiIgnore(); } @@ -1017,7 +1197,7 @@ void StateMachine::_EventCsiIgnore(const wchar_t wch) { _ActionIgnore(); } - else if (_isCsiParamValue(wch) || _isCsiInvalid(wch) || _isCsiDelimiter(wch) || _isCsiPrivateMarker(wch)) + else if (_isIntermediateInvalid(wch)) { _ActionIgnore(); } @@ -1051,7 +1231,7 @@ void StateMachine::_EventCsiParam(const wchar_t wch) { _ActionIgnore(); } - else if (_isCsiParamValue(wch) || _isCsiDelimiter(wch)) + else if (_isNumericParamValue(wch) || _isParameterDelimiter(wch)) { _ActionParam(wch); } @@ -1060,7 +1240,7 @@ void StateMachine::_EventCsiParam(const wchar_t wch) _ActionCollect(wch); _EnterCsiIntermediate(); } - else if (_isCsiInvalid(wch) || _isCsiPrivateMarker(wch)) + else if (_isParameterInvalid(wch)) { _EnterCsiIgnore(); } @@ -1088,7 +1268,7 @@ void StateMachine::_EventOscParam(const wchar_t wch) noexcept { _EnterGround(); } - else if (_isOscParamValue(wch)) + else if (_isNumericParamValue(wch)) { _ActionOscParam(wch); } @@ -1185,7 +1365,7 @@ void StateMachine::_EventSs3Entry(const wchar_t wch) // CSI sequences ignore characters the same way. _EnterCsiIgnore(); } - else if (_isCsiParamValue(wch) || _isCsiDelimiter(wch)) + else if (_isNumericParamValue(wch) || _isParameterDelimiter(wch)) { _ActionParam(wch); _EnterSs3Param(); @@ -1220,11 +1400,11 @@ void StateMachine::_EventSs3Param(const wchar_t wch) { _ActionIgnore(); } - else if (_isCsiParamValue(wch) || _isCsiDelimiter(wch)) + else if (_isNumericParamValue(wch) || _isParameterDelimiter(wch)) { _ActionParam(wch); } - else if (_isCsiInvalid(wch) || _isCsiPrivateMarker(wch)) + else if (_isParameterInvalid(wch)) { _EnterCsiIgnore(); } @@ -1270,6 +1450,217 @@ void StateMachine::_EventVt52Param(const wchar_t wch) } } +// Routine Description: +// - Processes a character event into an Action that occurs while in the DcsEntry state. +// Events in this state will: +// 1. Ignore C0 control characters +// 2. Ignore Delete characters +// 3. Begin to ignore all remaining characters when an invalid character is detected (DcsIgnore) +// 4. Store parameter data +// 5. Collect Intermediate characters +// 6. Pass through everything else +// DCS sequences are structurally almost the same as CSI sequences, just with an +// extra data string. It's safe to reuse CSI functions for +// determining if a character is a parameter, delimiter, or invalid. +// Arguments: +// - wch - Character that triggered the event +// Return Value: +// - +void StateMachine::_EventDcsEntry(const wchar_t wch) +{ + _trace.TraceOnEvent(L"DcsEntry"); + if (_isC0Code(wch)) + { + _ActionIgnore(); + } + else if (_isDelete(wch)) + { + _ActionIgnore(); + } + else if (_isCsiInvalid(wch)) + { + _EnterDcsIgnore(); + } + else if (_isNumericParamValue(wch) || _isParameterDelimiter(wch)) + { + _ActionParam(wch); + _EnterDcsParam(); + } + else if (_isIntermediate(wch)) + { + _ActionCollect(wch); + _EnterDcsIntermediate(); + } + else + { + _ActionDcsPassThrough(wch); + _EnterDcsPassThrough(); + } +} + +// Routine Description: +// - Processes a character event into an Action that occurs while in the DcsIgnore state. +// Events in this state will: +// 1. Enter ground on a String terminator +// 2. Ignore everything else. +// Arguments: +// - wch - Character that triggered the event +// Return Value: +// - +void StateMachine::_EventDcsIgnore(const wchar_t wch) noexcept +{ + _trace.TraceOnEvent(L"DcsIgnore"); + if (_isStringTerminator(wch)) + { + _EnterGround(); + } + else + { + _ActionIgnore(); + } +} + +// Routine Description: +// - Processes a character event into an Action that occurs while in the DcsIntermediate state. +// Events in this state will: +// 1. Ignore C0 control characters +// 2. Ignore Delete characters +// 3. Collect intermediate data. +// 4. Begin to ignore all remaining intermediates when an invalid character is detected (DcsIgnore) +// 5. Enter DcsPassThrough if we see DCS pass through indicator +// 6. Pass through everything else. +// Arguments: +// - wch - Character that triggered the event +// Return Value: +// - +void StateMachine::_EventDcsIntermediate(const wchar_t wch) +{ + _trace.TraceOnEvent(L"DcsIntermediate"); + if (_isC0Code(wch)) + { + _ActionIgnore(); + } + else if (_isDelete(wch)) + { + _ActionIgnore(); + } + else if (_isIntermediate(wch)) + { + _ActionCollect(wch); + } + else if (_isIntermediateInvalid(wch)) + { + _EnterDcsIgnore(); + } + else + { + _ActionDcsPassThrough(wch); + _EnterDcsPassThrough(); + } +} + +// Routine Description: +// - Processes a character event into an Action that occurs while in the DcsParam state. +// Events in this state will: +// 1. Ignore C0 control characters +// 2. Ignore Delete characters +// 3. Collect DCS parameter data +// 4. Enter DcsIntermediate if we see an intermediate +// 5. Begin to ignore all remaining parameters when an invalid character is detected (DcsIgnore) +// 6. Pass through everything else. +// Arguments: +// - wch - Character that triggered the event +// Return Value: +// - +void StateMachine::_EventDcsParam(const wchar_t wch) +{ + _trace.TraceOnEvent(L"DcsParam"); + if (_isC0Code(wch)) + { + _ActionIgnore(); + } + else if (_isDelete(wch)) + { + _ActionIgnore(); + } + if (_isNumericParamValue(wch) || _isParameterDelimiter(wch)) + { + _ActionParam(wch); + } + else if (_isIntermediate(wch)) + { + _ActionCollect(wch); + _EnterDcsIntermediate(); + } + else if (_isParameterInvalid(wch)) + { + _EnterDcsIgnore(); + } + else + { + _ActionDcsPassThrough(wch); + _EnterDcsPassThrough(); + } +} + +// Routine Description: +// - Processes a character event into an Action that occurs while in the DcsPassThrough state. +// Events in this state will: +// 1. Enter ground on a String terminator +// 2. Pass through if character is valid. +// 3. If we see a ESC, enter the DcsTermination state. +// 4. Ignore everything else. +// Arguments: +// - wch - Character that triggered the event +// Return Value: +// - +void StateMachine::_EventDcsPassThrough(const wchar_t wch) +{ + _trace.TraceOnEvent(L"DcsPassThrough"); + if (_isStringTerminator(wch)) + { + // TODO:GH#7316: The Dcs sequence has successfully terminated. This is where we'd be dispatching the DCS command. + _EnterGround(); + } + if (_isC0Code(wch) || _isDcsPassThroughValid(wch)) + { + _ActionDcsPassThrough(wch); + } + else if (_isDcsTerminationInitiator(wch)) + { + _EnterDcsTermination(); + } + else + { + _ActionIgnore(); + } +} + +// Routine Description: +// - Handle the two-character termination of a DCS sequence. +// Events in this state will: +// 1. Enter ground on a string terminator +// 2. Pass on everything else as the start of a regular escape sequence +// Arguments: +// - wch - Character that triggered the event +// Return Value: +// - +void StateMachine::_EventDcsTermination(const wchar_t wch) +{ + _trace.TraceOnEvent(L"DcsTermination"); + + if (_isStringTerminatorIndicator(wch)) + { + // TODO: The Dcs sequence has successfully terminated. This is where we'd be dispatching the DCS command. + _EnterGround(); + } + else + { + _EnterEscape(); + _EventEscape(wch); + } +} + // Routine Description: // - Entry to the state machine. Takes characters one by one and processes them according to the state machine rules. // Arguments: @@ -1292,10 +1683,12 @@ void StateMachine::ProcessCharacter(const wchar_t wch) _ActionExecute(wch); _EnterGround(); } - else if (_isEscape(wch) && _state != VTStates::OscString) + else if (_isEscape(wch) && _state != VTStates::OscString && _state != VTStates::DcsPassThrough) { // Don't go to escape from the OSC string state - ESC can be used to // terminate OSC strings. + // + // Same for DCS pass through state. _EnterEscape(); } else @@ -1329,6 +1722,18 @@ void StateMachine::ProcessCharacter(const wchar_t wch) return _EventSs3Param(wch); case VTStates::Vt52Param: return _EventVt52Param(wch); + case VTStates::DcsEntry: + return _EventDcsEntry(wch); + case VTStates::DcsIgnore: + return _EventDcsIgnore(wch); + case VTStates::DcsIntermediate: + return _EventDcsIntermediate(wch); + case VTStates::DcsParam: + return _EventDcsParam(wch); + case VTStates::DcsPassThrough: + return _EventDcsPassThrough(wch); + case VTStates::DcsTermination: + return _EventDcsTermination(wch); default: return; } diff --git a/src/terminal/parser/stateMachine.hpp b/src/terminal/parser/stateMachine.hpp index dfa66e5ec1e..bcbf3f95b90 100644 --- a/src/terminal/parser/stateMachine.hpp +++ b/src/terminal/parser/stateMachine.hpp @@ -62,6 +62,7 @@ namespace Microsoft::Console::VirtualTerminal void _ActionOscPut(const wchar_t wch); void _ActionOscDispatch(const wchar_t wch); void _ActionSs3Dispatch(const wchar_t wch); + void _ActionDcsPassThrough(const wchar_t wch); void _ActionClear(); void _ActionIgnore() noexcept; @@ -79,6 +80,12 @@ namespace Microsoft::Console::VirtualTerminal void _EnterSs3Entry(); void _EnterSs3Param() noexcept; void _EnterVt52Param() noexcept; + void _EnterDcsEntry(); + void _EnterDcsParam() noexcept; + void _EnterDcsIgnore() noexcept; + void _EnterDcsIntermediate() noexcept; + void _EnterDcsPassThrough() noexcept; + void _EnterDcsTermination() noexcept; void _EventGround(const wchar_t wch); void _EventEscape(const wchar_t wch); @@ -93,6 +100,12 @@ namespace Microsoft::Console::VirtualTerminal void _EventSs3Entry(const wchar_t wch); void _EventSs3Param(const wchar_t wch); void _EventVt52Param(const wchar_t wch); + void _EventDcsEntry(const wchar_t wch); + void _EventDcsIgnore(const wchar_t wch) noexcept; + void _EventDcsIntermediate(const wchar_t wch); + void _EventDcsParam(const wchar_t wch); + void _EventDcsPassThrough(const wchar_t wch); + void _EventDcsTermination(const wchar_t wch); void _AccumulateTo(const wchar_t wch, size_t& value) noexcept; @@ -110,7 +123,13 @@ namespace Microsoft::Console::VirtualTerminal OscTermination, Ss3Entry, Ss3Param, - Vt52Param + Vt52Param, + DcsEntry, + DcsIgnore, + DcsIntermediate, + DcsParam, + DcsPassThrough, + DcsTermination }; Microsoft::Console::VirtualTerminal::ParserTracing _trace; diff --git a/src/terminal/parser/ut_parser/OutputEngineTest.cpp b/src/terminal/parser/ut_parser/OutputEngineTest.cpp index 17caebefada..166578b8055 100644 --- a/src/terminal/parser/ut_parser/OutputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/OutputEngineTest.cpp @@ -56,7 +56,7 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final TEST_METHOD(TestEscapePath) { BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:uiTest", L"{0,1,2,3,4,5,6,7,8,9,10,11}") // one value for each type of state test below. + TEST_METHOD_PROPERTY(L"Data:uiTest", L"{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17}") // one value for each type of state test below. END_TEST_METHOD_PROPERTIES() size_t uiTest; @@ -66,6 +66,7 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final StateMachine mach(std::move(engine)); // The OscString state shouldn't escape out after an ESC. + // Same for DcsPassThrough state. bool shouldEscapeOut = true; switch (uiTest) @@ -143,6 +144,43 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final mach._state = StateMachine::VTStates::Ss3Param; break; } + case 12: + { + Log::Comment(L"Escape from DcsEntry"); + mach._state = StateMachine::VTStates::DcsEntry; + break; + } + case 13: + { + Log::Comment(L"Escape from DcsIgnore"); + mach._state = StateMachine::VTStates::DcsIgnore; + break; + } + case 14: + { + Log::Comment(L"Escape from DcsIntermediate"); + mach._state = StateMachine::VTStates::DcsIntermediate; + break; + } + case 15: + { + Log::Comment(L"Escape from DcsParam"); + mach._state = StateMachine::VTStates::DcsParam; + break; + } + case 16: + { + Log::Comment(L"Escape from DcsPassThrough"); + shouldEscapeOut = false; + mach._state = StateMachine::VTStates::DcsPassThrough; + break; + } + case 17: + { + Log::Comment(L"Escape from DcsTermination"); + mach._state = StateMachine::VTStates::DcsTermination; + break; + } } mach.ProcessCharacter(AsciiChars::ESC); @@ -284,6 +322,12 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam); mach.ProcessCharacter(L'J'); VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); + + VERIFY_ARE_EQUAL(mach._parameters.size(), 4u); + VERIFY_ARE_EQUAL(mach._parameters.at(0), 0u); + VERIFY_ARE_EQUAL(mach._parameters.at(1), 324u); + VERIFY_ARE_EQUAL(mach._parameters.at(2), 0u); + VERIFY_ARE_EQUAL(mach._parameters.at(3), 8u); } TEST_METHOD(TestLeadingZeroCsiParam) @@ -428,6 +472,7 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final mach.ProcessCharacter(L'\\'); VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); } + TEST_METHOD(TestLongOscString) { auto dispatch = std::make_unique(); @@ -550,6 +595,163 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final mach.ProcessCharacter(AsciiChars::BEL); VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); } + + TEST_METHOD(TestDcsEntry) + { + auto dispatch = std::make_unique(); + auto engine = std::make_unique(std::move(dispatch)); + StateMachine mach(std::move(engine)); + + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); + mach.ProcessCharacter(AsciiChars::ESC); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape); + mach.ProcessCharacter(L'P'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'\\'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); + } + + TEST_METHOD(TestC1DcsEntry) + { + auto dispatch = std::make_unique(); + auto engine = std::make_unique(std::move(dispatch)); + StateMachine mach(std::move(engine)); + + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); + mach.ProcessCharacter(L'\x90'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'\\'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); + } + + TEST_METHOD(TestDcsImmediate) + { + auto dispatch = std::make_unique(); + auto engine = std::make_unique(std::move(dispatch)); + StateMachine mach(std::move(engine)); + + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); + mach.ProcessCharacter(AsciiChars::ESC); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape); + mach.ProcessCharacter(L'P'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry); + mach.ProcessCharacter(L' '); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIntermediate); + mach.ProcessCharacter(L'#'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIntermediate); + mach.ProcessCharacter(L'%'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIntermediate); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'\\'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); + } + + TEST_METHOD(TestDcsIgnore) + { + auto dispatch = std::make_unique(); + auto engine = std::make_unique(std::move(dispatch)); + StateMachine mach(std::move(engine)); + + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); + mach.ProcessCharacter(AsciiChars::ESC); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape); + mach.ProcessCharacter(L'P'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry); + mach.ProcessCharacter(L':'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIgnore); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'\\'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); + } + + TEST_METHOD(TestDcsParam) + { + auto dispatch = std::make_unique(); + auto engine = std::make_unique(std::move(dispatch)); + StateMachine mach(std::move(engine)); + + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); + mach.ProcessCharacter(AsciiChars::ESC); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape); + mach.ProcessCharacter(L'P'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry); + mach.ProcessCharacter(L';'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsParam); + mach.ProcessCharacter(L'3'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsParam); + mach.ProcessCharacter(L'2'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsParam); + mach.ProcessCharacter(L'4'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsParam); + mach.ProcessCharacter(L';'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsParam); + mach.ProcessCharacter(L';'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsParam); + mach.ProcessCharacter(L'8'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsParam); + + VERIFY_ARE_EQUAL(mach._parameters.size(), 4u); + VERIFY_ARE_EQUAL(mach._parameters.at(0), 0u); + VERIFY_ARE_EQUAL(mach._parameters.at(1), 324u); + VERIFY_ARE_EQUAL(mach._parameters.at(2), 0u); + VERIFY_ARE_EQUAL(mach._parameters.at(3), 8u); + + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'\\'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); + } + + TEST_METHOD(TestDcsIntermediateAndPassThrough) + { + auto dispatch = std::make_unique(); + auto engine = std::make_unique(std::move(dispatch)); + StateMachine mach(std::move(engine)); + + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); + mach.ProcessCharacter(AsciiChars::ESC); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape); + mach.ProcessCharacter(L'P'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry); + mach.ProcessCharacter(L' '); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIntermediate); + mach.ProcessCharacter(L'x'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsPassThrough); + mach.ProcessCharacter(AsciiChars::ESC); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsTermination); + mach.ProcessCharacter(L'\\'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); + } + + TEST_METHOD(TestDcsLongStringPassThrough) + { + auto dispatch = std::make_unique(); + auto engine = std::make_unique(std::move(dispatch)); + StateMachine mach(std::move(engine)); + + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); + mach.ProcessCharacter(AsciiChars::ESC); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape); + mach.ProcessCharacter(L'P'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry); + mach.ProcessCharacter(L'q'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsPassThrough); + mach.ProcessCharacter(L'#'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsPassThrough); + mach.ProcessCharacter(L'1'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsPassThrough); + mach.ProcessCharacter(L'N'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsPassThrough); + mach.ProcessCharacter(L'N'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsPassThrough); + mach.ProcessCharacter(L'N'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsPassThrough); + mach.ProcessCharacter(AsciiChars::ESC); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsTermination); + mach.ProcessCharacter(L'\\'); + VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground); + } }; class StatefulDispatch final : public TermDispatch