Implement Hard Reset for Terminal (#4909)

## Summary of the Pull Request

This _actually_ implements `\033c`
([RIS](https://vt100.net/docs/vt220-rm/chapter4.html)) for the Windows Terminal.
I thought I had done this in #4433, but that PR actually only passthrough'd
`\x1b[3J`. I didn't realize at the time that #2715 was mostly about hard reset,
not erase scrollback.

Not only should conpty pass through RIS, but the Terminal should also be
prepared to actually handle that sequence. So this PR adds that support as well.

## References

* #4433: original PR I thought fixed this.

## PR Checklist
* [x] Closes #2715 for real this time
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Validation Steps Performed

Actually tested `printf \033c` in the Terminal this time
This commit is contained in:
Mike Griese 2020-03-16 10:32:01 -05:00 committed by GitHub
parent f1d3136a24
commit 3dc0672faa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 187 additions and 4 deletions

View file

@ -401,3 +401,101 @@ bool TerminalDispatch::_PrivateModeParamsHelper(const DispatchTypes::PrivateMode
}
return success;
}
bool TerminalDispatch::SoftReset() noexcept
{
// TODO:GH#1883 much of this method is not yet implemented in the Terminal,
// because the Terminal _doesn't need to_ yet. The terminal is only ever
// connected to conpty, so it doesn't implement most of these things that
// Hard/Soft Reset would reset. As those things are implemented, they should
// also get cleared here.
//
// This code is left here (from its original form in conhost) as a reminder
// of what needs to be done.
bool success = CursorVisibility(true); // Cursor enabled.
// if (success)
// {
// success = SetOriginMode(false); // Absolute cursor addressing.
// }
// if (success)
// {
// success = SetAutoWrapMode(true); // Wrap at end of line.
// }
if (success)
{
success = SetCursorKeysMode(false); // Normal characters.
}
if (success)
{
success = SetKeypadMode(false); // Numeric characters.
}
// if (success)
// {
// // Top margin = 1; bottom margin = page length.
// success = _DoSetTopBottomScrollingMargins(0, 0);
// }
// if (success)
// {
// success = DesignateCharset(DispatchTypes::VTCharacterSets::USASCII); // Default Charset
// }
if (success)
{
const auto opt = DispatchTypes::GraphicsOptions::Off;
success = SetGraphicsRendition({ &opt, 1 }); // Normal rendition.
}
// if (success)
// {
// // Reset the saved cursor state.
// // Note that XTerm only resets the main buffer state, but that
// // seems likely to be a bug. Most other terminals reset both.
// _savedCursorState.at(0) = {}; // Main buffer
// _savedCursorState.at(1) = {}; // Alt buffer
// }
return success;
}
bool TerminalDispatch::HardReset() noexcept
{
// TODO:GH#1883 much of this method is not yet implemented in the Terminal,
// because the Terminal _doesn't need to_ yet. The terminal is only ever
// connected to conpty, so it doesn't implement most of these things that
// Hard/Soft Reset would reset. As those things ar implemented, they should
// also get cleared here.
//
// This code is left here (from its original form in conhost) as a reminder
// of what needs to be done.
// Sets the SGR state to normal - this must be done before EraseInDisplay
// to ensure that it clears with the default background color.
bool success = SoftReset();
// Clears the screen - Needs to be done in two operations.
if (success)
{
success = EraseInDisplay(DispatchTypes::EraseType::All);
}
if (success)
{
success = EraseInDisplay(DispatchTypes::EraseType::Scrollback);
}
// // Set the DECSCNM screen mode back to normal.
// if (success)
// {
// success = SetScreenMode(false);
// }
// Cursor to 1,1 - the Soft Reset guarantees this is absolute
if (success)
{
success = CursorPosition(1, 1);
}
// // delete all current tab stops and reapply
// _pConApi->PrivateSetDefaultTabStops();
return success;
}

View file

@ -44,6 +44,9 @@ public:
bool SetCursorKeysMode(const bool applicationMode) noexcept override; // DECCKM
bool SetKeypadMode(const bool applicationMode) noexcept override; // DECKPAM, DECKPNM
bool SoftReset() noexcept override; // DECSTR
bool HardReset() noexcept override; // RIS
bool EnableVT200MouseMode(const bool enabled) noexcept override; // ?1000
bool EnableUTF8ExtendedMouseMode(const bool enabled) noexcept override; // ?1005
bool EnableSGRExtendedMouseMode(const bool enabled) noexcept override; // ?1006

View file

@ -158,6 +158,8 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
TEST_METHOD(PassthroughClearScrollback);
TEST_METHOD(PassthroughHardReset);
TEST_METHOD(PassthroughCursorShapeImmediately);
TEST_METHOD(TestWrappingALongString);
@ -926,7 +928,7 @@ void ConptyRoundtripTests::PassthroughCursorShapeImmediately()
void ConptyRoundtripTests::PassthroughClearScrollback()
{
Log::Comment(NoThrowString().Format(
L"Write more lines of outout. We should use \r\n to move the cursor"));
L"Write more lines of output than there are lines in the viewport. Clear the scrollback with ^[[3J"));
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
auto& g = ServiceLocator::LocateGlobals();
@ -985,7 +987,7 @@ void ConptyRoundtripTests::PassthroughClearScrollback()
const auto termSecondView = term->GetViewport();
VERIFY_ARE_EQUAL(0, termSecondView.Top());
// Verify the top of the Terminal veiwoprt contains the contents of the old viewport
// Verify the top of the Terminal viewport contains the contents of the old viewport
for (short y = 0; y < termSecondView.BottomInclusive(); y++)
{
TestUtils::VerifyExpectedString(termTb, L"X ", { 0, y });
@ -997,3 +999,70 @@ void ConptyRoundtripTests::PassthroughClearScrollback()
TestUtils::VerifyExpectedString(termTb, std::wstring(TerminalViewWidth, L' '), { 0, y });
}
}
void ConptyRoundtripTests::PassthroughHardReset()
{
// This test is highly similar to PassthroughClearScrollback.
Log::Comment(NoThrowString().Format(
L"Write more lines of output than there are lines in the viewport. Clear everything with ^[c"));
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& termTb = *term->_buffer;
_flushFirstFrame();
_logConpty = true;
const auto hostView = si.GetViewport();
const auto end = 2 * hostView.Height();
for (auto i = 0; i < end; i++)
{
Log::Comment(NoThrowString().Format(L"Writing line %d/%d", i, end));
expectedOutput.push_back("X");
if (i < hostView.BottomInclusive())
{
expectedOutput.push_back("\r\n");
}
else
{
// After we hit the bottom of the viewport, the newlines come in
// separated for whatever reason.
expectedOutput.push_back("\r");
expectedOutput.push_back("\n");
expectedOutput.push_back("");
}
hostSm.ProcessString(L"X\n");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
VERIFY_SUCCEEDED(renderer.PaintFrame());
// Verify that we've printed height*2 lines of X's to the Terminal
const auto termFirstView = term->GetViewport();
for (short y = 0; y < 2 * termFirstView.Height(); y++)
{
TestUtils::VerifyExpectedString(termTb, L"X ", { 0, y });
}
// Write a Hard Reset VT sequence to the host, it should come through to the Terminal
expectedOutput.push_back("\033c");
hostSm.ProcessString(L"\033c");
const auto termSecondView = term->GetViewport();
VERIFY_ARE_EQUAL(0, termSecondView.Top());
// Verify everything has been cleared out
for (short y = 0; y < termFirstView.BottomInclusive(); y++)
{
TestUtils::VerifyExpectedString(termTb, std::wstring(TerminalViewWidth, L' '), { 0, y });
}
}

View file

@ -1462,6 +1462,8 @@ bool AdaptDispatch::DesignateCharset(const wchar_t wchCharset) noexcept
// True if handled successfully. False otherwise.
bool AdaptDispatch::SoftReset()
{
const bool isPty = _pConApi->IsConsolePty();
bool success = CursorVisibility(true); // Cursor enabled.
if (success)
{
@ -1475,11 +1477,15 @@ bool AdaptDispatch::SoftReset()
{
success = SetCursorKeysMode(false); // Normal characters.
}
if (success)
// SetCursorKeysMode will return false if we're in conpty mode, as to
// trigger a passthrough. If that's the case, just power through here.
if (success || isPty)
{
success = SetKeypadMode(false); // Numeric characters.
}
if (success)
// SetKeypadMode will return false if we're in conpty mode, as to trigger a
// passthrough. If that's the case, just power through here.
if (success || isPty)
{
// Top margin = 1; bottom margin = page length.
success = _DoSetTopBottomScrollingMargins(0, 0);

View file

@ -274,6 +274,13 @@ bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch,
}
}
// If we were unable to process the string, and there's a TTY attached to us,
// trigger the state machine to flush the string to the terminal.
if (_pfnFlushToTerminal != nullptr && !success)
{
success = _pfnFlushToTerminal();
}
_ClearLastChar();
return success;