Skip to content

Commit

Permalink
Add buttons for selecting commands, output to context menu (#15020)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
zadjii-msft and DHowett authored Apr 25, 2023
1 parent 2cfd73d commit 0d1540b
Show file tree
Hide file tree
Showing 12 changed files with 249 additions and 19 deletions.
134 changes: 120 additions & 14 deletions src/cascadia/TerminalControl/ControlCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() };

Expand Down Expand Up @@ -2217,20 +2228,15 @@ 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 });
}
}

void ControlCore::SelectOutput(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() };

Expand All @@ -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 });
}
}

Expand Down Expand Up @@ -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(); });
}
}
20 changes: 20 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.idl
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions src/cascadia/TerminalControl/ControlInteractivity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
32 changes: 32 additions & 0 deletions src/cascadia/TerminalControl/Resources/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -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>
24 changes: 24 additions & 0 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}
}
11 changes: 7 additions & 4 deletions src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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;
Expand Down
28 changes: 28 additions & 0 deletions src/cascadia/TerminalControl/TermControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -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="&#xE756;" />
</AppBarButton.Icon>
</AppBarButton>
<AppBarButton x:Name="SelectOutputButton"
x:Uid="SelectOutputButton"
Click="_SelectOutputHandler"
Icon="AlignLeft" />
</mux:CommandBarFlyout.SecondaryCommands>
</mux:CommandBarFlyout>

<mux:CommandBarFlyout x:Name="SelectionContextMenu">
Expand All @@ -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="&#xE756;" />
</AppBarButton.Icon>
</AppBarButton>
<AppBarButton x:Name="SelectOutputWithSelectionButton"
x:Uid="SelectOutputWithSelectionButton"
Click="_SelectOutputHandler"
Icon="AlignLeft" />
</mux:CommandBarFlyout.SecondaryCommands>
</mux:CommandBarFlyout>

Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalSettingsModel/ActionArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 0d1540b

Please sign in to comment.