Implement and action for manually clearing the Terminal (and conpty) buffer (#10906)

## Summary of the Pull Request

![clear-buffer-000](https://user-images.githubusercontent.com/18356694/127570078-90c6089e-0430-4dfc-bcd4-a0cde20c9167.gif)

This adds a new action, `clearBuffer`. It accepts 3 values for the `clear` type:
* `"clear": "screen"`: Clear the terminal viewport content. Leaves the scrollback untouched. Moves the cursor row to the top of the viewport (unmodified).
* `"clear": "scrollback"`: Clear the scrollback. Leaves the viewport untouched.
* `"clear": "all"`: (**default**) Clear the scrollback and the visible viewport. Moves the cursor row to the top of the viewport (unmodified).

"Clear Buffer" has also been added to `defaults.json`.

## References
* From microsoft/vscode#75141 originally

## PR Checklist
* [x] Closes #1193
* [x] Closes #1882
* [x] I work here
* [x] Tests added/passed
* [ ] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments

This is a bit tricky, because we need to plumb it all the way through conpty to clear the buffer. If we don't, then conpty will immediately just redraw the screen. So this sends a signal to the attached conpty, and then waits for conpty to draw the updated, cleared, screen back to us.

## Validation Steps Performed
* works for each of the three clear types as expected
* tests pass.
* works even with `ping -t 8.8.8.8` as you'd hope.
This commit is contained in:
Mike Griese 2021-09-02 09:59:42 -05:00 committed by GitHub
parent 13bc71de3c
commit 6268a4779c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 536 additions and 1 deletions

View File

@ -875,6 +875,22 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPage::_HandleClearBuffer(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (args)
{
if (const auto& realArgs = args.ActionArgs().try_as<ClearBufferArgs>())
{
if (const auto termControl{ _GetActiveControl() })
{
termControl.ClearBuffer(realArgs.Clear());
args.Handled(true);
}
}
}
}
void TerminalPage::_HandleMultipleActions(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{

View File

@ -513,6 +513,16 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
}
void ConptyConnection::ClearBuffer()
{
// If we haven't connected yet, then we really don't need to do
// anything. The connection should already start clear!
if (_isConnected())
{
THROW_IF_FAILED(ConptyClearPseudoConsole(_hPC.get()));
}
}
void ConptyConnection::Close() noexcept
try
{

View File

@ -35,6 +35,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void WriteInput(hstring const& data);
void Resize(uint32_t rows, uint32_t columns);
void Close() noexcept;
void ClearBuffer();
winrt::guid Guid() const noexcept;

View File

@ -9,6 +9,7 @@ namespace Microsoft.Terminal.TerminalConnection
{
ConptyConnection();
Guid Guid { get; };
void ClearBuffer();
static event NewConnectionHandler NewConnection;
static void StartInboundListener();

View File

@ -1499,6 +1499,37 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_updatePatternLocations->Run();
}
// Method Description:
// - Clear the contents of the buffer. The region cleared is given by
// clearType:
// * Screen: Clear only the contents of the visible viewport, leaving the
// cursor row at the top of the viewport.
// * Scrollback: Clear the contents of the scrollback.
// * All: Do both - clear the visible viewport and the scrollback, leaving
// only the cursor row at the top of the viewport.
// Arguments:
// - clearType: The type of clear to perform.
// Return Value:
// - <none>
void ControlCore::ClearBuffer(Control::ClearBufferType clearType)
{
if (clearType == Control::ClearBufferType::Scrollback || clearType == Control::ClearBufferType::All)
{
_terminal->EraseInDisplay(::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType::Scrollback);
}
if (clearType == Control::ClearBufferType::Screen || clearType == Control::ClearBufferType::All)
{
// Send a signal to conpty to clear the buffer.
if (auto conpty{ _connection.try_as<TerminalConnection::ConptyConnection>() })
{
// ConPTY will emit sequences to sync up our buffer with its new
// contents.
conpty.ClearBuffer();
}
}
}
hstring ControlCore::ReadEntireBuffer() const
{
auto terminalLock = _terminal->LockForWriting();

View File

@ -112,6 +112,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const short wheelDelta,
const ::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state);
void UserScrollViewport(const int viewTop);
void ClearBuffer(Control::ClearBufferType clearType);
#pragma endregion
void BlinkAttributeTick();

View File

@ -22,6 +22,14 @@ namespace Microsoft.Terminal.Control
IsRightButtonDown = 0x4
};
enum ClearBufferType
{
Screen,
Scrollback,
All
};
[default_interface] runtimeclass ControlCore : ICoreState
{
ControlCore(IControlSettings settings,
@ -49,6 +57,7 @@ namespace Microsoft.Terminal.Control
Microsoft.Terminal.Core.ControlKeyStates modifiers);
void SendInput(String text);
void PasteText(String text);
void ClearBuffer(ClearBufferType clearType);
void SetHoveredCell(Microsoft.Terminal.Core.Point terminalPosition);
void ClearHoveredCell();

View File

@ -351,6 +351,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_core.SendInput(wstr);
}
void TermControl::ClearBuffer(Control::ClearBufferType clearType)
{
_core.ClearBuffer(clearType);
}
void TermControl::ToggleShaderEffects()
{

View File

@ -63,6 +63,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
til::point GetFontSize() const;
void SendInput(const winrt::hstring& input);
void ClearBuffer(Control::ClearBufferType clearType);
void ToggleShaderEffects();
winrt::fire_and_forget RenderEngineSwapChainChanged(IInspectable sender, IInspectable args);

View File

@ -6,6 +6,7 @@ import "IControlSettings.idl";
import "IDirectKeyListener.idl";
import "EventArgs.idl";
import "ICoreState.idl";
import "ControlCore.idl";
namespace Microsoft.Terminal.Control
{
@ -46,6 +47,7 @@ namespace Microsoft.Terminal.Control
Boolean CopySelectionToClipboard(Boolean singleLine, Windows.Foundation.IReference<CopyFormat> formats);
void PasteTextFromClipboard();
void ClearBuffer(ClearBufferType clearType);
void Close();
Windows.Foundation.Size CharacterDimensions { get; };
Windows.Foundation.Size MinimumSize { get; };

View File

@ -65,6 +65,7 @@ static constexpr std::string_view OpenWindowRenamerKey{ "openWindowRenamer" };
static constexpr std::string_view GlobalSummonKey{ "globalSummon" };
static constexpr std::string_view QuakeModeKey{ "quakeMode" };
static constexpr std::string_view FocusPaneKey{ "focusPane" };
static constexpr std::string_view ClearBufferKey{ "clearBuffer" };
static constexpr std::string_view MultipleActionsKey{ "multipleActions" };
static constexpr std::string_view ActionKey{ "action" };
@ -367,6 +368,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{ ShortcutAction::GlobalSummon, L"" }, // Intentionally omitted, must be generated by GenerateName
{ ShortcutAction::QuakeMode, RS_(L"QuakeModeCommandKey") },
{ ShortcutAction::FocusPane, L"" }, // Intentionally omitted, must be generated by GenerateName
{ ShortcutAction::ClearBuffer, L"" }, // Intentionally omitted, must be generated by GenerateName
{ ShortcutAction::MultipleActions, L"" }, // Intentionally omitted, must be generated by GenerateName
};
}();

View File

@ -34,6 +34,7 @@
#include "RenameWindowArgs.g.cpp"
#include "GlobalSummonArgs.g.cpp"
#include "FocusPaneArgs.g.cpp"
#include "ClearBufferArgs.g.cpp"
#include "MultipleActionsArgs.g.cpp"
#include <LibraryResources.h>
@ -688,6 +689,24 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
Id())
};
}
winrt::hstring ClearBufferArgs::GenerateName() const
{
// "Clear Buffer"
// "Clear Viewport"
// "Clear Scrollback"
switch (Clear())
{
case Control::ClearBufferType::All:
return RS_(L"ClearAllCommandKey");
case Control::ClearBufferType::Screen:
return RS_(L"ClearViewportCommandKey");
case Control::ClearBufferType::Scrollback:
return RS_(L"ClearScrollbackCommandKey");
}
// Return the empty string - the Clear() should be one of these values
return winrt::hstring{ L"" };
}
winrt::hstring MultipleActionsArgs::GenerateName() const
{

View File

@ -36,6 +36,7 @@
#include "RenameWindowArgs.g.h"
#include "GlobalSummonArgs.g.h"
#include "FocusPaneArgs.g.h"
#include "ClearBufferArgs.g.h"
#include "MultipleActionsArgs.g.h"
#include "../../cascadia/inc/cppwinrt_utils.h"
@ -1755,6 +1756,56 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
};
struct ClearBufferArgs : public ClearBufferArgsT<ClearBufferArgs>
{
ClearBufferArgs() = default;
ClearBufferArgs(winrt::Microsoft::Terminal::Control::ClearBufferType clearType) :
_Clear{ clearType } {};
WINRT_PROPERTY(winrt::Microsoft::Terminal::Control::ClearBufferType, Clear, winrt::Microsoft::Terminal::Control::ClearBufferType::All);
static constexpr std::string_view ClearKey{ "clear" };
public:
hstring GenerateName() const;
bool Equals(const IActionArgs& other)
{
auto otherAsUs = other.try_as<ClearBufferArgs>();
if (otherAsUs)
{
return otherAsUs->_Clear == _Clear;
}
return false;
};
static FromJsonResult FromJson(const Json::Value& json)
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<ClearBufferArgs>();
JsonUtils::GetValueForKey(json, ClearKey, args->_Clear);
return { *args, {} };
}
static Json::Value ToJson(const IActionArgs& val)
{
if (!val)
{
return {};
}
Json::Value json{ Json::ValueType::objectValue };
const auto args{ get_self<ClearBufferArgs>(val) };
JsonUtils::SetValueForKey(json, ClearKey, args->_Clear);
return json;
}
IActionArgs Copy() const
{
auto copy{ winrt::make_self<ClearBufferArgs>() };
copy->_Clear = _Clear;
return *copy;
}
size_t Hash() const
{
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_Clear);
}
};
struct MultipleActionsArgs : public MultipleActionsArgsT<MultipleActionsArgs>
{
MultipleActionsArgs() = default;
@ -1787,6 +1838,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return {};
}
Json::Value json{ Json::ValueType::objectValue };
const auto args{ get_self<MultipleActionsArgs>(val) };
JsonUtils::SetValueForKey(json, ActionsKey, args->_Actions);
return json;
@ -1826,5 +1878,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
BASIC_FACTORY(FocusPaneArgs);
BASIC_FACTORY(PrevTabArgs);
BASIC_FACTORY(NextTabArgs);
BASIC_FACTORY(ClearBufferArgs);
BASIC_FACTORY(MultipleActionsArgs);
}

