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

Make Conpty emit wrapped lines as actually wrapped lines #4415

Merged
26 commits merged into from
Feb 27, 2020
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b5c8c85
let's first move reflowing to the text buffer
zadjii-msft Jan 13, 2020
9aec694
add a doc comment because I'm not a barbarian
zadjii-msft Jan 13, 2020
1a2654d
Try to wrap the line properly with conpty
zadjii-msft Jan 13, 2020
aae6ce6
This works, fixing the ECH at end of wrapped line bug
zadjii-msft Jan 14, 2020
416be46
This is some cleanup, almost ready for PR but I need to write tests w…
zadjii-msft Jan 14, 2020
7f341a2
Merge branch 'master' into dev/migrie/f/conpty-wrapping-003
zadjii-msft Jan 27, 2020
edeb346
I think this is all I need to support wrapped lines in the Terminal
zadjii-msft Jan 27, 2020
ce3138c
Let's pull all the test fixes into their own file
zadjii-msft Jan 28, 2020
4a7f2e4
Merge branch 'dev/migrie/b/just-conpty-test-fixes' into dev/migrie/f/…
zadjii-msft Jan 28, 2020
bfde821
A simple test for wrapped lines
zadjii-msft Jan 28, 2020
e000388
add a roundtrip test
zadjii-msft Jan 28, 2020
c040a82
I've found a bug with the line wrapping, going to go update the Paint…
zadjii-msft Jan 28, 2020
0755fd7
This is polished for PR, ready to go in after #4382
zadjii-msft Jan 28, 2020
86623f5
Add PaintCursor tracing
zadjii-msft Jan 29, 2020
b3de042
remove some old TODO comments
zadjii-msft Jan 30, 2020
96642de
remove other dead code for PR
zadjii-msft Jan 30, 2020
2298e9a
hopefully fix the build
zadjii-msft Jan 31, 2020
a0c2e11
make the tests fail in the future
zadjii-msft Jan 31, 2020
5dc4f52
Hopefully fix tests in CI
zadjii-msft Jan 31, 2020
9c64d5c
This is the wrong way to use these, I'm sure
zadjii-msft Jan 31, 2020
dffe786
Revert "This is the wrong way to use these, I'm sure"
zadjii-msft Jan 31, 2020
f7faf8d
Merge remote-tracking branch 'origin/master' into dev/migrie/f/conpty…
zadjii-msft Jan 31, 2020
8bc6bff
safemath this line
zadjii-msft Jan 31, 2020
a788db2
Merge remote-tracking branch 'origin/master' into dev/migrie/f/conpty…
zadjii-msft Feb 11, 2020
d716308
Merge remote-tracking branch 'origin/master' into dev/migrie/f/conpty…
zadjii-msft Feb 19, 2020
52ccccf
Merge branch 'master' into dev/migrie/f/conpty-wrapping-003
zadjii-msft Feb 24, 2020
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
16 changes: 16 additions & 0 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,22 @@ void Terminal::_WriteBuffer(const std::wstring_view& stringView)
// With well behaving shells during normal operation this safeguard should normally not be encountered.
proposedCursorPosition.X = 0;
proposedCursorPosition.Y++;

// Try the character again.
i--;

// Mark the line we're currently on as wrapped

// TODO: GH#780 - This should really be a _deferred_ newline. If
// the next character to come in is a newline or a cursor
// movement or anything, then we should _not_ wrap this line
// here.
//
// This is more WriteCharsLegacy2ElectricBoogaloo work. I'm
// leaving it like this for now - it'll break for lines that
// _exactly_ wrap, but we can't re-wrap lines now anyways, so it
// doesn't matter.
_buffer->GetRowByOffset(cursorPosBefore.Y).GetCharRow().SetWrapForced(true);
}

_AdjustCursorPosition(proposedCursorPosition);
Expand Down
260 changes: 259 additions & 1 deletion src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ using namespace Microsoft::Terminal::Core;

namespace TerminalCoreUnitTests
{
class TerminalBufferTests;
class ConptyRoundtripTests;
};
using namespace TerminalCoreUnitTests;

