Merge remote-tracking branch 'origin/main' into dev/migrie/f/non-terminal-content-elevation-warning

This commit is contained in:
Mike Griese 2021-09-08 11:50:56 -05:00
commit b592155d2b
66 changed files with 843 additions and 225 deletions

View file

@ -1260,6 +1260,11 @@
"default": "false",
"description": "When set to true, the Terminal's tray icon will always be shown in the system tray.",
"type": "boolean"
},
"useAcrylicInTabRow": {
"default": "false",
"description": "When set to true, the tab row will have an acrylic background with 50% opacity.",
"type": "boolean"
},
"actions": {
"description": "Properties are specific to each custom action.",

View file

@ -29,8 +29,8 @@ Below is the schedule for when milestones will be included in release builds of
| 2021-03-01 | [1.7] in Windows Terminal Preview<br>[1.6] in Windows Terminal | [Windows Terminal Preview 1.7 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-7-release/) |
| 2021-04-14 | [1.8] in Windows Terminal Preview<br>[1.7] in Windows Terminal | [Windows Terminal Preview 1.8 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-8-release/) |
| 2021-05-31 | [1.9] in Windows Terminal Preview<br>[1.8] in Windows Terminal | [Windows Terminal Preview 1.9 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-9-release/) |
| 2021-07-31 | 1.10 in Windows Terminal Preview<br>[1.9] in Windows Terminal | |
| 2021-08-30 | 1.11 in Windows Terminal Preview<br>1.10 in Windows Terminal | |
| 2021-07-14 | [1.10] in Windows Terminal Preview<br>[1.9] in Windows Terminal | [Windows Terminal Preview 1.10 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-10-release/) |
| 2021-08-31 | [1.11] in Windows Terminal Preview<br>[1.10] in Windows Terminal | [Windows Terminal Preview 1.11 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-11-release/) |
| 2021-10-31 | 1.12 in Windows Terminal Preview<br>1.11 in Windows Terminal | |
| 2021-11-30 | 2.0 RC in Windows Terminal Preview<br>2.0 RC in Windows Terminal | |
| 2021-12-31 | [2.0] in Windows Terminal Preview<br>[2.0] in Windows Terminal | |
@ -89,6 +89,8 @@ Feature Notes:
[1.7]: https://github.com/microsoft/terminal/milestone/32
[1.8]: https://github.com/microsoft/terminal/milestone/33
[1.9]: https://github.com/microsoft/terminal/milestone/34
[1.10]: https://github.com/microsoft/terminal/milestone/35
[1.11]: https://github.com/microsoft/terminal/milestone/36
[2.0]: https://github.com/microsoft/terminal/milestone/22
[#1564]: https://github.com/microsoft/terminal/issues/1564
[#6720]: https://github.com/microsoft/terminal/pull/6720

View file

@ -613,39 +613,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
return *result;
}
// Method Description:
// - Helper for doing something on each and every peasant, with no regard
// for if the peasant is living or dead.
// - We'll try calling callback on every peasant.
// - If any single peasant is dead, then we'll call errorCallback, and move on.
// - We're taking an errorCallback here, because the thing we usually want
// to do is TraceLog a message, but TraceLoggingWrite is actually a macro
// that _requires_ the second arg to be a string literal. It can't just be
// a variable.
// Arguments:
// - callback: The function to call on each peasant
// - errorCallback: The function to call if a peasant is dead.
// Return Value:
// - <none>
void Monarch::_forAllPeasantsIgnoringTheDead(std::function<void(const Remoting::IPeasant&, const uint64_t)> callback,
std::function<void(const uint64_t)> errorCallback)
{
for (const auto& [id, p] : _peasants)
{
try
{
callback(p, id);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
// If this fails, we don't _really_ care. Just move on to the
// next one. Someone else will clean up the dead peasant.
errorCallback(id);
}
}
}
// Method Description:
// - This is an event handler for the IdentifyWindowsRequested event. A
// Peasant may raise that event if they want _all_ windows to identify
@ -660,17 +627,18 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
// Notify all the peasants to display their ID.
auto callback = [](auto&& p, auto&& /*id*/) {
const auto callback = [&](const auto& /*id*/, const auto& p) {
p.DisplayWindowId();
};
auto onError = [](auto&& id) {
const auto onError = [&](const auto& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_identifyWindows_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not identify"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forAllPeasantsIgnoringTheDead(callback, onError);
_forEachPeasant(callback, onError);
}
// Method Description:
@ -812,48 +780,68 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// - <none>
// Return Value:
// - A map of peasant IDs to their names.
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> Monarch::GetPeasantNames()
Windows::Foundation::Collections::IVectorView<PeasantInfo> Monarch::GetPeasantInfos()
{
auto names = winrt::single_threaded_map<uint64_t, winrt::hstring>();
std::vector<PeasantInfo> names;
names.reserve(_peasants.size());
std::vector<uint64_t> peasantsToErase{};
for (const auto& [id, p] : _peasants)
{
try
const auto func = [&](const auto& id, const auto& p) -> void {
names.push_back({ id, p.WindowName(), p.ActiveTabTitle() });
};
const auto onError = [&](const auto& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_identifyWindows_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not identify"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forEachPeasant(func, onError);
return winrt::single_threaded_vector<PeasantInfo>(std::move(names)).GetView();
}
bool Monarch::DoesQuakeWindowExist()
{
bool result = false;
const auto func = [&](const auto& /*id*/, const auto& p) {
if (p.WindowName() == QuakeWindowName)
{
names.Insert(id, p.WindowName());
result = true;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
peasantsToErase.push_back(id);
}
}
// continue if we didn't get a positive result
return !result;
};
// Remove the dead peasants we came across while iterating.
for (const auto& id : peasantsToErase)
{
_peasants.erase(id);
_clearOldMruEntries(id);
}
const auto onError = [&](const auto& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_DoesQuakeWindowExist_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not ask for its name"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
return names.GetView();
_forEachPeasant(func, onError);
return result;
}
void Monarch::SummonAllWindows()
{
auto callback = [](auto&& p, auto&& /*id*/) {
const auto func = [&](const auto& /*id*/, const auto& p) {
SummonWindowBehavior args{};
args.ToggleVisibility(false);
p.Summon(args);
};
auto onError = [](auto&& id) {
const auto onError = [&](const auto& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SummonAll_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not summon"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forAllPeasantsIgnoringTheDead(callback, onError);
_forEachPeasant(func, onError);
}
}

View file

@ -53,7 +53,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
void SummonAllWindows();
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> GetPeasantNames();
bool DoesQuakeWindowExist();
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> GetPeasantInfos();
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
@ -89,6 +90,67 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void _renameRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args);
// Method Description:
// - Helper for doing something on each and every peasant.
// - We'll try calling func on every peasant.
// - If the return type of func is void, it will perform it for all peasants.
// - If the return type is a boolean, we'll break out of the loop once func
// returns false.
// - If any single peasant is dead, then we'll call onError and then add it to a
// list of peasants to clean up once the loop ends.
// Arguments:
// - func: The function to call on each peasant
// - onError: The function to call if a peasant is dead.
// Return Value:
// - <none>
template<typename F, typename T>
void _forEachPeasant(F&& func, T&& onError)
{
using Map = decltype(_peasants);
using R = std::invoke_result_t<F, Map::key_type, Map::mapped_type>;
static constexpr auto IsVoid = std::is_void_v<R>;
std::vector<uint64_t> peasantsToErase;
for (const auto& [id, p] : _peasants)
{
try
{
if constexpr (IsVoid)
{
func(id, p);
}
else
{
if (!func(id, p))
{
break;
}
}
}
catch (const winrt::hresult_error& exception)
{
onError(id);
if (exception.code() == 0x800706ba) // The RPC server is unavailable.
{
peasantsToErase.emplace_back(id);
}
else
{
LOG_CAUGHT_EXCEPTION();
throw;
}
}
}
for (const auto& id : peasantsToErase)
{
_peasants.erase(id);
_clearOldMruEntries(id);
}
}
friend class RemotingUnitTests::RemotingTests;
};
}

View file

@ -31,6 +31,12 @@ namespace Microsoft.Terminal.Remoting
Windows.Foundation.IReference<UInt64> WindowID;
}
struct PeasantInfo
{
UInt64 Id;
String Name;
String TabTitle;
};
[default_interface] runtimeclass Monarch {
Monarch();
@ -42,7 +48,8 @@ namespace Microsoft.Terminal.Remoting
void SummonWindow(SummonWindowSelectionArgs args);
void SummonAllWindows();
Windows.Foundation.Collections.IMapView<UInt64, String> GetPeasantNames { get; };
Boolean DoesQuakeWindowExist();
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos { get; };
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;

View file

@ -35,6 +35,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
winrt::Microsoft::Terminal::Remoting::CommandlineArgs InitialArgs();
WINRT_PROPERTY(winrt::hstring, WindowName);
WINRT_PROPERTY(winrt::hstring, ActiveTabTitle);
TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs);
TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::CommandlineArgs);

View file

@ -61,6 +61,7 @@ namespace Microsoft.Terminal.Remoting
void DisplayWindowId(); // Tells us to display its own ID (which causes a DisplayWindowIdRequested to be raised)
String WindowName { get; };
String ActiveTabTitle { get; };
void RequestIdentifyWindows(); // Tells us to raise a IdentifyWindowsRequested
void RequestRename(RenameRequestArgs args); // Tells us to raise a RenameRequested
void Summon(SummonWindowBehavior behavior);

View file

@ -519,11 +519,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
}
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> WindowManager::GetPeasantNames()
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> WindowManager::GetPeasantInfos()
{
// We should only get called when we're the monarch since the monarch
// is the only one that knows about all peasants.
return _monarch.GetPeasantNames();
return _monarch.GetPeasantInfos();
}
// Method Description:
@ -553,15 +553,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
bool WindowManager::DoesQuakeWindowExist()
{
const auto names = GetPeasantNames();
for (const auto [id, name] : names)
{
if (name == QuakeWindowName)
{
return true;
}
}
return false;
return _monarch.DoesQuakeWindowExist();
}
void WindowManager::UpdateActiveTabTitle(winrt::hstring title)
{
winrt::get_self<implementation::Peasant>(_peasant)->ActiveTabTitle(title);
}
}

View file

@ -41,11 +41,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
void SummonAllWindows();
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> GetPeasantNames();
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> GetPeasantInfos();
winrt::fire_and_forget RequestShowTrayIcon();
winrt::fire_and_forget RequestHideTrayIcon();
bool DoesQuakeWindowExist();
void UpdateActiveTabTitle(winrt::hstring title);
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(BecameMonarch, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);

View file

@ -15,8 +15,9 @@ namespace Microsoft.Terminal.Remoting
void SummonAllWindows();
void RequestShowTrayIcon();
void RequestHideTrayIcon();
void UpdateActiveTabTitle(String title);
Boolean DoesQuakeWindowExist();
Windows.Foundation.Collections.IMapView<UInt64, String> GetPeasantNames();
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos();
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> BecameMonarch;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;

View file

@ -45,7 +45,7 @@
Color="{ThemeResource SystemErrorTextColor}" />
<!-- Suppress top padding -->
<Thickness x:Key="TabViewHeaderPadding">8,0,8,0</Thickness>
<Thickness x:Key="TabViewHeaderPadding">9,0,8,0</Thickness>
<!-- Remove when implementing WinUI 2.6 -->
<Thickness x:Key="FlyoutContentPadding">12</Thickness>

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

@ -1478,4 +1478,9 @@ namespace winrt::TerminalApp::implementation
return false;
}
}
bool AppLogic::GetShowTitleInTitlebar()
{
return _settings.GlobalSettings().ShowTitleInTitlebar();
}
}