View File

@ -311,9 +311,15 @@ namespace Microsoft.Terminal.Settings.Model
UInt32 Id { get; };
};
[default_interface] runtimeclass ClearBufferArgs : IActionArgs
{
ClearBufferArgs(Microsoft.Terminal.Control.ClearBufferType clear);
Microsoft.Terminal.Control.ClearBufferType Clear { get; };
};
[default_interface] runtimeclass MultipleActionsArgs : IActionArgs
{
MultipleActionsArgs();
Windows.Foundation.Collections.IVector<ActionAndArgs> Actions;
}
};
}

View File

@ -79,6 +79,7 @@
ON_ALL_ACTIONS(GlobalSummon) \
ON_ALL_ACTIONS(QuakeMode) \
ON_ALL_ACTIONS(FocusPane) \
ON_ALL_ACTIONS(ClearBuffer) \
ON_ALL_ACTIONS(MultipleActions)
#define ALL_SHORTCUT_ACTIONS_WITH_ARGS \
@ -111,4 +112,5 @@
ON_ALL_ACTIONS_WITH_ARGS(SwitchToTab) \
ON_ALL_ACTIONS_WITH_ARGS(ToggleCommandPalette) \
ON_ALL_ACTIONS_WITH_ARGS(FocusPane) \
ON_ALL_ACTIONS_WITH_ARGS(ClearBuffer) \
ON_ALL_ACTIONS_WITH_ARGS(MultipleActions)

