// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "precomp.h" #include "stateMachine.hpp" #include "ascii.hpp" using namespace Microsoft::Console::VirtualTerminal; //Takes ownership of the pEngine. StateMachine::StateMachine(std::unique_ptr engine) : _engine(std::move(engine)), _state(VTStates::Ground), _trace(Microsoft::Console::VirtualTerminal::ParserTracing()), _parameters{}, _parameterLimitReached(false), _oscString{}, _cachedSequence{ std::nullopt }, _processingIndividually(false) { _ActionClear(); } void StateMachine::SetParserMode(const Mode mode, const bool enabled) noexcept { _parserMode.set(mode, enabled); } bool StateMachine::GetParserMode(const Mode mode) const noexcept { return _parserMode.test(mode); } const IStateMachineEngine& StateMachine::Engine() const noexcept { return *_engine; } IStateMachineEngine& StateMachine::Engine() noexcept { return *_engine; } // Routine Description: // - Determines if a character is a valid number character, 0-9. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isNumericParamValue(const wchar_t wch) noexcept { return wch >= L'0' && wch <= L'9'; // 0x30 - 0x39 } #pragma warning(push) #pragma warning(disable : 26497) // We don't use any of these "constexprable" functions in that fashion // Routine Description: // - Determines if a character belongs to the C0 escape range. // This is character sequences less than a space character (null, backspace, new line, etc.) // See also https://en.wikipedia.org/wiki/C0_and_C1_control_codes // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isC0Code(const wchar_t wch) noexcept { return (wch >= AsciiChars::NUL && wch <= AsciiChars::ETB) || wch == AsciiChars::EM || (wch >= AsciiChars::FS && wch <= AsciiChars::US); } // Routine Description: // - Determines if a character is a C1 control characters. // This is a single-character way to start a control sequence, as opposed to using ESC // and their 7-bit equivalent. // // Not all single-byte codepages support C1 control codes--in some, the range that would // be used for C1 codes are instead used for additional graphic characters. // // However, we do not need to worry about confusion whether a single byte, for example, // \x9b in a single-byte stream represents a C1 CSI or some other glyph, because by the time we // get here, everything is Unicode. Knowing whether a single-byte \x9b represents a // single-character C1 CSI or some other glyph is handled by MultiByteToWideChar before // we get here (if the stream was not already UTF-16). For instance, in CP_ACP, if a // \x9b shows up, it will get converted to \x203a. So, if we get here, and have a // \x009b, we know that it unambiguously represents a C1 CSI. // // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isC1ControlCharacter(const wchar_t wch) noexcept { return (wch >= L'\x80' && wch <= L'\x9F'); } // Routine Description: // - Convert a C1 control characters to their 7-bit equivalent. // // Arguments: // - wch - Character to convert. // Return Value: // - The 7-bit equivalent of the 8-bit control characters. static constexpr wchar_t _c1To7Bit(const wchar_t wch) noexcept { return wch - L'\x40'; } // 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 // modify the operational mode of a command. // See also http://vt100.net/emu/dec_ansi_parser // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isIntermediate(const wchar_t wch) noexcept { return wch >= L' ' && wch <= L'/'; // 0x20 - 0x2F } // Routine Description: // - Determines if a character is the delete character. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isDelete(const wchar_t wch) noexcept { return wch == AsciiChars::DEL; } // Routine Description: // - Determines if a character is the escape character. // Used to start escape sequences. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. 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 an escape 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. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isCsiIndicator(const wchar_t wch) noexcept { return wch == L'['; // 0x5B } // Routine Description: // - 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 _isCsiPrivateMarker(const wchar_t wch) noexcept { return wch == L'<' || wch == L'=' || wch == L'>' || wch == L'?'; // 0x3C - 0x3F } // Routine Description: // - 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 _isCsiInvalid(const wchar_t wch) noexcept { return wch == L':'; // 0x3A } // Routine Description: // - 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 _isIntermediateInvalid(const wchar_t wch) noexcept { // 0x30 - 0x3F return _isNumericParamValue(wch) || _isCsiInvalid(wch) || _isParameterDelimiter(wch) || _isCsiPrivateMarker(wch); } // Routine Description: // - 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 _isParameterInvalid(const wchar_t wch) noexcept { // 0x3A, 0x3C - 0x3F return _isCsiInvalid(wch) || _isCsiPrivateMarker(wch); } // 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: // - Determines if a character is a "Single Shift Select" indicator. // This immediately follows an escape and signifies a varying length control string. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isSs3Indicator(const wchar_t wch) noexcept { return wch == L'O'; // 0x4F } // Routine Description: // - Determines if a character is the VT52 "Direct Cursor Address" command. // This immediately follows an escape and signifies the start of a multiple // character command sequence. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isVt52CursorAddress(const wchar_t wch) noexcept { return wch == L'Y'; // 0x59 } // Routine Description: // - Determines if a character is "operating system control string" beginning // indicator. // This immediately follows an escape and signifies a signifies a varying // length control sequence, quite similar to CSI. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isOscIndicator(const wchar_t wch) noexcept { return wch == L']'; // 0x5D } // Routine Description: // - Determines if a character is a delimiter between two parameters in a "operating system control sequence" // This occurs in the middle of a control sequence after escape and OscIndicator have been recognized, // after the parameter indicating which OSC action to take. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isOscDelimiter(const wchar_t wch) noexcept { return wch == L';'; // 0x3B } // Routine Description: // - Determines if a character should be ignored in a operating system control sequence // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isOscInvalid(const wchar_t wch) noexcept { return wch <= L'\x17' || wch == L'\x19' || (wch >= L'\x1c' && wch <= L'\x1f'); } // Routine Description: // - Determines if a character is "operating system control string" termination indicator. // This signals the end of an OSC string collection. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isOscTerminator(const wchar_t wch) noexcept { return wch == AsciiChars::BEL; // Bell character } // 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 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: // - Determines if a character is "start of string" beginning // indicator. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isSosIndicator(const wchar_t wch) noexcept { return wch == L'X'; // 0x58 } // Routine Description: // - Determines if a character is "private message" beginning // indicator. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isPmIndicator(const wchar_t wch) noexcept { return wch == L'^'; // 0x5E } // Routine Description: // - Determines if a character is "application program command" beginning // indicator. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isApcIndicator(const wchar_t wch) noexcept { return wch == L'_'; // 0x5F } // Routine Description: // - Determines if a character indicates an action that should be taken in the ground state - // These are C0 characters and the C1 [single-character] CSI. // Arguments: // - wch - Character to check. // Return Value: // - True if it is. False if it isn't. static constexpr bool _isActionableFromGround(const wchar_t wch) noexcept { return (wch <= AsciiChars::US) || _isC1ControlCharacter(wch) || _isDelete(wch); } #pragma warning(pop) // Routine Description: // - Triggers the Execute action to indicate that the listener should immediately respond to a C0 control character. // Arguments: // - wch - Character to dispatch. // Return Value: // - void StateMachine::_ActionExecute(const wchar_t wch) { _trace.TraceOnExecute(wch); const bool success = _engine->ActionExecute(wch); // Trace the result. _trace.DispatchSequenceTrace(success); } // Routine Description: // - Triggers the Execute action to indicate that the listener should // immediately respond to a C0 control character, with the added // information that we're executing it from the Escape state. // Arguments: // - wch - Character to dispatch. // Return Value: // - void StateMachine::_ActionExecuteFromEscape(const wchar_t wch) { _trace.TraceOnExecuteFromEscape(wch); const bool success = _engine->ActionExecuteFromEscape(wch); // Trace the result. _trace.DispatchSequenceTrace(success); } // Routine Description: // - Triggers the Print action to indicate that the listener should render the character given. // Arguments: // - wch - Character to dispatch. // Return Value: // - void StateMachine::_ActionPrint(const wchar_t wch) { _trace.TraceOnAction(L"Print"); const bool success = _engine->ActionPrint(wch); // Trace the result. _trace.DispatchSequenceTrace(success); } // Routine Description: // - Triggers the EscDispatch action to indicate that the listener should handle a simple escape sequence. // These sequences traditionally start with ESC and a simple letter. No complicated parameters. // Arguments: // - wch - Character to dispatch. // Return Value: // - void StateMachine::_ActionEscDispatch(const wchar_t wch) { _trace.TraceOnAction(L"EscDispatch"); const bool success = _engine->ActionEscDispatch(_identifier.Finalize(wch)); // Trace the result. _trace.DispatchSequenceTrace(success); if (!success) { // Suppress it and log telemetry on failed cases TermTelemetry::Instance().LogFailed(wch); } } // Routine Description: // - Triggers the Vt52EscDispatch action to indicate that the listener should handle a VT52 escape sequence. // These sequences start with ESC and a single letter, sometimes followed by parameters. // Arguments: // - wch - Character to dispatch. // Return Value: // - void StateMachine::_ActionVt52EscDispatch(const wchar_t wch) { _trace.TraceOnAction(L"Vt52EscDispatch"); const bool success = _engine->ActionVt52EscDispatch(_identifier.Finalize(wch), { _parameters.data(), _parameters.size() }); // Trace the result. _trace.DispatchSequenceTrace(success); if (!success) { // Suppress it and log telemetry on failed cases TermTelemetry::Instance().LogFailed(wch); } } // Routine Description: // - Triggers the CsiDispatch action to indicate that the listener should handle a control sequence. // These sequences perform various API-type commands that can include many parameters. // Arguments: // - wch - Character to dispatch. // Return Value: // - void StateMachine::_ActionCsiDispatch(const wchar_t wch) { _trace.TraceOnAction(L"CsiDispatch"); const bool success = _engine->ActionCsiDispatch(_identifier.Finalize(wch), { _parameters.data(), _parameters.size() }); // Trace the result. _trace.DispatchSequenceTrace(success); if (!success) { // Suppress it and log telemetry on failed cases TermTelemetry::Instance().LogFailed(wch); } } // Routine Description: // - Triggers the Collect action to indicate that the state machine should store this character as part of an escape/control sequence. // Arguments: // - wch - Character to dispatch. // Return Value: // - void StateMachine::_ActionCollect(const wchar_t wch) noexcept { _trace.TraceOnAction(L"Collect"); // store collect data _identifier.AddIntermediate(wch); } // Routine Description: // - Triggers the Param action to indicate that the state machine should store this character as a part of a parameter // to a control sequence. // Arguments: // - wch - Character to dispatch. // Return Value: // - void StateMachine::_ActionParam(const wchar_t wch) { _trace.TraceOnAction(L"Param"); // Once we've reached the parameter limit, additional parameters are ignored. if (!_parameterLimitReached) { // If we have no parameters and we're about to add one, get the next value ready here. if (_parameters.empty()) { _parameters.push_back({}); } // On a delimiter, increase the number of params we've seen. // "Empty" params should still count as a param - // eg "\x1b[0;;m" should be three params if (_isParameterDelimiter(wch)) { // If we receive a delimiter after we've already accumulated the // maximum allowed parameters, then we need to set a flag to // indicate that further parameter characters should be ignored. if (_parameters.size() >= MAX_PARAMETER_COUNT) { _parameterLimitReached = true; } else { // Otherwise move to next param. _parameters.push_back({}); } } else { // Accumulate the character given into the last (current) parameter. // If the value hasn't been initialized yet, it'll start as 0. auto currentParameter = _parameters.back().value_or(0); _AccumulateTo(wch, currentParameter); _parameters.back() = currentParameter; } } } // Routine Description: // - Triggers the Clear action to indicate that the state machine should erase all internal state. // Arguments: // - wch - Character to dispatch. // Return Value: // - void StateMachine::_ActionClear() { _trace.TraceOnAction(L"Clear"); // clear all internal stored state. _identifier.Clear(); _parameters.clear(); _parameterLimitReached = false; _oscString.clear(); _oscParameter = 0; _dcsStringHandler = nullptr; _engine->ActionClear(); } // Routine Description: // - Triggers the Ignore action to indicate that the state machine should eat this character and say nothing. // Arguments: // - wch - Character to dispatch. // Return Value: // - void StateMachine::_ActionIgnore() noexcept { // do nothing. _trace.TraceOnAction(L"Ignore"); } // Routine Description: // - Triggers the end of a data string when a CAN, SUB, or ESC is seen. // Arguments: // - // Return Value: // - void StateMachine::_ActionInterrupt() { // This is only applicable for DCS strings. OSC strings require a full // ST sequence to be received before they can be dispatched. if (_state == VTStates::DcsPassThrough) { // The ESC signals the end of the data string. _dcsStringHandler(AsciiChars::ESC); _dcsStringHandler = nullptr; } } // Routine Description: // - Stores this character as part of the param indicating which OSC action to take. // Arguments: // - wch - Character to collect. // Return Value: // - void StateMachine::_ActionOscParam(const wchar_t wch) noexcept { _trace.TraceOnAction(L"OscParamCollect"); _AccumulateTo(wch, _oscParameter); } // Routine Description: // - Stores this character as part of the OSC string // Arguments: // - wch - Character to dispatch. // Return Value: // - void StateMachine::_ActionOscPut(const wchar_t wch) { _trace.TraceOnAction(L"OscPut"); _oscString.push_back(wch); } // Routine Description: // - Triggers the CsiDispatch action to indicate that the listener should handle a control sequence. // These sequences perform various API-type commands that can include many parameters. // Arguments: // - wch - Character to dispatch. // Return Value: // - void StateMachine::_ActionOscDispatch(const wchar_t wch) { _trace.TraceOnAction(L"OscDispatch"); const bool success = _engine->ActionOscDispatch(wch, _oscParameter, _oscString); // Trace the result. _trace.DispatchSequenceTrace(success); if (!success) { // Suppress it and log telemetry on failed cases TermTelemetry::Instance().LogFailed(wch); } } // Routine Description: // - Triggers the Ss3Dispatch action to indicate that the listener should handle a control sequence. // These sequences perform various API-type commands that can include many parameters. // Arguments: // - wch - Character to dispatch. // Return Value: // - void StateMachine::_ActionSs3Dispatch(const wchar_t wch) { _trace.TraceOnAction(L"Ss3Dispatch"); const bool success = _engine->ActionSs3Dispatch(wch, { _parameters.data(), _parameters.size() }); // Trace the result. _trace.DispatchSequenceTrace(success); if (!success) { // Suppress it and log telemetry on failed cases TermTelemetry::Instance().LogFailed(wch); } } // Routine Description: // - Triggers the DcsDispatch action to indicate that the listener should handle a control sequence. // The returned handler function will be used to process the subsequent data string characters. // Arguments: // - wch - Character to dispatch. // Return Value: // - void StateMachine::_ActionDcsDispatch(const wchar_t wch) { _trace.TraceOnAction(L"DcsDispatch"); _dcsStringHandler = _engine->ActionDcsDispatch(_identifier.Finalize(wch), { _parameters.data(), _parameters.size() }); // If the returned handler is null, the sequence is not supported. const bool success = _dcsStringHandler != nullptr; // Trace the result. _trace.DispatchSequenceTrace(success); if (success) { // If successful, enter the pass through state. _EnterDcsPassThrough(); } else { // Otherwise ignore remaining chars and log telemetry on failed cases _EnterDcsIgnore(); TermTelemetry::Instance().LogFailed(wch); } } // Routine Description: // - Moves the state machine into the Ground state. // This state is entered: // 1. By default at the beginning of operation // 2. After any execute/dispatch action. // Arguments: // - // Return Value: // - void StateMachine::_EnterGround() noexcept { _state = VTStates::Ground; _cachedSequence.reset(); // entering ground means we've completed the pending sequence _trace.TraceStateChange(L"Ground"); } // Routine Description: // - Moves the state machine into the Escape state. // This state is entered: // 1. When the Escape character is seen at any time. // Arguments: // - // Return Value: // - void StateMachine::_EnterEscape() { _state = VTStates::Escape; _trace.TraceStateChange(L"Escape"); _ActionClear(); _trace.ClearSequenceTrace(); } // Routine Description: // - Moves the state machine into the EscapeIntermediate state. // This state is entered: // 1. When EscIntermediate characters are seen after an Escape entry (only from the Escape state) // Arguments: // - // Return Value: // - void StateMachine::_EnterEscapeIntermediate() noexcept { _state = VTStates::EscapeIntermediate; _trace.TraceStateChange(L"EscapeIntermediate"); } // Routine Description: // - Moves the state machine into the CsiEntry state. // This state is entered: // 1. When the CsiEntry character is seen after an Escape entry (only from the Escape state) // Arguments: // - // Return Value: // - void StateMachine::_EnterCsiEntry() { _state = VTStates::CsiEntry; _trace.TraceStateChange(L"CsiEntry"); _ActionClear(); } // Routine Description: // - Moves the state machine into the CsiParam state. // This state is entered: // 1. When valid parameter characters are detected on entering a CSI (from CsiEntry state) // Arguments: // - // Return Value: // - void StateMachine::_EnterCsiParam() noexcept { _state = VTStates::CsiParam; _trace.TraceStateChange(L"CsiParam"); } // Routine Description: // - Moves the state machine into the CsiIgnore state. // This state is entered: // 1. When an invalid character is detected during a CSI sequence indicating we should ignore the whole sequence. // (From CsiEntry, CsiParam, or CsiIntermediate states.) // Arguments: // - // Return Value: // - void StateMachine::_EnterCsiIgnore() noexcept { _state = VTStates::CsiIgnore; _trace.TraceStateChange(L"CsiIgnore"); } // Routine Description: // - Moves the state machine into the CsiIntermediate state. // This state is entered: // 1. When an intermediate character is seen immediately after entering a control sequence (from CsiEntry) // 2. When an intermediate character is seen while collecting parameter data (from CsiParam) // Arguments: // - // Return Value: // - void StateMachine::_EnterCsiIntermediate() noexcept { _state = VTStates::CsiIntermediate; _trace.TraceStateChange(L"CsiIntermediate"); } // Routine Description: // - Moves the state machine into the OscParam state. // This state is entered: // 1. When an OscEntry character (']') is seen after an Escape entry (only from the Escape state) // Arguments: // - // Return Value: // - void StateMachine::_EnterOscParam() noexcept { _state = VTStates::OscParam; _trace.TraceStateChange(L"OscParam"); } // Routine Description: // - Moves the state machine into the OscString state. // This state is entered: // 1. When a delimiter character (';') is seen in the OSC Param state. // Arguments: // - // Return Value: // - void StateMachine::_EnterOscString() noexcept { _state = VTStates::OscString; _trace.TraceStateChange(L"OscString"); } // Routine Description: // - Moves the state machine into the OscTermination state. // This state is entered: // 1. When an ESC is seen in an OSC 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::_EnterOscTermination() noexcept { _state = VTStates::OscTermination; _trace.TraceStateChange(L"OscTermination"); } // Routine Description: // - Moves the state machine into the Ss3Entry state. // This state is entered: // 1. When the Ss3Entry character is seen after an Escape entry (only from the Escape state) // Arguments: // - // Return Value: // - void StateMachine::_EnterSs3Entry() { _state = VTStates::Ss3Entry; _trace.TraceStateChange(L"Ss3Entry"); _ActionClear(); } // Routine Description: // - Moves the state machine into the Ss3Param state. // This state is entered: // 1. When valid parameter characters are detected on entering a SS3 (from Ss3Entry state) // Arguments: // - // Return Value: // - void StateMachine::_EnterSs3Param() noexcept { _state = VTStates::Ss3Param; _trace.TraceStateChange(L"Ss3Param"); } // Routine Description: // - Moves the state machine into the VT52Param state. // This state is entered: // 1. When a VT52 Cursor Address escape is detected, so parameters are expected to follow. // Arguments: // - // Return Value: // - void StateMachine::_EnterVt52Param() noexcept { _state = VTStates::Vt52Param; _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 SosPmApcString state. // This state is entered: // 1. When the Sos character is seen after an Escape entry // 2. When the Pm character is seen after an Escape entry // 3. When the Apc character is seen after an Escape entry // Arguments: // - // Return Value: // - void StateMachine::_EnterSosPmApcString() noexcept { _state = VTStates::SosPmApcString; _trace.TraceStateChange(L"SosPmApcString"); } // Routine Description: // - Processes a character event into an Action that occurs while in the Ground state. // Events in this state will: // 1. Execute C0 control characters // 2. Print all other characters // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventGround(const wchar_t wch) { _trace.TraceOnEvent(L"Ground"); if (_isC0Code(wch) || _isDelete(wch)) { _ActionExecute(wch); } else { _ActionPrint(wch); } } // Routine Description: // - Processes a character event into an Action that occurs while in the Escape state. // Events in this state will: // 1. Execute C0 control characters // 2. Ignore Delete characters // 3. Collect Intermediate characters // 4. Enter Control Sequence state // 5. Dispatch an Escape action. // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventEscape(const wchar_t wch) { _trace.TraceOnEvent(L"Escape"); if (_isC0Code(wch)) { if (_engine->DispatchControlCharsFromEscape()) { _ActionExecuteFromEscape(wch); _EnterGround(); } else { _ActionExecute(wch); } } else if (_isDelete(wch)) { _ActionIgnore(); } else if (_isIntermediate(wch)) { if (_engine->DispatchIntermediatesFromEscape()) { _ActionEscDispatch(wch); _EnterGround(); } else { _ActionCollect(wch); _EnterEscapeIntermediate(); } } else if (_parserMode.test(Mode::Ansi)) { if (_isCsiIndicator(wch)) { _EnterCsiEntry(); } else if (_isOscIndicator(wch)) { _EnterOscParam(); } else if (_isSs3Indicator(wch) && _engine->ParseControlSequenceAfterSs3()) { _EnterSs3Entry(); } else if (_isDcsIndicator(wch)) { _EnterDcsEntry(); } else if (_isSosIndicator(wch) || _isPmIndicator(wch) || _isApcIndicator(wch)) { _EnterSosPmApcString(); } else { _ActionEscDispatch(wch); _EnterGround(); } } else if (_isVt52CursorAddress(wch)) { _EnterVt52Param(); } else { _ActionVt52EscDispatch(wch); _EnterGround(); } } // Routine Description: // - Processes a character event into an Action that occurs while in the EscapeIntermediate state. // Events in this state will: // 1. Execute C0 control characters // 2. Ignore Delete characters // 3. Collect Intermediate characters // 4. Dispatch an Escape action. // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventEscapeIntermediate(const wchar_t wch) { _trace.TraceOnEvent(L"EscapeIntermediate"); if (_isC0Code(wch)) { _ActionExecute(wch); } else if (_isIntermediate(wch)) { _ActionCollect(wch); } else if (_isDelete(wch)) { _ActionIgnore(); } else if (_parserMode.test(Mode::Ansi)) { _ActionEscDispatch(wch); _EnterGround(); } else if (_isVt52CursorAddress(wch)) { _EnterVt52Param(); } else { _ActionVt52EscDispatch(wch); _EnterGround(); } } // Routine Description: // - Processes a character event into an Action that occurs while in the CsiEntry state. // Events in this state will: // 1. Execute C0 control characters // 2. Ignore Delete characters // 3. Collect Intermediate characters // 4. Begin to ignore all remaining parameters when an invalid character is detected (CsiIgnore) // 5. Store parameter data // 6. Collect Control Sequence Private markers // 7. Dispatch a control sequence with parameters for action // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventCsiEntry(const wchar_t wch) { _trace.TraceOnEvent(L"CsiEntry"); if (_isC0Code(wch)) { _ActionExecute(wch); } else if (_isDelete(wch)) { _ActionIgnore(); } else if (_isIntermediate(wch)) { _ActionCollect(wch); _EnterCsiIntermediate(); } else if (_isCsiInvalid(wch)) { _EnterCsiIgnore(); } else if (_isNumericParamValue(wch) || _isParameterDelimiter(wch)) { _ActionParam(wch); _EnterCsiParam(); } else if (_isCsiPrivateMarker(wch)) { _ActionCollect(wch); _EnterCsiParam(); } else { _ActionCsiDispatch(wch); _EnterGround(); } } // Routine Description: // - Processes a character event into an Action that occurs while in the CsiIntermediate state. // Events in this state will: // 1. Execute C0 control characters // 2. Ignore Delete characters // 3. Collect Intermediate characters // 4. Begin to ignore all remaining parameters when an invalid character is detected (CsiIgnore) // 5. Dispatch a control sequence with parameters for action // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventCsiIntermediate(const wchar_t wch) { _trace.TraceOnEvent(L"CsiIntermediate"); if (_isC0Code(wch)) { _ActionExecute(wch); } else if (_isIntermediate(wch)) { _ActionCollect(wch); } else if (_isDelete(wch)) { _ActionIgnore(); } else if (_isIntermediateInvalid(wch)) { _EnterCsiIgnore(); } else { _ActionCsiDispatch(wch); _EnterGround(); } } // Routine Description: // - Processes a character event into an Action that occurs while in the CsiIgnore state. // Events in this state will: // 1. Execute C0 control characters // 2. Ignore Delete characters // 3. Collect Intermediate characters // 4. Begin to ignore all remaining parameters when an invalid character is detected (CsiIgnore) // 5. Return to Ground // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventCsiIgnore(const wchar_t wch) { _trace.TraceOnEvent(L"CsiIgnore"); if (_isC0Code(wch)) { _ActionExecute(wch); } else if (_isDelete(wch)) { _ActionIgnore(); } else if (_isIntermediate(wch)) { _ActionIgnore(); } else if (_isIntermediateInvalid(wch)) { _ActionIgnore(); } else { _EnterGround(); } } // Routine Description: // - Processes a character event into an Action that occurs while in the CsiParam state. // Events in this state will: // 1. Execute C0 control characters // 2. Ignore Delete characters // 3. Collect Intermediate characters // 4. Begin to ignore all remaining parameters when an invalid character is detected (CsiIgnore) // 5. Store parameter data // 6. Dispatch a control sequence with parameters for action // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventCsiParam(const wchar_t wch) { _trace.TraceOnEvent(L"CsiParam"); if (_isC0Code(wch)) { _ActionExecute(wch); } else if (_isDelete(wch)) { _ActionIgnore(); } else if (_isNumericParamValue(wch) || _isParameterDelimiter(wch)) { _ActionParam(wch); } else if (_isIntermediate(wch)) { _ActionCollect(wch); _EnterCsiIntermediate(); } else if (_isParameterInvalid(wch)) { _EnterCsiIgnore(); } else { _ActionCsiDispatch(wch); _EnterGround(); } } // Routine Description: // - Processes a character event into an Action that occurs while in the OscParam state. // Events in this state will: // 1. Collect numeric values into an Osc Param // 2. Move to the OscString state on a delimiter // 3. Ignore everything else. // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventOscParam(const wchar_t wch) noexcept { _trace.TraceOnEvent(L"OscParam"); if (_isOscTerminator(wch)) { _EnterGround(); } else if (_isNumericParamValue(wch)) { _ActionOscParam(wch); } else if (_isOscDelimiter(wch)) { _EnterOscString(); } else { _ActionIgnore(); } } // Routine Description: // - Processes a character event into a Action that occurs while in the OscParam state. // Events in this state will: // 1. Trigger the OSC action associated with the param on an OscTerminator // 2. If we see a ESC, enter the OscTermination state. We'll wait for one // more character before we dispatch the string. // 3. Ignore OscInvalid characters. // 4. Collect everything else into the OscString // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventOscString(const wchar_t wch) { _trace.TraceOnEvent(L"OscString"); if (_isOscTerminator(wch)) { _ActionOscDispatch(wch); _EnterGround(); } else if (_isEscape(wch)) { _EnterOscTermination(); } else if (_isOscInvalid(wch)) { _ActionIgnore(); } else { // add this character to our OSC string _ActionOscPut(wch); } } // Routine Description: // - Handle the two-character termination of a OSC sequence. // Events in this state will: // 1. Trigger the OSC action associated with the param on an OscTerminator // 2. Otherwise treat this as a normal escape character event. // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventOscTermination(const wchar_t wch) { _trace.TraceOnEvent(L"OscTermination"); if (_isStringTerminatorIndicator(wch)) { _ActionOscDispatch(wch); _EnterGround(); } else { _EnterEscape(); _EventEscape(wch); } } // Routine Description: // - Processes a character event into an Action that occurs while in the Ss3Entry state. // Events in this state will: // 1. Execute C0 control characters // 2. Ignore Delete characters // 3. Begin to ignore all remaining parameters when an invalid character is detected (CsiIgnore) // 4. Store parameter data // 5. Dispatch a control sequence with parameters for action // SS3 sequences are structurally the same as CSI sequences, just with a // different initiation. It's safe to reuse CSI's functions for // determining if a character is a parameter, delimiter, or invalid. // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventSs3Entry(const wchar_t wch) { _trace.TraceOnEvent(L"Ss3Entry"); if (_isC0Code(wch)) { _ActionExecute(wch); } else if (_isDelete(wch)) { _ActionIgnore(); } else if (_isCsiInvalid(wch)) { // It's safe for us to go into the CSI ignore here, because both SS3 and // CSI sequences ignore characters the same way. _EnterCsiIgnore(); } else if (_isNumericParamValue(wch) || _isParameterDelimiter(wch)) { _ActionParam(wch); _EnterSs3Param(); } else { _ActionSs3Dispatch(wch); _EnterGround(); } } // Routine Description: // - Processes a character event into an Action that occurs while in the CsiParam state. // Events in this state will: // 1. Execute C0 control characters // 2. Ignore Delete characters // 3. Begin to ignore all remaining parameters when an invalid character is detected (CsiIgnore) // 4. Store parameter data // 5. Dispatch a control sequence with parameters for action // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventSs3Param(const wchar_t wch) { _trace.TraceOnEvent(L"Ss3Param"); if (_isC0Code(wch)) { _ActionExecute(wch); } else if (_isDelete(wch)) { _ActionIgnore(); } else if (_isNumericParamValue(wch) || _isParameterDelimiter(wch)) { _ActionParam(wch); } else if (_isParameterInvalid(wch)) { _EnterCsiIgnore(); } else { _ActionSs3Dispatch(wch); _EnterGround(); } } // Routine Description: // - Processes a character event into an Action that occurs while in the Vt52Param state. // Events in this state will: // 1. Execute C0 control characters // 2. Ignore Delete characters // 3. Store exactly two parameter characters // 4. Dispatch a control sequence with parameters for action (always Direct Cursor Address) // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventVt52Param(const wchar_t wch) { _trace.TraceOnEvent(L"Vt52Param"); if (_isC0Code(wch)) { _ActionExecute(wch); } else if (_isDelete(wch)) { _ActionIgnore(); } else { _parameters.push_back(wch); if (_parameters.size() == 2) { // The command character is processed before the parameter values, // but it will always be 'Y', the Direct Cursor Address command. _ActionVt52EscDispatch(L'Y'); _EnterGround(); } } } // 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. Dispatch the Final character in preparation for parsing the data string // 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 { _ActionDcsDispatch(wch); } } // Routine Description: // - Processes a character event into an Action that occurs while in the DcsIgnore state. // In this state the entire DCS string is considered invalid and we will ignore everything. // The termination state is handled outside when an ESC is seen. // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventDcsIgnore() noexcept { _trace.TraceOnEvent(L"DcsIgnore"); _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. Dispatch the Final character in preparation for parsing the data string // 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 { _ActionDcsDispatch(wch); } } // 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. Dispatch the Final character in preparation for parsing the data string // 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 { _ActionDcsDispatch(wch); } } // Routine Description: // - Processes a character event into an Action that occurs while in the DcsPassThrough state. // Events in this state will: // 1. Pass through if character is valid. // 2. Ignore everything else. // The termination state is handled outside when an ESC is seen. // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventDcsPassThrough(const wchar_t wch) { _trace.TraceOnEvent(L"DcsPassThrough"); if (_isC0Code(wch) || _isDcsPassThroughValid(wch)) { if (!_dcsStringHandler(wch)) { _EnterDcsIgnore(); } } else { _ActionIgnore(); } } // Routine Description: // - Handle SOS/PM/APC string. // In this state the entire string is ignored. // The termination state is handled outside when an ESC is seen. // Arguments: // - wch - Character that triggered the event // Return Value: // - void StateMachine::_EventSosPmApcString(const wchar_t /*wch*/) noexcept { _trace.TraceOnEvent(L"SosPmApcString"); _ActionIgnore(); } // Routine Description: // - Entry to the state machine. Takes characters one by one and processes them according to the state machine rules. // Arguments: // - wch - New character to operate upon // Return Value: // - void StateMachine::ProcessCharacter(const wchar_t wch) { _trace.TraceCharInput(wch); // Process "from anywhere" events first. const bool isFromAnywhereChar = (wch == AsciiChars::CAN || wch == AsciiChars::SUB); // GH#4201 - If this sequence was ^[^X or ^[^Z, then we should // _ActionExecuteFromEscape, as to send a Ctrl+Alt+key key. We should only // do this for the InputStateMachineEngine - the OutputEngine should execute // these from any state. if (isFromAnywhereChar && !(_state == VTStates::Escape && _engine->DispatchControlCharsFromEscape())) { _ActionInterrupt(); _ActionExecute(wch); _EnterGround(); } // Preprocess C1 control characters and treat them as ESC + their 7-bit equivalent. else if (_isC1ControlCharacter(wch)) { // But note that we only do this if C1 control code parsing has been // explicitly requested, since there are some code pages with "unmapped" // code points that get translated as C1 controls when that is not their // intended use. In order to avoid them triggering unintentional escape // sequences, we ignore these characters by default. if (_parserMode.test(Mode::AcceptC1)) { ProcessCharacter(AsciiChars::ESC); ProcessCharacter(_c1To7Bit(wch)); } } // Don't go to escape from the OSC string state - ESC can be used to terminate OSC strings. else if (_isEscape(wch) && _state != VTStates::OscString) { _ActionInterrupt(); _EnterEscape(); } else { // Then pass to the current state as an event switch (_state) { case VTStates::Ground: return _EventGround(wch); case VTStates::Escape: return _EventEscape(wch); case VTStates::EscapeIntermediate: return _EventEscapeIntermediate(wch); case VTStates::CsiEntry: return _EventCsiEntry(wch); case VTStates::CsiIntermediate: return _EventCsiIntermediate(wch); case VTStates::CsiIgnore: return _EventCsiIgnore(wch); case VTStates::CsiParam: return _EventCsiParam(wch); case VTStates::OscParam: return _EventOscParam(wch); case VTStates::OscString: return _EventOscString(wch); case VTStates::OscTermination: return _EventOscTermination(wch); case VTStates::Ss3Entry: return _EventSs3Entry(wch); case VTStates::Ss3Param: return _EventSs3Param(wch); case VTStates::Vt52Param: return _EventVt52Param(wch); case VTStates::DcsEntry: return _EventDcsEntry(wch); case VTStates::DcsIgnore: return _EventDcsIgnore(); case VTStates::DcsIntermediate: return _EventDcsIntermediate(wch); case VTStates::DcsParam: return _EventDcsParam(wch); case VTStates::DcsPassThrough: return _EventDcsPassThrough(wch); case VTStates::SosPmApcString: return _EventSosPmApcString(wch); default: return; } } } // Method Description: // - Pass the current string we're processing through to the engine. It may eat // the string, it may write it straight to the input unmodified, it might // write the string to the tty application. A pointer to this function will // get handed to the OutputStateMachineEngine, so that it can write strings // it doesn't understand to the tty. // This does not modify the state of the state machine. Callers should be in // the Action*Dispatch state, and upon completion, the state's handler (eg // _EventCsiParam) should move us into the ground state. // Arguments: // - // Return Value: // - true if the engine successfully handled the string. bool StateMachine::FlushToTerminal() { bool success{ true }; if (success && _cachedSequence.has_value()) { // Flush the partial sequence to the terminal before we flush the rest of it. // We always want to clear the sequence, even if we failed, so we don't accumulate bad state // and dump it out elsewhere later. success = _engine->ActionPassThroughString(*_cachedSequence); _cachedSequence.reset(); } if (success) { // _pwchCurr is incremented after a call to ProcessCharacter to indicate // that pwchCurr was processed. // However, if we're here, then the processing of pwchChar triggered the // engine to request the entire sequence get passed through, including pwchCurr. success = _engine->ActionPassThroughString(_CurrentRun()); } return success; } // Routine Description: // - Helper for entry to the state machine. Will take an array of characters // and print as many as it can without encountering a character indicating // a escape sequence, then feed characters into the state machine one at a // time until we return to the ground state. // Arguments: // - string - Characters to operate upon // Return Value: // - void StateMachine::ProcessString(const std::wstring_view string) { size_t start = 0; size_t current = start; _currentString = string; _runOffset = 0; _runSize = 0; while (current < string.size()) { // The run will be everything from the start INCLUDING the current one // in case we process the current character and it turns into a passthrough // fallback that picks up this _run inside `FlushToTerminal` above. _runOffset = start; _runSize = current - start + 1; if (_processingIndividually) { // If we're processing characters individually, send it to the state machine. ProcessCharacter(til::at(string, current)); ++current; if (_state == VTStates::Ground) // Then check if we're back at ground. If we are, the next character (pwchCurr) { // is the start of the next run of characters that might be printable. _processingIndividually = false; start = current; } } else { if (_isActionableFromGround(til::at(string, current))) // If the current char is the start of an escape sequence, or should be executed in ground state... { if (_runSize > 0) { // Because the run above is composed INCLUDING current, we must // trim it off here since we just determined it's actionable // and only pass through everything before it. _runSize -= 1; const auto allLeadingUpTo = _CurrentRun(); _engine->ActionPrintString(allLeadingUpTo); // ... print all the chars leading up to it as part of the run... _trace.DispatchPrintRunTrace(allLeadingUpTo); } _processingIndividually = true; // begin processing future characters individually... start = current; continue; } else { ++current; // Otherwise, add this char to the current run to be printed. } } } // When we leave the loop, current has been advanced to the length of the string itself // (or one past the array index to the final char) so this `substr` operation doesn't +1 // to include the final character (unlike the one inside the top of the loop above.) if (start < string.size()) { _runOffset = start; _runSize = std::string::npos; } else { _runSize = 0; } const auto run = _CurrentRun(); // If we're at the end of the string and have remaining un-printed characters, if (!_processingIndividually && !run.empty()) { // print the rest of the characters in the string _engine->ActionPrintString(run); _trace.DispatchPrintRunTrace(run); } else if (_processingIndividually) { // One of the "weird things" in VT input is the case of something like // alt+[. In VT, that's encoded as `\x1b[`. However, that's // also the start of a CSI, and could be the start of a longer sequence, // there's no way to know for sure. For an alt+[ keypress, // the parser originally would just sit in the `CsiEntry` state after // processing it, which would pollute the following keypress (e.g. // alt+[, A would be processed like `\x1b[A`, // which is _wrong_). // // Fortunately, for VT input, each keystroke comes in as an individual // write operation. So, if at the end of processing a string for the // InputEngine, we find that we're not in the Ground state, that implies // that we've processed some input, but not dispatched it yet. This // block at the end of `ProcessString` will then re-process the // undispatched string, but it will ensure that it dispatches on the // last character of the string. For our previous `\x1b[` scenario, that // means we'll make sure to call `_ActionEscDispatch('[')`., which will // properly decode the string as alt+[. if (_engine->FlushAtEndOfString()) { // Reset our state, and put all but the last char in again. ResetState(); // Chars to flush are [pwchSequenceStart, pwchCurr) auto wchIter = run.cbegin(); while (wchIter < run.cend() - 1) { ProcessCharacter(*wchIter); wchIter++; } // Manually execute the last char [pwchCurr] switch (_state) { case VTStates::Ground: _ActionExecute(*wchIter); break; case VTStates::Escape: case VTStates::EscapeIntermediate: _ActionEscDispatch(*wchIter); break; case VTStates::CsiEntry: case VTStates::CsiIntermediate: case VTStates::CsiIgnore: case VTStates::CsiParam: _ActionCsiDispatch(*wchIter); break; case VTStates::OscParam: case VTStates::OscString: case VTStates::OscTermination: _ActionOscDispatch(*wchIter); break; case VTStates::Ss3Entry: case VTStates::Ss3Param: _ActionSs3Dispatch(*wchIter); break; } // microsoft/terminal#2746: Make sure to return to the ground state // after dispatching the characters _EnterGround(); } else { // If the engine doesn't require flushing at the end of the string, we // want to cache the partial sequence in case we have to flush the whole // thing to the terminal later. if (!_cachedSequence) { _cachedSequence.emplace(std::wstring{}); } auto& cachedSequence = *_cachedSequence; cachedSequence.append(run); } } } // Routine Description: // - Wherever the state machine is, whatever it's going, go back to ground. // This is used by conhost to "jiggle the handle" - when VT support is // turned off, we don't want any bad state left over for the next input it's turned on for // Arguments: // - // Return Value: // - void StateMachine::ResetState() noexcept { _EnterGround(); } // Routine Description: // - Takes the given printable character and accumulates it as the new ones digit // into the given size_t. All existing value is moved up by 10. // - For example, if your value had 437 and you put in the printable number 2, // this function will update value to 4372. // - Clamps to 32767 if it gets too big. // Arguments: // - wch - Printable character to accumulate into the value (after conversion to number, of course) // - value - The value to update with the printable character. See example above. // Return Value: // - - But really it's the update to the given value parameter. void StateMachine::_AccumulateTo(const wchar_t wch, size_t& value) noexcept { const size_t digit = wch - L'0'; value = value * 10 + digit; // Values larger than the maximum should be mapped to the largest supported value. if (value > MAX_PARAMETER_VALUE) { value = MAX_PARAMETER_VALUE; } }