diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index f4768776dc5..2ede93d2c0a 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -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 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 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& filter, + const std::function& 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& 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(); }); + } } diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 1e223376d72..c2a56658f7d 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -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> _updatePatternLocations; std::shared_ptr> _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& filter, + const std::function& getSpan); + + bool _clickedOnMark(const til::point& pos, const std::function& filter); + inline bool _IsClosing() const noexcept { #ifndef NDEBUG diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index e5b5834745b..10ab1b11d3e 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -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 CopyToClipboard; diff --git a/src/cascadia/TerminalControl/ControlInteractivity.cpp b/src/cascadia/TerminalControl/ControlInteractivity.cpp index b626eb77ce1..d0b74edfaa2 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.cpp +++ b/src/cascadia/TerminalControl/ControlInteractivity.cpp @@ -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(til::point{ pixelPosition }.to_winrt_point()); _ContextMenuRequestedHandlers(*this, contextArgs); } diff --git a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw index aa2473890f2..4d95586e120 100644 --- a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw @@ -240,4 +240,36 @@ Please either install the missing font or choose another one. Find The tooltip for a button for searching for the selected text + + Select command + The label of a button for selecting all of the text of a command + + + Select command + The tooltip for a button for selecting all of the text of a command + + + Select output + The label of a button for selecting all of a command's output + + + Select output + The tooltip for a button for selecting all of a command's output + + + Select command + The label of a button for selecting all of the text of a command + + + Select command + The tooltip for a button for selecting all of the text of a command + + + Select output + The label of a button for selecting all of a command's output + + + Select output + The tooltip for a button for selecting all of a command's output + diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 2bc9dc5e82c..c2a52d1ebd6 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -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(); + } } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 575a6ec84c6..abebe6c94e1 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -232,10 +232,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool _isBackgroundLight{ false }; bool _detached{ false }; - winrt::Windows::Foundation::Collections::IObservableVector _originalPrimaryElements{ nullptr }; - winrt::Windows::Foundation::Collections::IObservableVector _originalSecondaryElements{ nullptr }; - winrt::Windows::Foundation::Collections::IObservableVector _originalSelectedPrimaryElements{ nullptr }; - winrt::Windows::Foundation::Collections::IObservableVector _originalSelectedSecondaryElements{ nullptr }; + Windows::Foundation::Collections::IObservableVector _originalPrimaryElements{ nullptr }; + Windows::Foundation::Collections::IObservableVector _originalSecondaryElements{ nullptr }; + Windows::Foundation::Collections::IObservableVector _originalSelectedPrimaryElements{ nullptr }; + Windows::Foundation::Collections::IObservableVector _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; diff --git a/src/cascadia/TerminalControl/TermControl.xaml b/src/cascadia/TerminalControl/TermControl.xaml index ff74b716786..eff29b6b694 100644 --- a/src/cascadia/TerminalControl/TermControl.xaml +++ b/src/cascadia/TerminalControl/TermControl.xaml @@ -37,6 +37,21 @@ x:Uid="PasteCommandButton" Click="_PasteCommandHandler" Icon="Paste" /> + + + + + + + + @@ -53,6 +68,19 @@ x:Uid="SearchCommandButton" Click="_SearchCommandHandler" Icon="Find" /> + + + + + + diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 000ecade500..30367fdae37 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -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); diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index c64e2c8449c..06b81964481 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -154,6 +154,7 @@ namespace Microsoft.Terminal.Settings.Model [default_interface] runtimeclass CopyTextArgs : IActionArgs { + CopyTextArgs(); Boolean SingleLine { get; }; Windows.Foundation.IReference CopyFormatting { get; }; }; diff --git a/src/inc/til/coalesce.h b/src/inc/til/coalesce.h index 2bc789b4779..e9948d362ed 100644 --- a/src/inc/til/coalesce.h +++ b/src/inc/til/coalesce.h @@ -8,7 +8,7 @@ namespace til // Method Description: // - Base case provided to handle the last argument to coalesce_value() template - T coalesce_value(const T& base) + T coalesce_value(const T& base) noexcept { return base; } diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index 0f8b6ede82c..b7286f955d2 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -589,5 +589,10 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes { return outputEnd.has_value() && *outputEnd != *commandEnd; } + std::pair GetExtent() const + { + til::point realEnd{ til::coalesce_value(outputEnd, commandEnd, end) }; + return std::make_pair(til::point{ start }, realEnd); + } }; }