Repository: microsoft/terminal (language: cpp)

Issue:
Ability to select/copy all the text in the session
# Summary of the new feature/enhancement

Sometimes there are situations when you need to select/copy all the text in the session.

> ConEmu has such functionality (`Menu` → `Edit` → `Copy All`).

# Proposed technical implementation details (optional)

* Keybinding for action `Select All Text`.

May intersect with #715.


Discussion / hints:
I should add this to the #4993 scenario.
@carlos-zamora what is the plan for this feature? Looks like an important feature.
Select by mouse/trackpad works. Adding by keyboard short cut would be easy to use.
For what its worth and to whomever finds this useful, if you zoom out really far with ctrl + scroll wheel highlighting the complete buffer is much less painful even for relatively long histories. Not ideal of course, workable. 
Make sure one can choose between plain text and formatted text e.g. with colors. MobaXterm supports saving as RTF, preserving formatting. 
We already support RTF and HTML copy, @sba923 :)
Typing my comment, on my phone, I kinda remembered reading about this somewhere... 😝 I promise I will check out the docs first thing in the morning 😜
> We already support RTF and HTML copy, @sba923 :)

I know, but you can't choose dynamically.

You have to define this in the settings. So it's either "always formatted" or "always plain text."

Being able to choose between formatted copy or unformatted copy e.g. by using two different key bindings would be nice.

