mirror of
https://github.com/microsoft/terminal.git
synced 2026-09-25 16:34:42 +00:00
Add initial support for VT DCS sequences (#6328)
As the title suggests, this commit adds initial support for the VT DCS sequences. The parameters are parsed but not yet used. The pass through data is yet to be handled. This effectively fixes #120 by making Sixel graphics sequences *ignored* instead of printed. * https://vt100.net/docs/vt510-rm/chapter4.html * https://vt100.net/emu/dec_ansi_parser Tests added. References #448 Closes #120
This commit is contained in:
@@ -99401,6 +99401,8 @@ DCP
|
||||
DCPR
|
||||
DCPSK
|
||||
DCS
|
||||
Dcs
|
||||
dcs
|
||||
DCT
|
||||
DCTN
|
||||
DCTS
|
||||
|
||||
@@ -186,6 +186,12 @@ bool OutputStateMachineEngine::ActionPassThroughString(const std::wstring_view s
|
||||
bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch,
|
||||
const gsl::span<const wchar_t> 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.
|
||||
|
||||
@@ -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.
|
||||
@@ -141,31 +167,6 @@ static constexpr bool _isCsiIndicator(const wchar_t wch) noexcept
|
||||
return wch == L'['; // 0x5B
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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
|
||||
{
|
||||
return wch == L';'; // 0x3B
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Determines if a character is a valid parameter value
|
||||
// Parameters must be numerical digits.
|
||||
// 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
|
||||
{
|
||||
return wch >= L'0' && wch <= L'9'; // 0x30 - 0x39
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Determines if a character is a private range marker for a control sequence.
|
||||
// Private range markers indicate vendor-specific behavior.
|
||||
@@ -189,6 +190,52 @@ 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.
|
||||
// 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:
|
||||
// - Determines if a character is a "Single Shift Select" indicator.
|
||||
// This immediately follows an escape and signifies a varying length control string.
|
||||
@@ -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:
|
||||
// - <none>
|
||||
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:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
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:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
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:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
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:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
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:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
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:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
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:
|
||||
// - <none>
|
||||
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:
|
||||
// - <none>
|
||||
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:
|
||||
// - <none>
|
||||
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:
|
||||
// - <none>
|
||||
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:
|
||||
// - <none>
|
||||
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:
|
||||
// - <none>
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<DummyDispatch>();
|
||||
@@ -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<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(AsciiChars::ESC);
|
||||
mach.ProcessCharacter(L'\\');
|
||||
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestC1DcsEntry)
|
||||
{
|
||||
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'\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<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' ');
|
||||
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<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':');
|
||||
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<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';');
|
||||
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<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' ');
|
||||
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<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(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
|
||||
|
||||
Reference in New Issue
Block a user