View file

@ -94,6 +94,7 @@ namespace winrt::TerminalApp::implementation
bool GetMinimizeToTray();
bool GetAlwaysShowTrayIcon();
bool GetShowTitleInTitlebar();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);

View file

@ -72,6 +72,7 @@ namespace TerminalApp
Boolean GetMinimizeToTray();
Boolean GetAlwaysShowTrayIcon();
Boolean GetShowTitleInTitlebar();
FindTargetWindowResult FindTargetWindow(String[] args);

View file

@ -1275,15 +1275,39 @@ namespace winrt::TerminalApp::implementation
void CommandPalette::_updateRecentCommands(const hstring& command)
{
const auto recentCommands = ApplicationState::SharedInstance().RecentCommands();
// If there aren't and recent commands already in the state, then we
// don't need to copy any.
const auto countToCopy = std::min(recentCommands ? recentCommands.Size() : 0, CommandLineHistoryLength - 1);
std::vector<hstring> newRecentCommands{ countToCopy + 1 };
til::at(newRecentCommands, 0) = command;
if (countToCopy)
// If this is the first time we've opened the commandline mode and
// there aren't any recent commands, then just store the new command.
if (!recentCommands)
{
recentCommands.GetMany(0, { newRecentCommands.data() + 1, countToCopy });
ApplicationState::SharedInstance().RecentCommands(single_threaded_vector(std::move(std::vector{ command })));
return;
}
const auto numNewRecentCommands = std::min(recentCommands.Size() + 1, CommandLineHistoryLength);
std::vector<hstring> newRecentCommands;
newRecentCommands.reserve(numNewRecentCommands);
std::unordered_set<hstring> uniqueCommands;
uniqueCommands.reserve(numNewRecentCommands);
newRecentCommands.push_back(command);
uniqueCommands.insert(command);
for (const auto& c : recentCommands)
{
if (newRecentCommands.size() >= CommandLineHistoryLength)
{
// Don't store more than CommandLineHistoryLength commands
break;
}
if (uniqueCommands.emplace(c).second)
{
newRecentCommands.push_back(c);
}
}
ApplicationState::SharedInstance().RecentCommands(single_threaded_vector(std::move(newRecentCommands)));
}
}