Or maybe the choice should be at _paste_ time, not at _copy_ time. (I know, one day Win+V will allow pasting the plain-text version of whatever's on the clipboard. But this is not yet for corporate systems running non-Insider Windows 10 builds 😉).

> Or maybe the choice should be at paste time, not at copy time. 

That's completely up to the app _reading_ the clipboard. Can't do anything about that.

> Being able to choose between formatted copy or unformatted copy e.g. by using two different key bindings would be nice.

You mean like...

```json
{ "command": { "action": "copy", "copyFormatting": "rtf" }, "keys": "ctrl+shift+r" },
{ "command": { "action": "copy", "copyFormatting": "html" }, "keys": "ctrl+shift+h" },
{ "command": { "action": "copy", "copyFormatting": ["html", "rtf"] }, "keys": "ctrl+win+alt+shift+h" },
```

? From the [docs for the copy action](https://docs.microsoft.com/en-us/windows/terminal/customize-settings/actions#copy).
> > Or maybe the choice should be at paste time, not at copy time.
> 
> That's completely up to the app _reading_ the clipboard. Can't do anything about that.


I know. It's too bad too many apps don't provide a way to choose between _plain-text_ paste and _formatted_ paste.

I paste tons of stuff into Teams, from various sources like VScode and Windows Terminal, and I often don't get what I'd expect. 

My current workaround is to first rewrite the clipboard as plain-text-only with some one-liner, such as this running in a WSL2 tab in Windows Terminal 😉

```none
pwsh.exe -noprofile -c Get-ClipboardText | fold -w80 | clip.exe
```

(this also tackles a Teams issue with the lack of line-wrapping but that's another issue altogether)

> 
> > Being able to choose between formatted copy or unformatted copy e.g. by using two different key bindings would be nice.
> 
> You mean like...
> 
> ```json
> { "command": { "action": "copy", "copyFormatting": "rtf" }, "keys": "ctrl+shift+r" },
> { "command": { "action": "copy", "copyFormatting": "html" }, "keys": "ctrl+shift+h" },
> { "command": { "action": "copy", "copyFormatting": ["html", "rtf"] }, "keys": "ctrl+win+alt+shift+h" },
> ```
> 
> ? From the [docs for the copy action](https://docs.microsoft.com/en-us/windows/terminal/customize-settings/actions#copy).

Exactly. _[blushing mode on]_ My bad for not RTFM. _[blushing mode off]_. I'll try defining a "copy as plain text" keybinding and see whether my muscle memory can be trained to using _that_.

> > Or maybe the choice should be at paste time, not at copy time.
> 
> That's completely up to the app _reading_ the clipboard. Can't do anything about that.


I know. It's too bad too many apps don't provide a way to choose between _plain-text_ paste and _formatted_ paste.

I paste tons of stuff into Teams, from various sources like VScode and Windows Terminal, and I often don't get what I'd expect. 

My current workaround is to first rewrite the clipboard as plain-text-only with some one-liner, such as this running in a WSL2 tab in Windows Terminal 😉

```none
pwsh.exe -noprofile -c Get-ClipboardText | fold -w80 | clip.exe
```

(this also tackles a Teams issue with the lack of line-wrapping but that's another issue altogether)

> 
> > Being able to choose between formatted copy or unformatted copy e.g. by using two different key bindings would be nice.
> 
> You mean like...
> 
> ```json
> { "command": { "action": "copy", "copyFormatting": "rtf" }, "keys": "ctrl+shift+r" },
> { "command": { "action": "copy", "copyFormatting": "html" }, "keys": "ctrl+shift+h" },
> { "command": { "action": "copy", "copyFormatting": ["html", "rtf"] }, "keys": "ctrl+win+alt+shift+h" },
> ```
> 
> ? From the [docs for the copy action](https://docs.microsoft.com/en-us/windows/terminal/customize-settings/actions#copy).

Exactly. _[blushing mode on]_ My bad for not RTFM. _[blushing mode off]_. I'll try defining a "copy as plain text" keybinding and see whether my muscle memory can be trained to using _that_.

Leaving the text format aside, what is the status of being able to copy all text to the clipboard? Since we just got the ability to change the language to English, I can now actually read the menu options and in the "Command Palette" window there is nothing that is related to selecting text. Ctrl+A just inserts ```^A``` instead of selecting the text.
This is a major blocker because I might have a program that spits out dozens of screens worth of output and I want to search for the first error in the output (since usually a single error triggers a cascade of error messages, checking the last one is useless).
@gyorokpeter 
> I might have a program that spits out dozens of screens worth of output and I want to search for the first error in the output

https://docs.microsoft.com/en-us/windows/terminal/search

I second request to support text selection and copying though.
> @gyorokpeter
> 
> > I might have a program that spits out dozens of screens worth of output and I want to search for the first error in the output
> 
> https://docs.microsoft.com/en-us/windows/terminal/search

Unless you want wildcard search, regular expression search, find all, count, match whole word, find and replace, mark line and a million other things you'll never have inside a terminal...

One common scenario is using a command that dumps a huge JSON on the screen and you want to copy it over for further processing and formatting (yes, you can use `stdout` redirect but maybe you forgot, maybe you want to see the output first before saving it, etc).


I have found a workaround to select all text:
1. Scroll to the beginning of the console window, for example by pressing `Ctrl+Shift+Home`
2. Start selecting text by holding left mouse button
3. While holding left mouse button press `Ctrl+Shift+End`, it will move the cursor to the bottom of the window and all console's text will be selected
4. Release left mouse button
5. Press `Ctrl+C` to copy the text
Is it really so hard to make a keybind for atleast "Select all current input" ? 😢 
Oh, no this should be super easy. This should be marked "Easy Starter", that's my bad. Alas, we haven't had time to get around to it, but it shouldn't be too hard to:
* add a new `ShortcutAction` to `ALL_SHORTCUT_ACTIONS` in `AllShortcutActions.h`
* Implement the handler in 	`AppActionHandlers` to call into the active panes's `TermControl`s
* Have the `TermControl` tell the `COntrolCore` to tell the `Terminal` to select all the text from 0,0 to the end of the mutable viewport

If anyone is interested, feel free to take a stab! I'll relabel this appropriately ☺️ 


https://github.com/microsoft/terminal/blob/d0d42c4ba1196148c973f5fbaaa5516a76c8507b/doc/specs/Keyboard-Selection.md?plain=1#L120-L130
FYI `ctrl`+`a` works if the tab is powershell but not if its cmd
I probably have a silly workflow, but I just wanted to echo that for me "Select All" (<kbd>Ctrl</kbd>+<kbd>A</kbd>) and "Copy" (<kbd>Ctrl</kbd>+<kbd>C</kbd>) console output would be a pretty sweet feature to have. 
Dear devs, could you please implement this? As a blind developer I'm not able to use the mouse at all and, as it seems, there is no way of selecting text in Windows Terminal using the keyboard only. Otherwise, a great app, thank you.

Fix patch:
diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp
index 55654ef1b1a..3d61450e8c5 100644
--- a/src/cascadia/TerminalApp/AppActionHandlers.cpp
+++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp
@@ -1033,4 +1033,14 @@ namespace winrt::TerminalApp::implementation
             }
         }
     }
+
+    void TerminalPage::_HandleSelectAll(const IInspectable& /*sender*/,
+                                        const ActionEventArgs& args)
+    {
+        if (const auto& control{ _GetActiveControl() })
+        {
+            control.SelectAll();
+            args.Handled(true);
+        }
+    }
 }
diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp
index cc14f228770..be5e7a70c1a 100644
--- a/src/cascadia/TerminalControl/ControlCore.cpp
+++ b/src/cascadia/TerminalControl/ControlCore.cpp
@@ -994,6 +994,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         return true;
     }
 
+    void ControlCore::SelectAll()
+    {
+        auto lock = _terminal->LockForWriting();
+        _terminal->SelectAll();
+        _renderer->TriggerSelection();
+    }
+
     // 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 dfb40441130..1a8b744cb7f 100644
--- a/src/cascadia/TerminalControl/ControlCore.h
+++ b/src/cascadia/TerminalControl/ControlCore.h
@@ -80,6 +80,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         void SendInput(const winrt::hstring& wstr);
         void PasteText(const winrt::hstring& hstr);
         bool CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats);