View File

@ -446,6 +446,18 @@
<value>Focus pane {0}</value>
<comment>{0} will be replaced with a user-specified number</comment>
</data>
<data name="ClearAllCommandKey" xml:space="preserve">
<value>Clear Buffer</value>
<comment>A command to clear the entirety of the Terminal output buffer</comment>
</data>
<data name="ClearViewportCommandKey" xml:space="preserve">
<value>Clear Viewport</value>
<comment>A command to clear the active viewport of the Terminal</comment>
</data>
<data name="ClearScrollbackCommandKey" xml:space="preserve">
<value>Clear Scrollback</value>
<comment>A command to clear the part of the buffer above the viewport</comment>
</data>
<data name="InboxWindowsConsoleAuthor" xml:space="preserve">
<value>Microsoft Corporation</value>
<comment>Paired with `InboxWindowsConsoleName`, this is the application author... which is us: Microsoft.</comment>

View File

@ -472,6 +472,15 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::MonitorBehavior)
};
};
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::ClearBufferType)
{
JSON_MAPPINGS(3) = {
pair_type{ "all", ValueType::All },
pair_type{ "screen", ValueType::Screen },
pair_type{ "scrollback", ValueType::Scrollback },
};
};
JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::IntenseStyle)
{
static constexpr std::array<pair_type, 4> mappings = {
@ -479,5 +488,6 @@ JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::IntenseStyle)
pair_type{ "bold", ValueType::Bold },
pair_type{ "bright", ValueType::Bright },
pair_type{ "all", AllSet },
};
};