Expand Down Expand Up @@ -126,6 +126,8 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
g.EnableConptyModeForTests();

expectedOutput.clear();
_checkConptyOutput = true;
_logConpty = false;

return true;
}
Expand All @@ -149,6 +151,11 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
TEST_METHOD(WriteTwoLinesUsesNewline);
TEST_METHOD(WriteAFewSimpleLines);

TEST_METHOD(TestWrappingALongString);
TEST_METHOD(TestAdvancedWrapping);
TEST_METHOD(TestExactWrappingWithoutSpaces);
TEST_METHOD(TestExactWrappingWithSpaces);

private:
bool _writeCallback(const char* const pch, size_t const cch);
void _flushFirstFrame();
Expand Down Expand Up @@ -340,3 +347,254 @@ void ConptyRoundtripTests::WriteAFewSimpleLines()

verifyData(termTb);
}

void ConptyRoundtripTests::TestWrappingALongString()
{
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;

_flushFirstFrame();
_checkConptyOutput = false;

const auto initialTermView = term->GetViewport();

const auto charsToWrite = TestUtils::Test100CharsString.size();
VERIFY_ARE_EQUAL(100u, charsToWrite);

VERIFY_ARE_EQUAL(0, initialTermView.Top());
VERIFY_ARE_EQUAL(32, initialTermView.BottomExclusive());

hostSm.ProcessString(TestUtils::Test100CharsString);

const auto secondView = term->GetViewport();

VERIFY_ARE_EQUAL(0, secondView.Top());
VERIFY_ARE_EQUAL(32, secondView.BottomExclusive());

auto verifyBuffer = [&](const TextBuffer& tb) {
auto& cursor = tb.GetCursor();
// Verify the cursor wrapped to the second line
VERIFY_ARE_EQUAL(charsToWrite % initialTermView.Width(), cursor.GetPosition().X);
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);

// Verify that we marked the 0th row as _wrapped_
const auto& row0 = tb.GetRowByOffset(0);
VERIFY_IS_TRUE(row0.GetCharRow().WasWrapForced());

const auto& row1 = tb.GetRowByOffset(1);
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());

TestUtils::VerifyExpectedString(tb, TestUtils::Test100CharsString, { 0, 0 });
};

verifyBuffer(hostTb);

VERIFY_SUCCEEDED(renderer.PaintFrame());

verifyBuffer(termTb);
}

void ConptyRoundtripTests::TestAdvancedWrapping()
{
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
const auto initialTermView = term->GetViewport();

_flushFirstFrame();

const auto charsToWrite = TestUtils::Test100CharsString.size();
VERIFY_ARE_EQUAL(100u, charsToWrite);

hostSm.ProcessString(TestUtils::Test100CharsString);
hostSm.ProcessString(L"\n");
hostSm.ProcessString(L" ");
hostSm.ProcessString(L"1234567890");

auto verifyBuffer = [&](const TextBuffer& tb) {
auto& cursor = tb.GetCursor();
// Verify the cursor wrapped to the second line
VERIFY_ARE_EQUAL(2, cursor.GetPosition().Y);
VERIFY_ARE_EQUAL(20, cursor.GetPosition().X);

// Verify that we marked the 0th row as _wrapped_
const auto& row0 = tb.GetRowByOffset(0);
VERIFY_IS_TRUE(row0.GetCharRow().WasWrapForced());

const auto& row1 = tb.GetRowByOffset(1);
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());

TestUtils::VerifyExpectedString(tb, TestUtils::Test100CharsString, { 0, 0 });
TestUtils::VerifyExpectedString(tb, L" 1234567890", { 0, 2 });
};

verifyBuffer(hostTb);

// First write the first 80 characters from the string
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");
// Without line breaking, write the remaining 20 chars
expectedOutput.push_back(R"(qrstuvwxyz{|}~!"#$%&)");
// Clear the rest of row 1
expectedOutput.push_back("\x1b[K");
// This is the hard line break
expectedOutput.push_back("\r\n");
// Now write row 2 of the buffer
expectedOutput.push_back(" 1234567890");
// and clear everything after the text, because the buffer is empty.
expectedOutput.push_back("\x1b[K");
VERIFY_SUCCEEDED(renderer.PaintFrame());

verifyBuffer(termTb);
}

void ConptyRoundtripTests::TestExactWrappingWithoutSpaces()
{
// This test (and TestExactWrappingWitSpaces) reveals a bug in the old
// implementation.
//
// If a line _exactly_ wraps to the next line, we can't tell if the line
// should really wrap, or manually break. The client app is writing a line
// that's exactly the width of the buffer that manually linebreaked at the
// end of the line, followed by another line.
//
// With the old PaintBufferLine interface, there's no way to know if this
// case is because the line wrapped or not. Hence, the addition of the
// `lineWrapped` parameter

auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
const auto initialTermView = term->GetViewport();

_flushFirstFrame();

const auto charsToWrite = initialTermView.Width();
VERIFY_ARE_EQUAL(80, charsToWrite);

for (auto i = 0; i < charsToWrite; i++)
{
// This is a handy way of just printing the printable characters that
// _aren't_ the space character.
const wchar_t wch = static_cast<wchar_t>(33 + (i % 94));
hostSm.ProcessCharacter(wch);
}

hostSm.ProcessString(L"\n");
hostSm.ProcessString(L"1234567890");

auto verifyBuffer = [&](const TextBuffer& tb) {
auto& cursor = tb.GetCursor();
// Verify the cursor wrapped to the second line
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
VERIFY_ARE_EQUAL(10, cursor.GetPosition().X);

// TODO: GH#780 - In the Terminal, neither line should be wrapped.
// Unfortunately, until WriteCharsLegacy2ElectricBoogaloo is complete,
// the Terminal will still treat the first line as wrapped.

// // Verify that we marked the 0th row as _wrapped_
// const auto& row0 = tb.GetRowByOffset(0);
// VERIFY_IS_FALSE(row0.GetCharRow().WasWrapForced());

// const auto& row1 = tb.GetRowByOffset(1);
// VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved

TestUtils::VerifyExpectedString(tb, LR"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)", { 0, 0 });
TestUtils::VerifyExpectedString(tb, L"1234567890", { 0, 1 });
};

verifyBuffer(hostTb);

// First write the first 80 characters from the string
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");

// This is the hard line break
expectedOutput.push_back("\r\n");
// Now write row 2 of the buffer
expectedOutput.push_back("1234567890");
// and clear everything after the text, because the buffer is empty.
expectedOutput.push_back("\x1b[K");
VERIFY_SUCCEEDED(renderer.PaintFrame());

verifyBuffer(termTb);
}

void ConptyRoundtripTests::TestExactWrappingWithSpaces()
{
// This test is also explained by the comment at the top of TestExactWrappingWithoutSpaces

auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
const auto initialTermView = term->GetViewport();

_flushFirstFrame();

const auto charsToWrite = initialTermView.Width();
VERIFY_ARE_EQUAL(80, charsToWrite);

for (auto i = 0; i < charsToWrite; i++)
{
// This is a handy way of just printing the printable characters that
// _aren't_ the space character.
const wchar_t wch = static_cast<wchar_t>(33 + (i % 94));
hostSm.ProcessCharacter(wch);
}

hostSm.ProcessString(L"\n");
hostSm.ProcessString(L" ");
hostSm.ProcessString(L"1234567890");

auto verifyBuffer = [&](const TextBuffer& tb) {
auto& cursor = tb.GetCursor();
// Verify the cursor wrapped to the second line
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
VERIFY_ARE_EQUAL(20, cursor.GetPosition().X);

// TODO: GH#780 - In the Terminal, neither line should be wrapped.
// Unfortunately, until WriteCharsLegacy2ElectricBoogaloo is complete,
// the Terminal will still treat the first line as wrapped.

// // Verify that we marked the 0th row as _wrapped_
// const auto& row0 = tb.GetRowByOffset(0);
// VERIFY_IS_FALSE(row0.GetCharRow().WasWrapForced());

// const auto& row1 = tb.GetRowByOffset(1);
// VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());

TestUtils::VerifyExpectedString(tb, LR"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)", { 0, 0 });
TestUtils::VerifyExpectedString(tb, L" 1234567890", { 0, 1 });
};

verifyBuffer(hostTb);

// First write the first 80 characters from the string
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");

// This is the hard line break
expectedOutput.push_back("\r\n");
// Now write row 2 of the buffer
expectedOutput.push_back(" 1234567890");
// and clear everything after the text, because the buffer is empty.
expectedOutput.push_back("\x1b[K");
VERIFY_SUCCEEDED(renderer.PaintFrame());

verifyBuffer(termTb);
}
76 changes: 76 additions & 0 deletions src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ class TerminalCoreUnitTests::TerminalBufferTests final

