From bf41a90ad8cb596c4adae7565b0e6bcf917fc34e Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 20 May 2022 16:04:53 -0700 Subject: [PATCH] Introduce Mark Mode (#13053) ## Summary of the Pull Request Introduces a non-configurable version of mark mode to Windows Terminal. It has the following interactions defined: - ctrl+shift+m --> Enter Mark Mode - when in Mark Mode... - ESC --> Exit Mark Mode - arrow keys --> move "start" - shift + arrow keys --> anchor "start", move "end" - ctrl+a --> select all - when a selection is active... When in mark mode, the cursor does not blink. ## References #4993 - [Epic] Keyboard Selection ## PR Checklist * [X] Closes #715 * [X] Provides a resolution for #11985 ## Detailed Description of the Pull Request / Additional comments - `TermControl`: - `TermControl.cpp` just adds logic to prevent the cursor from blinking when in mark mode - `ControlCore` - in the same place we handle quick edit, we add an entry point to mark mode - `TerminalCore` - this leverages `UpdateSelection()` and other quick edit functions to make mark mode happen ## Validation Steps Performed - [x] Make selection, split pane, close pane - NOTE: A similar scenario caused a crash at one point. Really weird. Keep an eye on it. - [x] Cursor is off when in mark mode - [x] general movement/selection - [x] general movement/selection that forces the viewport to move - [x] In mark mode, selectAll... - [x] arrow keys --> move start - [x] shift + arrow keys --> move end - [x] (regardless of mark mode) if selection active, enter --> copy to clipboard --- doc/cascadia/profiles.schema.json | 1 + .../TerminalApp/AppActionHandlers.cpp | 10 +++ src/cascadia/TerminalControl/ControlCore.cpp | 24 ++++++- src/cascadia/TerminalControl/ControlCore.h | 2 + src/cascadia/TerminalControl/ControlCore.idl | 2 + src/cascadia/TerminalControl/TermControl.cpp | 9 ++- src/cascadia/TerminalControl/TermControl.h | 1 + src/cascadia/TerminalControl/TermControl.idl | 1 + src/cascadia/TerminalCore/Terminal.cpp | 3 +- src/cascadia/TerminalCore/Terminal.hpp | 7 +- .../TerminalCore/TerminalSelection.cpp | 66 +++++++++++++++++-- .../TerminalSettingsModel/ActionAndArgs.cpp | 2 + .../AllShortcutActions.h | 3 +- .../Resources/en-US/Resources.resw | 4 ++ .../TerminalSettingsModel/defaults.json | 1 + 15 files changed, 121 insertions(+), 15 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 95ce2ff9990..391095c379f 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -318,6 +318,7 @@ "moveFocus", "movePane", "swapPane", + "markMode", "moveTab", "multipleActions", "newTab", diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 3d61450e8c5..0cc395287a3 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -1043,4 +1043,14 @@ namespace winrt::TerminalApp::implementation args.Handled(true); } } + + void TerminalPage::_HandleMarkMode(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if (const auto& control{ _GetActiveControl() }) + { + control.ToggleMarkMode(); + args.Handled(true); + } + } } diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 0f648fd0a49..5bbe2cc367f 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -390,11 +390,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation vkey != VK_SNAPSHOT && keyDown) { + if (_terminal->IsInMarkMode() && modifiers.IsCtrlPressed() && vkey == 'A') + { + auto lock = _terminal->LockForWriting(); + _terminal->SelectAll(); + _renderer->TriggerSelection(); + return true; + } + // try to update the selection - if (const auto updateSlnParams{ ::Terminal::ConvertKeyEventToUpdateSelectionParams(modifiers, vkey) }) + if (const auto updateSlnParams{ _terminal->ConvertKeyEventToUpdateSelectionParams(modifiers, vkey) }) { auto lock = _terminal->LockForWriting(); - _terminal->UpdateSelection(updateSlnParams->first, updateSlnParams->second); + _terminal->UpdateSelection(updateSlnParams->first, updateSlnParams->second, modifiers); _renderer->TriggerSelection(); return true; } @@ -1002,6 +1010,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation _renderer->TriggerSelection(); } + void ControlCore::ToggleMarkMode() + { + auto lock = _terminal->LockForWriting(); + _terminal->ToggleMarkMode(); + _renderer->TriggerSelection(); + } + + bool ControlCore::IsInMarkMode() const + { + return _terminal->IsInMarkMode(); + } + // 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 b235a93fc0b..9593ee0c5a2 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -81,6 +81,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation void PasteText(const winrt::hstring& hstr); bool CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference& formats); void SelectAll(); + void ToggleMarkMode(); + bool IsInMarkMode() const; void GotFocus(); void LostFocus(); diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index 55b718d1fd1..edcb9fb69b1 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -66,7 +66,9 @@ namespace Microsoft.Terminal.Control void SendInput(String text); void PasteText(String text); void SelectAll(); + void ToggleMarkMode(); void ClearBuffer(ClearBufferType clearType); + Boolean IsInMarkMode(); void SetHoveredCell(Microsoft.Terminal.Core.Point terminalPosition); void ClearHoveredCell(); diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index fdb19c31acf..37e4ad8096e 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -1137,7 +1137,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { // Manually show the cursor when a key is pressed. Restarting // the timer prevents flickering. - _core.CursorOn(true); + _core.CursorOn(!_core.IsInMarkMode()); _cursorTimer->Start(); } @@ -1608,7 +1608,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (_cursorTimer) { // When the terminal focuses, show the cursor immediately - _core.CursorOn(true); + _core.CursorOn(!_core.IsInMarkMode()); _cursorTimer->Start(); } @@ -1859,6 +1859,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation _core.SelectAll(); } + void TermControl::ToggleMarkMode() + { + _core.ToggleMarkMode(); + } + void TermControl::Close() { if (!_IsClosing()) diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 656e1f68451..b8d70e7741f 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -38,6 +38,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference& formats); void PasteTextFromClipboard(); void SelectAll(); + void ToggleMarkMode(); 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 8633931c7df..f208046dd90 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -51,6 +51,7 @@ namespace Microsoft.Terminal.Control Boolean CopySelectionToClipboard(Boolean singleLine, Windows.Foundation.IReference formats); void PasteTextFromClipboard(); void SelectAll(); + void ToggleMarkMode(); 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 e18be09b270..b8e32d53cee 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -45,6 +45,7 @@ Terminal::Terminal() : _snapOnInput{ true }, _altGrAliasing{ true }, _blockSelection{ false }, + _markMode{ false }, _selection{ std::nullopt }, _taskbarState{ 0 }, _taskbarProgress{ 0 }, @@ -1318,7 +1319,7 @@ void Terminal::SetCursorOn(const bool isOn) bool Terminal::IsCursorBlinkingAllowed() const noexcept { const auto& cursor = _activeBuffer().GetCursor(); - return cursor.IsBlinkingAllowed(); + return !_markMode && cursor.IsBlinkingAllowed(); } // Method Description: diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 677a0501e89..635993d4238 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -238,11 +238,13 @@ class Microsoft::Terminal::Core::Terminal final : void SetSelectionAnchor(const COORD position); void SetSelectionEnd(const COORD position, std::optional newExpansionMode = std::nullopt); void SetBlockSelection(const bool isEnabled) noexcept; - void UpdateSelection(SelectionDirection direction, SelectionExpansion mode); + void UpdateSelection(SelectionDirection direction, SelectionExpansion mode, ControlKeyStates mods); void SelectAll(); + bool IsInMarkMode() const; + void ToggleMarkMode(); using UpdateSelectionParams = std::optional>; - static UpdateSelectionParams ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey); + UpdateSelectionParams ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey) const; const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace); #pragma endregion @@ -310,6 +312,7 @@ class Microsoft::Terminal::Core::Terminal final : bool _blockSelection; std::wstring _wordDelimiters; SelectionExpansion _multiClickSelectionMode; + bool _markMode; #pragma endregion std::unique_ptr _mainBuffer; diff --git a/src/cascadia/TerminalCore/TerminalSelection.cpp b/src/cascadia/TerminalCore/TerminalSelection.cpp index b730836f114..6d75d6d20c5 100644 --- a/src/cascadia/TerminalCore/TerminalSelection.cpp +++ b/src/cascadia/TerminalCore/TerminalSelection.cpp @@ -235,13 +235,40 @@ void Terminal::SetBlockSelection(const bool isEnabled) noexcept _blockSelection = isEnabled; } -Terminal::UpdateSelectionParams Terminal::ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey) +bool Terminal::IsInMarkMode() const { - if (mods.IsShiftPressed() && !mods.IsAltPressed()) + return _markMode; +} + +void Terminal::ToggleMarkMode() +{ + if (_markMode) + { + // Exit Mark Mode + ClearSelection(); + } + else + { + // Enter Mark Mode + // NOTE: directly set cursor state. We already should have locked before calling this function. + _activeBuffer().GetCursor().SetIsOn(false); + const auto cursorPos{ _activeBuffer().GetCursor().GetPosition() }; + _selection = SelectionAnchors{}; + _selection->start = cursorPos; + _selection->end = cursorPos; + _selection->pivot = cursorPos; + _markMode = true; + } +} + +Terminal::UpdateSelectionParams Terminal::ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey) const +{ + if ((_markMode || mods.IsShiftPressed()) && !mods.IsAltPressed()) { if (mods.IsCtrlPressed()) { // Ctrl + Shift + _ + // (Mark Mode) Ctrl + _ switch (vkey) { case VK_LEFT: @@ -257,6 +284,7 @@ Terminal::UpdateSelectionParams Terminal::ConvertKeyEventToUpdateSelectionParams else { // Shift + _ + // (Mark Mode) Just the vkeys switch (vkey) { case VK_HOME: @@ -281,11 +309,19 @@ Terminal::UpdateSelectionParams Terminal::ConvertKeyEventToUpdateSelectionParams return std::nullopt; } -void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion mode) +// Method Description: +// - updates the selection endpoints based on a direction and expansion mode. Primarily used for keyboard selection. +// Arguments: +// - direction: the direction to move the selection endpoint in +// - mode: the type of movement to be performed (i.e. move by word) +// - mods: the key modifiers pressed when performing this update +void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion mode, ControlKeyStates mods) { // 1. Figure out which endpoint to update - // One of the endpoints is the pivot, signifying that the other endpoint is the one we want to move. - const auto movingEnd{ _selection->start == _selection->pivot }; + // If we're in mark mode, shift dictates whether you are moving the end or not. + // Otherwise, we're updating an existing selection, so one of the endpoints is the pivot, + // signifying that the other endpoint is the one we want to move. + const auto movingEnd{ _markMode ? mods.IsShiftPressed() : _selection->start == _selection->pivot }; auto targetPos{ movingEnd ? _selection->end : _selection->start }; // 2. Perform the movement @@ -307,8 +343,23 @@ void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion // 3. Actually modify the selection // NOTE: targetStart doesn't matter here - auto targetStart = false; - std::tie(_selection->start, _selection->end) = _PivotSelection(targetPos, targetStart); + if (_markMode) + { + // [Mark Mode] + // - moveSelectionEnd --> just move end (i.e. shift + arrow keys) + // - !moveSelectionEnd --> move all three (i.e. just use arrow keys) + _selection->end = targetPos; + if (!movingEnd) + { + _selection->start = targetPos; + _selection->pivot = targetPos; + } + } + else + { + auto targetStart = false; + std::tie(_selection->start, _selection->end) = _PivotSelection(targetPos, targetStart); + } // 4. Scroll (if necessary) if (const auto viewport = _GetVisibleViewport(); !viewport.IsInBounds(targetPos)) @@ -473,6 +524,7 @@ void Terminal::_MoveByBuffer(SelectionDirection direction, COORD& pos) void Terminal::ClearSelection() { _selection = std::nullopt; + _markMode = false; } // Method Description: diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index 15d48a51e50..517c85551df 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -76,6 +76,7 @@ static constexpr std::string_view QuitKey{ "quit" }; static constexpr std::string_view AdjustOpacityKey{ "adjustOpacity" }; 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 ActionKey{ "action" }; @@ -388,6 +389,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ShortcutAction::AdjustOpacity, L"" }, // Intentionally omitted, must be generated by GenerateName { ShortcutAction::RestoreLastClosed, RS_(L"RestoreLastClosedCommandKey") }, { ShortcutAction::SelectAll, RS_(L"SelectAllCommandKey") }, + { ShortcutAction::MarkMode, RS_(L"MarkModeCommandKey") }, }; }(); diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h index d988fc234a4..24f04b232a1 100644 --- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h +++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h @@ -89,7 +89,8 @@ ON_ALL_ACTIONS(Quit) \ ON_ALL_ACTIONS(AdjustOpacity) \ ON_ALL_ACTIONS(RestoreLastClosed) \ - ON_ALL_ACTIONS(SelectAll) + ON_ALL_ACTIONS(SelectAll) \ + ON_ALL_ACTIONS(MarkMode) #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 314ecf8f8bf..7a23d7a111e 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -525,4 +525,8 @@ Select all text + + Toggle mark mode + A command that will toggle "mark mode". This is a mode in the application where the user can mark the text by selecting portions of the text. + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index 7a5c2d1f9d5..95118b5d884 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -385,6 +385,7 @@ { "command": "paste", "keys": "ctrl+shift+v" }, { "command": "paste", "keys": "shift+insert" }, { "command": "selectAll", "keys": "ctrl+shift+a" }, + { "command": "markMode", "keys": "ctrl+shift+m" }, // Scrollback { "command": "scrollDown", "keys": "ctrl+shift+down" },