Repository: microsoft/terminal (language: cpp)

Issue:
Copy text without dismissing the selection


> In Notepad, if there is a selection, pressing Ctrl+C, the text will be copied to the clipboard, otherwise nothing happens. This is the same in other edit boxes and controls
> ... 
> I would like Terminal/PowerShell to behave exactly like Notepad

This seems like a reasonable property to add to the `copy` action. 

```ts
class CopyActionArgs {
  singleLine: bool = false;
  copyFormatting = false;
  dismissSelection: bool = false;
}
```


_Originally suggested by @gaberiel44 in https://github.com/microsoft/terminal/discussions/15349_


> **Note**
> ## Walkthrough 

* Add a `Boolean DismissSelection` to `CopyTextArgs` in `src\cascadia\TerminalSettingsModel\ActionArgs.idl`
* Add `DismissSelection` to `COPY_TEXT_ARGS` in `ActionArgs.h`
* The actual copy args are retrieved in `TerminalPage::_HandleCopyText` (in `AppActionHandlers.cpp`), and passed to `TerminalPage::_CopyText`, which calls down to `TermControl::CopySelectionToClipboard`. 
  * In `CopySelectionToClipboard`, just don't `ClearSelection`.
* This arg will also need localization, in `ActionArgs.cpp`, `CopyTextArgs::GenerateName`, and `src\cascadia\TerminalSettingsModel\Resources\en-US\Resources.resw`


Discussion / hints:
@zadjii-msft Can I work on this?
Absolutely!
I've got a fix working. I've removed `_core.ClearSelection();` from `TermControl.cpp` which leads to the intended behaviour. Is this correct?
Looking for feedback on my first ever PR on GitHub. https://github.com/microsoft/terminal/pull/15383
@zadjii-msft Hi, could you please explain me the point of  `bool dismissSelection`?
> could you please explain me the point of `bool dismissSelection`?

Sure - by making this a setting, then users can choose which behavior they want. They could even have both! Like, a user could have the following settings:

```json
"actions":
[
    { "keys": "ctrl+c", "command": { "action": "copy", "dismissSelection": "true" }, },
    { "keys": "ctrl+d", "command": { "action": "copy", "dismissSelection": "false" }, },
]
```

And then 
* <kbd>ctrl+c</kbd> would copy and dismiss the selection (as it currently does)
* <kbd>ctrl+d</kbd> would copy and leave the selection unchanged (as in Notepad et al.)

Or a user could just rebind <kbd>ctrl+c</kbd> to `"action": "copy", "dismissSelection": "false"`, or whatever! 

Does that make sense?
@zadjii-msft can I kindly work on this issue?

Fix patch:
diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json
index 41a671626b6..d3ccfa8d122 100644
--- a/doc/cascadia/profiles.schema.json
+++ b/doc/cascadia/profiles.schema.json
@@ -799,6 +799,11 @@
               "default": false,
               "description": "If true, the copied content will be copied as a single line (even if there are hard line breaks present in the text). If false, newlines persist from the selected text."
             },