View file

@ -9,7 +9,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
HorizontalAlignment="Left"
VerticalAlignment="Top"
d:DesignHeight="36"
d:DesignHeight="40"
d:DesignWidth="400"
Background="Transparent"
Orientation="Horizontal"
@ -124,8 +124,9 @@
tabs will be flush with the top of the window. See GH#2541 for
details.
-->
<x:Double x:Key="CaptionButtonHeightWindowed">36.0</x:Double>
<x:Double x:Key="CaptionButtonHeightMaximized">32.0</x:Double>
<x:Double x:Key="CaptionButtonHeightWindowed">40.0</x:Double>
<!-- 32 + 1 to compensate for GH#10746 -->
<x:Double x:Key="CaptionButtonHeightMaximized">33.0</x:Double>
<Style x:Key="CaptionButton"
TargetType="Button">

View file

@ -227,10 +227,11 @@ bool Pane::ResizePane(const ResizeDirection& direction)
// Arguments:
// - sourcePane: the pane to navigate from
// - direction: which direction to go in
// - mruPanes: the list of most recently used panes, in order
// Return Value:
// - The result of navigating from source according to direction, which may be
// nullptr (i.e. no pane was found in that direction).
std::shared_ptr<Pane> Pane::NavigateDirection(const std::shared_ptr<Pane> sourcePane, const FocusDirection& direction)
std::shared_ptr<Pane> Pane::NavigateDirection(const std::shared_ptr<Pane> sourcePane, const FocusDirection& direction, const std::vector<uint32_t>& mruPanes)
{
// Can't navigate anywhere if we are a leaf
if (_IsLeaf())
@ -238,12 +239,23 @@ std::shared_ptr<Pane> Pane::NavigateDirection(const std::shared_ptr<Pane> source
return nullptr;
}
// If the MRU previous pane is requested we can't move; the tab handles MRU
if (direction == FocusDirection::None || direction == FocusDirection::Previous)
if (direction == FocusDirection::None)
{
return nullptr;
}
// Previous movement relies on the last used panes
if (direction == FocusDirection::Previous)
{
// If there is actually a previous pane.
if (mruPanes.size() > 1)
{
// This could return nullptr if the id is not actually in the tree.
return FindPane(mruPanes.at(1));
}
return nullptr;
}
// Check if we in-order traversal is requested
if (direction == FocusDirection::NextInOrder)
{
@ -281,11 +293,11 @@ std::shared_ptr<Pane> Pane::NavigateDirection(const std::shared_ptr<Pane> source
// and its neighbor must necessarily be contained within the same child.
if (!DirectionMatchesSplit(direction, _splitState))
{
if (auto p = _firstChild->NavigateDirection(sourcePane, direction))
if (const auto p = _firstChild->NavigateDirection(sourcePane, direction, mruPanes))
{
return p;
}
return _secondChild->NavigateDirection(sourcePane, direction);
return _secondChild->NavigateDirection(sourcePane, direction, mruPanes);
}
// Since the direction is the same as our split, it is possible that we must
@ -572,16 +584,14 @@ winrt::Windows::UI::Xaml::Controls::UserControl Pane::ReplaceControl(const winrt
}
// Method Description:
// - Given two panes, test whether the `direction` side of first is adjacent to second.
// - Given two panes' offsets, test whether the `direction` side of first is adjacent to second.
// Arguments:
// - first: The reference pane.
// - second: the pane to test adjacency with.
// - firstOffset: The offset for the reference pane
// - secondOffset: the offset to test adjacency with.
// - direction: The direction to search in from the reference pane.
// Return Value:
// - true if the two panes are adjacent.
bool Pane::_IsAdjacent(const std::shared_ptr<Pane> first,
const PanePoint firstOffset,
const std::shared_ptr<Pane> second,
bool Pane::_IsAdjacent(const PanePoint firstOffset,
const PanePoint secondOffset,
const FocusDirection& direction) const
{
@ -591,25 +601,11 @@ bool Pane::_IsAdjacent(const std::shared_ptr<Pane> first,
return abs(left - right) < 1e-4F;
};
auto getXMax = [](PanePoint offset, std::shared_ptr<Pane> pane) {
// If we are past startup panes should have real dimensions
if (pane->GetRootElement().ActualWidth() > 0)
{
return offset.x + gsl::narrow_cast<float>(pane->GetRootElement().ActualWidth());
}
// If we have simulated dimensions we rely on the calculated scale
auto getXMax = [](PanePoint offset) {
return offset.x + offset.scaleX;
};
auto getYMax = [](PanePoint offset, std::shared_ptr<Pane> pane) {
// If we are past startup panes should have real dimensions
if (pane->GetRootElement().ActualHeight() > 0)
{
return offset.y + gsl::narrow_cast<float>(pane->GetRootElement().ActualHeight());
}
// If we have simulated dimensions we rely on the calculated scale
auto getYMax = [](PanePoint offset) {
return offset.y + offset.scaleY;
};
@ -619,8 +615,8 @@ bool Pane::_IsAdjacent(const std::shared_ptr<Pane> first,
// corner of the first element is within the second element's height
if (direction == FocusDirection::Left)
{
auto sharesBorders = floatEqual(firstOffset.x, getXMax(secondOffset, second));
auto withinHeight = (firstOffset.y >= secondOffset.y) && (firstOffset.y < getYMax(secondOffset, second));
const auto sharesBorders = floatEqual(firstOffset.x, getXMax(secondOffset));
const auto withinHeight = (firstOffset.y >= secondOffset.y) && (firstOffset.y < getYMax(secondOffset));
return sharesBorders && withinHeight;
}
@ -629,8 +625,8 @@ bool Pane::_IsAdjacent(const std::shared_ptr<Pane> first,
// corner of the first element is within the second element's height
else if (direction == FocusDirection::Right)
{
auto sharesBorders = floatEqual(getXMax(firstOffset, first), secondOffset.x);
auto withinHeight = (firstOffset.y >= secondOffset.y) && (firstOffset.y < getYMax(secondOffset, second));
const auto sharesBorders = floatEqual(getXMax(firstOffset), secondOffset.x);
const auto withinHeight = (firstOffset.y >= secondOffset.y) && (firstOffset.y < getYMax(secondOffset));
return sharesBorders && withinHeight;
}
@ -639,8 +635,8 @@ bool Pane::_IsAdjacent(const std::shared_ptr<Pane> first,
// corner of the first element is within the second element's width
else if (direction == FocusDirection::Up)
{
auto sharesBorders = floatEqual(firstOffset.y, getYMax(secondOffset, second));
auto withinWidth = (firstOffset.x >= secondOffset.x) && (firstOffset.x < getXMax(secondOffset, second));
const auto sharesBorders = floatEqual(firstOffset.y, getYMax(secondOffset));
const auto withinWidth = (firstOffset.x >= secondOffset.x) && (firstOffset.x < getXMax(secondOffset));
return sharesBorders && withinWidth;
}
@ -649,8 +645,8 @@ bool Pane::_IsAdjacent(const std::shared_ptr<Pane> first,
// corner of the first element is within the second element's width
else if (direction == FocusDirection::Down)
{
auto sharesBorders = floatEqual(getYMax(firstOffset, first), secondOffset.y);
auto withinWidth = (firstOffset.x >= secondOffset.x) && (firstOffset.x < getXMax(secondOffset, second));
const auto sharesBorders = floatEqual(getYMax(firstOffset), secondOffset.y);
const auto withinWidth = (firstOffset.x >= secondOffset.x) && (firstOffset.x < getXMax(secondOffset));
return sharesBorders && withinWidth;
}
@ -671,51 +667,25 @@ std::pair<Pane::PanePoint, Pane::PanePoint> Pane::_GetOffsetsForPane(const PaneP
auto firstOffset = parentOffset;
auto secondOffset = parentOffset;
// When panes are initialized they don't have dimensions yet.
if (_firstChild->GetRootElement().ActualHeight() > 0)
// Make up fake dimensions using an exponential layout. This is useful
// since we might need to navigate when there are panes not attached to
// the ui tree, such as initialization, command running, and zoom.
// Basically create the tree layout on the fly by partitioning [0,1].
// This could run into issues if the tree depth is >127 (or other
// degenerate splits) as a float's mantissa only has so many bits of
// precision.
if (_splitState == SplitState::Horizontal)
{
// The second child has an offset depending on the split
if (_splitState == SplitState::Horizontal)
{
const auto diff = gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualHeight());
secondOffset.y += diff;
// However, if a command is run in an existing window that opens multiple new panes
// the parent will have a size (triggering this) and then the children will go
// to the other branch.
firstOffset.scaleY = diff;
secondOffset.scaleY = parentOffset.scaleY - diff;
}
else
{
const auto diff = gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualWidth());
secondOffset.x += diff;
firstOffset.scaleX = diff;
secondOffset.scaleX = parentOffset.scaleX - diff;
}
secondOffset.y += (1 - _desiredSplitPosition) * parentOffset.scaleY;
firstOffset.scaleY *= _desiredSplitPosition;
secondOffset.scaleY *= (1 - _desiredSplitPosition);
}
else
{
// Since we don't have real dimensions make up fake ones using an
// exponential layout. Basically create the tree layout on the fly by
// partitioning [0,1].
// This could run into issues if the tree depth is >127 (or other
// degenerate splits) as a float's mantissa only has so many bits of
// precision.
// In theory this could always be used, but there might be edge cases
// where using the actual sizing information provides a better result.
if (_splitState == SplitState::Horizontal)
{
secondOffset.y += (1 - _desiredSplitPosition) * parentOffset.scaleY;
firstOffset.scaleY *= _desiredSplitPosition;
secondOffset.scaleY *= (1 - _desiredSplitPosition);
}
else
{
secondOffset.x += (1 - _desiredSplitPosition) * parentOffset.scaleX;
firstOffset.scaleX *= _desiredSplitPosition;
secondOffset.scaleX *= (1 - _desiredSplitPosition);
}
secondOffset.x += (1 - _desiredSplitPosition) * parentOffset.scaleX;
firstOffset.scaleX *= _desiredSplitPosition;
secondOffset.scaleX *= (1 - _desiredSplitPosition);
}
return { firstOffset, secondOffset };
@ -752,7 +722,7 @@ Pane::PaneNeighborSearch Pane::_FindNeighborForPane(const FocusDirection& direct
// If we are a leaf node test if we adjacent to the focus node
if (_IsLeaf())
{
if (_IsAdjacent(searchResult.source, searchResult.sourceOffset, shared_from_this(), offset, direction))
if (_IsAdjacent(searchResult.sourceOffset, offset, direction))
{
searchResult.neighbor = shared_from_this();
}

View file

@ -76,7 +76,9 @@ public:
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
void Relayout();
bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
std::shared_ptr<Pane> NavigateDirection(const std::shared_ptr<Pane> sourcePane, const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
std::shared_ptr<Pane> NavigateDirection(const std::shared_ptr<Pane> sourcePane,
const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction,
const std::vector<uint32_t>& mruPanes);
bool SwapPanes(std::shared_ptr<Pane> first, std::shared_ptr<Pane> second);
std::shared_ptr<Pane> NextPane(const std::shared_ptr<Pane> pane);
@ -204,7 +206,7 @@ private:
std::shared_ptr<Pane> _FindParentOfPane(const std::shared_ptr<Pane> pane);
std::pair<PanePoint, PanePoint> _GetOffsetsForPane(const PanePoint parentOffset) const;
bool _IsAdjacent(const std::shared_ptr<Pane> first, const PanePoint firstOffset, const std::shared_ptr<Pane> second, const PanePoint secondOffset, const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction) const;
bool _IsAdjacent(const PanePoint firstOffset, const PanePoint secondOffset, const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction) const;
PaneNeighborSearch _FindNeighborForPane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction,
PaneNeighborSearch searchResult,
const bool focusIsSecondSide,

View file

@ -611,9 +611,13 @@ namespace winrt::TerminalApp::implementation
tabIndex = std::clamp(tabIndex, 0u, _tabs.Size() - 1);
auto tab{ _tabs.GetAt(tabIndex) };
// GH#11107 - Always just set the item directly first so that if
// tab movement is done as part of multiple actions following calls
// to _GetFocusedTab will return the correct tab.
_tabView.SelectedItem(tab.TabViewItem());
if (_startupState == StartupState::InStartup)
{
_tabView.SelectedItem(tab.TabViewItem());
_UpdatedSelectedTab(tab);
}
else

View file

@ -1034,7 +1034,7 @@ namespace winrt::TerminalApp::implementation
{
auto newTabTitle = tab.Title();
if (_settings.GlobalSettings().ShowTitleInTitlebar() && tab == _GetFocusedTab())
if (tab == _GetFocusedTab())
{
_TitleChangedHandlers(*this, newTabTitle);
}
@ -1179,7 +1179,6 @@ namespace winrt::TerminalApp::implementation
{
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
_UnZoomIfNeeded();
return terminalTab->NavigateFocus(direction);
}
return false;

View file

@ -652,26 +652,21 @@ namespace winrt::TerminalApp::implementation
// to the terminal when no other panes are present (GH#6219)
bool TerminalTab::NavigateFocus(const FocusDirection& direction)
{
if (direction == FocusDirection::Previous)
// NOTE: This _must_ be called on the root pane, so that it can propagate
// throughout the entire tree.
if (const auto newFocus = _rootPane->NavigateDirection(_activePane, direction, _mruPanes))
{
if (_mruPanes.size() < 2)
const auto res = _rootPane->FocusPane(newFocus);
if (_zoomedPane)
{
return false;
}
// To get to the previous pane, get the id of the previous pane and focus to that
return _rootPane->FocusPane(_mruPanes.at(1));
}
else
{
// NOTE: This _must_ be called on the root pane, so that it can propagate
// throughout the entire tree.
if (auto newFocus = _rootPane->NavigateDirection(_activePane, direction))
{
return _rootPane->FocusPane(newFocus);
UpdateZoom(newFocus);
}
return false;
return res;
}
return false;
}
// Method Description:
@ -684,27 +679,11 @@ namespace winrt::TerminalApp::implementation
// - true if two panes were swapped.
bool TerminalTab::SwapPane(const FocusDirection& direction)
{
if (direction == FocusDirection::Previous)
// NOTE: This _must_ be called on the root pane, so that it can propagate
// throughout the entire tree.
if (auto neighbor = _rootPane->NavigateDirection(_activePane, direction, _mruPanes))
{
if (_mruPanes.size() < 2)
{
return false;
}
if (auto lastPane = _rootPane->FindPane(_mruPanes.at(1)))
{
return _rootPane->SwapPanes(_activePane, lastPane);
}
}
else
{
// NOTE: This _must_ be called on the root pane, so that it can propagate
// throughout the entire tree.
if (auto neighbor = _rootPane->NavigateDirection(_activePane, direction))
{
return _rootPane->SwapPanes(_activePane, neighbor);
}
return false;
return _rootPane->SwapPanes(_activePane, neighbor);
}
return false;
@ -1519,6 +1498,22 @@ namespace winrt::TerminalApp::implementation
return _rootPane->PreCalculateCanSplit(_activePane, splitType, splitSize, availableSpace).value_or(false);
}
// Method Description:
// - Updates the zoomed pane when the focus changes
// Arguments:
// - newFocus: the new pane to be zoomed
// Return Value:
// - <none>
void TerminalTab::UpdateZoom(std::shared_ptr<Pane> newFocus)
{
// clear the existing content so the old zoomed pane can be added back to the root tree
Content(nullptr);
_rootPane->Restore(_zoomedPane);
_zoomedPane = newFocus;
_rootPane->Maximize(_zoomedPane);
Content(_zoomedPane->GetRootElement());
}
// Method Description:
// - Toggle our zoom state.
// * If we're not zoomed, then zoom the active pane, making it take the

View file

@ -80,6 +80,7 @@ namespace winrt::TerminalApp::implementation
void ResetRuntimeTabColor();
void ActivateColorPicker();
void UpdateZoom(std::shared_ptr<Pane> newFocus);
void ToggleZoom();
bool IsZoomed();
void EnterZoom();

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

@ -239,6 +239,7 @@
VerticalAlignment="Center"
CornerRadius="2"
FontSize="15"
IsSpellCheckEnabled="False"
KeyDown="TextBoxKeyDown"
PlaceholderForeground="{ThemeResource TextBoxPlaceholderTextThemeBrush}" />

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

@ -206,6 +206,7 @@
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBox IsEnabled="{x:Bind local:Converters.StringsAreNotEqual('desktopWallpaper', Appearance.BackgroundImagePath), Mode=OneWay}"
IsSpellCheckEnabled="False"
Style="{StaticResource TextBoxSettingStyle}"
Text="{x:Bind local:Converters.StringFallBackToEmptyString('desktopWallpaper', Appearance.BackgroundImagePath), Mode=TwoWay, BindBack=Appearance.SetBackgroundImagePath}" />
<Button x:Uid="Profile_BackgroundImageBrowse"

View file

@ -46,7 +46,8 @@
<!-- Word Delimiters -->
<local:SettingContainer x:Uid="Globals_WordDelimiters">
<TextBox Style="{StaticResource TextBoxSettingStyle}"
<TextBox IsSpellCheckEnabled="False"
Style="{StaticResource TextBoxSettingStyle}"
Text="{x:Bind State.Globals.WordDelimiters, Mode=TwoWay}" />
</local:SettingContainer>

View file

@ -81,7 +81,8 @@
SettingOverrideSource="{x:Bind State.Profile.CommandlineOverrideSource, Mode=OneWay}"
Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(State.Profile.IsBaseLayer), Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<TextBox Style="{StaticResource TextBoxSettingStyle}"
<TextBox IsSpellCheckEnabled="False"
Style="{StaticResource TextBoxSettingStyle}"
Text="{x:Bind State.Profile.Commandline, Mode=TwoWay}" />
<Button x:Uid="Profile_CommandlineBrowse"
Click="Commandline_Click"
@ -99,6 +100,7 @@
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBox IsEnabled="{x:Bind State.Profile.UseCustomStartingDirectory, Mode=OneWay}"
IsSpellCheckEnabled="False"
Style="{StaticResource TextBoxSettingStyle}"
Text="{x:Bind State.Profile.StartingDirectory, Mode=TwoWay}" />
<Button x:Name="StartingDirectoryBrowse"
@ -120,6 +122,7 @@
SettingOverrideSource="{x:Bind State.Profile.IconOverrideSource, Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<TextBox FontFamily="Segoe UI, Segoe MDL2 Assets"
IsSpellCheckEnabled="False"
Style="{StaticResource TextBoxSettingStyle}"
Text="{x:Bind State.Profile.Icon, Mode=TwoWay}" />
<Button x:Uid="Profile_IconBrowse"

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

@ -62,6 +62,8 @@ namespace RemotingUnitTests
void AssignID(uint64_t /*id*/) { throw winrt::hresult_error{}; };
uint64_t GetID() { throw winrt::hresult_error{}; };
winrt::hstring WindowName() { throw winrt::hresult_error{}; };
winrt::hstring ActiveTabTitle() { throw winrt::hresult_error{}; };
void ActiveTabTitle(const winrt::hstring& /*value*/) { throw winrt::hresult_error{}; };
uint64_t GetPID() { throw winrt::hresult_error{}; };
bool ExecuteCommandline(const Remoting::CommandlineArgs& /*args*/) { throw winrt::hresult_error{}; }
void ActivateWindow(const Remoting::WindowActivatedArgs& /*args*/) { throw winrt::hresult_error{}; }

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

@ -299,8 +299,9 @@ void AppHost::Initialize()
}
// Method Description:
// - Called when the app's title changes. Fires off a window message so we can
// update the window's title on the main thread.
// - Called everytime when the active tab's title changes. We'll also fire off
// a window message so we can update the window's title on the main thread,
// though we'll only do so if the settings are configured for that.
// Arguments:
// - sender: unused
// - newTitle: the string to use as the new window title
@ -308,7 +309,11 @@ void AppHost::Initialize()
// - <none>
void AppHost::AppTitleChanged(const winrt::Windows::Foundation::IInspectable& /*sender*/, winrt::hstring newTitle)
{
_window->UpdateTitle(newTitle);
if (_logic.GetShowTitleInTitlebar())
{
_window->UpdateTitle(newTitle);
}
_windowManager.UpdateActiveTabTitle(newTitle);
}
// Method Description:
@ -1031,7 +1036,7 @@ void AppHost::_CreateTrayIcon()
// Hookup the handlers, save the tokens for revoking if settings change.
_ReAddTrayIconToken = _window->NotifyReAddTrayIcon([this]() { _trayIcon->ReAddTrayIcon(); });
_TrayIconPressedToken = _window->NotifyTrayIconPressed([this]() { _trayIcon->TrayIconPressed(); });
_ShowTrayContextMenuToken = _window->NotifyShowTrayContextMenu([this](til::point coord) { _trayIcon->ShowTrayContextMenu(coord, _windowManager.GetPeasantNames()); });
_ShowTrayContextMenuToken = _window->NotifyShowTrayContextMenu([this](til::point coord) { _trayIcon->ShowTrayContextMenu(coord, _windowManager.GetPeasantInfos()); });
_TrayMenuItemSelectedToken = _window->NotifyTrayMenuItemSelected([this](HMENU hm, UINT idx) { _trayIcon->TrayMenuItemSelected(hm, idx); });
_trayIcon->SummonWindowRequested([this](auto& args) { _windowManager.SummonWindow(args); });
}

View file

@ -109,8 +109,8 @@ void TrayIcon::CreateTrayIcon()
// - peasants: The map of all peasants that should be available in the context menu.
// Return Value:
// - <none>
void TrayIcon::ShowTrayContextMenu(const til::point coord,
IMapView<uint64_t, winrt::hstring> peasants)
void TrayIcon::ShowTrayContextMenu(const til::point& coord,
const IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo>& peasants)
{
if (const auto hMenu = _CreateTrayContextMenu(peasants))
{
@ -142,7 +142,7 @@ void TrayIcon::ShowTrayContextMenu(const til::point coord,
// - peasants: A map of all peasants' ID to their window name.
// Return Value:
// - The handle to the newly created context menu.
HMENU TrayIcon::_CreateTrayContextMenu(IMapView<uint64_t, winrt::hstring> peasants)
HMENU TrayIcon::_CreateTrayContextMenu(const IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo>& peasants)
{
auto hMenu = CreatePopupMenu();
if (hMenu)
@ -161,17 +161,22 @@ HMENU TrayIcon::_CreateTrayContextMenu(IMapView<uint64_t, winrt::hstring> peasan
// Submenu for Windows
if (auto submenu = CreatePopupMenu())
{
const auto locWindow = RS_(L"WindowIdLabel");
const auto locUnnamed = RS_(L"UnnamedWindowName");
for (const auto [id, name] : peasants)
for (const auto& p : peasants)
{
winrt::hstring displayText = name;
if (name.empty())
std::wstringstream displayText;
displayText << L"#" << p.Id;
if (!p.TabTitle.empty())
{
displayText = fmt::format(L"{} {} - <{}>", locWindow, id, locUnnamed);
displayText << L": " << std::wstring_view{ p.TabTitle };
}
AppendMenu(submenu, MF_STRING, gsl::narrow<UINT_PTR>(id), displayText.c_str());
if (!p.Name.empty())
{
displayText << L" [" << std::wstring_view{ p.Name } << L"]";
}
AppendMenu(submenu, MF_STRING, gsl::narrow<UINT_PTR>(p.Id), displayText.str().c_str());
}
MENUINFO submenuInfo{};

View file

@ -24,14 +24,14 @@ public:
void ReAddTrayIcon();
void TrayIconPressed();
void ShowTrayContextMenu(const til::point coord, winrt::Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> peasants);
void ShowTrayContextMenu(const til::point& coord, const winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo>& peasants);
void TrayMenuItemSelected(const HMENU menu, const UINT menuItemIndex);
WINRT_CALLBACK(SummonWindowRequested, winrt::delegate<void(winrt::Microsoft::Terminal::Remoting::SummonWindowSelectionArgs)>);
private:
void _CreateWindow();
HMENU _CreateTrayContextMenu(winrt::Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> peasants);
HMENU _CreateTrayContextMenu(const winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo>& peasants);
wil::unique_hwnd _trayIconHwnd;
HWND _owningHwnd;

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

@ -14,8 +14,6 @@ terminal and the console without a lot of work.
This script has both some "simple" emoji - burrito, cheese, etc. and some more
complex ones - WOMAN COOK is actually two emoji with a zero width joiner.
"""
import sys
import time # time.sleep is in seconds
from common import *
# Run this file with:

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);