View File

@ -383,6 +383,7 @@
{ "command": "scrollUpPage", "keys": "ctrl+shift+pgup" },
{ "command": "scrollToTop", "keys": "ctrl+shift+home" },
{ "command": "scrollToBottom", "keys": "ctrl+shift+end" },
{ "command": { "action": "clearBuffer", "clear": "all" } },
// Visual Adjustments
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+plus" },

View File

@ -6,6 +6,7 @@
#include "../TerminalControl/ControlCore.h"
#include "MockControlSettings.h"
#include "MockConnection.h"
#include "../UnitTests_TerminalCore/TestUtils.h"
using namespace Microsoft::Console;
using namespace WEX::Logging;
@ -32,6 +33,10 @@ namespace ControlUnitTests
TEST_METHOD(TestFontInitializedInCtor);
TEST_METHOD(TestClearScrollback);
TEST_METHOD(TestClearScreen);
TEST_METHOD(TestClearAll);
TEST_CLASS_SETUP(ModuleSetup)
{
winrt::init_apartment(winrt::apartment_type::single_threaded);
@ -66,6 +71,15 @@ namespace ControlUnitTests
core->_inUnitTests = true;
return core;
}
void _standardInit(winrt::com_ptr<Control::implementation::ControlCore> core)
{
// "Consolas" ends up with an actual size of 9x21 at 96DPI. So
// let's just arbitrarily start with a 270x420px (30x20 chars) window
core->Initialize(270, 420, 1.0);
VERIFY_IS_TRUE(core->_initializedTerminal);
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
}
};
void ControlCoreTests::ComPtrSettings()
@ -202,4 +216,122 @@ namespace ControlUnitTests
VERIFY_ARE_EQUAL(L"Impact", std::wstring_view{ core->_actualFont.GetFaceName() });
}
void ControlCoreTests::TestClearScrollback()
{
auto [settings, conn] = _createSettingsAndConnection();
Log::Comment(L"Create ControlCore object");
auto core = winrt::make_self<Control::implementation::ControlCore>(*settings, *conn);
VERIFY_IS_NOT_NULL(core);
_standardInit(core);
Log::Comment(L"Print 40 rows of 'Foo', and a single row of 'Bar' "
L"(leaving the cursor afer 'Bar')");
for (int i = 0; i < 40; ++i)
{
conn->WriteInput(L"Foo\r\n");
}
conn->WriteInput(L"Bar");
// We printed that 40 times, but the final \r\n bumped the view down one MORE row.
Log::Comment(L"Check the buffer viewport before the clear");
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
VERIFY_ARE_EQUAL(20, core->ViewHeight());
VERIFY_ARE_EQUAL(41, core->BufferHeight());
Log::Comment(L"Clear the buffer");
core->ClearBuffer(Control::ClearBufferType::Scrollback);
Log::Comment(L"Check the buffer after the clear");
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
VERIFY_ARE_EQUAL(0, core->ScrollOffset());
VERIFY_ARE_EQUAL(20, core->ViewHeight());
VERIFY_ARE_EQUAL(20, core->BufferHeight());
// In this test, we can't actually check if we cleared the buffer
// contents. ConPTY will handle the actual clearing of the buffer
// contents. We can only ensure that the viewport moved when we did a
// clear scrollback.
//
// The ConptyRoundtripTests test the actual clearing of the contents.
}
void ControlCoreTests::TestClearScreen()
{
auto [settings, conn] = _createSettingsAndConnection();
Log::Comment(L"Create ControlCore object");
auto core = winrt::make_self<Control::implementation::ControlCore>(*settings, *conn);
VERIFY_IS_NOT_NULL(core);
_standardInit(core);
Log::Comment(L"Print 40 rows of 'Foo', and a single row of 'Bar' "
L"(leaving the cursor afer 'Bar')");
for (int i = 0; i < 40; ++i)
{
conn->WriteInput(L"Foo\r\n");
}
conn->WriteInput(L"Bar");
// We printed that 40 times, but the final \r\n bumped the view down one MORE row.
Log::Comment(L"Check the buffer viewport before the clear");
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
VERIFY_ARE_EQUAL(20, core->ViewHeight());
VERIFY_ARE_EQUAL(41, core->BufferHeight());
Log::Comment(L"Clear the buffer");
core->ClearBuffer(Control::ClearBufferType::Screen);
Log::Comment(L"Check the buffer after the clear");
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
VERIFY_ARE_EQUAL(20, core->ViewHeight());
VERIFY_ARE_EQUAL(41, core->BufferHeight());
// In this test, we can't actually check if we cleared the buffer
// contents. ConPTY will handle the actual clearing of the buffer
// contents. We can only ensure that the viewport moved when we did a
// clear scrollback.
//
// The ConptyRoundtripTests test the actual clearing of the contents.
}
void ControlCoreTests::TestClearAll()
{
auto [settings, conn] = _createSettingsAndConnection();
Log::Comment(L"Create ControlCore object");
auto core = winrt::make_self<Control::implementation::ControlCore>(*settings, *conn);
VERIFY_IS_NOT_NULL(core);
_standardInit(core);
Log::Comment(L"Print 40 rows of 'Foo', and a single row of 'Bar' "
L"(leaving the cursor afer 'Bar')");
for (int i = 0; i < 40; ++i)
{
conn->WriteInput(L"Foo\r\n");
}
conn->WriteInput(L"Bar");
// We printed that 40 times, but the final \r\n bumped the view down one MORE row.
Log::Comment(L"Check the buffer viewport before the clear");
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
VERIFY_ARE_EQUAL(20, core->ViewHeight());
VERIFY_ARE_EQUAL(41, core->BufferHeight());
Log::Comment(L"Clear the buffer");
core->ClearBuffer(Control::ClearBufferType::All);
Log::Comment(L"Check the buffer after the clear");
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
VERIFY_ARE_EQUAL(0, core->ScrollOffset());
VERIFY_ARE_EQUAL(20, core->ViewHeight());
VERIFY_ARE_EQUAL(20, core->BufferHeight());
// In this test, we can't actually check if we cleared the buffer
// contents. ConPTY will handle the actual clearing of the buffer
// contents. We can only ensure that the viewport moved when we did a
// clear scrollback.
//
// The ConptyRoundtripTests test the actual clearing of the contents.
}
}

View File

@ -218,10 +218,13 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
TEST_METHOD(ResizeInitializeBufferWithDefaultAttrs);
TEST_METHOD(ClearBufferSignal);
private:
bool _writeCallback(const char* const pch, size_t const cch);
void _flushFirstFrame();
void _resizeConpty(const unsigned short sx, const unsigned short sy);
void _clearConpty();
[[nodiscard]] std::tuple<TextBuffer*, TextBuffer*> _performResize(const til::size& newSize);
@ -297,6 +300,12 @@ void ConptyRoundtripTests::_resizeConpty(const unsigned short sx,
}
}
void ConptyRoundtripTests::_clearConpty()
{
// Taken verbatim from implementation in PtySignalInputThread::_DoClearBuffer
_pConApi->PrivateClearBuffer();
}
[[nodiscard]] std::tuple<TextBuffer*, TextBuffer*> ConptyRoundtripTests::_performResize(const til::size& newSize)
{
// IMPORTANT! Anyone calling this should make sure that the test is running
@ -3675,3 +3684,77 @@ void ConptyRoundtripTests::HyperlinkIdConsistency()
verifyData(hostTb);
verifyData(termTb);
}
void ConptyRoundtripTests::ClearBufferSignal()
{
Log::Comment(L"Write some text to the conpty buffer. Send a ClearBuffer "
L"signal, and check that all but the cursor line is removed "
L"from the host and the terminal.");
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& sm = si.GetStateMachine();
auto* hostTb = &si.GetTextBuffer();
auto* termTb = term->_buffer.get();
_flushFirstFrame();
_checkConptyOutput = false;
_logConpty = true;
// Print two lines of text:
// |AAAAAAAAAAAAA BBBBBB| <wrap>
// |BBBBBBBB_ | <break>
// (cursor on the '_')
// A's are in blue-on-green,
// B's are in red-on-yellow
sm.ProcessString(L"\x1b[?25l");
sm.ProcessString(L"\x1b[?34;42m");
sm.ProcessString(std::wstring(50, L'A'));
sm.ProcessString(L" ");
sm.ProcessString(L"\x1b[?31;43m");
sm.ProcessString(std::wstring(50, L'B'));
sm.ProcessString(L"\x1b[?m");
sm.ProcessString(L"\x1b[?25h");
auto verifyBuffer = [&](const TextBuffer& tb, const til::rectangle viewport, const bool before) {
const short width = viewport.width<short>();
const short numCharsOnSecondLine = 50 - (width - 51);
auto iter1 = tb.GetCellDataAt({ 0, 0 });
if (before)
{
TestUtils::VerifySpanOfText(L"A", iter1, 0, 50);
TestUtils::VerifySpanOfText(L" ", iter1, 0, 1);
TestUtils::VerifySpanOfText(L"B", iter1, 0, 50);
COORD expectedCursor{ numCharsOnSecondLine, 1 };
VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition());
}
else
{
TestUtils::VerifySpanOfText(L"B", iter1, 0, numCharsOnSecondLine);
COORD expectedCursor{ numCharsOnSecondLine, 0 };
VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition());
}
};
Log::Comment(L"========== Checking the host buffer state (before) ==========");
verifyBuffer(*hostTb, si.GetViewport().ToInclusive(), true);
Log::Comment(L"Painting the frame");
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"========== Checking the terminal buffer state (before) ==========");
verifyBuffer(*termTb, term->_mutableViewport.ToInclusive(), true);
Log::Comment(L"========== Clear the ConPTY buffer with the signal ==========");
_clearConpty();
Log::Comment(L"========== Checking the host buffer state (after) ==========");
verifyBuffer(*hostTb, si.GetViewport().ToInclusive(), false);
Log::Comment(L"Painting the frame");
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"========== Checking the terminal buffer state (after) ==========");
verifyBuffer(*termTb, term->_mutableViewport.ToInclusive(), false);
}

View File

@ -82,6 +82,21 @@ void PtySignalInputThread::ConnectConsole() noexcept
{
switch (signalId)
{
case PtySignal::ClearBuffer:
{
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
// If the client app hasn't yet connected, stash the new size in the launchArgs.
// We'll later use the value in launchArgs to set up the console buffer
// We must be under lock here to ensure that someone else doesn't come in
// and set with `ConnectConsole` while we're looking and modifying this.
if (_consoleConnected)
{
_DoClearBuffer();
}
break;
}
case PtySignal::ResizeWindow:
{
ResizeWindowData resizeMsg = { 0 };
@ -128,6 +143,11 @@ void PtySignalInputThread::_DoResizeWindow(const ResizeWindowData& data)
}
}
void PtySignalInputThread::_DoClearBuffer()
{
_pConApi->PrivateClearBuffer();
}
// Method Description:
// - Retrieves bytes from the file stream and exits or throws errors should the pipe state
// be compromised.

View File

@ -35,6 +35,7 @@ namespace Microsoft::Console
private:
enum class PtySignal : unsigned short
{
ClearBuffer = 2,
ResizeWindow = 8
};
@ -47,6 +48,7 @@ namespace Microsoft::Console
[[nodiscard]] HRESULT _InputThread();
bool _GetData(_Out_writes_bytes_(cbBuffer) void* const pBuffer, const DWORD cbBuffer);
void _DoResizeWindow(const ResizeWindowData& data);
void _DoClearBuffer();
void _Shutdown();
wil::unique_hfile _hFile;

View File

@ -1609,6 +1609,12 @@ void DoSrvPrivateEnableAlternateScroll(const bool fEnable)
return screenInfo.GetActiveBuffer().VtEraseAll();
}
// See SCREEN_INFORMATION::ClearBuffer's description for details.
[[nodiscard]] HRESULT DoSrvPrivateClearBuffer(SCREEN_INFORMATION& screenInfo)
{
return screenInfo.GetActiveBuffer().ClearBuffer();
}
void DoSrvSetCursorStyle(SCREEN_INFORMATION& screenInfo,
const CursorType cursorType)
{

View File

@ -43,6 +43,7 @@ void DoSrvPrivateEnableAnyEventMouseMode(const bool fEnable);
void DoSrvPrivateEnableAlternateScroll(const bool fEnable);
[[nodiscard]] HRESULT DoSrvPrivateEraseAll(SCREEN_INFORMATION& screenInfo);
[[nodiscard]] HRESULT DoSrvPrivateClearBuffer(SCREEN_INFORMATION& screenInfo);
void DoSrvSetCursorStyle(SCREEN_INFORMATION& screenInfo,
const CursorType cursorType);

View File

@ -545,6 +545,11 @@ bool ConhostInternalGetSet::PrivateEraseAll()
return SUCCEEDED(DoSrvPrivateEraseAll(_io.GetActiveOutputBuffer()));
}
bool ConhostInternalGetSet::PrivateClearBuffer()
{
return SUCCEEDED(DoSrvPrivateClearBuffer(_io.GetActiveOutputBuffer()));
}
// Method Description:
// - Retrieves the current user default cursor style.
// Arguments:

