diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp index a497a82659..a283ed25df 100644 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp @@ -173,6 +173,7 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final TEST_METHOD(PassthroughClearScrollback); TEST_METHOD(PassthroughClearAll); + TEST_METHOD(ClearAllInTheMiddleOfAString); TEST_METHOD(PassthroughHardReset); @@ -985,6 +986,8 @@ void ConptyRoundtripTests::PassthroughClearScrollback() Log::Comment(NoThrowString().Format( L"Write more lines of output than there are lines in the viewport. Clear the scrollback with ^[[3J")); + WEX::TestExecution::SetVerifyOutput settings(WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures); + auto& g = ServiceLocator::LocateGlobals(); auto& renderer = *g.pRender; auto& gci = g.getConsoleInformation(); @@ -1030,12 +1033,11 @@ void ConptyRoundtripTests::PassthroughClearScrollback() TestUtils::VerifyExpectedString(termTb, L"X ", { 0, y }); } - // Write a Erase Scrollback VT sequence to the host, it should come through to the Terminal - expectedOutput.push_back("\x1b[3J"); - hostSm.ProcessString(L"\x1b[3J"); - _checkConptyOutput = false; + // Write a Erase Scrollback VT sequence to the host, it should come through to the Terminal + hostSm.ProcessString(L"\x1b[3J"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); const auto termSecondView = term->GetViewport(); @@ -1063,6 +1065,9 @@ void ConptyRoundtripTests::PassthroughClearAll() L"all the _current_ buffer contents are moved to scrollback. " L"We shouldn't just paint over the current viewport with spaces."); + WEX::TestExecution::SetVerifyOutput settings(WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures); + const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; + auto& g = ServiceLocator::LocateGlobals(); auto& renderer = *g.pRender; auto& gci = g.getConsoleInformation(); @@ -1123,6 +1128,7 @@ void ConptyRoundtripTests::PassthroughClearAll() // Here, we'll emit the 2J to EraseAll, and move the viewport contents into // the scrollback. + Log::Comment(L"Send the EraseAll"); sm.ProcessString(L"\x1b[2J"); Log::Comment(L"Painting the frame"); @@ -1143,6 +1149,112 @@ void ConptyRoundtripTests::PassthroughClearAll() verifyBuffer(*termTb, newTerminalView, true); } +void ConptyRoundtripTests::ClearAllInTheMiddleOfAString() +{ + // see TODO! + Log::Comment(L"TODO!"); + + WEX::TestExecution::SetVerifyOutput settings(WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures); + const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; + + auto& g = ServiceLocator::LocateGlobals(); + auto& renderer = *g.pRender; + auto& gci = g.getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer(); + auto* hostTb = &si.GetTextBuffer(); + auto* termTb = term->_mainBuffer.get(); + + auto& sm = si.GetStateMachine(); + + _flushFirstFrame(); + + _checkConptyOutput = false; + _logConpty = true; + + const auto hostView = si.GetViewport(); + const auto end = 2 * hostView.Height(); + for (auto i = 0; i < end; i++) + { + if (i > 0) + { + sm.ProcessString(L"\r\n"); + } + + sm.ProcessString(L"~"); + } + + auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport, const bool afterClear = false) { + const auto width = viewport.width(); + + // "~" rows + for (til::CoordType row = 0; row < viewport.bottom; row++) + { + Log::Comment(NoThrowString().Format(L"Checking row %d", row)); + VERIFY_IS_FALSE(tb.GetRowByOffset(row).WasWrapForced()); + auto iter = tb.GetCellDataAt({ 0, row }); + if (afterClear && row >= viewport.top) + { + if (row == viewport.bottom - 1) + { + TestUtils::VerifyExpectedString(L" ", iter); + TestUtils::VerifyExpectedString(L"bar", iter); + TestUtils::VerifySpanOfText(L" ", iter, 0, width - 8); + } + else + { + TestUtils::VerifySpanOfText(L" ", iter, 0, width); + } + } + else + { + TestUtils::VerifySpanOfText(L"~", iter, 0, 1); + if (afterClear && row == viewport.top - 1) + { + TestUtils::VerifyExpectedString(L"foo", iter); + TestUtils::VerifySpanOfText(L" ", iter, 0, width - 4); + } + else + { + TestUtils::VerifySpanOfText(L" ", iter, 0, width - 1); + } + } + } + }; + + Log::Comment(L"========== Checking the host buffer state (before) =========="); + verifyBuffer(*hostTb, si.GetViewport().ToExclusive()); + + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + Log::Comment(L"========== Checking the terminal buffer state (before) =========="); + verifyBuffer(*termTb, term->_mutableViewport.ToExclusive()); + + const til::rect originalTerminalView{ term->_mutableViewport.ToInclusive() }; + + // Here, we'll emit the 2J to EraseAll, and move the viewport contents into + // the scrollback. + Log::Comment(L"Send the EraseAll between foo and bar"); + sm.ProcessString(L"foo\x1b[2Jbar"); + + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + // Make sure that the terminal's new viewport is actually just lower than it + // used to be. + const til::rect newTerminalView{ term->_mutableViewport.ToInclusive() }; + VERIFY_ARE_EQUAL(end, newTerminalView.top); + VERIFY_IS_GREATER_THAN(newTerminalView.top, originalTerminalView.top); + + Log::Comment(L"========== Checking the host buffer state (after) =========="); + verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), true); + + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + Log::Comment(L"========== Checking the terminal buffer state (after) =========="); + verifyBuffer(*termTb, newTerminalView, true); +} + void ConptyRoundtripTests::PassthroughHardReset() { // This test is highly similar to PassthroughClearScrollback. diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 7209b7a0e8..37780694f2 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -606,15 +606,25 @@ bool AdaptDispatch::EraseInDisplay(const DispatchTypes::EraseType eraseType) // by moving the current contents of the viewport into the scrollback. if (eraseType == DispatchTypes::EraseType::Scrollback) { + _renderer.TriggerFlush(false); + _EraseScrollback(); // GH#2715 - If this succeeded, but we're in a conpty, return `false` to // make the state machine propagate this ED sequence to the connected // terminal application. While we're in conpty mode, we don't really // have a scrollback, but the attached terminal might. - return !_api.IsConsolePty(); + + if (_api.IsConsolePty()) + { + _api.GetStateMachine().Engine().ActionPassThroughString(L"\x1b[3J"); + } + + return true; // !_api.IsConsolePty(); } else if (eraseType == DispatchTypes::EraseType::All) { + _renderer.TriggerFlush(false); + // GH#5683 - If this succeeded, but we're in a conpty, return `false` to // make the state machine propagate this ED sequence to the connected // terminal application. While we're in conpty mode, when the client @@ -622,7 +632,14 @@ bool AdaptDispatch::EraseInDisplay(const DispatchTypes::EraseType eraseType) // connected terminal to do the same thing, so that the terminal will // move it's own buffer contents into the scrollback. _EraseAll(); - return !_api.IsConsolePty(); + + if (_api.IsConsolePty()) + { + // TODO! HardReset I guess also relied on this code path. Yikes. + _api.GetStateMachine().Engine().ActionPassThroughString(L"\x1b[2J"); + } + + return true; // !_api.IsConsolePty(); } const auto viewport = _api.GetViewport();