diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 6acfcf21708..c1caa85f5db 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -38,6 +38,7 @@ ansicpg ANSISYS ANSISYSRC ANSISYSSC +answerback antialiasing ANull anycpu diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index c7856418cca..48d4e277190 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -118,7 +118,7 @@ class ScreenBufferTests TEST_METHOD(EraseAllTests); - TEST_METHOD(OutputNULTest); + TEST_METHOD(InactiveControlCharactersTest); TEST_METHOD(VtResize); TEST_METHOD(VtResizeComprehensive); @@ -1014,8 +1014,19 @@ void ScreenBufferTests::EraseAllTests() viewport.BottomInclusive())); } -void ScreenBufferTests::OutputNULTest() +void ScreenBufferTests::InactiveControlCharactersTest() { + // These are the control characters that don't write anything to the + // output buffer, and are expected not to move the cursor position. + + BEGIN_TEST_METHOD_PROPERTIES() + TEST_METHOD_PROPERTY(L"Data:ordinal", L"{0, 1, 2, 3, 4, 5, 6, 7, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 28, 29, 30, 31}") + END_TEST_METHOD_PROPERTIES() + + unsigned ordinal; + VERIFY_SUCCEEDED(TestData::TryGetValue(L"ordinal", ordinal)); + const auto ch = static_cast(ordinal); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto& si = gci.GetActiveOutputBuffer(); auto& stateMachine = si.GetStateMachine(); @@ -1024,21 +1035,20 @@ void ScreenBufferTests::OutputNULTest() VERIFY_ARE_EQUAL(0, cursor.GetPosition().x); VERIFY_ARE_EQUAL(0, cursor.GetPosition().y); - Log::Comment(NoThrowString().Format( - L"Writing a single NUL")); - stateMachine.ProcessString({ L"\0", 1 }); + Log::Comment(L"Writing a single control character"); + const auto singleChar = std::wstring(1, ch); + stateMachine.ProcessString(singleChar); VERIFY_ARE_EQUAL(0, cursor.GetPosition().x); VERIFY_ARE_EQUAL(0, cursor.GetPosition().y); - Log::Comment(NoThrowString().Format( - L"Writing many NULs")); - stateMachine.ProcessString({ L"\0\0\0\0\0\0\0\0", 8 }); + Log::Comment(L"Writing many control characters"); + const auto manyChars = std::wstring(8, ch); + stateMachine.ProcessString(manyChars); VERIFY_ARE_EQUAL(0, cursor.GetPosition().x); VERIFY_ARE_EQUAL(0, cursor.GetPosition().y); - Log::Comment(NoThrowString().Format( - L"Testing a single NUL followed by real text")); - stateMachine.ProcessString({ L"\0foo", 4 }); + Log::Comment(L"Testing a single control character followed by real text"); + stateMachine.ProcessString(singleChar + L"foo"); VERIFY_ARE_EQUAL(3, cursor.GetPosition().x); VERIFY_ARE_EQUAL(0, cursor.GetPosition().y); @@ -1046,9 +1056,8 @@ void ScreenBufferTests::OutputNULTest() VERIFY_ARE_EQUAL(0, cursor.GetPosition().x); VERIFY_ARE_EQUAL(1, cursor.GetPosition().y); - Log::Comment(NoThrowString().Format( - L"Writing NULs in between other strings")); - stateMachine.ProcessString({ L"\0foo\0bar\0", 9 }); + Log::Comment(L"Writing controls in between other strings"); + stateMachine.ProcessString(singleChar + L"foo" + singleChar + L"bar" + singleChar); VERIFY_ARE_EQUAL(6, cursor.GetPosition().x); VERIFY_ARE_EQUAL(1, cursor.GetPosition().y); } diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 3f191e34198..56eb7b0f091 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -44,10 +44,10 @@ bool OutputStateMachineEngine::ActionExecute(const wchar_t wch) { switch (wch) { - case AsciiChars::NUL: - // microsoft/terminal#1825 - VT applications expect to be able to write NUL - // and have _nothing_ happen. Filter the NULs here, so they don't fill the - // buffer with empty spaces. + case AsciiChars::ENQ: + // GH#11946: At some point we may want to add support for the VT + // answerback feature, which requires responding to an ENQ control + // with a user-defined reply, but until then we just ignore it. break; case AsciiChars::BEL: _dispatch->WarningBell(); @@ -79,9 +79,24 @@ bool OutputStateMachineEngine::ActionExecute(const wchar_t wch) case AsciiChars::SO: _dispatch->LockingShift(1); break; - default: + case AsciiChars::SUB: + // The SUB control is used to cancel a control sequence in the same + // way as CAN, but unlike CAN it also displays an error character, + // typically a reverse question mark. + _dispatch->Print(L'\u2E2E'); + break; + case AsciiChars::DEL: + // The DEL control can sometimes be translated into a printable glyph + // if a 96-character set is designated, so we need to pass it through + // to the Print method. If not translated, it will be filtered out + // there. _dispatch->Print(wch); break; + default: + // GH#1825, GH#10786: VT applications expect to be able to write other + // control characters and have _nothing_ happen. We filter out these + // characters here, so they don't fill the buffer. + break; } _ClearLastChar();