+        void SelectAll();
 
         void GotFocus();
         void LostFocus();
diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl
index 931b86a4df1..55b718d1fd1 100644
--- a/src/cascadia/TerminalControl/ControlCore.idl
+++ b/src/cascadia/TerminalControl/ControlCore.idl
@@ -65,6 +65,7 @@ namespace Microsoft.Terminal.Control
                            Microsoft.Terminal.Core.ControlKeyStates modifiers);
         void SendInput(String text);
         void PasteText(String text);
+        void SelectAll();
         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 3aeeb35786c..f90a2a96bd1 100644
--- a/src/cascadia/TerminalControl/TermControl.cpp
+++ b/src/cascadia/TerminalControl/TermControl.cpp
@@ -1840,6 +1840,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         _interactivity.RequestPasteTextFromClipboard();
     }
 
+    void TermControl::SelectAll()
+    {
+        _core.SelectAll();
+    }
+
     void TermControl::Close()
     {
         if (!_IsClosing())
diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h
index fdb10e9b978..c76c4b58703 100644
--- a/src/cascadia/TerminalControl/TermControl.h
+++ b/src/cascadia/TerminalControl/TermControl.h
@@ -37,6 +37,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
 
         bool CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats);
         void PasteTextFromClipboard();
+        void SelectAll();
         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 b221cc85cd4..b235962a73f 100644
--- a/src/cascadia/TerminalControl/TermControl.idl
+++ b/src/cascadia/TerminalControl/TermControl.idl
@@ -50,6 +50,7 @@ namespace Microsoft.Terminal.Control
 
         Boolean CopySelectionToClipboard(Boolean singleLine, Windows.Foundation.IReference<CopyFormat> formats);
         void PasteTextFromClipboard();
+        void SelectAll();
         void ClearBuffer(ClearBufferType clearType);
         void Close();
         Windows.Foundation.Size CharacterDimensions { get; };
diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp
index 99419b2b6f6..e89e9167b47 100644
--- a/src/cascadia/TerminalCore/Terminal.hpp
+++ b/src/cascadia/TerminalCore/Terminal.hpp
@@ -240,6 +240,7 @@ class Microsoft::Terminal::Core::Terminal final :
     void SetSelectionEnd(const COORD position, std::optional<SelectionExpansion> newExpansionMode = std::nullopt);
     void SetBlockSelection(const bool isEnabled) noexcept;
     void UpdateSelection(SelectionDirection direction, SelectionExpansion mode);