View File

@ -104,6 +104,7 @@ public:
bool PrivateEnableAnyEventMouseMode(const bool enabled) override;
bool PrivateEnableAlternateScroll(const bool enabled) override;
bool PrivateEraseAll() override;
bool PrivateClearBuffer() override;
bool GetUserDefaultCursorStyle(CursorType& style) override;
bool SetCursorStyle(CursorType const style) override;

View File

@ -2277,6 +2277,55 @@ void SCREEN_INFORMATION::SetViewport(const Viewport& newViewport,
return S_OK;
}
// Method Description:
// - Clear the entire contents of the viewport, except for the cursor's row,
// which is moved to the top line of the viewport.
// - This is used exclusively by ConPTY to support GH#1193, GH#1882. This allows
// a terminal to clear the contents of the ConPTY buffer, which is important
// if the user would like to be able to clear the terminal-side buffer.
// Arguments:
// - <none>
// Return Value:
// - S_OK
[[nodiscard]] HRESULT SCREEN_INFORMATION::ClearBuffer()
{
const COORD oldCursorPos = _textBuffer->GetCursor().GetPosition();
short sNewTop = oldCursorPos.Y;
const Viewport oldViewport = _viewport;
short delta = (sNewTop + _viewport.Height()) - (GetBufferSize().Height());
for (auto i = 0; i < delta; i++)
{
_textBuffer->IncrementCircularBuffer();
sNewTop--;
}
const COORD coordNewOrigin = { 0, sNewTop };
RETURN_IF_FAILED(SetViewportOrigin(true, coordNewOrigin, true));
// Place the cursor at the same x coord, on the row that's now the top
RETURN_IF_FAILED(SetCursorPosition(COORD{ oldCursorPos.X, sNewTop }, false));
// Update all the rows in the current viewport with the standard erase attributes,
// i.e. the current background color, but with no meta attributes set.
auto fillAttributes = GetAttributes();
fillAttributes.SetStandardErase();
// +1 on the y coord because we don't want to clear the attributes of the
// cursor row, the one we saved.
auto fillPosition = COORD{ 0, _viewport.Top() + 1 };
auto fillLength = gsl::narrow_cast<size_t>(_viewport.Height() * GetBufferSize().Width());
auto fillData = OutputCellIterator{ fillAttributes, fillLength };
Write(fillData, fillPosition, false);
_textBuffer->GetRenderTarget().TriggerRedrawAll();
// Also reset the line rendition for the erased rows.
_textBuffer->ResetLineRenditionRange(_viewport.Top(), _viewport.BottomExclusive());
return S_OK;
}
// Method Description:
// - Sets up the Output state machine to be in pty mode. Sequences it doesn't
// understand will be written to the pTtyConnection passed in here.

View File

@ -222,6 +222,7 @@ public:
const TextAttribute& popupAttributes);
[[nodiscard]] HRESULT VtEraseAll();
[[nodiscard]] HRESULT ClearBuffer();
void SetTerminalConnection(_In_ Microsoft::Console::ITerminalOutputConnection* const pTtyConnection);

View File

