Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clip the "current commandline" at the cursor position #17781

Merged
merged 2 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .wt.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
"name": "Upload package to nuget feed",
"icon": "\uE898",
"description": "Go download a .nupkg, put it in ~/Downloads, and use this to push to our private feed."
},
{
"input": "runut /name:**\u001b[D",
"name": "Run a test",
"icon": "",
"description": "Enter the name of a test to run"
}
]
}
19 changes: 15 additions & 4 deletions src/buffer/out/textBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3266,23 +3266,30 @@ MarkExtents TextBuffer::_scrollMarkExtentForRow(const til::CoordType rowOffset,
return mark;
}

std::wstring TextBuffer::_commandForRow(const til::CoordType rowOffset, const til::CoordType bottomInclusive) const
std::wstring TextBuffer::_commandForRow(const til::CoordType rowOffset,
const til::CoordType bottomInclusive,
const bool clipAtCursor) const
{
std::wstring commandBuilder;
MarkKind lastMarkKind = MarkKind::Prompt;
const auto cursorPosition = GetCursor().GetPosition();
for (auto y = rowOffset; y <= bottomInclusive; y++)
{
const bool onCursorRow = clipAtCursor && y == cursorPosition.y;
// Now we need to iterate over text attributes. We need to find a
// segment of Prompt attributes, we'll skip those. Then there should be
// Command attributes. Collect up all of those, till we get to the next
// Output attribute.

const auto& row = GetRowByOffset(y);
const auto runs = row.Attributes().runs();
auto x = 0;
for (const auto& [attr, length] : runs)
{
const auto nextX = gsl::narrow_cast<uint16_t>(x + length);
auto nextX = gsl::narrow_cast<uint16_t>(x + length);
if (onCursorRow)
{
nextX = std::min(nextX, gsl::narrow_cast<uint16_t>(cursorPosition.x));
}
const auto markKind{ attr.GetMarkAttributes() };
if (markKind != lastMarkKind)
{
Expand All @@ -3302,6 +3309,10 @@ std::wstring TextBuffer::_commandForRow(const til::CoordType rowOffset, const ti
}
// advance to next run of text
x = nextX;
if (onCursorRow && x == cursorPosition.x)
{
return commandBuilder;
}
}
// we went over all the runs in this row, but we're not done yet. Keep iterating on the next row.
}
Expand All @@ -3325,7 +3336,7 @@ std::wstring TextBuffer::CurrentCommand() const
// This row did start a prompt! Find the prompt that starts here.
// Presumably, no rows below us will have prompts, so pass in the last
// row with text as the bottom
return _commandForRow(promptY, _estimateOffsetOfLastCommittedRow());
return _commandForRow(promptY, _estimateOffsetOfLastCommittedRow(), true);
}
return L"";
}
Expand Down
2 changes: 1 addition & 1 deletion src/buffer/out/textBuffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ class TextBuffer final
til::point _GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const;
void _PruneHyperlinks();

std::wstring _commandForRow(const til::CoordType rowOffset, const til::CoordType bottomInclusive) const;
std::wstring _commandForRow(const til::CoordType rowOffset, const til::CoordType bottomInclusive, const bool clipAtCursor = false) const;
MarkExtents _scrollMarkExtentForRow(const til::CoordType rowOffset, const til::CoordType bottomInclusive) const;
bool _createPromptMarkIfNeeded();

Expand Down
5 changes: 4 additions & 1 deletion src/cascadia/TerminalControl/ControlCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2285,7 +2285,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// If the very last thing in the list of recent commands, is exactly the
// same as the current command, then let's not include it in the
// history. It's literally the thing the user has typed, RIGHT now.
if (!commands.empty() && commands.back() == trimmedCurrentCommand)
// (also account for the fact that the cursor may be in the middle of a commandline)
if (!commands.empty() &&
!trimmedCurrentCommand.empty() &&
std::wstring_view{ commands.back() }.substr(0, trimmedCurrentCommand.size()) == trimmedCurrentCommand)
{
commands.pop_back();
}
Expand Down
57 changes: 57 additions & 0 deletions src/cascadia/UnitTests_Control/ControlCoreTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ namespace ControlUnitTests
TEST_METHOD(TestSelectCommandSimple);
TEST_METHOD(TestSelectOutputSimple);
TEST_METHOD(TestCommandContext);
TEST_METHOD(TestCommandContextWithPwshGhostText);

TEST_METHOD(TestSelectOutputScrolling);
TEST_METHOD(TestSelectOutputExactWrap);

Expand Down Expand Up @@ -556,6 +558,61 @@ namespace ControlUnitTests
}
}

void ControlCoreTests::TestCommandContextWithPwshGhostText()
{
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(winrt_wstring_to_array_view(L"Foo-bar"));
conn->WriteInput(winrt_wstring_to_array_view(L"\x1b]133;C\x7"));

conn->WriteInput(winrt_wstring_to_array_view(L"\r\n"));
conn->WriteInput(winrt_wstring_to_array_view(L"This is some text \r\n"));
conn->WriteInput(winrt_wstring_to_array_view(L"with varying amounts \r\n"));
conn->WriteInput(winrt_wstring_to_array_view(L"of whitespace \r\n"));

_writePrompt(conn, L"C:\\Windows");

Log::Comment(L"Check the command context");

const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
{
auto historyContext{ core->CommandHistory() };
VERIFY_ARE_EQUAL(1u, historyContext.History().Size());
VERIFY_ARE_EQUAL(L"", historyContext.CurrentCommandline());
}

Log::Comment(L"Write 'BarBar' to the command...");
conn->WriteInput(winrt_wstring_to_array_view(L"BarBar"));
{
auto historyContext{ core->CommandHistory() };
// BarBar shouldn't be in the history, it should be the current command
VERIFY_ARE_EQUAL(1u, historyContext.History().Size());
VERIFY_ARE_EQUAL(L"BarBar", historyContext.CurrentCommandline());
}

Log::Comment(L"then move the cursor to the left");
// This emulates the state the buffer is in when pwsh does it's "ghost
// text" thing. We don't want to include all that ghost text in the
// current commandline.
conn->WriteInput(winrt_wstring_to_array_view(L"\x1b[D"));
conn->WriteInput(winrt_wstring_to_array_view(L"\x1b[D"));
{
auto historyContext{ core->CommandHistory() };
VERIFY_ARE_EQUAL(1u, historyContext.History().Size());
// The current commandline is only the text to the left of the cursor
auto curr{ historyContext.CurrentCommandline() };
VERIFY_ARE_EQUAL(4u, curr.size());
VERIFY_ARE_EQUAL(L"BarB", curr);
}
}

void ControlCoreTests::TestSelectOutputScrolling()
{
auto [settings, conn] = _createSettingsAndConnection();
Expand Down
4 changes: 4 additions & 0 deletions src/host/ut_host/ScreenBufferTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8337,6 +8337,10 @@ void ScreenBufferTests::SimpleMarkCommand()

void ScreenBufferTests::SimpleWrappedCommand()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
END_TEST_METHOD_PROPERTIES()

auto& g = ServiceLocator::LocateGlobals();
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
Expand Down
Loading