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:
parent
13bc71de3c
commit
6268a4779c
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ namespace Microsoft.Terminal.TerminalConnection
|
|||
{
|
||||
ConptyConnection();
|
||||
Guid Guid { get; };
|
||||
void ClearBuffer();
|
||||
|
||||
static event NewConnectionHandler NewConnection;
|
||||
static void StartInboundListener();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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; };
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}();
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 },
|
||||
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -222,6 +222,7 @@ public:
|
|||
const TextAttribute& popupAttributes);
|
||||
|
||||
[[nodiscard]] HRESULT VtEraseAll();
|
||||
[[nodiscard]] HRESULT ClearBuffer();
|
||||
|
||||
void SetTerminalConnection(_In_ Microsoft::Console::ITerminalOutputConnection* const pTtyConnection);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -2,3 +2,4 @@ EXPORTS
|
|||
CreatePseudoConsole = ConptyCreatePseudoConsole
|
||||
ResizePseudoConsole = ConptyResizePseudoConsole
|
||||
ClosePseudoConsole = ConptyClosePseudoConsole
|
||||
ClearPseudoConsole = ConptyClearPseudoConsole
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue