Add the ability to select a whole command (or its output) (#14807)

Adds two new commands, `selectOutput` and `selectCommand`. These don't
do much without shell integration enabled, unfortunately. If you do
enable it, however, you can use these commands to quickly navigate the
history to select whole commands (or their output).

Some sample JSON:

```json
        { "keys": "ctrl+shift+<", "command": { "action": "selectCommand", "direction": "prev" } },
        { "keys": "ctrl+shift+>", "command": { "action": "selectCommand", "direction": "next" } },
        { "keys": "ctrl+shift+[", "command": { "action": "selectOutput", "direction": "prev" } },
        { "keys": "ctrl+shift+]", "command": { "action": "selectOutput", "direction": "next" } },
```

**Demo gifs** in
https://github.com/microsoft/terminal/issues/4588#issuecomment-1352042789

closes #4588

Tested manually. 

<details>
<summary>CMD.exe user? It's dangerous to go alone! Take this.</summary>

Surely, there's a simpler way to do it, this is adapted from my own
script.

```cmd
prompt $e]133;D$e\$e]133;A$e\$e\$e]9;9;$P$e\$e[30;107m[$T]$e[97;46m$g$P$e[36;49m$g$e[0m$e[K$_$e[0m$e[94m%username%$e[0m@$e[32m%computername%$e[0m$G$e]133;B$e\
```

</details>
This commit is contained in:
Mike Griese
2023-04-20 07:34:58 -05:00
committed by GitHub
parent 35b9e75574
commit 0e86ce559e
16 changed files with 398 additions and 20 deletions

View File

@@ -1145,6 +1145,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)
{

View File

@@ -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())

View File

@@ -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

View File

@@ -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; };
};

View File

@@ -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);

View File

@@ -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

View File

@@ -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") },
};
}();

View File

@@ -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"";
}
}

View File

@@ -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);
}

View File

@@ -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; };
}
}

View File

@@ -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)

View File

@@ -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>

View File

@@ -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>
{

View File

@@ -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);
}
}
}

View File

@@ -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;
}
};
}

View File

@@ -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;
}