diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 99b49a58653..085db8cadf4 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -1399,6 +1399,25 @@ void StateMachine::ProcessString(const wchar_t* const rgwch, const size_t cch) } else if (s_fProcessIndividually) { + // 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 (_pEngine->FlushAtEndOfString()) { // Reset our state, and put all but the last char in again. @@ -1413,25 +1432,31 @@ void StateMachine::ProcessString(const wchar_t* const rgwch, const size_t cch) switch (_state) { case VTStates::Ground: - return _ActionExecute(*pwch); + _ActionExecute(*pwch); + break; case VTStates::Escape: case VTStates::EscapeIntermediate: - return _ActionEscDispatch(*pwch); + _ActionEscDispatch(*pwch); + break; case VTStates::CsiEntry: case VTStates::CsiIntermediate: case VTStates::CsiIgnore: case VTStates::CsiParam: - return _ActionCsiDispatch(*pwch); + _ActionCsiDispatch(*pwch); + break; case VTStates::OscParam: case VTStates::OscString: case VTStates::OscTermination: - return _ActionOscDispatch(*pwch); + _ActionOscDispatch(*pwch); + break; case VTStates::Ss3Entry: case VTStates::Ss3Param: - return _ActionSs3Dispatch(*pwch); - default: - return; + _ActionSs3Dispatch(*pwch); + break; } + // microsoft/terminal#2746: Make sure to return to the ground state + // after dispatching the characters + _EnterGround(); } } } diff --git a/src/terminal/parser/ut_parser/InputEngineTest.cpp b/src/terminal/parser/ut_parser/InputEngineTest.cpp index fcb67d59a1e..27fbd25cb3e 100644 --- a/src/terminal/parser/ut_parser/InputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/InputEngineTest.cpp @@ -230,6 +230,7 @@ class Microsoft::Console::VirtualTerminal::InputEngineTest TEST_METHOD(AltBackspaceTest); TEST_METHOD(AltCtrlDTest); TEST_METHOD(AltIntermediateTest); + TEST_METHOD(AltBackspaceEnterTest); friend class TestInteractDispatch; }; @@ -826,3 +827,54 @@ void InputEngineTest::AltIntermediateTest() Log::Comment(NoThrowString().Format(L"Processing \"\\x05\"")); stateMachine->ProcessString(seq); } + +void InputEngineTest::AltBackspaceEnterTest() +{ + // Created as a test for microsoft/terminal#2746. See that issue for mode + // details. We're going to send an Alt+Backspace to conpty, followed by an + // enter. The enter should be processed as just a single VK_ENTER, not a + // alt+enter. + + TestState testState; + auto pfn = std::bind(&TestState::TestInputCallback, &testState, std::placeholders::_1); + + auto inputEngine = std::make_unique(new TestInteractDispatch(pfn, &testState)); + auto _stateMachine = std::make_unique(inputEngine.release()); + VERIFY_IS_NOT_NULL(_stateMachine); + testState._stateMachine = _stateMachine.get(); + + INPUT_RECORD inputRec; + + inputRec.EventType = KEY_EVENT; + inputRec.Event.KeyEvent.bKeyDown = TRUE; + inputRec.Event.KeyEvent.dwControlKeyState = LEFT_ALT_PRESSED; + inputRec.Event.KeyEvent.wRepeatCount = 1; + inputRec.Event.KeyEvent.wVirtualKeyCode = VK_BACK; + inputRec.Event.KeyEvent.wVirtualScanCode = static_cast(MapVirtualKeyW(VK_BACK, MAPVK_VK_TO_VSC)); + inputRec.Event.KeyEvent.uChar.UnicodeChar = L'\x08'; + + // First, expect a alt+backspace. + testState.vExpectedInput.push_back(inputRec); + + std::wstring seq = L"\x1b\x7f"; + Log::Comment(NoThrowString().Format(L"Processing \"\\x1b\\x7f\"")); + _stateMachine->ProcessString(seq); + + // Ensure the state machine has correctly returned to the ground state + VERIFY_ARE_EQUAL(StateMachine::VTStates::Ground, _stateMachine->_state); + + inputRec.Event.KeyEvent.wVirtualKeyCode = VK_RETURN; + inputRec.Event.KeyEvent.dwControlKeyState = 0; + inputRec.Event.KeyEvent.wVirtualScanCode = static_cast(MapVirtualKeyW(VK_RETURN, MAPVK_VK_TO_VSC)); + inputRec.Event.KeyEvent.uChar.UnicodeChar = L'\x0d'; //maybe \xa + + // Then, expect a enter + testState.vExpectedInput.push_back(inputRec); + + seq = L"\x0d"; + Log::Comment(NoThrowString().Format(L"Processing \"\\x0d\"")); + _stateMachine->ProcessString(seq); + + // Ensure the state machine has correctly returned to the ground state + VERIFY_ARE_EQUAL(StateMachine::VTStates::Ground, _stateMachine->_state); +} diff --git a/src/terminal/parser/ut_parser/Parser.UnitTests-common.vcxproj b/src/terminal/parser/ut_parser/Parser.UnitTests-common.vcxproj deleted file mode 100644 index 24754a9dd5b..00000000000 --- a/src/terminal/parser/ut_parser/Parser.UnitTests-common.vcxproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - Create - - - - - - - - - - ..;%(AdditionalIncludeDirectories) - - - diff --git a/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj b/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj index 57daef497c5..dd7acda4668 100644 --- a/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj +++ b/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj @@ -2,14 +2,27 @@ - + + + + + + + Create + + + + ..;%(AdditionalIncludeDirectories) + + + {18d09a24-8240-42d6-8cb6-236eee820263}