Repository: microsoft/terminal (language: cpp)

Issue:
UI to Page / up and down by command + copy all output
It would be good if there was a UI element somewhere to page up or down to the previous or next command: [▲][▼]

This is something so useful, I'm not sure why no terminal emulator has it.

Would need a keybinding,  ctrl pageup / down ?

Similarly, useful:
Right click to copy all the output from a single command to the clipboard.

The terminal would probably need to have some idea about when it is rendering a prompt vs something else to enable this.


Discussion / hints:
As much as I love the idea, you've hit the nail on the head as to why no other terminals have this:

> The terminal would probably need to have some idea about when it is rendering a prompt vs something else to enable this.

I'm not totally sure that's possible at all. I'm gonna leave this open for discussion, probably @egmontkob would have thoughts here. If there was some mechanism that terminal emulators agreed upon for marking a line as a "prompt", then we'd totally implement it. It would have to work for things like `vim`, `emacs`, Far Manager, in addition to simple shells like `powershell` and `bash`. Even then, it probably wouldn't be possible to ad to `cmd.exe` _ever_ unfortunately, but that's not terribly relevant to this discussion 😝 
I guess this really needs some new ANSI sequences.

To bikeshed for a moment:  there could be an idea of text types, "prompt" - the non editable bit of the prompt "editable" - the editable bit of the prompt, and "output", where program output goes.

ANSI codes would specify all output from that point is of one of those types.

Being able to see where the editable bit of the prompt is would make it easier for terminal emulators to implement selecting just the user edited text (e.g. by triple click) and not the prompt as well.

Example showing where ANSI codes would go (but not the actual sequences):

```shell
/home/me $ ls
burger.txt
```

```shell
[text-type-prompt]/home/me/ $[text-editable]ls
[text-type-output]burger.txt
```
[gnome-terminal discussion counterpart here](https://bugzilla.gnome.org/show_bug.cgi?id=767230)

One possibility for the terminal is to guess, based on (hardcoded or customizable) prompt regexes or such. Not user friendly in the long term, the ideal goal is to make it work out of the box even for those who customize their prompt, and also there's always a chance of false matches when such detection happens. If done this way, the feature is a close friend of the "search in the terminal" feature, in fact, a user might already use the "search" feature to search for the previous some-string-specific-to-the-prompt. This approach is doable solely in the terminal, without requiring cooperation from other parties, and might work with cmd.exe too.

Another possibility is to remember the rows where the user pressed Enter and jump through them.

Probably the "proper" solution would indeed require for shells to emit markers. A huge short-term disadvantage is that it requires cooperation across some parties, like popular terminals and shells. Unfortunately my previous experiences with trying to get buy-in from bash's author to other integration ideas weren't quite supportive, I doubt he would be interested in this, but maybe. Also this would be one of the first steps towards a "semantical terminal", a pretty new direction, as right now terminals are about the look only. Whether it's a good direction or a bad one, opens up a can of worms or a can of great ideas, I don't know. iTerm2 does its "shell integration" along these lines, it configures the shell to emit some escape sequences. I don't know how robust it is, e.g. how it interferes with the shell occasionally redisplaying its prompt, or maybe just a part thereof, etc. I also don't know any further details, like which points are remembered (as per the "bikeshed" part of stuaxo's comment). It's also unclear to me how it's best to define "where" this data is stored, e.g. are these markers tied to the previous character cell, or the subsequent, or a new in-between-cell storage, or to the current line (and then what to do when rewrapping on a resize?), or to the current paragraph (between explicit newlines); what operations exactly erase them, scroll them, etc., it's all brand new area.

I'm gonna close this one out, but only for now. I'd love to see if we can pursue a standards-compliant way to do this that all TEs can agree upon, but _in the meantime_ search is a perfectly serviceable option. Once search gets things like regex support and marker highlighting, it will be an even more compelling solution to this problem.
Oh I'm SO reopening this. This is gonna be so easy with the rest of #11000. Yes, it requires shell integration, but there's gonna be a lot of reasons to want to enable that. 

Fix patch:
diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp
index c61ab8502a4..b1cb85a67b4 100644
--- a/src/cascadia/TerminalApp/AppActionHandlers.cpp
+++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp
@@ -1144,6 +1144,35 @@ namespace winrt::TerminalApp::implementation
         }
     }
 