+            "dismissSelection": {
+              "type": "boolean",
+              "default": true,
+              "description": "If false, the copied content will be copied and the selection will not be dismissed. If true, the selection will be dismissed."
+            },
             "copyFormatting": {
               "default": null,
               "description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied. Not setting this value inherits the behavior of the `copyFormatting` global setting.",
diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp
index aa912fd22d7..74ae4cdbb6a 100644
--- a/src/cascadia/TerminalApp/AppActionHandlers.cpp
+++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp
@@ -512,7 +512,7 @@ namespace winrt::TerminalApp::implementation
     {
         if (const auto& realArgs = args.ActionArgs().try_as<CopyTextArgs>())
         {
-            const auto handled = _CopyText(realArgs.SingleLine(), realArgs.CopyFormatting());
+            const auto handled = _CopyText(realArgs.DismissSelection(), realArgs.SingleLine(), realArgs.CopyFormatting());
             args.Handled(handled);
         }
     }
diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp
index dd960999b4c..c0b3f0d5943 100644
--- a/src/cascadia/TerminalApp/TerminalPage.cpp
+++ b/src/cascadia/TerminalApp/TerminalPage.cpp
@@ -2748,15 +2748,16 @@ namespace winrt::TerminalApp::implementation
     // Method Description:
     // - Copy text from the focused terminal to the Windows Clipboard
     // Arguments:
+    // - dismissSelection: if not enabled, copying text doesn't dismiss the selection
     // - singleLine: if enabled, copy contents as a single line of text
     // - formats: dictate which formats need to be copied
     // Return Value:
     // - true iff we we able to copy text (if a selection was active)
-    bool TerminalPage::_CopyText(const bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats)
+    bool TerminalPage::_CopyText(const bool dismissSelection, const bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats)
     {
         if (const auto& control{ _GetActiveControl() })
         {
-            return control.CopySelectionToClipboard(singleLine, formats);
+            return control.CopySelectionToClipboard(dismissSelection, singleLine, formats);
         }
         return false;
     }
diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h
index 276f6687bac..bde39cca453 100644
--- a/src/cascadia/TerminalApp/TerminalPage.h
+++ b/src/cascadia/TerminalApp/TerminalPage.h
@@ -410,7 +410,7 @@ namespace winrt::TerminalApp::implementation
         bool _IsUriSupported(const winrt::Windows::Foundation::Uri& parsedUri);
 
         void _ShowCouldNotOpenDialog(winrt::hstring reason, winrt::hstring uri);
-        bool _CopyText(const bool singleLine, const Windows::Foundation::IReference<Microsoft::Terminal::Control::CopyFormat>& formats);
+        bool _CopyText(const bool dismissSelection, const bool singleLine, const Windows::Foundation::IReference<Microsoft::Terminal::Control::CopyFormat>& formats);
 
         winrt::fire_and_forget _SetTaskbarProgressHandler(const IInspectable sender, const IInspectable eventArgs);
 
diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp
index 215d8ee1344..37ded61dd2f 100644
--- a/src/cascadia/TerminalControl/TermControl.cpp
+++ b/src/cascadia/TerminalControl/TermControl.cpp
@@ -2066,10 +2066,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
     //     Windows Clipboard (CascadiaWin32:main.cpp).
     // - CopyOnSelect does NOT clear the selection
     // Arguments:
+    // - dismissSelection: dismiss the text selection after copy
     // - singleLine: collapse all of the text to one line
     // - formats: which formats to copy (defined by action's CopyFormatting arg). nullptr
     //             if we should defer which formats are copied to the global setting
-    bool TermControl::CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats)
+    bool TermControl::CopySelectionToClipboard(bool dismissSelection, bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats)
     {
         if (_IsClosing())
         {
@@ -2077,7 +2078,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         }
 
         const auto successfulCopy = _interactivity.CopySelectionToClipboard(singleLine, formats);
-        _core.ClearSelection();
+
+        if (dismissSelection)
+        {
+            _core.ClearSelection();
+        }
+
         return successfulCopy;
     }
 
diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h
index f177c816ab7..827c0d29a4e 100644
--- a/src/cascadia/TerminalControl/TermControl.h
+++ b/src/cascadia/TerminalControl/TermControl.h
@@ -39,7 +39,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
 
         hstring GetProfileName() const;
 
-        bool CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats);
+        bool CopySelectionToClipboard(bool dismissSelection, bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats);
         void PasteTextFromClipboard();
         void SelectAll();
         bool ToggleBlockSelection();
diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl
index f9f5b3ea454..b52cfea794b 100644
--- a/src/cascadia/TerminalControl/TermControl.idl
+++ b/src/cascadia/TerminalControl/TermControl.idl
@@ -67,7 +67,7 @@ namespace Microsoft.Terminal.Control
         event Windows.Foundation.TypedEventHandler<Object, Object> CloseTerminalRequested;
         event Windows.Foundation.TypedEventHandler<Object, Object> RestartTerminalRequested;
 
-        Boolean CopySelectionToClipboard(Boolean singleLine, Windows.Foundation.IReference<CopyFormat> formats);
+        Boolean CopySelectionToClipboard(Boolean dismissSelection, Boolean singleLine, Windows.Foundation.IReference<CopyFormat> formats);
         void PasteTextFromClipboard();
         void SelectAll();
         Boolean ToggleBlockSelection();
diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp
index b002f7d0b7a..64f7bd13b0c 100644
--- a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp
+++ b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp
@@ -197,6 +197,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
             ss << RS_(L"CopyTextCommandKey").c_str();
         }
 
+        if (!DismissSelection())
+        {
+            ss << L", dismissSelection: false";
+        }
+
         if (CopyFormatting())
         {
             ss << L", copyFormatting: ";
diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h
index 0cf0eb4d593..99f52ad991d 100644
--- a/src/cascadia/TerminalSettingsModel/ActionArgs.h
+++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h
@@ -100,8 +100,9 @@ private:                                                                    \
 // false, if we don't really care if the parameter is required or not.
 
 ////////////////////////////////////////////////////////////////////////////////
-#define COPY_TEXT_ARGS(X)                           \
-    X(bool, SingleLine, "singleLine", false, false) \
+#define COPY_TEXT_ARGS(X)                                      \
+    X(bool, DismissSelection, "dismissSelection", false, true) \
+    X(bool, SingleLine, "singleLine", false, false)            \
     X(Windows::Foundation::IReference<Control::CopyFormat>, CopyFormatting, "copyFormatting", false, nullptr)
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl
index d2127922dcd..a5daa935ece 100644
--- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl
+++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl
@@ -155,6 +155,7 @@ namespace Microsoft.Terminal.Settings.Model
     [default_interface] runtimeclass CopyTextArgs : IActionArgs
     {
         CopyTextArgs();
+        Boolean DismissSelection { get; };
         Boolean SingleLine { get; };
         Windows.Foundation.IReference<Microsoft.Terminal.Control.CopyFormat> CopyFormatting { get; };
     };


Test patch: