Preprocess and convert C1 controls to their 7 bit equivalent (#7340)
C1 control characters are now first converted to their 7 bit equivalent. This allows us to unify the logic of C1 and C0 escape handling. This also adds support for SOS/PM/APC string. * Unify the logic for C1 and C0 escape handling by converting C1 to C0 beforehand. This adds support for various C1 characters, including IND(8/4), NEL(8/5), HTS(8/8), RI(8/13), SS2(8/14), SS3(8/15), OSC(9/13), etc. * Add support for SOS/PM/APC escape sequences. Fixes #7032 * Use "Variable Length String" logic to unify the string termination handling of OSC, DCS and SOS/PM/APC. This fixes an issue where OSC action is successfully dispatched even when terminated with non-ST character. Introduced by #6328, the DCS PassThrough is spared from this issue. This PR puts them together and add test cases for them. References: https://vt100.net/docs/vt510-rm/chapter4.html https://vt100.net/emu/dec_ansi_parser Closes #7032 Closes #7317
This commit is contained in:
parent
863e3e5c22
commit
f91b53d5fd
|
@ -20937,6 +20937,8 @@ apay
|
|||
Apayao
|
||||
APB
|
||||
APC
|
||||
Apc
|
||||
apc
|
||||
APDA
|
||||
APDU
|
||||
APE
|
||||
|
@ -383889,6 +383891,7 @@ sorus
|
|||
sorva
|
||||
sory
|
||||
SOS
|
||||
Sos
|
||||
sos
|
||||
Sosanna
|
||||
so-seeming
|
||||
|
|
|
@ -68,14 +68,15 @@ static constexpr bool _isC0Code(const wchar_t wch) noexcept
|
|||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Determines if a character is a C1 CSI (Control Sequence Introducer)
|
||||
// This is a single-character way to start a control sequence, as opposed to "ESC[".
|
||||
// - 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 \x9b in a
|
||||
// single-byte stream represents a C1 CSI or some other glyph, because by the time we
|
||||
// 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
|
||||
|
@ -86,24 +87,21 @@ static constexpr bool _isC0Code(const wchar_t wch) noexcept
|
|||
// - wch - Character to check.
|
||||
// Return Value:
|
||||
// - True if it is. False if it isn't.
|
||||
static constexpr bool _isC1Csi(const wchar_t wch) noexcept
|
||||
static constexpr bool _isC1ControlCharacter(const wchar_t wch) noexcept
|
||||
{
|
||||
return wch == L'\x9b';
|
||||
return (wch >= L'\x80' && wch <= L'\x9F');
|
||||
}
|
||||
|
||||
// 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.
|
||||
// - Convert a C1 control characters to their 7-bit equivalent.
|
||||
//
|
||||
// Arguments:
|
||||
// - wch - Character to check.
|
||||
// - wch - Character to convert.
|
||||
// Return Value:
|
||||
// - True if it is. False if it isn't.
|
||||
static constexpr bool _isC1Dcs(const wchar_t wch) noexcept
|
||||
// - The 7-bit equivalent of the 8-bit control characters.
|
||||
static constexpr wchar_t _c1To7Bit(const wchar_t wch) noexcept
|
||||
{
|
||||
return wch == L'\x90';
|
||||
return wch - L'\x40';
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
@ -144,7 +142,7 @@ static constexpr bool _isEscape(const wchar_t wch) noexcept
|
|||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Determines if a character is a delimiter between two parameters in a "control sequence".
|
||||
// - Determines if a character is a delimiter between two parameters in an escape sequence.
|
||||
// Arguments:
|
||||
// - wch - Character to check.
|
||||
// Return Value:
|
||||
|
@ -213,17 +211,6 @@ static constexpr bool _isParameterInvalid(const wchar_t wch) noexcept
|
|||
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:
|
||||
|
@ -287,17 +274,6 @@ static constexpr bool _isOscDelimiter(const wchar_t wch) noexcept
|
|||
return wch == L';'; // 0x3B
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Determines if a character should be initiate the end of an OSC sequence.
|
||||
// Arguments:
|
||||
// - wch - Character to check.
|
||||
// Return Value:
|
||||
// - True if it is. False if it isn't.
|
||||
static constexpr bool _isOscTerminationInitiator(const wchar_t wch) noexcept
|
||||
{
|
||||
return wch == AsciiChars::ESC;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Determines if a character should be ignored in a operating system control sequence
|
||||
// Arguments:
|
||||
|
@ -320,7 +296,7 @@ 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 == AsciiChars::BEL || _isStringTerminator(wch); // Bell character or C1 terminator
|
||||
return wch == AsciiChars::BEL; // Bell character
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
@ -335,17 +311,6 @@ 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:
|
||||
|
@ -358,6 +323,42 @@ static constexpr bool _isDcsPassThroughValid(const wchar_t wch) noexcept
|
|||
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.
|
||||
|
@ -367,7 +368,7 @@ static constexpr bool _isDcsPassThroughValid(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) || _isC1Dcs(wch) || _isDelete(wch);
|
||||
return (wch <= AsciiChars::US) || _isC1ControlCharacter(wch) || _isDelete(wch);
|
||||
}
|
||||
|
||||
#pragma warning(pop)
|
||||
|
@ -926,12 +927,42 @@ void StateMachine::_EnterDcsTermination() noexcept
|
|||
_trace.TraceStateChange(L"DcsTermination");
|
||||
}
|
||||
|
||||
// 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:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_EnterSosPmApcString() noexcept
|
||||
{
|
||||
_state = VTStates::SosPmApcString;
|
||||
_trace.TraceStateChange(L"SosPmApcString");
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Moves the state machine into the SosPmApcStringTermination state.
|
||||
// This state is entered:
|
||||
// 1. When an ESC is seen in a SOS/PM/APC string. This escape will be followed by a
|
||||
// '\', as to encode a 0x9C as a 7-bit ASCII char stream.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_EnterSosPmApcTermination() noexcept
|
||||
{
|
||||
_state = VTStates::SosPmApcTermination;
|
||||
_trace.TraceStateChange(L"SosPmApcStringTermination");
|
||||
}
|
||||
|
||||
// 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. Handle a C1 Control Sequence Introducer
|
||||
// 3. Print all other characters
|
||||
// 2. Print all other characters
|
||||
// Arguments:
|
||||
// - wch - Character that triggered the event
|
||||
// Return Value:
|
||||
|
@ -943,14 +974,6 @@ void StateMachine::_EventGround(const wchar_t wch)
|
|||
{
|
||||
_ActionExecute(wch);
|
||||
}
|
||||
else if (_isC1Csi(wch) && _isInAnsiMode)
|
||||
{
|
||||
_EnterCsiEntry();
|
||||
}
|
||||
else if (_isC1Dcs(wch) && _isInAnsiMode)
|
||||
{
|
||||
_EnterDcsEntry();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ActionPrint(wch);
|
||||
|
@ -1019,6 +1042,10 @@ void StateMachine::_EventEscape(const wchar_t wch)
|
|||
{
|
||||
_EnterDcsEntry();
|
||||
}
|
||||
else if (_isSosIndicator(wch) || _isPmIndicator(wch) || _isApcIndicator(wch))
|
||||
{
|
||||
_EnterSosPmApcString();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ActionEscDispatch(wch);
|
||||
|
@ -1299,7 +1326,7 @@ void StateMachine::_EventOscString(const wchar_t wch)
|
|||
_ActionOscDispatch(wch);
|
||||
_EnterGround();
|
||||
}
|
||||
else if (_isOscTerminationInitiator(wch))
|
||||
else if (_isEscape(wch))
|
||||
{
|
||||
_EnterOscTermination();
|
||||
}
|
||||
|
@ -1322,13 +1349,6 @@ void StateMachine::_EventOscString(const wchar_t wch)
|
|||
// - wch - Character that triggered the event
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_EventOscTermination(const wchar_t wch)
|
||||
{
|
||||
_trace.TraceOnEvent(L"OscTermination");
|
||||
|
||||
_ActionOscDispatch(wch);
|
||||
_EnterGround();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Processes a character event into an Action that occurs while in the Ss3Entry state.
|
||||
|
@ -1497,24 +1517,16 @@ void StateMachine::_EventDcsEntry(const wchar_t wch)
|
|||
|
||||
// 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.
|
||||
// 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:
|
||||
// - <none>
|
||||
void StateMachine::_EventDcsIgnore(const wchar_t wch) noexcept
|
||||
void StateMachine::_EventDcsIgnore() noexcept
|
||||
{
|
||||
_trace.TraceOnEvent(L"DcsIgnore");
|
||||
if (_isStringTerminator(wch))
|
||||
{
|
||||
_EnterGround();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ActionIgnore();
|
||||
}
|
||||
_ActionIgnore();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
@ -1603,10 +1615,9 @@ void StateMachine::_EventDcsParam(const wchar_t wch)
|
|||
// 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.
|
||||
// 1. Pass through if character is valid.
|
||||
// 2. If we see a ESC, enter the DcsTermination state.
|
||||
// 3. Ignore everything else.
|
||||
// Arguments:
|
||||
// - wch - Character that triggered the event
|
||||
// Return Value:
|
||||
|
@ -1614,16 +1625,11 @@ void StateMachine::_EventDcsParam(const wchar_t wch)
|
|||
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))
|
||||
else if (_isEscape(wch))
|
||||
{
|
||||
_EnterDcsTermination();
|
||||
}
|
||||
|
@ -1634,21 +1640,52 @@ void StateMachine::_EventDcsPassThrough(const wchar_t wch)
|
|||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Handle the two-character termination of a DCS sequence.
|
||||
// - Handle SOS/PM/APC string.
|
||||
// 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
|
||||
// 1. If we see a ESC, enter the SosPmApcTermination state.
|
||||
// 2. Ignore everything else.
|
||||
// Arguments:
|
||||
// - wch - Character that triggered the event
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_EventDcsTermination(const wchar_t wch)
|
||||
void StateMachine::_EventSosPmApcString(const wchar_t wch) noexcept
|
||||
{
|
||||
_trace.TraceOnEvent(L"DcsTermination");
|
||||
_trace.TraceOnEvent(L"SosPmApcString");
|
||||
if (_isEscape(wch))
|
||||
{
|
||||
_EnterSosPmApcTermination();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ActionIgnore();
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Handle "Variable Length String" termination.
|
||||
// Events in this state will:
|
||||
// 1. Trigger the corresponding action and enter ground if we see a string terminator,
|
||||
// 2. Otherwise treat this as a normal escape character event.
|
||||
// Arguments:
|
||||
// - wch - Character that triggered the event
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_EventVariableLengthStringTermination(const wchar_t wch)
|
||||
{
|
||||
if (_isStringTerminatorIndicator(wch))
|
||||
{
|
||||
// TODO: The Dcs sequence has successfully terminated. This is where we'd be dispatching the DCS command.
|
||||
if (_state == VTStates::OscTermination)
|
||||
{
|
||||
_ActionOscDispatch(wch);
|
||||
}
|
||||
else if (_state == VTStates::DcsTermination)
|
||||
{
|
||||
// TODO:GH#7316: The Dcs sequence has successfully terminated. This is where we'd be dispatching the DCS command.
|
||||
}
|
||||
else if (_state == VTStates::SosPmApcTermination)
|
||||
{
|
||||
// We don't support any SOS/PM/APC control string yet.
|
||||
}
|
||||
_EnterGround();
|
||||
}
|
||||
else
|
||||
|
@ -1680,12 +1717,40 @@ void StateMachine::ProcessCharacter(const wchar_t wch)
|
|||
_ActionExecute(wch);
|
||||
_EnterGround();
|
||||
}
|
||||
else if (_isEscape(wch) && _state != VTStates::OscString && _state != VTStates::DcsPassThrough)
|
||||
// Preprocess C1 control characters and treat them as ESC + their 7-bit equivalent.
|
||||
else if (_isC1ControlCharacter(wch))
|
||||
{
|
||||
// When we are in "Variable Length String" state, a C1 control character
|
||||
// should effectively acts as an ESC and move us into the corresponding
|
||||
// termination state.
|
||||
if (_IsVariableLengthStringState())
|
||||
{
|
||||
if (_state == VTStates::OscString)
|
||||
{
|
||||
_EnterOscTermination();
|
||||
}
|
||||
else if (_state == VTStates::DcsPassThrough)
|
||||
{
|
||||
_EnterDcsTermination();
|
||||
}
|
||||
else if (_state == VTStates::SosPmApcString)
|
||||
{
|
||||
_EnterSosPmApcTermination();
|
||||
}
|
||||
|
||||
_EventVariableLengthStringTermination(_c1To7Bit(wch));
|
||||
}
|
||||
// Enter Escape state and pass the converted 7-bit character.
|
||||
else
|
||||
{
|
||||
_EnterEscape();
|
||||
_EventEscape(_c1To7Bit(wch));
|
||||
}
|
||||
}
|
||||
// Don't go to escape from the "Variable Length String" state - ESC (and C1 String Terminator)
|
||||
// can be used to terminate variable length control string.
|
||||
else if (_isEscape(wch) && !_IsVariableLengthStringState())
|
||||
{
|
||||
// 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
|
||||
|
@ -1712,7 +1777,7 @@ void StateMachine::ProcessCharacter(const wchar_t wch)
|
|||
case VTStates::OscString:
|
||||
return _EventOscString(wch);
|
||||
case VTStates::OscTermination:
|
||||
return _EventOscTermination(wch);
|
||||
return _EventVariableLengthStringTermination(wch);
|
||||
case VTStates::Ss3Entry:
|
||||
return _EventSs3Entry(wch);
|
||||
case VTStates::Ss3Param:
|
||||
|
@ -1722,7 +1787,7 @@ void StateMachine::ProcessCharacter(const wchar_t wch)
|
|||
case VTStates::DcsEntry:
|
||||
return _EventDcsEntry(wch);
|
||||
case VTStates::DcsIgnore:
|
||||
return _EventDcsIgnore(wch);
|
||||
return _EventDcsIgnore();
|
||||
case VTStates::DcsIntermediate:
|
||||
return _EventDcsIntermediate(wch);
|
||||
case VTStates::DcsParam:
|
||||
|
@ -1730,7 +1795,11 @@ void StateMachine::ProcessCharacter(const wchar_t wch)
|
|||
case VTStates::DcsPassThrough:
|
||||
return _EventDcsPassThrough(wch);
|
||||
case VTStates::DcsTermination:
|
||||
return _EventDcsTermination(wch);
|
||||
return _EventVariableLengthStringTermination(wch);
|
||||
case VTStates::SosPmApcString:
|
||||
return _EventSosPmApcString(wch);
|
||||
case VTStates::SosPmApcTermination:
|
||||
return _EventVariableLengthStringTermination(wch);
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
@ -1952,3 +2021,16 @@ void StateMachine::_AccumulateTo(const wchar_t wch, size_t& value) noexcept
|
|||
value = MAX_PARAMETER_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Determines if the engine is in "Variable Length String" state, which is a combination
|
||||
// of all states that are expecting a string that has a undetermined length.
|
||||
//
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - True if it is. False if it isn't.
|
||||
const bool StateMachine::_IsVariableLengthStringState() const noexcept
|
||||
{
|
||||
return _state == VTStates::OscString || _state == VTStates::DcsPassThrough || _state == VTStates::SosPmApcString;
|
||||
}
|
||||
|
|
|
@ -86,6 +86,8 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
void _EnterDcsIntermediate() noexcept;
|
||||
void _EnterDcsPassThrough() noexcept;
|
||||
void _EnterDcsTermination() noexcept;
|
||||
void _EnterSosPmApcString() noexcept;
|
||||
void _EnterSosPmApcTermination() noexcept;
|
||||
|
||||
void _EventGround(const wchar_t wch);
|
||||
void _EventEscape(const wchar_t wch);
|
||||
|
@ -96,18 +98,19 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
void _EventCsiParam(const wchar_t wch);
|
||||
void _EventOscParam(const wchar_t wch) noexcept;
|
||||
void _EventOscString(const wchar_t wch);
|
||||
void _EventOscTermination(const wchar_t wch);
|
||||
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 _EventDcsIgnore() 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 _EventSosPmApcString(const wchar_t wch) noexcept;
|
||||
void _EventVariableLengthStringTermination(const wchar_t wch);
|
||||
|
||||
void _AccumulateTo(const wchar_t wch, size_t& value) noexcept;
|
||||
const bool _IsVariableLengthStringState() const noexcept;
|
||||
|
||||
enum class VTStates
|
||||
{
|
||||
|
@ -129,7 +132,9 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
DcsIntermediate,
|
||||
DcsParam,
|
||||
DcsPassThrough,
|
||||
DcsTermination
|
||||
DcsTermination,
|
||||
SosPmApcString,
|
||||
SosPmApcTermination
|
||||
};
|
||||
|
||||
Microsoft::Console::VirtualTerminal::ParserTracing _trace;
|
||||
|
|
|
@ -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,12,13,14,15,16,17}") // 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,18,19}") // one value for each type of state test below.
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
size_t uiTest;
|
||||
|
@ -66,7 +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.
|
||||
// Same for DcsPassThrough and SosPmApcString state.
|
||||
bool shouldEscapeOut = true;
|
||||
|
||||
switch (uiTest)
|
||||
|
@ -181,6 +181,19 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final
|
|||
mach._state = StateMachine::VTStates::DcsTermination;
|
||||
break;
|
||||
}
|
||||
case 18:
|
||||
{
|
||||
Log::Comment(L"Escape from SosPmApcString");
|
||||
shouldEscapeOut = false;
|
||||
mach._state = StateMachine::VTStates::SosPmApcString;
|
||||
break;
|
||||
}
|
||||
case 19:
|
||||
{
|
||||
Log::Comment(L"Escape from SosPmApcTermination");
|
||||
mach._state = StateMachine::VTStates::SosPmApcTermination;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
|
@ -405,6 +418,19 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final
|
|||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestC1Osc)
|
||||
{
|
||||
auto dispatch = std::make_unique<DummyDispatch>();
|
||||
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
||||
StateMachine mach(std::move(engine));
|
||||
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
mach.ProcessCharacter(L'\x9d');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
||||
mach.ProcessCharacter(AsciiChars::BEL);
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestOscStringSimple)
|
||||
{
|
||||
auto dispatch = std::make_unique<DummyDispatch>();
|
||||
|
@ -596,6 +622,35 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final
|
|||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestOscStringInvalidTermination)
|
||||
{
|
||||
auto dispatch = std::make_unique<DummyDispatch>();
|
||||
auto engine = std::make_unique<OutputStateMachineEngine>(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']');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
||||
mach.ProcessCharacter(L'1');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
||||
mach.ProcessCharacter(L';');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
||||
mach.ProcessCharacter(L's');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscTermination);
|
||||
mach.ProcessCharacter(L'['); // This is not a string terminator.
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
|
||||
mach.ProcessCharacter(L'4');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
||||
mach.ProcessCharacter(L';');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
||||
mach.ProcessCharacter(L'm');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestDcsEntry)
|
||||
{
|
||||
auto dispatch = std::make_unique<DummyDispatch>();
|
||||
|
@ -752,6 +807,143 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final
|
|||
mach.ProcessCharacter(L'\\');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestDcsInvalidTermination)
|
||||
{
|
||||
auto dispatch = std::make_unique<DummyDispatch>();
|
||||
auto engine = std::make_unique<OutputStateMachineEngine>(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(AsciiChars::ESC);
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsTermination);
|
||||
mach.ProcessCharacter(L'['); // This is not a string terminator.
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
|
||||
mach.ProcessCharacter(L'4');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
||||
mach.ProcessCharacter(L';');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
||||
mach.ProcessCharacter(L'm');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestSosPmApcString)
|
||||
{
|
||||
auto dispatch = std::make_unique<DummyDispatch>();
|
||||
auto engine = std::make_unique<OutputStateMachineEngine>(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'X');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
||||
mach.ProcessCharacter(L'1');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
||||
mach.ProcessCharacter(L'2');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcTermination);
|
||||
mach.ProcessCharacter(L'\\');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
||||
mach.ProcessCharacter(L'^');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
||||
mach.ProcessCharacter(L'3');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
||||
mach.ProcessCharacter(L'4');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcTermination);
|
||||
mach.ProcessCharacter(L'\\');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
||||
mach.ProcessCharacter(L'_');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
||||
mach.ProcessCharacter(L'5');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
||||
mach.ProcessCharacter(L'6');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcTermination);
|
||||
mach.ProcessCharacter(L'\\');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestC1StringTerminator)
|
||||
{
|
||||
auto dispatch = std::make_unique<DummyDispatch>();
|
||||
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
||||
StateMachine mach(std::move(engine));
|
||||
|
||||
// C1 ST should terminate OSC string.
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
||||
mach.ProcessCharacter(L']');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
||||
mach.ProcessCharacter(L'1');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
||||
mach.ProcessCharacter(L';');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
||||
mach.ProcessCharacter(L's');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
||||
mach.ProcessCharacter(L'\x9c');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
|
||||
// C1 ST should terminate DCS passthrough string.
|
||||
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');
|
||||
mach.ProcessCharacter(L'\x9c');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
|
||||
// C1 ST should terminate SOS/PM/APC string.
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
||||
mach.ProcessCharacter(L'X');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
||||
mach.ProcessCharacter(L'1');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
||||
mach.ProcessCharacter(L'\x9c');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
||||
mach.ProcessCharacter(L'^');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
||||
mach.ProcessCharacter(L'2');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
||||
mach.ProcessCharacter(L'\x9c');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
||||
mach.ProcessCharacter(L'_');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
||||
mach.ProcessCharacter(L'3');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
||||
mach.ProcessCharacter(L'\x9c');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
}
|
||||
};
|
||||
|
||||
class StatefulDispatch final : public TermDispatch
|
||||
|
|
Loading…
Reference in a new issue