+    void TerminalPage::_HandleSelectCommand(const IInspectable& /*sender*/,
+                                            const ActionEventArgs& args)
+    {
+        if (args)
+        {
+            if (const auto& realArgs = args.ActionArgs().try_as<SelectCommandArgs>())
+            {
+                const auto res = _ApplyToActiveControls([&](auto& control) {
+                    control.SelectCommand(realArgs.Direction() == Settings::Model::SelectOutputDirection::Previous);
+                });
+                args.Handled(res);
+            }
+        }
+    }
+    void TerminalPage::_HandleSelectOutput(const IInspectable& /*sender*/,
+                                           const ActionEventArgs& args)
+    {
+        if (args)
+        {
+            if (const auto& realArgs = args.ActionArgs().try_as<SelectOutputArgs>())
+            {
+                const auto res = _ApplyToActiveControls([&](auto& control) {
+                    control.SelectOutput(realArgs.Direction() == Settings::Model::SelectOutputDirection::Previous);
+                });
+                args.Handled(res);
+            }
+        }
+    }
+
     void TerminalPage::_HandleMarkMode(const IInspectable& /*sender*/,
                                        const ActionEventArgs& args)
     {
diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp
index 18bcf861438..f4768776dc5 100644
--- a/src/cascadia/TerminalControl/ControlCore.cpp
+++ b/src/cascadia/TerminalControl/ControlCore.cpp
@@ -2182,6 +2182,90 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         }
     }
 
+    void ControlCore::SelectCommand(const bool goUp)
+    {
+        const til::point start = HasSelection() ? (goUp ? _terminal->GetSelectionAnchor() : _terminal->GetSelectionEnd()) :
+                                                  _terminal->GetTextBuffer().GetCursor().GetPosition();
+        std::optional<DispatchTypes::ScrollMark> nearest{ std::nullopt };
+        const auto& marks{ _terminal->GetScrollMarks() };
+
+        // Early return so we don't have to check for the validity of `nearest` below after the loop exits.
+        if (marks.empty())
+        {
+            return;
+        }
+
+        static constexpr til::point worst{ til::CoordTypeMax, til::CoordTypeMax };
+        til::point bestDistance{ worst };
+
+        for (const auto& m : marks)
+        {
+            if (!m.HasCommand())
+            {
+                continue;
+            }
+
+            const auto distance = goUp ? start - m.end : m.end - start;
+            if ((distance > til::point{ 0, 0 }) && distance < bestDistance)
+            {
+                nearest = m;
+                bestDistance = distance;
+            }
+        }
+
+        if (nearest.has_value())
+        {
+            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();
+        }
+    }
+
+    void ControlCore::SelectOutput(const bool goUp)
+    {
+        const til::point start = HasSelection() ? (goUp ? _terminal->GetSelectionAnchor() : _terminal->GetSelectionEnd()) :
+                                                  _terminal->GetTextBuffer().GetCursor().GetPosition();
+        std::optional<DispatchTypes::ScrollMark> nearest{ std::nullopt };
+        const auto& marks{ _terminal->GetScrollMarks() };
+
+        static constexpr til::point worst{ til::CoordTypeMax, til::CoordTypeMax };
+        til::point bestDistance{ worst };
+
+        for (const auto& m : marks)
+        {
+            if (!m.HasOutput())
+            {
+                continue;
+            }
+
+            const auto distance = goUp ? start - *m.commandEnd : *m.commandEnd - start;
+            if ((distance > til::point{ 0, 0 }) && distance < bestDistance)
+            {
+                nearest = m;
+                bestDistance = distance;
+            }
+        }
+
+        if (nearest.has_value())
+        {
+            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();
+        }
+    }
+
     void ControlCore::ColorSelection(const Control::SelectionColor& fg, const Control::SelectionColor& bg, Core::MatchMode matchMode)
     {
         if (HasSelection())
diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h
index 1d11495d8d5..1e223376d72 100644
--- a/src/cascadia/TerminalControl/ControlCore.h
+++ b/src/cascadia/TerminalControl/ControlCore.h
@@ -152,7 +152,8 @@ 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);
 #pragma endregion
 
 #pragma region ITerminalInput