+    void SelectAll();
 
     using UpdateSelectionParams = std::optional<std::pair<SelectionDirection, SelectionExpansion>>;
     static UpdateSelectionParams ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey);
diff --git a/src/cascadia/TerminalCore/TerminalSelection.cpp b/src/cascadia/TerminalCore/TerminalSelection.cpp
index a6db3372f4f..b730836f114 100644
--- a/src/cascadia/TerminalCore/TerminalSelection.cpp
+++ b/src/cascadia/TerminalCore/TerminalSelection.cpp
@@ -329,6 +329,15 @@ void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion
     }
 }
 
+void Terminal::SelectAll()
+{
+    const auto bufferSize{ _activeBuffer().GetSize() };
+    _selection = SelectionAnchors{};
+    _selection->start = bufferSize.Origin();
+    _selection->end = { bufferSize.RightInclusive(), _GetMutableViewport().BottomInclusive() };
+    _selection->pivot = _selection->end;
+}
+
 void Terminal::_MoveByChar(SelectionDirection direction, COORD& pos)
 {
     switch (direction)
diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp
index d6d0af624b7..15d48a51e50 100644
--- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp
+++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp
@@ -75,6 +75,7 @@ static constexpr std::string_view MultipleActionsKey{ "multipleActions" };
 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 ActionKey{ "action" };
 
@@ -386,6 +387,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
                 { ShortcutAction::Quit, RS_(L"QuitCommandKey") },
                 { ShortcutAction::AdjustOpacity, L"" }, // Intentionally omitted, must be generated by GenerateName
                 { ShortcutAction::RestoreLastClosed, RS_(L"RestoreLastClosedCommandKey") },
+                { ShortcutAction::SelectAll, RS_(L"SelectAllCommandKey") },
             };
         }();
 
diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h
index 32ba5d6b631..d988fc234a4 100644
--- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h
+++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h
@@ -88,7 +88,8 @@
     ON_ALL_ACTIONS(MultipleActions)        \
     ON_ALL_ACTIONS(Quit)                   \
     ON_ALL_ACTIONS(AdjustOpacity)          \
-    ON_ALL_ACTIONS(RestoreLastClosed)
+    ON_ALL_ACTIONS(RestoreLastClosed)      \
+    ON_ALL_ACTIONS(SelectAll)
 
 #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 5d7d029bfda..314ecf8f8bf 100644
--- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw
@@ -522,4 +522,7 @@
   <data name="RestoreLastClosedCommandKey" xml:space="preserve">
     <value>Restore the last closed pane or tab</value>
   </data>
-</root>
+  <data name="SelectAllCommandKey" xml:space="preserve">
+    <value>Select all text</value>
+  </data>
+</root>
\ No newline at end of file
diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json
index 4740536ae0c..7a5c2d1f9d5 100644
--- a/src/cascadia/TerminalSettingsModel/defaults.json
+++ b/src/cascadia/TerminalSettingsModel/defaults.json
@@ -384,6 +384,7 @@
         { "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+insert" },
         { "command": "paste", "keys": "ctrl+shift+v" },
         { "command": "paste", "keys": "shift+insert" },
+        { "command": "selectAll", "keys": "ctrl+shift+a" },
 
         // Scrollback
         { "command": "scrollDown", "keys": "ctrl+shift+down" },
diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp
index 330ff85217d..37febe8dba5 100644
--- a/src/renderer/dx/DxRenderer.cpp
+++ b/src/renderer/dx/DxRenderer.cpp
@@ -1064,8 +1064,8 @@ HANDLE DxEngine::GetSwapChainHandle() noexcept
 void DxEngine::_InvalidateRectangle(const til::rect& rc)
 {
     const auto size = _invalidMap.size();
-    const auto topLeft = til::point{ 0, std::min(size.height, rc.top) };
-    const auto bottomRight = til::point{ size.width, std::min(size.height, rc.bottom) };
+    const auto topLeft = til::point{ 0, std::clamp(rc.top, 0, size.height) };
+    const auto bottomRight = til::point{ size.width, std::clamp(rc.bottom, 0, size.height) };
     _invalidMap.set(til::rect{ topLeft, bottomRight });
 }
 


Test patch: