Compare commits

...

4 Commits

Author SHA1 Message Date
Mike Griese
36f3591280 actually fix this 2024-04-26 16:57:23 -05:00
Mike Griese
0076e42607 start with a test 2024-04-26 16:28:17 -05:00
Mike Griese
0dc10f9180 move this for sanity 2024-04-26 11:45:42 -05:00
Mike Griese
16f6af5086 i don't know how this ever worked 2024-04-26 11:41:39 -05:00
5 changed files with 206 additions and 99 deletions

View File

@@ -1189,6 +1189,8 @@ void TextBuffer::ClearScrollback(const til::CoordType start, const til::CoordTyp
return;
}
ClearMarksInRange(til::point{ 0, 0 }, til::point{ _width, height });
// Our goal is to move the viewport to the absolute start of the underlying memory buffer so that we can
// MEM_DECOMMIT the remaining memory. _firstRow is used to make the TextBuffer behave like a circular buffer.
// The start parameter is relative to the _firstRow. The trick to get the content to the absolute start
@@ -1204,8 +1206,6 @@ void TextBuffer::ClearScrollback(const til::CoordType start, const til::CoordTyp
{
GetMutableRowByOffset(y).Reset(_initialAttributes);
}
ClearMarksInRange(til::point{ 0, height }, til::point{ _width, _height });
}
// Routine Description:

View File

@@ -2009,93 +2009,98 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
else if (_settings->RepositionCursorWithMouse()) // This is also mode==Char && !shiftEnabled
{
// If we're handling a single left click, without shift pressed, and
// outside mouse mode, AND the user has RepositionCursorWithMouse turned
// on, let's try to move the cursor.
//
// We'll only move the cursor if the user has clicked after the last
// mark, if there is one. That means the user also needs to set up
// shell integration to enable this feature.
//
// As noted in GH #8573, there's plenty of edge cases with this
// approach, but it's good enough to bring value to 90% of use cases.
const auto cursorPos{ _terminal->GetCursorPosition() };
_repositionCursorWithMouse(terminalPosition);
}
_updateSelectionUI();
}
// Does the current buffer line have a mark on it?
const auto& marks{ _terminal->GetMarkExtents() };
if (!marks.empty())
void ControlCore::_repositionCursorWithMouse(const til::point terminalPosition)
{
// If we're handling a single left click, without shift pressed, and
// outside mouse mode, AND the user has RepositionCursorWithMouse turned
// on, let's try to move the cursor.
//
// We'll only move the cursor if the user has clicked after the last
// mark, if there is one. That means the user also needs to set up
// shell integration to enable this feature.
//
// As noted in GH #8573, there's plenty of edge cases with this
// approach, but it's good enough to bring value to 90% of use cases.
const auto cursorPos{ _terminal->GetCursorPosition() };
// Does the current buffer line have a mark on it?
const auto& marks{ _terminal->GetMarkExtents() };
if (!marks.empty())
{
const auto& last{ marks.back() };
const auto [start, end] = last.GetExtent();
const auto bufferSize = _terminal->GetTextBuffer().GetSize();
auto lastNonSpace = _terminal->GetTextBuffer().GetLastNonSpaceCharacter();
bufferSize.IncrementInBounds(lastNonSpace, true);
// If the user clicked off to the right side of the prompt, we
// want to send keystrokes to the last character in the prompt +1.
//
// We don't want to send too many here. In CMD, if the user's
// last command is longer than what they've currently typed, and
// they press right arrow at the end of the prompt, COOKED_READ
// will fill in characters from the previous command.
//
// By only sending keypresses to the end of the command + 1, we
// should leave the cursor at the very end of the prompt,
// without adding any characters from a previous command.
// terminalPosition is viewport-relative.
const auto bufferPos = _terminal->GetViewport().Origin() + terminalPosition;
if (bufferPos.y > lastNonSpace.y)
{
const auto& last{ marks.back() };
const auto [start, end] = last.GetExtent();
const auto bufferSize = _terminal->GetTextBuffer().GetSize();
auto lastNonSpace = _terminal->GetTextBuffer().GetLastNonSpaceCharacter();
bufferSize.IncrementInBounds(lastNonSpace, true);
// Clicked under the prompt. Bail.
return;
}
// If the user clicked off to the right side of the prompt, we
// want to send keystrokes to the last character in the prompt +1.
//
// We don't want to send too many here. In CMD, if the user's
// last command is longer than what they've currently typed, and
// they press right arrow at the end of the prompt, COOKED_READ
// will fill in characters from the previous command.
//
// By only sending keypresses to the end of the command + 1, we
// should leave the cursor at the very end of the prompt,
// without adding any characters from a previous command.
// Limit the click to 1 past the last character on the last line.
const auto clampedClick = std::min(bufferPos, lastNonSpace);
// terminalPosition is viewport-relative.
const auto bufferPos = _terminal->GetViewport().Origin() + terminalPosition;
if (bufferPos.y > lastNonSpace.y)
if (clampedClick >= last.end)
{
// Get the distance between the cursor and the click, in cells.
// First, make sure to iterate from the first point to the
// second. The user may have clicked _earlier_ in the
// buffer!
auto goRight = clampedClick > cursorPos;
const auto startPoint = goRight ? cursorPos : clampedClick;
const auto endPoint = goRight ? clampedClick : cursorPos;
const auto delta = _terminal->GetTextBuffer().GetCellDistance(startPoint, endPoint);
const WORD key = goRight ? VK_RIGHT : VK_LEFT;
std::wstring buffer;
const auto append = [&](TerminalInput::OutputType&& out) {
if (out)
{
buffer.append(std::move(*out));
}
};
// Send an up and a down once per cell. This won't
// accurately handle wide characters, or continuation
// prompts, or cases where a single escape character in the
// command (e.g. ^[) takes up two cells.
for (size_t i = 0u; i < delta; i++)
{
// Clicked under the prompt. Bail.
return;
append(_terminal->SendKeyEvent(key, 0, {}, true));
append(_terminal->SendKeyEvent(key, 0, {}, false));
}
// Limit the click to 1 past the last character on the last line.
const auto clampedClick = std::min(bufferPos, lastNonSpace);
if (clampedClick >= end)
{
// Get the distance between the cursor and the click, in cells.
// First, make sure to iterate from the first point to the
// second. The user may have clicked _earlier_ in the
// buffer!
auto goRight = clampedClick > cursorPos;
const auto startPoint = goRight ? cursorPos : clampedClick;
const auto endPoint = goRight ? clampedClick : cursorPos;
const auto delta = _terminal->GetTextBuffer().GetCellDistance(startPoint, endPoint);
const WORD key = goRight ? VK_RIGHT : VK_LEFT;
std::wstring buffer;
const auto append = [&](TerminalInput::OutputType&& out) {
if (out)
{
buffer.append(std::move(*out));
}
};
// Send an up and a down once per cell. This won't
// accurately handle wide characters, or continuation
// prompts, or cases where a single escape character in the
// command (e.g. ^[) takes up two cells.
for (size_t i = 0u; i < delta; i++)
{
append(_terminal->SendKeyEvent(key, 0, {}, true));
append(_terminal->SendKeyEvent(key, 0, {}, false));
}
{
// Sending input requires that we're unlocked, because
// writing the input pipe may block indefinitely.
const auto suspension = _terminal->SuspendLock();
_sendInputToConnection(buffer);
}
// Sending input requires that we're unlocked, because
// writing the input pipe may block indefinitely.
const auto suspension = _terminal->SuspendLock();
_sendInputToConnection(buffer);
}
}
}
_updateSelectionUI();
}
// Method Description:

View File

@@ -403,6 +403,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _focusChanged(bool focused);
void _selectSpan(til::point_span s);
void _repositionCursorWithMouse(const til::point terminalPosition);
void _contextMenuSelectMark(
const til::point& pos,

View File

@@ -239,12 +239,14 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
TEST_METHOD(MultilinePromptRegions);
TEST_METHOD(ManyMultilinePromptsWithTrailingSpaces);
TEST_METHOD(ReflowPromptRegions);
TEST_METHOD(ClearMarksTest);
private:
bool _writeCallback(const char* const pch, const size_t cch);
void _flushFirstFrame();
void _resizeConpty(const til::CoordType sx, const til::CoordType sy);
void _clearConpty();
void _clear(int clearBufferMethod, SCREEN_INFORMATION& si);
[[nodiscard]] std::tuple<TextBuffer*, TextBuffer*> _performResize(const til::size newSize);
@@ -2720,9 +2722,6 @@ void ConptyRoundtripTests::ClsAndClearHostClearsScrollbackTest()
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:clearBufferMethod", L"{0, 1, 2}")
END_TEST_METHOD_PROPERTIES();
constexpr auto ClearLikeCls = 0;
constexpr auto ClearLikeClearHost = 1;
constexpr auto ClearWithVT = 2;
INIT_TEST_PROPERTY(int, clearBufferMethod, L"Controls whether we clear the buffer like cmd or like powershell");
Log::Comment(L"This test checks the shims for cmd.exe and powershell.exe. "
@@ -2789,6 +2788,31 @@ void ConptyRoundtripTests::ClsAndClearHostClearsScrollbackTest()
VERIFY_ARE_EQUAL(si.GetViewport().Dimensions(), si.GetBufferSize().Dimensions());
VERIFY_ARE_EQUAL(si.GetViewport(), si.GetBufferSize());
_clear(clearBufferMethod, si);
VERIFY_ARE_EQUAL(si.GetViewport().Dimensions(), si.GetBufferSize().Dimensions());
VERIFY_ARE_EQUAL(si.GetViewport(), si.GetBufferSize());
Log::Comment(L"Painting the frame");
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"========== Checking the host buffer state (after) ==========");
verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), true);
Log::Comment(L"Painting the frame");
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"========== Checking the terminal buffer state (after) ==========");
verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), true);
}
void ConptyRoundtripTests::_clear(int clearBufferMethod, SCREEN_INFORMATION& si)
{
constexpr auto ClearLikeCls = 0;
constexpr auto ClearLikeClearHost = 1;
constexpr auto ClearWithVT = 2;
auto& sm = si.GetStateMachine();
if (clearBufferMethod == ClearLikeCls)
{
// Execute the cls, EXACTLY LIKE CMD.
@@ -2840,20 +2864,6 @@ void ConptyRoundtripTests::ClsAndClearHostClearsScrollbackTest()
sm.ProcessString(L"\x1b[3J");
}
VERIFY_ARE_EQUAL(si.GetViewport().Dimensions(), si.GetBufferSize().Dimensions());
VERIFY_ARE_EQUAL(si.GetViewport(), si.GetBufferSize());
Log::Comment(L"Painting the frame");
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"========== Checking the host buffer state (after) ==========");
verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), true);
Log::Comment(L"Painting the frame");
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"========== Checking the terminal buffer state (after) ==========");
verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), true);
}
void ConptyRoundtripTests::TestResizeWithCookedRead()
@@ -4945,3 +4955,98 @@ void ConptyRoundtripTests::ReflowPromptRegions()
Log::Comment(L"========== Checking the terminal buffer state (after) ==========");
verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), true, true);
}
void ConptyRoundtripTests::ClearMarksTest()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:clearBufferMethod", L"{0, 1, 2}")
END_TEST_METHOD_PROPERTIES();
INIT_TEST_PROPERTY(int, clearBufferMethod, L"Controls whether we clear the buffer like cmd or like powershell");
Log::Comment(L"This test checks the shims for cmd.exe and powershell.exe. "
L"Their build in commands for clearing the console buffer "
L"should work to clear the terminal buffer, not just the "
L"terminal viewport.");
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto* hostTb = &si.GetTextBuffer();
auto* termTb = term->_mainBuffer.get();
auto& sm = si.GetStateMachine();
_flushFirstFrame();
_checkConptyOutput = false;
_logConpty = false;
const auto hostView = si.GetViewport();
const auto end = 2 * hostView.Height();
auto writePrompt = [](StateMachine& stateMachine, const auto& path) {
// A prompt looks like:
// `PWSH C:\> `
//
// which is 10 characters for "C:\"
stateMachine.ProcessString(FTCS_D);
stateMachine.ProcessString(FTCS_A);
stateMachine.ProcessString(L"\x1b]9;9;");
stateMachine.ProcessString(path);
stateMachine.ProcessString(L"\x7");
stateMachine.ProcessString(L"PWSH ");
stateMachine.ProcessString(path);
stateMachine.ProcessString(L"> ");
stateMachine.ProcessString(FTCS_B);
};
auto writeCommand = [](StateMachine& stateMachine, const auto& cmd) {
stateMachine.ProcessString(cmd);
stateMachine.ProcessString(FTCS_C);
stateMachine.ProcessString(L"\r\n");
};
for (auto i = 0; i < end; i++)
{
writePrompt(sm, L"C:\\");
writeCommand(sm, L"Foo-bar");
sm.ProcessString(L"This is some text \r\n");
}
writePrompt(sm, L"C:\\");
auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& /*viewport*/, const bool afterClear = false) {
const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
const auto marks = tb.GetMarkExtents();
if (afterClear)
{
VERIFY_ARE_EQUAL(0u, marks.size());
}
else
{
VERIFY_IS_GREATER_THAN(marks.size(), 1u, L"There should be at least one mark");
}
};
Log::Comment(L"========== Checking the host buffer state (before) ==========");
verifyBuffer(*hostTb, si.GetViewport().ToExclusive());
Log::Comment(L"Painting the frame");
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"========== Checking the terminal buffer state (before) ==========");
verifyBuffer(*termTb, term->_mutableViewport.ToExclusive());
VERIFY_ARE_EQUAL(si.GetViewport().Dimensions(), si.GetBufferSize().Dimensions());
VERIFY_ARE_EQUAL(si.GetViewport(), si.GetBufferSize());
_clear(clearBufferMethod, si);
VERIFY_ARE_EQUAL(si.GetViewport().Dimensions(), si.GetBufferSize().Dimensions());
VERIFY_ARE_EQUAL(si.GetViewport(), si.GetBufferSize());
Log::Comment(L"Painting the frame");
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"========== Checking the terminal buffer state (after) ==========");
verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), true);
}

View File

@@ -3294,10 +3294,6 @@ bool AdaptDispatch::_EraseAll()
// Also reset the line rendition for the erased rows.
textBuffer.ResetLineRenditionRange(newViewportTop, newViewportBottom);
// Clear any marks that remain below the start of the
textBuffer.ClearMarksInRange(til::point{ 0, newViewportTop },
til::point{ bufferSize.Width(), bufferSize.Height() });
// GH#5683 - If this succeeded, but we're in a conpty, return `false` to
// make the state machine propagate this ED sequence to the connected
// terminal application. While we're in conpty mode, when the client