diff --git a/src/cascadia/TerminalControl/ICoreState.idl b/src/cascadia/TerminalControl/ICoreState.idl
index 4d4d2e2e4ed..405d1bd00f8 100644
--- a/src/cascadia/TerminalControl/ICoreState.idl
+++ b/src/cascadia/TerminalControl/ICoreState.idl
@@ -57,6 +57,8 @@ namespace Microsoft.Terminal.Control
         void ClearMark();
         void ClearAllMarks();
         void ScrollToMark(ScrollToMarkDirection direction);
+        void SelectCommand(Boolean goUp);
+        void SelectOutput(Boolean goUp);
         IVector<ScrollMark> ScrollMarks { get; };
 
     };
diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp
index 12c7ca25416..58d93e71179 100644
--- a/src/cascadia/TerminalControl/TermControl.cpp
+++ b/src/cascadia/TerminalControl/TermControl.cpp
@@ -3316,6 +3316,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         return _core.ScrollMarks();
     }
 
+    void TermControl::SelectCommand(const bool goUp)
+    {
+        _core.SelectCommand(goUp);
+    }
+
+    void TermControl::SelectOutput(const bool goUp)
+    {
+        _core.SelectOutput(goUp);
+    }
+
     void TermControl::ColorSelection(Control::SelectionColor fg, Control::SelectionColor bg, Core::MatchMode matchMode)
     {
         _core.ColorSelection(fg, bg, matchMode);
diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h
index ef4bf00fdfa..575a6ec84c6 100644
--- a/src/cascadia/TerminalControl/TermControl.h
+++ b/src/cascadia/TerminalControl/TermControl.h
@@ -81,6 +81,8 @@ 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);
 
 #pragma endregion
 
diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp
index 5453319df9d..8b5613bd006 100644
--- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp
+++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp
@@ -83,6 +83,8 @@ 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 SelectCommandKey{ "selectCommand" };
+static constexpr std::string_view SelectOutputKey{ "selectOutput" };
 static constexpr std::string_view MarkModeKey{ "markMode" };
 static constexpr std::string_view ToggleBlockSelectionKey{ "toggleBlockSelection" };
 static constexpr std::string_view SwitchSelectionEndpointKey{ "switchSelectionEndpoint" };
@@ -329,22 +331,24 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
 
     winrt::hstring ActionAndArgs::GenerateName() const
     {
+        // Sentinel used to indicate this command must ALWAYS be generated by GenerateName
+        static const winrt::hstring MustGenerate{ L"" };
         // Use a magic static to initialize this map, because we won't be able
         // to load the resources at _init_, only at runtime.
         static const auto GeneratedActionNames = []() {
             return std::unordered_map<ShortcutAction, winrt::hstring>{
                 { ShortcutAction::AdjustFontSize, RS_(L"AdjustFontSizeCommandKey") },
                 { ShortcutAction::CloseOtherPanes, RS_(L"CloseOtherPanesCommandKey") },
-                { ShortcutAction::CloseOtherTabs, L"" }, // Intentionally omitted, must be generated by GenerateName
+                { ShortcutAction::CloseOtherTabs, MustGenerate },
                 { ShortcutAction::ClosePane, RS_(L"ClosePaneCommandKey") },
-                { ShortcutAction::CloseTab, L"" }, // Intentionally omitted, must be generated by GenerateName
-                { ShortcutAction::CloseTabsAfter, L"" }, // Intentionally omitted, must be generated by GenerateName
+                { ShortcutAction::CloseTab, MustGenerate },
+                { ShortcutAction::CloseTabsAfter, MustGenerate },
                 { ShortcutAction::CloseWindow, RS_(L"CloseWindowCommandKey") },
                 { ShortcutAction::CopyText, RS_(L"CopyTextCommandKey") },
                 { ShortcutAction::DuplicateTab, RS_(L"DuplicateTabCommandKey") },
                 { ShortcutAction::ExecuteCommandline, RS_(L"ExecuteCommandlineCommandKey") },
                 { ShortcutAction::Find, RS_(L"FindCommandKey") },
-                { ShortcutAction::Invalid, L"" },
+                { ShortcutAction::Invalid, MustGenerate },
                 { ShortcutAction::MoveFocus, RS_(L"MoveFocusCommandKey") },
                 { ShortcutAction::MovePane, RS_(L"MovePaneCommandKey") },
                 { ShortcutAction::SwapPane, RS_(L"SwapPaneCommandKey") },
@@ -369,25 +373,25 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
                 { ShortcutAction::AddMark, RS_(L"AddMarkCommandKey") },
                 { ShortcutAction::ClearMark, RS_(L"ClearMarkCommandKey") },
                 { ShortcutAction::ClearAllMarks, RS_(L"ClearAllMarksCommandKey") },
-                { ShortcutAction::SendInput, L"" },
-                { ShortcutAction::SetColorScheme, L"" },
+                { ShortcutAction::SendInput, MustGenerate },
+                { ShortcutAction::SetColorScheme, MustGenerate },
                 { ShortcutAction::SetTabColor, RS_(L"ResetTabColorCommandKey") },
                 { ShortcutAction::SplitPane, RS_(L"SplitPaneCommandKey") },
                 { ShortcutAction::SwitchToTab, RS_(L"SwitchToTabCommandKey") },
                 { ShortcutAction::TabSearch, RS_(L"TabSearchCommandKey") },
                 { ShortcutAction::ToggleAlwaysOnTop, RS_(L"ToggleAlwaysOnTopCommandKey") },
-                { ShortcutAction::ToggleCommandPalette, L"" },
+                { ShortcutAction::ToggleCommandPalette, MustGenerate },
                 { ShortcutAction::ToggleFocusMode, RS_(L"ToggleFocusModeCommandKey") },
-                { ShortcutAction::SetFocusMode, L"" },
+                { ShortcutAction::SetFocusMode, MustGenerate },
                 { ShortcutAction::ToggleFullscreen, RS_(L"ToggleFullscreenCommandKey") },
-                { ShortcutAction::SetFullScreen, L"" },
-                { ShortcutAction::SetMaximized, L"" },
+                { ShortcutAction::SetFullScreen, MustGenerate },
+                { ShortcutAction::SetMaximized, MustGenerate },
                 { ShortcutAction::TogglePaneZoom, RS_(L"TogglePaneZoomCommandKey") },
                 { ShortcutAction::ToggleSplitOrientation, RS_(L"ToggleSplitOrientationCommandKey") },
                 { ShortcutAction::ToggleShaderEffects, RS_(L"ToggleShaderEffectsCommandKey") },
-                { ShortcutAction::MoveTab, L"" }, // Intentionally omitted, must be generated by GenerateName
+                { ShortcutAction::MoveTab, MustGenerate },
                 { ShortcutAction::BreakIntoDebugger, RS_(L"BreakIntoDebuggerCommandKey") },
-                { ShortcutAction::FindMatch, L"" }, // Intentionally omitted, must be generated by GenerateName
+                { ShortcutAction::FindMatch, MustGenerate },
                 { ShortcutAction::TogglePaneReadOnly, RS_(L"TogglePaneReadOnlyCommandKey") },
                 { ShortcutAction::EnablePaneReadOnly, RS_(L"EnablePaneReadOnlyCommandKey") },
                 { ShortcutAction::DisablePaneReadOnly, RS_(L"DisablePaneReadOnlyCommandKey") },
@@ -396,21 +400,23 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
                 { ShortcutAction::IdentifyWindows, RS_(L"IdentifyWindowsCommandKey") },
                 { ShortcutAction::RenameWindow, RS_(L"ResetWindowNameCommandKey") },
                 { ShortcutAction::OpenWindowRenamer, RS_(L"OpenWindowRenamerCommandKey") },
-                { ShortcutAction::GlobalSummon, L"" }, // Intentionally omitted, must be generated by GenerateName
+                { ShortcutAction::GlobalSummon, MustGenerate },
                 { ShortcutAction::QuakeMode, RS_(L"QuakeModeCommandKey") },
-                { ShortcutAction::FocusPane, L"" }, // Intentionally omitted, must be generated by GenerateName
+                { ShortcutAction::FocusPane, MustGenerate },
                 { ShortcutAction::OpenSystemMenu, RS_(L"OpenSystemMenuCommandKey") },
-                { ShortcutAction::ExportBuffer, L"" }, // Intentionally omitted, must be generated by GenerateName
-                { ShortcutAction::ClearBuffer, L"" }, // Intentionally omitted, must be generated by GenerateName
-                { ShortcutAction::MultipleActions, L"" }, // Intentionally omitted, must be generated by GenerateName
+                { ShortcutAction::ExportBuffer, MustGenerate },
+                { ShortcutAction::ClearBuffer, MustGenerate },
+                { ShortcutAction::MultipleActions, MustGenerate },
                 { ShortcutAction::Quit, RS_(L"QuitCommandKey") },
-                { ShortcutAction::AdjustOpacity, L"" }, // Intentionally omitted, must be generated by GenerateName
+                { ShortcutAction::AdjustOpacity, MustGenerate },
                 { ShortcutAction::RestoreLastClosed, RS_(L"RestoreLastClosedCommandKey") },
+                { ShortcutAction::SelectCommand, MustGenerate },
+                { ShortcutAction::SelectOutput, MustGenerate },
                 { ShortcutAction::SelectAll, RS_(L"SelectAllCommandKey") },
                 { ShortcutAction::MarkMode, RS_(L"MarkModeCommandKey") },
                 { ShortcutAction::ToggleBlockSelection, RS_(L"ToggleBlockSelectionCommandKey") },
                 { ShortcutAction::SwitchSelectionEndpoint, RS_(L"SwitchSelectionEndpointCommandKey") },
-                { ShortcutAction::ColorSelection, L"" }, // Intentionally omitted, must be generated by GenerateName
+                { ShortcutAction::ColorSelection, MustGenerate },
                 { ShortcutAction::ExpandSelectionToWord, RS_(L"ExpandSelectionToWordCommandKey") },
             };
         }();
diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp
index 422c4d2ce5a..626817b0366 100644
--- a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp
+++ b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp
@@ -43,6 +43,8 @@
 #include "ClearBufferArgs.g.cpp"
 #include "MultipleActionsArgs.g.cpp"
 #include "AdjustOpacityArgs.g.cpp"
+#include "SelectCommandArgs.g.cpp"
+#include "SelectOutputArgs.g.cpp"
 #include "ColorSelectionArgs.g.cpp"
 
 #include <LibraryResources.h>
@@ -970,4 +972,27 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
             };
         }
     }
+
+    winrt::hstring SelectOutputArgs::GenerateName() const
+    {
+        switch (Direction())
+        {
+        case SelectOutputDirection::Next:
+            return RS_(L"SelectOutputNextCommandKey");
+        case SelectOutputDirection::Previous:
+            return RS_(L"SelectOutputPreviousCommandKey");
+        }
+        return L"";
+    }
+    winrt::hstring SelectCommandArgs::GenerateName() const
+    {
+        switch (Direction())
+        {
+        case SelectOutputDirection::Next:
+            return RS_(L"SelectCommandNextCommandKey");
+        case SelectOutputDirection::Previous:
+            return RS_(L"SelectCommandPreviousCommandKey");
+        }
+        return L"";
+    }
 }
diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h
index cc598dbb6ad..000ecade500 100644
--- a/src/cascadia/TerminalSettingsModel/ActionArgs.h
+++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h
@@ -45,6 +45,8 @@
 #include "ClearBufferArgs.g.h"
 #include "MultipleActionsArgs.g.h"
 #include "AdjustOpacityArgs.g.h"
+#include "SelectCommandArgs.g.h"
+#include "SelectOutputArgs.g.h"
 #include "ColorSelectionArgs.g.h"
 
 #include "JsonUtils.h"
@@ -250,6 +252,14 @@ private:                                                                    \
     X(int32_t, Opacity, "opacity", false, 0) \
     X(bool, Relative, "relative", false, true)
 
+////////////////////////////////////////////////////////////////////////////////
+#define SELECT_COMMAND_ARGS(X) \
+    X(SelectOutputDirection, Direction, "direction", false, SelectOutputDirection::Previous)
+
+////////////////////////////////////////////////////////////////////////////////
+#define SELECT_OUTPUT_ARGS(X) \
+    X(SelectOutputDirection, Direction, "direction", false, SelectOutputDirection::Previous)
+
 ////////////////////////////////////////////////////////////////////////////////
 #define COLOR_SELECTION_ARGS(X)                                                                      \
     X(winrt::Microsoft::Terminal::Control::SelectionColor, Foreground, "foreground", false, nullptr) \
@@ -778,6 +788,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
 
     ACTION_ARGS_STRUCT(AdjustOpacityArgs, ADJUST_OPACITY_ARGS);
 
+    ACTION_ARGS_STRUCT(SelectCommandArgs, SELECT_COMMAND_ARGS);
+    ACTION_ARGS_STRUCT(SelectOutputArgs, SELECT_OUTPUT_ARGS);
+
     ACTION_ARGS_STRUCT(ColorSelectionArgs, COLOR_SELECTION_ARGS);
 
 }
@@ -814,4 +827,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
     BASIC_FACTORY(ClearBufferArgs);
     BASIC_FACTORY(MultipleActionsArgs);
     BASIC_FACTORY(AdjustOpacityArgs);
+    BASIC_FACTORY(SelectCommandArgs);
+    BASIC_FACTORY(SelectOutputArgs);
 }
diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl
index 7c7d38a61ac..c64e2c8449c 100644
--- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl
+++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl
@@ -80,6 +80,12 @@ namespace Microsoft.Terminal.Settings.Model
         Previous
     };
 
+    enum SelectOutputDirection
+    {
+        Previous = 0,
+        Next,
+    };
+
     enum CommandPaletteLaunchMode
     {
         Action = 0,
@@ -388,4 +394,17 @@ namespace Microsoft.Terminal.Settings.Model
         Microsoft.Terminal.Control.SelectionColor Background;
         Microsoft.Terminal.Core.MatchMode MatchMode { get; };
     };
+
+    [default_interface] runtimeclass SelectCommandArgs : IActionArgs
+    {
+        SelectCommandArgs(SelectOutputDirection direction);
+        SelectOutputDirection Direction { get; };
+    }
+    [default_interface] runtimeclass SelectOutputArgs : IActionArgs
+    {
+        SelectOutputArgs(SelectOutputDirection direction);
+        SelectOutputDirection Direction { get; };
+    }
+
+
 }
diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h
index 326409a7c23..fd6f1a080e2 100644
--- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h
+++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h
@@ -96,6 +96,8 @@
     ON_ALL_ACTIONS(AdjustOpacity)           \
     ON_ALL_ACTIONS(RestoreLastClosed)       \
     ON_ALL_ACTIONS(SelectAll)               \
+    ON_ALL_ACTIONS(SelectCommand)           \
+    ON_ALL_ACTIONS(SelectOutput)            \
     ON_ALL_ACTIONS(MarkMode)                \
     ON_ALL_ACTIONS(ToggleBlockSelection)    \
     ON_ALL_ACTIONS(SwitchSelectionEndpoint) \
@@ -142,4 +144,6 @@
     ON_ALL_ACTIONS_WITH_ARGS(ClearBuffer)          \
     ON_ALL_ACTIONS_WITH_ARGS(MultipleActions)      \
     ON_ALL_ACTIONS_WITH_ARGS(AdjustOpacity)        \
+    ON_ALL_ACTIONS_WITH_ARGS(SelectCommand)        \
+    ON_ALL_ACTIONS_WITH_ARGS(SelectOutput)         \
     ON_ALL_ACTIONS_WITH_ARGS(ColorSelection)
diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw
index 2f4514bb70e..a64259f182d 100644
--- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw
@@ -675,4 +675,16 @@
   <data name="CloseOtherPanesCommandKey" xml:space="preserve">
     <value>Close all other panes</value>
   </data>
+  <data name="SelectOutputNextCommandKey" xml:space="preserve">
+    <value>Select next command output</value>
+  </data>
+  <data name="SelectOutputPreviousCommandKey" xml:space="preserve">
+    <value>Select previous command output</value>
+  </data>
+  <data name="SelectCommandNextCommandKey" xml:space="preserve">
+    <value>Select next command</value>
+  </data>
+  <data name="SelectCommandPreviousCommandKey" xml:space="preserve">
+    <value>Select previous command</value>
+  </data>
 </root>
diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h
index fd5738da4a0..ef36cb98c91 100644
--- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h
+++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h
@@ -672,6 +672,14 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::FolderEntryInlin
     };
 };
 
+JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::SelectOutputDirection)
+{
+    JSON_MAPPINGS(2) = {
+        pair_type{ "prev", ValueType::Previous },
+        pair_type{ "next", ValueType::Next },
+    };
+};
+
 template<>
 struct ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<::winrt::Microsoft::Terminal::Control::SelectionColor>
 {
diff --git a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp
index 63f93cdb910..5e9ecfcc64c 100644
--- a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp
+++ b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp
@@ -38,6 +38,9 @@ namespace ControlUnitTests
         TEST_METHOD(TestClearAll);
         TEST_METHOD(TestReadEntireBuffer);
 
+        TEST_METHOD(TestSelectCommandSimple);
+        TEST_METHOD(TestSelectOutputSimple);
+
         TEST_CLASS_SETUP(ModuleSetup)
         {
             winrt::init_apartment(winrt::apartment_type::single_threaded);
@@ -358,5 +361,142 @@ namespace ControlUnitTests
         VERIFY_ARE_EQUAL(L"This is some text\r\nwith varying amounts\r\nof whitespace\r\n",
                          core->ReadEntireBuffer());
     }
+    void _writePrompt(const winrt::com_ptr<MockConnection>& conn, const auto& path)
+    {
+        conn->WriteInput(L"\x1b]133;D\x7");
+        conn->WriteInput(L"\x1b]133;A\x7");
+        conn->WriteInput(L"\x1b]9;9;");
+        conn->WriteInput(path);
+        conn->WriteInput(L"\x7");
+        conn->WriteInput(L"PWSH ");
+        conn->WriteInput(path);
+        conn->WriteInput(L"> ");
+        conn->WriteInput(L"\x1b]133;B\x7");
+    }
+
+    void ControlCoreTests::TestSelectCommandSimple()
+    {
+        auto [settings, conn] = _createSettingsAndConnection();
+        Log::Comment(L"Create ControlCore object");
+        auto core = createCore(*settings, *conn);
+        VERIFY_IS_NOT_NULL(core);
+        _standardInit(core);
+
+        Log::Comment(L"Print some text");
+
+        _writePrompt(conn, L"C:\\Windows");
+        conn->WriteInput(L"Foo-bar");
+        conn->WriteInput(L"\x1b]133;C\x7");
+
+        conn->WriteInput(L"\r\n");
+        conn->WriteInput(L"This is some text     \r\n");
+        conn->WriteInput(L"with varying amounts  \r\n");
+        conn->WriteInput(L"of whitespace         \r\n");
+
+        _writePrompt(conn, L"C:\\Windows");
+
+        Log::Comment(L"Check the buffer contents");
+        const auto& buffer = core->_terminal->GetTextBuffer();
+        const auto& cursor = buffer.GetCursor();
+
+        {
+            const til::point expectedCursor{ 17, 4 };
+            VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition());
+        }
 
+        VERIFY_IS_FALSE(core->HasSelection());
+        core->SelectCommand(true);
+        VERIFY_IS_TRUE(core->HasSelection());
+        {
+            const auto& start = core->_terminal->GetSelectionAnchor();
+            const auto& end = core->_terminal->GetSelectionEnd();
+            const til::point expectedStart{ 17, 0 };
+            const til::point expectedEnd{ 23, 0 };
+            VERIFY_ARE_EQUAL(expectedStart, start);
+            VERIFY_ARE_EQUAL(expectedEnd, end);
+        }
+
+        core->_terminal->ClearSelection();
+        conn->WriteInput(L"Boo-far");
+        conn->WriteInput(L"\x1b]133;C\x7");
+
+        VERIFY_IS_FALSE(core->HasSelection());
+        {
+            const til::point expectedCursor{ 24, 4 };
+            VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition());
+        }
+        VERIFY_IS_FALSE(core->HasSelection());
+        core->SelectCommand(true);
+        VERIFY_IS_TRUE(core->HasSelection());
+        {
+            const auto& start = core->_terminal->GetSelectionAnchor();
+            const auto& end = core->_terminal->GetSelectionEnd();
+            const til::point expectedStart{ 17, 4 };
+            const til::point expectedEnd{ 23, 4 };
+            VERIFY_ARE_EQUAL(expectedStart, start);
+            VERIFY_ARE_EQUAL(expectedEnd, end);
+        }
+        core->SelectCommand(true);
+        VERIFY_IS_TRUE(core->HasSelection());
+        {
+            const auto& start = core->_terminal->GetSelectionAnchor();
+            const auto& end = core->_terminal->GetSelectionEnd();
+            const til::point expectedStart{ 17, 0 };
+            const til::point expectedEnd{ 23, 0 };
+            VERIFY_ARE_EQUAL(expectedStart, start);
+            VERIFY_ARE_EQUAL(expectedEnd, end);
+        }
+        core->SelectCommand(false);
+        VERIFY_IS_TRUE(core->HasSelection());
+        {
+            const auto& start = core->_terminal->GetSelectionAnchor();
+            const auto& end = core->_terminal->GetSelectionEnd();
+            const til::point expectedStart{ 17, 4 };
+            const til::point expectedEnd{ 23, 4 };
+            VERIFY_ARE_EQUAL(expectedStart, start);
+            VERIFY_ARE_EQUAL(expectedEnd, end);
+        }
+    }
+    void ControlCoreTests::TestSelectOutputSimple()
+    {
+        auto [settings, conn] = _createSettingsAndConnection();
+        Log::Comment(L"Create ControlCore object");
+        auto core = createCore(*settings, *conn);
+        VERIFY_IS_NOT_NULL(core);
+        _standardInit(core);
+
+        Log::Comment(L"Print some text");
+
+        _writePrompt(conn, L"C:\\Windows");
+        conn->WriteInput(L"Foo-bar");
+        conn->WriteInput(L"\x1b]133;C\x7");
+
+        conn->WriteInput(L"\r\n");
+        conn->WriteInput(L"This is some text     \r\n");
+        conn->WriteInput(L"with varying amounts  \r\n");
+        conn->WriteInput(L"of whitespace         \r\n");
+
+        _writePrompt(conn, L"C:\\Windows");
+
+        Log::Comment(L"Check the buffer contents");
+        const auto& buffer = core->_terminal->GetTextBuffer();
+        const auto& cursor = buffer.GetCursor();
+
+        {
+            const til::point expectedCursor{ 17, 4 };
+            VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition());
+        }
+
+        VERIFY_IS_FALSE(core->HasSelection());
+        core->SelectOutput(true);
+        VERIFY_IS_TRUE(core->HasSelection());
+        {
+            const auto& start = core->_terminal->GetSelectionAnchor();
+            const auto& end = core->_terminal->GetSelectionEnd();
+            const til::point expectedStart{ 24, 0 }; // The character after the prompt
+            const til::point expectedEnd{ 29, 3 }; // x = buffer.right
+            VERIFY_ARE_EQUAL(expectedStart, start);
+            VERIFY_ARE_EQUAL(expectedEnd, end);
+        }
+    }
 }
diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp
index eeb19db1366..0f8b6ede82c 100644
--- a/src/terminal/adapter/DispatchTypes.hpp
+++ b/src/terminal/adapter/DispatchTypes.hpp
@@ -580,5 +580,14 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
         MarkCategory category{ MarkCategory::Info };
         // Other things we may want to think about in the future are listed in
         // GH#11000
+
+        bool HasCommand() const noexcept
+        {
+            return commandEnd.has_value() && *commandEnd != end;
+        }
+        bool HasOutput() const noexcept
+        {
+            return outputEnd.has_value() && *outputEnd != *commandEnd;
+        }
     };
 }
diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp
index 37e7125627d..b8ba580d45b 100644
--- a/src/terminal/adapter/adaptDispatch.cpp
+++ b/src/terminal/adapter/adaptDispatch.cpp
@@ -3173,6 +3173,18 @@ bool AdaptDispatch::DoConEmuAction(const std::wstring_view string)
             return true;
         }
     }
+    // 12: "Let ConEmu treat current cursor position as prompt start"
+    //
+    // Based on the official conemu docs:
+    // * https://conemu.github.io/en/ShellWorkDir.html#connector-ps1
+    // * https://conemu.github.io/en/ShellWorkDir.html#PowerShell
+    //
+    // This seems like basically the same as 133;B - the end of the prompt, the start of the commandline.
+    else if (subParam == 12)
+    {
+        _api.MarkCommandStart();
+        return true;
+    }
 
     return false;
 }


Test patch: