mirror of
https://github.com/microsoft/terminal.git
synced 2026-07-09 02:26:45 +00:00
Add buttons for selecting commands, output to context menu (#15020)
Adds a "Select command" and a "Select output" entry to the right-click context menu when the user has shell integration enabled. This lets the user quickly right-click on a command and select the entire commandline or all of its output. This was a "I'm waiting for reviews" sorta idea. Seemed like a reasonable combination of features. Related to #13445, #11000. Tested manually. --------- Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
This commit is contained in:
@@ -2182,10 +2182,21 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void ControlCore::_selectSpan(til::point_span s)
|
||||
{
|
||||
const auto bufferSize{ _terminal->GetTextBuffer().GetSize() };
|
||||
bufferSize.DecrementInBounds(s.end);
|
||||
|
||||
auto lock = _terminal->LockForWriting();
|
||||
_terminal->SelectNewRegion(s.start, s.end);
|
||||
_renderer->TriggerSelection();
|
||||
}
|
||||
|
||||
void ControlCore::SelectCommand(const bool goUp)
|
||||
{
|
||||
const til::point start = HasSelection() ? (goUp ? _terminal->GetSelectionAnchor() : _terminal->GetSelectionEnd()) :
|
||||
_terminal->GetTextBuffer().GetCursor().GetPosition();
|
||||
|
||||
std::optional<DispatchTypes::ScrollMark> nearest{ std::nullopt };
|
||||
const auto& marks{ _terminal->GetScrollMarks() };
|
||||
|
||||
@@ -2217,13 +2228,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
const auto start = nearest->end;
|
||||
auto end = *nearest->commandEnd;
|
||||
|
||||
const auto bufferSize{ _terminal->GetTextBuffer().GetSize() };
|
||||
bufferSize.DecrementInBounds(end);
|
||||
|
||||
auto lock = _terminal->LockForWriting();
|
||||
_terminal->SelectNewRegion(start, end);
|
||||
_renderer->TriggerSelection();
|
||||
_selectSpan(til::point_span{ start, end });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2231,6 +2236,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
const til::point start = HasSelection() ? (goUp ? _terminal->GetSelectionAnchor() : _terminal->GetSelectionEnd()) :
|
||||
_terminal->GetTextBuffer().GetCursor().GetPosition();
|
||||
|
||||
std::optional<DispatchTypes::ScrollMark> nearest{ std::nullopt };
|
||||
const auto& marks{ _terminal->GetScrollMarks() };
|
||||
|
||||
@@ -2256,13 +2262,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
const auto start = *nearest->commandEnd;
|
||||
auto end = *nearest->outputEnd;
|
||||
|
||||
const auto bufferSize{ _terminal->GetTextBuffer().GetSize() };
|
||||
bufferSize.DecrementInBounds(end);
|
||||
|
||||
auto lock = _terminal->LockForWriting();
|
||||
_terminal->SelectNewRegion(start, end);
|
||||
_renderer->TriggerSelection();
|
||||
_selectSpan(til::point_span{ start, end });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2301,4 +2301,110 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ControlCore::AnchorContextMenu(const til::point viewportRelativeCharacterPosition)
|
||||
{
|
||||
// viewportRelativeCharacterPosition is relative to the current
|
||||
// viewport, so adjust for that:
|
||||
_contextMenuBufferPosition = _terminal->GetViewport().Origin() + viewportRelativeCharacterPosition;
|
||||
}
|
||||
|
||||
void ControlCore::_contextMenuSelectMark(
|
||||
const til::point& pos,
|
||||
const std::function<bool(const DispatchTypes::ScrollMark&)>& filter,
|
||||
const std::function<til::point_span(const DispatchTypes::ScrollMark&)>& getSpan)
|
||||
{
|
||||
// Do nothing if the caller didn't give us a way to get the span to select for this mark.
|
||||
if (!getSpan)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const auto& marks{ _terminal->GetScrollMarks() };
|
||||
for (auto&& m : marks)
|
||||
{
|
||||
// If the caller gave us a way to filter marks, check that now.
|
||||
// This can be used to filter to only marks that have a command, or output.
|
||||
if (filter && filter(m))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// If they clicked _anywhere_ in the mark...
|
||||
const auto [markStart, markEnd] = m.GetExtent();
|
||||
if (markStart <= pos &&
|
||||
markEnd >= pos)
|
||||
{
|
||||
// ... select the part of the mark the caller told us about.
|
||||
_selectSpan(getSpan(m));
|
||||
// And quick bail
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ControlCore::ContextMenuSelectCommand()
|
||||
{
|
||||
_contextMenuSelectMark(
|
||||
_contextMenuBufferPosition,
|
||||
[](const DispatchTypes::ScrollMark& m) -> bool { return !m.HasCommand(); },
|
||||
[](const DispatchTypes::ScrollMark& m) { return til::point_span{ m.end, *m.commandEnd }; });
|
||||
}
|
||||
void ControlCore::ContextMenuSelectOutput()
|
||||
{
|
||||
_contextMenuSelectMark(
|
||||
_contextMenuBufferPosition,
|
||||
[](const DispatchTypes::ScrollMark& m) -> bool { return !m.HasOutput(); },
|
||||
[](const DispatchTypes::ScrollMark& m) { return til::point_span{ *m.commandEnd, *m.outputEnd }; });
|
||||
}
|
||||
|
||||
bool ControlCore::_clickedOnMark(
|
||||
const til::point& pos,
|
||||
const std::function<bool(const DispatchTypes::ScrollMark&)>& filter)
|
||||
{
|
||||
// Don't show this if the click was on the selection
|
||||
if (_terminal->IsSelectionActive() &&
|
||||
_terminal->GetSelectionAnchor() <= pos &&
|
||||
_terminal->GetSelectionEnd() >= pos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// DO show this if the click was on a mark with a command
|
||||
const auto& marks{ _terminal->GetScrollMarks() };
|
||||
for (auto&& m : marks)
|
||||
{
|
||||
if (filter && filter(m))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const auto [start, end] = m.GetExtent();
|
||||
if (start <= pos &&
|
||||
end >= pos)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Didn't click on a mark with a command - don't show.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// * Don't show this if the click was on the _current_ selection
|
||||
// * Don't show this if the click wasn't on a mark with at least a command
|
||||
// * Otherwise yea, show it.
|
||||
bool ControlCore::ShouldShowSelectCommand()
|
||||
{
|
||||
// Relies on the anchor set in AnchorContextMenu
|
||||
return _clickedOnMark(_contextMenuBufferPosition,
|
||||
[](const DispatchTypes::ScrollMark& m) -> bool { return !m.HasCommand(); });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// * Same as ShouldShowSelectCommand, but with the mark needing output
|
||||
bool ControlCore::ShouldShowSelectOutput()
|
||||
{
|
||||
// Relies on the anchor set in AnchorContextMenu
|
||||
return _clickedOnMark(_contextMenuBufferPosition,
|
||||
[](const DispatchTypes::ScrollMark& m) -> bool { return !m.HasOutput(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,8 +152,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void ClearMark();
|
||||
void ClearAllMarks();
|
||||
void ScrollToMark(const Control::ScrollToMarkDirection& direction);
|
||||
|
||||
void SelectCommand(const bool goUp);
|
||||
void SelectOutput(const bool goUp);
|
||||
|
||||
void ContextMenuSelectCommand();
|
||||
void ContextMenuSelectOutput();
|
||||
#pragma endregion
|
||||
|
||||
#pragma region ITerminalInput
|
||||
@@ -220,6 +224,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
uint64_t OwningHwnd();
|
||||
void OwningHwnd(uint64_t owner);
|
||||
|
||||
void AnchorContextMenu(til::point viewportRelativeCharacterPosition);
|
||||
|
||||
bool ShouldShowSelectCommand();
|
||||
bool ShouldShowSelectOutput();
|
||||
|
||||
RUNTIME_SETTING(double, Opacity, _settings->Opacity());
|
||||
RUNTIME_SETTING(bool, UseAcrylic, _settings->UseAcrylic());
|
||||
|
||||
@@ -304,6 +313,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
std::unique_ptr<til::throttled_func_trailing<>> _updatePatternLocations;
|
||||
std::shared_ptr<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>> _updateScrollBar;
|
||||
|
||||
til::point _contextMenuBufferPosition{ 0, 0 };
|
||||
|
||||
void _setupDispatcherAndCallbacks();
|
||||
|
||||
bool _setFontSizeUnderLock(float fontSize);
|
||||
@@ -349,6 +360,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
bool _isBackgroundTransparent();
|
||||
void _focusChanged(bool focused);
|
||||
|
||||
void _selectSpan(til::point_span s);
|
||||
|
||||
void _contextMenuSelectMark(
|
||||
const til::point& pos,
|
||||
const std::function<bool(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark&)>& filter,
|
||||
const std::function<til::point_span(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark&)>& getSpan);
|
||||
|
||||
bool _clickedOnMark(const til::point& pos, const std::function<bool(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark&)>& filter);
|
||||
|
||||
inline bool _IsClosing() const noexcept
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
|
||||
@@ -142,6 +142,11 @@ namespace Microsoft.Terminal.Control
|
||||
|
||||
void ColorSelection(SelectionColor fg, SelectionColor bg, Microsoft.Terminal.Core.MatchMode matchMode);
|
||||
|
||||
void ContextMenuSelectCommand();
|
||||
void ContextMenuSelectOutput();
|
||||
Boolean ShouldShowSelectCommand();
|
||||
Boolean ShouldShowSelectOutput();
|
||||
|
||||
event FontSizeChangedEventArgs FontSizeChanged;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, CopyToClipboardEventArgs> CopyToClipboard;
|
||||
|
||||
@@ -310,6 +310,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
if (_core->Settings().RightClickContextMenu())
|
||||
{
|
||||
// Let the core know we're about to open a menu here. It has
|
||||
// some separate conditional logic based on _where_ the user
|
||||
// wanted to open the menu.
|
||||
_core->AnchorContextMenu(terminalPosition);
|
||||
|
||||
auto contextArgs = winrt::make<ContextMenuRequestedEventArgs>(til::point{ pixelPosition }.to_winrt_point());
|
||||
_ContextMenuRequestedHandlers(*this, contextArgs);
|
||||
}
|
||||
|
||||
@@ -240,4 +240,36 @@ Please either install the missing font or choose another one.</value>
|
||||
<value>Find</value>
|
||||
<comment>The tooltip for a button for searching for the selected text</comment>
|
||||
</data>
|
||||
<data name="SelectCommandButton.Label" xml:space="preserve">
|
||||
<value>Select command</value>
|
||||
<comment>The label of a button for selecting all of the text of a command</comment>
|
||||
</data>
|
||||
<data name="SelectCommandButton.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Select command</value>
|
||||
<comment>The tooltip for a button for selecting all of the text of a command</comment>
|
||||
</data>
|
||||
<data name="SelectOutputButton.Label" xml:space="preserve">
|
||||
<value>Select output</value>
|
||||
<comment>The label of a button for selecting all of a command's output</comment>
|
||||
</data>
|
||||
<data name="SelectOutputButton.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Select output</value>
|
||||
<comment>The tooltip for a button for selecting all of a command's output</comment>
|
||||
</data>
|
||||
<data name="SelectCommandWithSelectionButton.Label" xml:space="preserve">
|
||||
<value>Select command</value>
|
||||
<comment>The label of a button for selecting all of the text of a command</comment>
|
||||
</data>
|
||||
<data name="SelectCommandWithSelectionButton.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Select command</value>
|
||||
<comment>The tooltip for a button for selecting all of the text of a command</comment>
|
||||
</data>
|
||||
<data name="SelectOutputWithSelectionButton.Label" xml:space="preserve">
|
||||
<value>Select output</value>
|
||||
<comment>The label of a button for selecting all of a command's output</comment>
|
||||
</data>
|
||||
<data name="SelectOutputWithSelectionButton.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Select output</value>
|
||||
<comment>The tooltip for a button for selecting all of a command's output</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -3350,6 +3350,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
const auto pos = (absolutePointerPos - absoluteWindowOrigin - controlOrigin).to_winrt_point();
|
||||
myOption.Position(pos);
|
||||
|
||||
// The "Select command" and "Select output" buttons should only be
|
||||
// visible if shell integration is actually turned on.
|
||||
const auto shouldShowSelectCommand{ _core.ShouldShowSelectCommand() };
|
||||
const auto shouldShowSelectOutput{ _core.ShouldShowSelectOutput() };
|
||||
SelectCommandButton().Visibility(shouldShowSelectCommand ? Visibility::Visible : Visibility::Collapsed);
|
||||
SelectOutputButton().Visibility(shouldShowSelectOutput ? Visibility::Visible : Visibility::Collapsed);
|
||||
SelectCommandWithSelectionButton().Visibility(shouldShowSelectCommand ? Visibility::Visible : Visibility::Collapsed);
|
||||
SelectOutputWithSelectionButton().Visibility(shouldShowSelectOutput ? Visibility::Visible : Visibility::Collapsed);
|
||||
|
||||
(_core.HasSelection() ? SelectionContextMenu() :
|
||||
ContextMenu())
|
||||
.ShowAt(*this, myOption);
|
||||
@@ -3378,4 +3387,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
SearchMatch(false);
|
||||
}
|
||||
|
||||
void TermControl::_SelectCommandHandler(const IInspectable& /*sender*/,
|
||||
const IInspectable& /*args*/)
|
||||
{
|
||||
ContextMenu().Hide();
|
||||
SelectionContextMenu().Hide();
|
||||
_core.ContextMenuSelectCommand();
|
||||
}
|
||||
|
||||
void TermControl::_SelectOutputHandler(const IInspectable& /*sender*/,
|
||||
const IInspectable& /*args*/)
|
||||
{
|
||||
ContextMenu().Hide();
|
||||
SelectionContextMenu().Hide();
|
||||
_core.ContextMenuSelectOutput();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,10 +232,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
bool _isBackgroundLight{ false };
|
||||
bool _detached{ false };
|
||||
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<winrt::Windows::UI::Xaml::Controls::ICommandBarElement> _originalPrimaryElements{ nullptr };
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<winrt::Windows::UI::Xaml::Controls::ICommandBarElement> _originalSecondaryElements{ nullptr };
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<winrt::Windows::UI::Xaml::Controls::ICommandBarElement> _originalSelectedPrimaryElements{ nullptr };
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<winrt::Windows::UI::Xaml::Controls::ICommandBarElement> _originalSelectedSecondaryElements{ nullptr };
|
||||
Windows::Foundation::Collections::IObservableVector<Windows::UI::Xaml::Controls::ICommandBarElement> _originalPrimaryElements{ nullptr };
|
||||
Windows::Foundation::Collections::IObservableVector<Windows::UI::Xaml::Controls::ICommandBarElement> _originalSecondaryElements{ nullptr };
|
||||
Windows::Foundation::Collections::IObservableVector<Windows::UI::Xaml::Controls::ICommandBarElement> _originalSelectedPrimaryElements{ nullptr };
|
||||
Windows::Foundation::Collections::IObservableVector<Windows::UI::Xaml::Controls::ICommandBarElement> _originalSelectedSecondaryElements{ nullptr };
|
||||
|
||||
inline bool _IsClosing() const noexcept
|
||||
{
|
||||
@@ -347,6 +347,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void _CopyCommandHandler(const IInspectable& sender, const IInspectable& args);
|
||||
void _SearchCommandHandler(const IInspectable& sender, const IInspectable& args);
|
||||
|
||||
void _SelectCommandHandler(const IInspectable& sender, const IInspectable& args);
|
||||
void _SelectOutputHandler(const IInspectable& sender, const IInspectable& args);
|
||||
|
||||
struct Revokers
|
||||
{
|
||||
Control::ControlCore::ScrollPositionChanged_revoker coreScrollPositionChanged;
|
||||
|
||||
@@ -37,6 +37,21 @@
|
||||
x:Uid="PasteCommandButton"
|
||||
Click="_PasteCommandHandler"
|
||||
Icon="Paste" />
|
||||
<mux:CommandBarFlyout.SecondaryCommands>
|
||||
<AppBarButton x:Name="SelectCommandButton"
|
||||
x:Uid="SelectCommandButton"
|
||||
Click="_SelectCommandHandler">
|
||||
<AppBarButton.Icon>
|
||||
<FontIcon FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="11"
|
||||
Glyph="" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
<AppBarButton x:Name="SelectOutputButton"
|
||||
x:Uid="SelectOutputButton"
|
||||
Click="_SelectOutputHandler"
|
||||
Icon="AlignLeft" />
|
||||
</mux:CommandBarFlyout.SecondaryCommands>
|
||||
</mux:CommandBarFlyout>
|
||||
|
||||
<mux:CommandBarFlyout x:Name="SelectionContextMenu">
|
||||
@@ -53,6 +68,19 @@
|
||||
x:Uid="SearchCommandButton"
|
||||
Click="_SearchCommandHandler"
|
||||
Icon="Find" />
|
||||
<AppBarButton x:Name="SelectCommandWithSelectionButton"
|
||||
x:Uid="SelectCommandWithSelectionButton"
|
||||
Click="_SelectCommandHandler">
|
||||
<AppBarButton.Icon>
|
||||
<FontIcon FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="11"
|
||||
Glyph="" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
<AppBarButton x:Name="SelectOutputWithSelectionButton"
|
||||
x:Uid="SelectOutputWithSelectionButton"
|
||||
Click="_SelectOutputHandler"
|
||||
Icon="AlignLeft" />
|
||||
</mux:CommandBarFlyout.SecondaryCommands>
|
||||
</mux:CommandBarFlyout>
|
||||
|
||||
|
||||
@@ -798,6 +798,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(ActionEventArgs);
|
||||
BASIC_FACTORY(CopyTextArgs);
|
||||
BASIC_FACTORY(SwitchToTabArgs);
|
||||
BASIC_FACTORY(NewTerminalArgs);
|
||||
BASIC_FACTORY(NewTabArgs);
|
||||
|
||||
@@ -154,6 +154,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
|
||||
[default_interface] runtimeclass CopyTextArgs : IActionArgs
|
||||
{
|
||||
CopyTextArgs();
|
||||
Boolean SingleLine { get; };
|
||||
Windows.Foundation.IReference<Microsoft.Terminal.Control.CopyFormat> CopyFormatting { get; };
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace til
|
||||
// Method Description:
|
||||
// - Base case provided to handle the last argument to coalesce_value<T...>()
|
||||
template<typename T>
|
||||
T coalesce_value(const T& base)
|
||||
T coalesce_value(const T& base) noexcept
|
||||
{
|
||||
return base;
|
||||
}
|
||||
|
||||
@@ -589,5 +589,10 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
|
||||
{
|
||||
return outputEnd.has_value() && *outputEnd != *commandEnd;
|
||||
}
|
||||
std::pair<til::point, til::point> GetExtent() const
|
||||
{
|
||||
til::point realEnd{ til::coalesce_value(outputEnd, commandEnd, end) };
|
||||
return std::make_pair(til::point{ start }, realEnd);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user