@ -22,6 +22,8 @@ HRESULT WINAPI ConptyCreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutp
HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size);
HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC);
VOID WINAPI ConptyClosePseudoConsole(HPCON hPC);
HRESULT WINAPI ConptyPackPseudoConsole(HANDLE hServerProcess, HANDLE hRef, HANDLE hSignal, HPCON* phPC);

View File

@ -7,6 +7,7 @@
#include <memory>
#pragma once
const unsigned int PTY_SIGNAL_CLEAR_WINDOW = 2u;
const unsigned int PTY_SIGNAL_RESIZE_WINDOW = 8u;
HRESULT CreateConPty(const std::wstring& cmdline, // _In_

View File

@ -73,6 +73,7 @@ namespace Microsoft::Console::VirtualTerminal
virtual bool PrivateEnableAnyEventMouseMode(const bool enabled) = 0;
virtual bool PrivateEnableAlternateScroll(const bool enabled) = 0;
virtual bool PrivateEraseAll() = 0;
virtual bool PrivateClearBuffer() = 0;
virtual bool GetUserDefaultCursorStyle(CursorType& style) = 0;
virtual bool SetCursorStyle(const CursorType style) = 0;
virtual bool SetCursorColor(const COLORREF color) = 0;

View File

@ -418,6 +418,12 @@ public:
return TRUE;
}
bool PrivateClearBuffer() override
{
Log::Comment(L"PrivateClearBuffer MOCK called...");
return TRUE;
}
bool GetUserDefaultCursorStyle(CursorType& style) override
{
style = CursorType::Legacy;

View File

@ -2,3 +2,4 @@ EXPORTS
CreatePseudoConsole = ConptyCreatePseudoConsole
ResizePseudoConsole = ConptyResizePseudoConsole
ClosePseudoConsole = ConptyClosePseudoConsole
ClearPseudoConsole = ConptyClearPseudoConsole

View File

@ -231,6 +231,27 @@ HRESULT _ResizePseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const CO
return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError());
}
// Function Description:
// - Clears the conpty
// Arguments:
// - hSignal: A signal pipe as returned by CreateConPty.
// Return Value:
// - S_OK if the call succeeded, else an appropriate HRESULT for failing to
// write the clear message to the pty.
HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty)
{
if (pPty == nullptr)
{
return E_INVALIDARG;
}
unsigned short signalPacket[1];
signalPacket[0] = PTY_SIGNAL_CLEAR_WINDOW;
const BOOL fSuccess = WriteFile(pPty->hSignal, signalPacket, sizeof(signalPacket), nullptr, nullptr);
return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError());
}
// Function Description:
// - This closes each of the members of a PseudoConsole. It does not free the
// data associated with the PseudoConsole. This is helpful for testing,
@ -385,6 +406,23 @@ extern "C" HRESULT WINAPI ConptyResizePseudoConsole(_In_ HPCON hPC, _In_ COORD s
return hr;
}
// Function Description:
// - Clear the contents of the conpty buffer, leaving the cursor row at the top
// of the viewport.
// - This is used exclusively by ConPTY to support GH#1193, GH#1882. This allows
// a terminal to clear the contents of the ConPTY buffer, which is important
// if the user would like to be able to clear the terminal-side buffer.
extern "C" HRESULT WINAPI ConptyClearPseudoConsole(_In_ HPCON hPC)
{
const PseudoConsole* const pPty = (PseudoConsole*)hPC;
HRESULT hr = pPty == nullptr ? E_INVALIDARG : S_OK;
if (SUCCEEDED(hr))
{
hr = _ClearPseudoConsole(pPty);
}
return hr;
}
// Function Description:
// Closes the conpty and all associated state.
// Client applications attached to the conpty will also behave as though the

View File

@ -17,6 +17,7 @@ typedef struct _PseudoConsole
// Signals
// These are not defined publicly, but are used for controlling the conpty via
// the signal pipe.
#define PTY_SIGNAL_CLEAR_WINDOW (2u)
#define PTY_SIGNAL_RESIZE_WINDOW (8u)
// CreatePseudoConsole Flags
@ -34,6 +35,7 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken,
_Inout_ PseudoConsole* pPty);
HRESULT _ResizePseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const COORD size);
HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty);
void _ClosePseudoConsoleMembers(_In_ PseudoConsole* pPty);
VOID _ClosePseudoConsole(_In_ PseudoConsole* pPty);