TEST_METHOD(TestSimpleBufferWriting);

TEST_METHOD(TestWrappingCharByChar);
TEST_METHOD(TestWrappingALongString);

TEST_METHOD_SETUP(MethodSetup)
{
// STEP 1: Set up the Terminal
Expand Down Expand Up @@ -66,3 +69,76 @@ void TerminalBufferTests::TestSimpleBufferWriting()

TestUtils::VerifyExpectedString(termTb, L"Hello World", { 0, 0 });
}

void TerminalBufferTests::TestWrappingCharByChar()
{
auto& termTb = *term->_buffer;
auto& termSm = *term->_stateMachine;
const auto initialView = term->GetViewport();
auto& cursor = termTb.GetCursor();

const auto charsToWrite = TestUtils::Test100CharsString.size();

VERIFY_ARE_EQUAL(0, initialView.Top());
VERIFY_ARE_EQUAL(32, initialView.BottomExclusive());

for (auto i = 0; i < charsToWrite; i++)
{
// This is a handy way of just printing the printable characters that
// _aren't_ the space character.
const wchar_t wch = static_cast<wchar_t>(33 + (i % 94));
termSm.ProcessCharacter(wch);
}

const auto secondView = term->GetViewport();

VERIFY_ARE_EQUAL(0, secondView.Top());
VERIFY_ARE_EQUAL(32, secondView.BottomExclusive());

// Verify the cursor wrapped to the second line
VERIFY_ARE_EQUAL(charsToWrite % initialView.Width(), cursor.GetPosition().X);
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);

// Verify that we marked the 0th row as _wrapped_
const auto& row0 = termTb.GetRowByOffset(0);
VERIFY_IS_TRUE(row0.GetCharRow().WasWrapForced());

const auto& row1 = termTb.GetRowByOffset(1);
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());

TestUtils::VerifyExpectedString(termTb, TestUtils::Test100CharsString, { 0, 0 });
}

void TerminalBufferTests::TestWrappingALongString()
{
auto& termTb = *term->_buffer;
auto& termSm = *term->_stateMachine;
const auto initialView = term->GetViewport();
auto& cursor = termTb.GetCursor();

const auto charsToWrite = TestUtils::Test100CharsString.size();
VERIFY_ARE_EQUAL(100u, charsToWrite);

VERIFY_ARE_EQUAL(0, initialView.Top());
VERIFY_ARE_EQUAL(32, initialView.BottomExclusive());

termSm.ProcessString(TestUtils::Test100CharsString);

const auto secondView = term->GetViewport();

VERIFY_ARE_EQUAL(0, secondView.Top());
VERIFY_ARE_EQUAL(32, secondView.BottomExclusive());

// Verify the cursor wrapped to the second line
VERIFY_ARE_EQUAL(charsToWrite % initialView.Width(), cursor.GetPosition().X);
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);

// Verify that we marked the 0th row as _wrapped_
const auto& row0 = termTb.GetRowByOffset(0);
VERIFY_IS_TRUE(row0.GetCharRow().WasWrapForced());

const auto& row1 = termTb.GetRowByOffset(1);
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());

TestUtils::VerifyExpectedString(termTb, TestUtils::Test100CharsString, { 0, 0 });
}
Loading