diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 2179a4c294f..54ec07c895c 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -354,6 +354,7 @@ "toggleFocusMode", "selectAll", "setFocusMode", + "switchSelectionEndpoint", "toggleFullscreen", "setFullScreen", "setMaximized", diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 5b035f38ebe..10a7f8cfa31 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -1112,4 +1112,14 @@ namespace winrt::TerminalApp::implementation args.Handled(handled); } } + + void TerminalPage::_HandleSwitchSelectionEndpoint(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if (const auto& control{ _GetActiveControl() }) + { + const auto handled = control.SwitchSelectionEndpoint(); + args.Handled(handled); + } + } } diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 3472bf1016e..d60f90bd380 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1089,6 +1089,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation return static_cast(_terminal->SelectionMode()); } + bool ControlCore::SwitchSelectionEndpoint() + { + if (_terminal->IsSelectionActive()) + { + _terminal->SwitchSelectionEndpoint(); + return true; + } + return false; + } + // Method Description: // - Pre-process text pasted (presumably from the clipboard) // before sending it over the terminal's connection. diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 5f021c10a07..4208798e10d 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -86,6 +86,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool ToggleBlockSelection(); void ToggleMarkMode(); Control::SelectionInteractionMode SelectionMode() const; + bool SwitchSelectionEndpoint(); void GotFocus(); void LostFocus(); diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index a1d348d8a26..1d7801bdad4 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -92,6 +92,7 @@ namespace Microsoft.Terminal.Control void ClearSelection(); Boolean ToggleBlockSelection(); void ToggleMarkMode(); + Boolean SwitchSelectionEndpoint(); void ClearBuffer(ClearBufferType clearType); void SetHoveredCell(Microsoft.Terminal.Core.Point terminalPosition); diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 1e5dcdc9f1a..7a1b57df3a6 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -1929,6 +1929,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation _core.ToggleMarkMode(); } + bool TermControl::SwitchSelectionEndpoint() + { + return _core.SwitchSelectionEndpoint(); + } + void TermControl::Close() { if (!_IsClosing()) diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 0bde6b53fb0..e67f7c773ce 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -40,6 +40,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void SelectAll(); bool ToggleBlockSelection(); void ToggleMarkMode(); + bool SwitchSelectionEndpoint(); void Close(); Windows::Foundation::Size CharacterDimensions() const; Windows::Foundation::Size MinimumSize(); diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index f2aadf16934..3d8cde2714e 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -53,6 +53,7 @@ namespace Microsoft.Terminal.Control void SelectAll(); Boolean ToggleBlockSelection(); void ToggleMarkMode(); + Boolean SwitchSelectionEndpoint(); void ClearBuffer(ClearBufferType clearType); void Close(); Windows.Foundation.Size CharacterDimensions { get; }; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 2e78808f8e2..d41e4f53b65 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -48,6 +48,7 @@ Terminal::Terminal() : _selectionMode{ SelectionInteractionMode::None }, _selection{ std::nullopt }, _selectionEndpoint{ static_cast(0) }, + _anchorInactiveSelectionEndpoint{ false }, _taskbarState{ 0 }, _taskbarProgress{ 0 }, _trimBlockSelection{ false }, diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 7f08390cbd2..c63eb887b65 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -271,6 +271,7 @@ class Microsoft::Terminal::Core::Terminal final : void UpdateSelection(SelectionDirection direction, SelectionExpansion mode, ControlKeyStates mods); void SelectAll(); SelectionInteractionMode SelectionMode() const noexcept; + void SwitchSelectionEndpoint(); void ToggleMarkMode(); using UpdateSelectionParams = std::optional>; @@ -349,6 +350,7 @@ class Microsoft::Terminal::Core::Terminal final : SelectionExpansion _multiClickSelectionMode; SelectionInteractionMode _selectionMode; SelectionEndpoint _selectionEndpoint; + bool _anchorInactiveSelectionEndpoint; #pragma endregion std::unique_ptr _mainBuffer; diff --git a/src/cascadia/TerminalCore/TerminalSelection.cpp b/src/cascadia/TerminalCore/TerminalSelection.cpp index b2626ec0245..007a26b0c3f 100644 --- a/src/cascadia/TerminalCore/TerminalSelection.cpp +++ b/src/cascadia/TerminalCore/TerminalSelection.cpp @@ -309,6 +309,33 @@ void Terminal::ToggleMarkMode() } } +// Method Description: +// - switch the targeted selection endpoint with the other one (i.e. start <--> end) +void Terminal::SwitchSelectionEndpoint() +{ + if (IsSelectionActive()) + { + if (WI_IsFlagSet(_selectionEndpoint, SelectionEndpoint::Start) && WI_IsFlagSet(_selectionEndpoint, SelectionEndpoint::End)) + { + // moving cursor --> anchor start, move end + _selectionEndpoint = SelectionEndpoint::End; + _anchorInactiveSelectionEndpoint = true; + } + else if (WI_IsFlagSet(_selectionEndpoint, SelectionEndpoint::End)) + { + // moving end --> now we're moving start + _selectionEndpoint = SelectionEndpoint::Start; + _selection->pivot = _selection->end; + } + else if (WI_IsFlagSet(_selectionEndpoint, SelectionEndpoint::Start)) + { + // moving start --> now we're moving end + _selectionEndpoint = SelectionEndpoint::End; + _selection->pivot = _selection->start; + } + } +} + Terminal::UpdateSelectionParams Terminal::ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey) const { if ((_selectionMode == SelectionInteractionMode::Mark || mods.IsShiftPressed()) && !mods.IsAltPressed()) @@ -365,6 +392,12 @@ Terminal::UpdateSelectionParams Terminal::ConvertKeyEventToUpdateSelectionParams // - mods: the key modifiers pressed when performing this update void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion mode, ControlKeyStates mods) { + // This is a special variable used to track if we should move the cursor when in mark mode. + // We have special functionality where if you use the "switchSelectionEndpoint" action + // when in mark mode (moving the cursor), we anchor an endpoint and you can use the + // plain arrow keys to move the endpoint. This way, you don't have to hold shift anymore! + const bool shouldMoveBothEndpoints = _selectionMode == SelectionInteractionMode::Mark && !_anchorInactiveSelectionEndpoint && !mods.IsShiftPressed(); + // 1. Figure out which endpoint to update // [Mark Mode] // - shift pressed --> only move "end" (or "start" if "pivot" == "end") @@ -372,7 +405,7 @@ void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion // [Quick Edit] // - just move "end" (or "start" if "pivot" == "end") _selectionEndpoint = static_cast(0); - if (_selectionMode == SelectionInteractionMode::Mark && !mods.IsShiftPressed()) + if (shouldMoveBothEndpoints) { WI_SetAllFlags(_selectionEndpoint, SelectionEndpoint::Start | SelectionEndpoint::End); } @@ -405,7 +438,7 @@ void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion // 3. Actually modify the selection state _selectionMode = std::max(_selectionMode, SelectionInteractionMode::Keyboard); - if (_selectionMode == SelectionInteractionMode::Mark && !mods.IsShiftPressed()) + if (shouldMoveBothEndpoints) { // [Mark Mode] + shift unpressed --> move all three (i.e. just use arrow keys) _selection->start = targetPos; @@ -598,6 +631,7 @@ void Terminal::ClearSelection() _selection = std::nullopt; _selectionMode = SelectionInteractionMode::None; _selectionEndpoint = static_cast(0); + _anchorInactiveSelectionEndpoint = false; } // Method Description: diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index 1b49cc3dd4c..8591457e611 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -82,6 +82,7 @@ static constexpr std::string_view RestoreLastClosedKey{ "restoreLastClosed" }; static constexpr std::string_view SelectAllKey{ "selectAll" }; static constexpr std::string_view MarkModeKey{ "markMode" }; static constexpr std::string_view ToggleBlockSelectionKey{ "toggleBlockSelection" }; +static constexpr std::string_view SwitchSelectionEndpointKey{ "switchSelectionEndpoint" }; static constexpr std::string_view ActionKey{ "action" }; @@ -400,6 +401,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ShortcutAction::SelectAll, RS_(L"SelectAllCommandKey") }, { ShortcutAction::MarkMode, RS_(L"MarkModeCommandKey") }, { ShortcutAction::ToggleBlockSelection, RS_(L"ToggleBlockSelectionCommandKey") }, + { ShortcutAction::SwitchSelectionEndpoint, RS_(L"SwitchSelectionEndpointCommandKey") }, }; }(); diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h index c9bd6bd2076..23d4cf2110c 100644 --- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h +++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h @@ -95,7 +95,8 @@ ON_ALL_ACTIONS(RestoreLastClosed) \ ON_ALL_ACTIONS(SelectAll) \ ON_ALL_ACTIONS(MarkMode) \ - ON_ALL_ACTIONS(ToggleBlockSelection) + ON_ALL_ACTIONS(ToggleBlockSelection) \ + ON_ALL_ACTIONS(SwitchSelectionEndpoint) #define ALL_SHORTCUT_ACTIONS_WITH_ARGS \ ON_ALL_ACTIONS_WITH_ARGS(AdjustFontSize) \ diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index e71dcf94a0e..ab0befb1533 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -564,4 +564,7 @@ Toggle block selection + + Switch selection endpoint + diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index 6067aa6130e..3527bf125a7 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -388,6 +388,7 @@ { "command": "selectAll", "keys": "ctrl+shift+a" }, { "command": "markMode", "keys": "ctrl+shift+m" }, { "command": "toggleBlockSelection" }, + { "command": "switchSelectionEndpoint" }, // Scrollback { "command": "scrollDown", "keys": "ctrl+shift+down" },