Compare commits

...

47 Commits

Author SHA1 Message Date
Mike Griese
1470abe1fe Switch back to conpty
We still get lots of duplicated lines in the "resize twice before the terminal
  catches the whole frame" case, which is a bummer.

  This also revealed that the maximize/restore scenario really doesn't work all
  that well. I think the "shift down cause one line de-wrapped" doesn't work
  well for when many lines are de-wrapping.

  I'm going to revert this commit and try fixing the maximize/restore
2020-03-04 12:17:23 -06:00
Mike Griese
f1ff8345c6 method.7 & fix resizing down - method.8
B-A-B-Y this works for both horizontal and vertical resizes. It's
    really a mix of m.7 and m.2. Basically took that m.2 code for
    figuring out if the text was on the last line of the viewport and put
    it in here too.

    Lets cross our fingers that it works for a real conpty as well.
2020-03-04 11:19:53 -06:00
Mike Griese
4849dab5dc classic me wrote the comment then didn't save. This belongs with the previous commit. 2020-03-04 10:58:03 -06:00
Mike Griese
58aa64da55 screenInfo + de-wrap last scrollback line adjustment - method.7
This one seems a tad bit hacky, but works alright enough.

    This is basically the screeninfo method with a small change. After
    calculating where the viewport should be, check the scrollback line that was
    immediately before the viewport. If it previously wrapped, that means the
    old top line of the viewport had wrapped from a scrollback line. If that
    scrollback line doesn't wrap anymore, we took the content from the top line
    of the viewport and moved it into that scrollback line. Conpty however
    couldn't do that, so it's going to reprint that top line, even if it's now
    been successfully dewrapped into the scrollback. To avoid losing output,
    we'll shift our new viewport location down one row, so that the new output
    from conpty doesn't blow away the previously scrolled back line.

    I have yet to test this with vertical resizing, but I want to checkpoint
    this as-is
2020-03-04 10:57:14 -06:00
Mike Griese
658295eab0 Fix the screenInfo method
I don't know what crack I was smoking before, this code didn't even sorta
    resemble the screenInfo code. Re-written like this, it handles the
    scrollback lines wrapping and unwrapping just fine - viewport is in the
    right spot.

    Unfortunately this suffers from the same problem as 5. As the lines in the
    original viewport get unwrapped, they come back into view, but they'll be
    gone from conpty. So when conpty re-prints, we'll blow them away. Maybe we
    could combo this with 4?
2020-03-04 09:17:03 -06:00
Mike Griese
1448c52228 don't calulate the last scrollback row, instead try the last viewport row
This works quite well for the decreasing width. As lines get wrapped,
    we'll move down with them appropriately. Where it falls apart is in
    the unwrapping of lines - we'll basically keep the bottom in the same
    place, and when conpty reprints, we'll duplicate the lines. This is
    because the top line that conpty actually still knows about will get
    moved up in our buffer (from previous lines de-wrapping).  We need to
    instead keep the top in the same place in that case, so that all the
    lines conpty knows about stay on the screen.

    This should be paired with
      short scrollbackLines = ::base::saturated_cast<short>(_mutableViewport.BottomInclusive());
    and
      *lastScrollbackRow = gsl::narrow_cast<short>(newCursor.GetPosition().Y - 1);
    TextBuffer::Reflow
2020-03-04 08:59:40 -06:00
Mike Griese
bb3d9102e7 Adjust the "let the textbuffer figure out the scrollback" algo
This works very well for decreasing width up until the point where
    the lines _in_ the viewport start wrapping. In that case, the
    viewport really _should_ be moving down as well, but this method
    can't really tell that.
2020-03-04 08:33:39 -06:00
Mike Griese
7a177100e1 Use a dummy connection to test reflowing 2020-03-04 08:33:05 -06:00
Mike Griese
25c593f2ea Revert "Maybe try to be smarter about how we mark lines as wrapped"
This reverts commit e7c55117a7.
2020-03-04 08:10:22 -06:00
Mike Griese
e7c55117a7 Maybe try to be smarter about how we mark lines as wrapped
But conpty is giving us lines that are wrapped when they really aren't
2020-03-04 08:10:05 -06:00
Mike Griese
e6c2746900 Try having the textbuffer figure out for us where the scrollback ended? 2020-03-03 15:54:10 -06:00
Mike Griese
8ca2a4abf8 Try a bunch of different methods of placing the viewport after a resize
None of these seem particularily right though
2020-03-03 15:53:42 -06:00
Mike Griese
cc7b2d34e1 okay I'll actually build the SA locally 2020-03-02 15:05:17 -06:00
Mike Griese
2ef2d88d0a add safemath for carlos 2020-03-02 11:13:25 -06:00
Mike Griese
0f283b4723 Merge remote-tracking branch 'origin/master' into dev/migrie/f/reflow-buffer-on-resize 2020-03-02 10:00:17 -06:00
Mike Griese
0c91a9b4d4 @ our static analysis build: you're wrong here, but fine 2020-02-28 12:15:32 -06:00
Mike Griese
1d87f66792 Merge branch 'master' into dev/migrie/f/reflow-buffer-on-resize
# Conflicts:
#	src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
#	src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp
#	src/renderer/base/renderer.cpp
#	src/renderer/vt/paint.cpp
2020-02-28 09:32:19 -06:00
Mike Griese
e65371393d Merge remote-tracking branch 'origin/master' into dev/migrie/f/reflow-buffer-on-resize
# Conflicts:
#	src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
2020-02-11 10:48:13 -08:00
Mike Griese
de9911df02 Merge remote-tracking branch 'origin/master' into dev/migrie/f/reflow-buffer-on-resize 2020-02-07 10:53:22 -06:00
Mike Griese
2e815c8954 fix tests 2020-01-31 17:05:10 -06:00
Mike Griese
097b62c896 Merge remote-tracking branch 'origin/master' into dev/migrie/f/reflow-buffer-on-resize 2020-01-31 16:48:37 -06:00
Mike Griese
edea9a335c Cleanup for review - this is a _great_ fix for #3490 as well as #1465 2020-01-30 15:27:34 -06:00
Mike Griese
95807154b8 wait why does this work so well 2020-01-30 14:51:58 -06:00
Mike Griese
74a528357c ResizeWithReflow the Terminal buffer 2020-01-30 14:28:27 -06:00
Mike Griese
1788cb1e45 Merge branch 'dev/migrie/b/1245-I-actually-did-it-this-time' into dev/migrie/f/reflow-buffer-on-resize
# Conflicts:
#	src/renderer/vt/XtermEngine.cpp
#	src/renderer/vt/vtrenderer.hpp
2020-01-30 14:28:03 -06:00
Mike Griese
5a72af9822 Merge branch 'dev/migrie/f/conpty-wrapping-003' into dev/migrie/f/reflow-buffer-on-resize 2020-01-30 12:35:00 -06:00
Mike Griese
96642deb39 remove other dead code for PR 2020-01-30 11:10:45 -06:00
Mike Griese
b3de042c46 remove some old TODO comments 2020-01-30 10:31:07 -06:00
Mike Griese
e0d251c349 Merge remote-tracking branch 'origin/master' into dev/migrie/b/1245-I-actually-did-it-this-time 2020-01-29 16:07:20 -06:00
Mike Griese
40b4966782 Revert "Try adding a test, but I can't get the test to repro the original behavior quite right"
This reverts commit 0a98cceddb.
2020-01-29 16:06:34 -06:00
Mike Griese
0a98cceddb Try adding a test, but I can't get the test to repro the original behavior quite right 2020-01-29 16:06:25 -06:00
Mike Griese
9b6554b10f Add some comments for PR 2020-01-29 16:05:53 -06:00
Mike Griese
7fd5d515b2 This actually fixes this bug - different terminals EOL wrap differently, esp conhost v wt v gnome-terminal. Remove ambiguity - just hardcore move the cursor in this scenario. 2020-01-29 12:52:19 -06:00
Mike Griese
86623f57d5 Add PaintCursor tracing 2020-01-29 11:38:07 -06:00
Mike Griese
0755fd73e1 This is polished for PR, ready to go in after #4382 2020-01-28 15:12:42 -06:00
Mike Griese
c040a82e62 I've found a bug with the line wrapping, going to go update the PaintBufferLine interface 2020-01-28 12:34:55 -06:00
Mike Griese
e000388baf add a roundtrip test 2020-01-28 11:17:56 -06:00
Mike Griese
bfde821b2f A simple test for wrapped lines 2020-01-28 10:42:34 -06:00
Mike Griese
4a7f2e4f7b Merge branch 'dev/migrie/b/just-conpty-test-fixes' into dev/migrie/f/conpty-wrapping-003 2020-01-28 09:35:18 -06:00
Mike Griese
ce3138c685 Let's pull all the test fixes into their own file 2020-01-28 09:24:04 -06:00
Mike Griese
edeb346325 I think this is all I need to support wrapped lines in the Terminal
(cherry picked from commit c21f74029d)
2020-01-28 08:36:08 -06:00
Mike Griese
7f341a25a5 Merge branch 'master' into dev/migrie/f/conpty-wrapping-003
# Conflicts:
#	src/buffer/out/textBuffer.cpp
#	src/buffer/out/textBuffer.hpp
#	src/host/screenInfo.cpp
2020-01-27 16:46:29 -06:00
Mike Griese
416be4656f This is some cleanup, almost ready for PR but I need to write tests which are blocked on #4213 2020-01-14 16:46:33 -06:00
Mike Griese
aae6ce60a4 This works, fixing the ECH at end of wrapped line bug
The trick was that when a line that was exactly filled was followed by an
  empty line, we'd ECH when the cursor is virtually on the last char of the
  wrapped line. That was wrong. For a scenario like:

  |ABCDEF|
  |      |

  We'd incorrectly render that as ["ABCDEF", "^[[K", "\r\n"]. That ECH in the
  middle there would erase the 'F', because the cursor was virtually still on
  the 'F' (because it had deferred wrapping to the next char).

  So, when we're about to ECH following a wrapped line, reset the _wrappedRow
  first, to make sure we correctly \r\n first.
2020-01-14 16:07:22 -06:00
Mike Griese
1a2654d291 Try to wrap the line properly with conpty
This confusingly doesn't always work
2020-01-13 17:07:43 -06:00
Mike Griese
9aec69467c add a doc comment because I'm not a barbarian 2020-01-13 11:34:50 -06:00
Mike Griese
b5c8c854cc let's first move reflowing to the text buffer 2020-01-13 11:23:42 -06:00
8 changed files with 602 additions and 37 deletions

View File

@@ -1900,9 +1900,15 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi
// Arguments:
// - oldBuffer - the text buffer to copy the contents FROM
// - newBuffer - the text buffer to copy the contents TO
// - lastCharacterViewport - Optional. If the caller knows that the last
// nonspace character is in a particular Viewport, the caller can provide this
// parameter as an optimization, as opposed to searching the entire buffer.
// Return Value:
// - S_OK if we successfully copied the contents to the new buffer, otherwise an appropriate HRESULT.
HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer)
HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
TextBuffer& newBuffer,
const std::optional<Viewport> lastCharacterViewport,
short* const lastScrollbackRow) noexcept
{
Cursor& oldCursor = oldBuffer.GetCursor();
Cursor& newCursor = newBuffer.GetCursor();
@@ -1914,18 +1920,30 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer)
// place the new cursor back on the equivalent character in
// the new buffer.
const COORD cOldCursorPos = oldCursor.GetPosition();
const COORD cOldLastChar = oldBuffer.GetLastNonSpaceCharacter();
const COORD cOldLastChar = lastCharacterViewport.has_value() ?
oldBuffer.GetLastNonSpaceCharacter(lastCharacterViewport.value()) :
oldBuffer.GetLastNonSpaceCharacter();
short const cOldRowsTotal = cOldLastChar.Y + 1;
short const cOldColsTotal = oldBuffer.GetSize().Width();
const short cOldRowsTotal = cOldLastChar.Y + 1;
const short cOldColsTotal = oldBuffer.GetSize().Width();
COORD cNewCursorPos = { 0 };
bool fFoundCursorPos = false;
bool foundScrollbackEnd = false;
HRESULT hr = S_OK;
// Loop through all the rows of the old buffer and reprint them into the new buffer
for (short iOldRow = 0; iOldRow < cOldRowsTotal; iOldRow++)
{
if (lastScrollbackRow && !foundScrollbackEnd)
{
if (iOldRow >= *lastScrollbackRow)
{
// *lastScrollbackRow = gsl::narrow_cast<short>(newCursor.GetPosition().Y - 1);
*lastScrollbackRow = gsl::narrow_cast<short>(newCursor.GetPosition().Y);
foundScrollbackEnd = true;
}
}
// Fetch the row and its "right" which is the last printable character.
const ROW& row = oldBuffer.GetRowByOffset(iOldRow);
const CharRow& charRow = row.GetCharRow();

View File

@@ -162,7 +162,10 @@ public:
const std::wstring_view fontFaceName,
const COLORREF backgroundColor);
static HRESULT Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer);
static HRESULT Reflow(TextBuffer& oldBuffer,
TextBuffer& newBuffer,
const std::optional<Microsoft::Console::Types::Viewport> lastCharacterViewport = std::nullopt,
short* const lastScrollbackRow = nullptr) noexcept;
private:
std::deque<ROW> _storage;

View File

@@ -510,7 +510,7 @@ namespace winrt::TerminalApp::implementation
// Create a connection based on the values in our settings object.
const auto connection = _CreateConnectionFromSettings(profileGuid, settings);
// const auto connection = TerminalConnection::EchoConnection();
TermControl term{ settings, connection };
// Add the new tab to the list of our tabs.

View File

@@ -26,7 +26,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
std::wstringstream prettyPrint;
for (const auto& wch : data)
{
if (wch < 0x20)
if (wch < 0x20 && (wch != L'\r' && wch != L'\n'))
{
prettyPrint << L"^" << gsl::narrow_cast<wchar_t>(wch + 0x40);
}

View File

@@ -637,6 +637,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_connection.Start();
_initializedTerminal = true;
_InitializedHandlers(*this, nullptr);
// {
// for (int i = 0; i < 40; i++)
// {
// // _connection.WriteInput(L"01234567890123456789012345678901234567890123456789 ");
// _connection.WriteInput(L"0123456789 123456789 ");
// _connection.WriteInput({ std::wstring(80 - (i * 2), static_cast<wchar_t>(L'@' + i)) });
// _connection.WriteInput(L"\r\n");
// }
// }
return true;
}

View File

@@ -178,19 +178,314 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
const short newBufferHeight = viewportSize.Y + _scrollbackLines;
COORD bufferSize{ viewportSize.X, newBufferHeight };
RETURN_IF_FAILED(_buffer->ResizeTraditional(bufferSize));
auto proposedTop = oldTop;
const auto newView = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
const auto proposedBottom = newView.BottomExclusive();
// If the new bottom would be below the bottom of the buffer, then slide the
// top up so that we'll still fit within the buffer.
if (proposedBottom > bufferSize.Y)
// Save cursor's relative height versus the viewport
const short sCursorHeightInViewportBefore = _buffer->GetCursor().GetPosition().Y - _mutableViewport.Top();
// short scrollbackLines = ::base::saturated_cast<short>(_mutableViewport.Top()); // method.4
// short scrollbackLines = ::base::saturated_cast<short>(_mutableViewport.BottomInclusive()); // method.5
// short scrollbackLines = ::base::saturated_cast<short>(_mutableViewport.Top() + 1); // method.6
short scrollbackLines = ::base::saturated_cast<short>(_mutableViewport.Top()); // method.7
// First allocate a new text buffer to take the place of the current one.
std::unique_ptr<TextBuffer> newTextBuffer;
try
{
proposedTop -= (proposedBottom - bufferSize.Y);
newTextBuffer = std::make_unique<TextBuffer>(bufferSize,
_buffer->GetCurrentAttributes(),
0, // temporarily set size to 0 so it won't render.
_buffer->GetRenderTarget());
}
CATCH_RETURN();
RETURN_IF_FAILED(TextBuffer::Reflow(*_buffer.get(), *newTextBuffer.get(), _mutableViewport, &scrollbackLines));
// {
// // Original code - Method.1
// // This doesn't work, because the top stays in the same place. As lines
// // in the scrollback begin to wrap, they'll push the contents of the
// // buffer down, and the real mutable viewport will be below this.
// //
// // This will lead to "losing lines" of scrollback, as conpty will blat
// // the viewport contents over thse lines.
//
// auto proposedTop = oldTop;
// const auto newView = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
// const auto proposedBottom = newView.BottomExclusive();
// // If the new bottom would be below the bottom of the buffer, then slide the
// // top up so that we'll still fit within the buffer.
// if (proposedBottom > bufferSize.Y)
// {
// proposedTop -= (proposedBottom - bufferSize.Y);
// }
// _mutableViewport = Viewport::FromDimensions({ 0, ::base::saturated_cast<short>(proposedTop) }, viewportSize);
// }
// {
// // RwR PR code - Method.2
// // However conpty resizes a little oddly - if the height decreased, and
// // there were blank lines at the bottom, those lines will get trimmed.
// // If there's not blank lines, then the top will get "shifted down",
// // moving the top line into scrollback.
// // See GH#3490 for more details.
// // If the final position in the buffer is on the bottom row of the new
// // viewport, then we're going to need to move the top down. Otherwise,
// // move the bottom up.
// const auto dy = viewportSize.Y - oldDimensions.Y;
// const COORD oldCursorPos = _buffer->GetCursor().GetPosition();
// const COORD newCursorPos = newTextBuffer->GetCursor().GetPosition();
// #pragma warning(push)
// #pragma warning(disable : 26496) // cpp core checks wants this const, but it's assigned immediately below...
// COORD oldLastChar = oldCursorPos;
// // COORD newLastChar = newCursorPos;
// try
// {
// oldLastChar = _buffer->GetLastNonSpaceCharacter(_mutableViewport);
// // newLastChar = newTextBuffer->GetLastNonSpaceCharacter(_mutableViewport);
// }
// CATCH_LOG();
// #pragma warning(pop)
// const short sCursorHeightInViewportAfter = newTextBuffer->GetCursor().GetPosition().Y - _mutableViewport.Top();
// const auto coordCursorHeightDiff = sCursorHeightInViewportAfter - sCursorHeightInViewportBefore;
// const auto maxRow = std::max(oldLastChar.Y, oldCursorPos.Y);
// const bool beforeLastRowOfView = maxRow < _mutableViewport.BottomInclusive();
// // const auto adjustment = beforeLastRowOfView ? 0 : std::max(0, -dy); // original calculation
// // const auto adjustment = coordCursorHeightDiff; // For decreasing width leading to scrollback lines newly wrapping, this works nicely. For lines in the viewport wrapping, this _does not_
// // const auto adjustment = -(oldCursorPos.Y - (newCursorPos.Y + sCursorHeightInViewportAfter));
// const auto adjustment = coordCursorHeightDiff;
// auto proposedTop = oldTop + adjustment;
// // auto proposedTop = adjustment;
// const auto newView = Viewport::FromDimensions({ 0, ::base::saturated_cast<short>(proposedTop) }, viewportSize);
// const auto proposedBottom = newView.BottomExclusive();
// // If the new bottom would be below the bottom of the buffer, then slide the
// // top up so that we'll still fit within the buffer.
// if (proposedBottom > bufferSize.Y)
// {
// proposedTop -= (proposedBottom - bufferSize.Y);
// }
// _mutableViewport = Viewport::FromDimensions({ 0, ::base::saturated_cast<short>(proposedTop) }, viewportSize);
// }
// {
// // screeninfo code - Method.3
// //
// // I don't know what crack I was smoking before, this code didn't even
// // sorta resemble the screenInfo code. Re-written like this, it handles
// // the scrollback lines wrapping and unwrapping just fine - viewport is
// // in the right spot.
// //
// // Unfortunately this suffers from the same problem as #5. As the lines
// // in the original viewport get unwrapped, they come back into view, but
// // they'll be gone from conpty. So when conpty re-prints, we'll blow
// // them away. Maybe we could combo this with #4?
// Cursor& newCursor = newTextBuffer->GetCursor();
// // Adjust the viewport so the cursor doesn't wildly fly off up or down.
// const auto sCursorHeightInViewportAfter = newCursor.GetPosition().Y - _mutableViewport.Top();
// const auto coordCursorHeightDiff = sCursorHeightInViewportAfter - sCursorHeightInViewportBefore;
// const auto newTop = _mutableViewport.Top() + coordCursorHeightDiff;
// _mutableViewport = Viewport::FromDimensions({ 0, ::base::saturated_cast<short>(newTop) }, viewportSize);
// }
// {
// // Ask the TextBuffer for the scrollback start - Method.4
// // This works very well for decreasing width up until the point where
// // the lines _in_ the viewport start wrapping. In that case, the
// // viewport really _should_ be moving down as well, but this method
// // can't really tell that.
// //
// // This should be paired with
// // short scrollbackLines = ::base::saturated_cast<short>(_mutableViewport.Top());
// // and
// // *lastScrollbackRow = gsl::narrow_cast<short>(newCursor.GetPosition().Y - 1);
// // TextBuffer::Reflow
// _mutableViewport = Viewport::FromDimensions({ 0, ::base::saturated_cast<short>(scrollbackLines + 1) }, viewportSize);
// }
// {
// // Ask the TextBuffer for the viewport bottom - Method.5
// // Instead of trying to figure out the last scrollback row, try figuring
// // out the last viewport row in the new buffer.
// //
// // This works quite well for the decreasing width. As lines get wrapped,
// // we'll move down with them appropriately. Where it falls apart is in
// // the unwrapping of lines - we'll basically keep the bottom in the same
// // place, and when conpty reprints, we'll duplicate the lines. This is
// // because the top line that conpty actually still knows about will get
// // moved up in our buffer (from previous lines de-wrapping). We need to
// // instead keep the top in the same place in that case, so that all the
// // lines conpty knows about stay on the screen.
// //
// // This should be paired with
// // short scrollbackLines = ::base::saturated_cast<short>(_mutableViewport.BottomInclusive());
// // and
// // *lastScrollbackRow = gsl::narrow_cast<short>(newCursor.GetPosition().Y - 1);
// // TextBuffer::Reflow
// auto proposedBottom = scrollbackLines;
// auto proposedTop = proposedBottom - viewportSize.Y;
// // Honestly, we should be using +1 here, and not a -1 in TextBuffer, but (shrug)
// _mutableViewport = Viewport::FromDimensions({ 0, ::base::saturated_cast<short>(proposedTop + 2) }, viewportSize);
// }
// {
// // Combo screeninfo + method 4 - method.6
// //
// // I've tried this with both
// // short scrollbackLines = ::base::saturated_cast<short>(_mutableViewport.Top() + 1);
// // and
// // short scrollbackLines = ::base::saturated_cast<short>(_mutableViewport.Top());
// //
// // above, and with a bunch of permutations in this scope. All to no
// // avail. I think part of the problem is that it's hard to know when the
// // top line is getting unwrapped up into the scrollback. That's the case
// // where the movement goes bad.
// //
// // If the last scrollback line in the old buffer wrapped, and it didn't
// // in this buffer, we should bump the viewport down one from where we
// // think it should be. So begins experiment 7.
// Cursor& newCursor = newTextBuffer->GetCursor();
// // Adjust the viewport so the cursor doesn't wildly fly off up or down.
// const auto sCursorHeightInViewportAfter = newCursor.GetPosition().Y - _mutableViewport.Top();
// const auto cursorHeightDiff = sCursorHeightInViewportAfter - sCursorHeightInViewportBefore;
// const auto screenInfoProposedTop = _mutableViewport.Top() + cursorHeightDiff;
// const auto scrollbackProposedTop = scrollbackLines; // + 1;
// auto realProposedTop = screenInfoProposedTop;
// // if (scrollbackProposedTop >= screenInfoProposedTop)
// if (scrollbackProposedTop > screenInfoProposedTop)
// {
// realProposedTop = scrollbackProposedTop - 1;
// // realProposedTop = scrollbackProposedTop;
// // realProposedTop = scrollbackProposedTop + 1;
// }
// // const auto realProposedTop = std::max(scrollbackProposedTop, screenInfoProposedTop);
// _mutableViewport = Viewport::FromDimensions({ 0, ::base::saturated_cast<short>(realProposedTop) }, viewportSize);
// }
// {
// // screenInfo + de-wrap last scrollback line adjustment - method.7
// //
// // This one seems a tad bit hacky, but works alright enough.
// //
// // This is basically the screeninfo method with a small change. After
// // calculating where the viewport should be, check the scrollback line
// // that was immediately before the viewport. If it previously wrapped,
// // that means the old top line of the viewport had wrapped from a
// // scrollback line. If that scrollback line doesn't wrap anymore, we
// // took the content from the top line of the viewport and moved it into
// // that scrollback line. Conpty however couldn't do that, so it's going
// // to reprint that top line, even if it's now been successfully
// // dewrapped into the scrollback. To avoid losing output, we'll shift
// // our new viewport location down one row, so that the new output from
// // conpty doesn't blow away the previously scrolled back line.
// //
// // I have yet to test this with vertical resizing, but I want to
// // checkpoint this as-is
// //
// // LOL this doesn't handle decreasing height at all. We'll just stay at
// // the same top position, and not move the viewport down to make the new
// // cursor actually visible. That's horrible. Method.8 will try to fix this.
// //
// Cursor& newCursor = newTextBuffer->GetCursor();
// // Adjust the viewport so the cursor doesn't wildly fly off up or down.
// const auto sCursorHeightInViewportAfter = newCursor.GetPosition().Y - _mutableViewport.Top();
// const auto cursorHeightDiff = sCursorHeightInViewportAfter - sCursorHeightInViewportBefore;
// const auto screenInfoProposedTop = _mutableViewport.Top() + cursorHeightDiff;
// auto realProposedTop = screenInfoProposedTop;
// const auto& originalBufferLastScrollbackLine = _buffer->GetRowByOffset(_mutableViewport.Top() - 1);
// const auto& newBufferLastScrollbackLine = newTextBuffer->GetRowByOffset(_scrollbackLines - 1);
// const bool oldLineWrapped = originalBufferLastScrollbackLine.GetCharRow().WasWrapForced();
// const bool oldExactlyFit = originalBufferLastScrollbackLine.GetCharRow().MeasureRight() == _mutableViewport.Width();
// const bool newLineWrapped = newBufferLastScrollbackLine.GetCharRow().WasWrapForced();
// // if ((oldLineWrapped || oldExactlyFit) && !newLineWrapped)
// if ((oldLineWrapped) && !newLineWrapped)
// {
// realProposedTop++;
// }
// _mutableViewport = Viewport::FromDimensions({ 0, ::base::saturated_cast<short>(realProposedTop) }, viewportSize);
// }
{
// method.7 & fix resizing down - method.8
//
// B-A-B-Y this works for both horizontal and vertical resizes. It's
// really a mix of m.7 and m.2. Basically took that m.2 code for
// figuring out if the text was on the last line of the viewport and put
// it in here too.
//
// Lets cross our fingers that it works for a real conpty as well.
const auto dy = viewportSize.Y - oldDimensions.Y;
const COORD oldCursorPos = _buffer->GetCursor().GetPosition();
#pragma warning(push)
#pragma warning(disable : 26496) // cpp core checks wants this const, but it's assigned immediately below...
COORD oldLastChar = oldCursorPos;
// COORD newLastChar = newCursorPos;
try
{
oldLastChar = _buffer->GetLastNonSpaceCharacter(_mutableViewport);
// newLastChar = newTextBuffer->GetLastNonSpaceCharacter(_mutableViewport);
}
CATCH_LOG();
#pragma warning(pop)
const auto maxRow = std::max(oldLastChar.Y, oldCursorPos.Y);
const bool beforeLastRowOfView = maxRow < _mutableViewport.BottomInclusive();
Cursor& newCursor = newTextBuffer->GetCursor();
// Adjust the viewport so the cursor doesn't wildly fly off up or down.
const auto sCursorHeightInViewportAfter = newCursor.GetPosition().Y - _mutableViewport.Top();
const auto cursorHeightDiff = sCursorHeightInViewportAfter - sCursorHeightInViewportBefore;
const auto screenInfoProposedTop = _mutableViewport.Top() + cursorHeightDiff;
auto realProposedTop = screenInfoProposedTop;
const auto& originalBufferLastScrollbackLine = _buffer->GetRowByOffset(_mutableViewport.Top() - 1);
const auto& newBufferLastScrollbackLine = newTextBuffer->GetRowByOffset(_scrollbackLines - 1);
const bool oldLineWrapped = originalBufferLastScrollbackLine.GetCharRow().WasWrapForced();
const bool oldExactlyFit = originalBufferLastScrollbackLine.GetCharRow().MeasureRight() == _mutableViewport.Width();
const bool newLineWrapped = newBufferLastScrollbackLine.GetCharRow().WasWrapForced();
// if ((oldLineWrapped || oldExactlyFit) && !newLineWrapped)
if ((oldLineWrapped) && !newLineWrapped)
{
realProposedTop++;
}
if (!beforeLastRowOfView)
{
realProposedTop += std::max(0, -dy);
// const auto adjustment = beforeLastRowOfView ? 0 : std::max(0, -dy); // original calculation
}
_mutableViewport = Viewport::FromDimensions({ 0, ::base::saturated_cast<short>(realProposedTop) }, viewportSize);
}
_mutableViewport = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
_buffer.swap(newTextBuffer);
_scrollOffset = 0;
_NotifyScrollEvent();

View File

@@ -162,6 +162,8 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
TEST_METHOD(MoveCursorAtEOL);
TEST_METHOD(TestResizeHeight);
private:
bool _writeCallback(const char* const pch, size_t const cch);
void _flushFirstFrame();
@@ -550,6 +552,7 @@ void ConptyRoundtripTests::TestExactWrappingWithSpaces()
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();
@@ -620,6 +623,7 @@ void ConptyRoundtripTests::MoveCursorAtEOL()
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
_flushFirstFrame();
@@ -673,6 +677,230 @@ void ConptyRoundtripTests::MoveCursorAtEOL()
verifyData1(termTb);
}
void ConptyRoundtripTests::TestResizeHeight()
{
// This test class is _60_ tests to ensure that resizing the terminal works
// with conpty correctly. There's a lot of min/maxing in expressions here,
// to account for the sheer number of cases here, and that we have to handle
// both resizing larger and smaller all in one test.
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
TEST_METHOD_PROPERTY(L"Data:dx", L"{-1, 0, 1}")
TEST_METHOD_PROPERTY(L"Data:dy", L"{-10, -1, 0, 1, 10}")
TEST_METHOD_PROPERTY(L"Data:printedRows", L"{1, 10, 50, 200}")
END_TEST_METHOD_PROPERTIES()
int dx, dy;
int printedRows;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"dx", dx), L"change in width of buffer");
VERIFY_SUCCEEDED(TestData::TryGetValue(L"dy", dy), L"change in height of buffer");
VERIFY_SUCCEEDED(TestData::TryGetValue(L"printedRows", printedRows), L"Number of rows of text to print");
_checkConptyOutput = false;
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.get();
const auto initialHostView = si.GetViewport();
const auto initialTermView = term->GetViewport();
const auto initialTerminalBufferHeight = term->GetTextBuffer().GetSize().Height();
VERIFY_ARE_EQUAL(0, initialHostView.Top());
VERIFY_ARE_EQUAL(TerminalViewHeight, initialHostView.BottomExclusive());
VERIFY_ARE_EQUAL(0, initialTermView.Top());
VERIFY_ARE_EQUAL(TerminalViewHeight, initialTermView.BottomExclusive());
Log::Comment(NoThrowString().Format(
L"Print %d lines of output, which will scroll the viewport", printedRows));
for (auto i = 0; i < printedRows; i++)
{
// This looks insane, but this expression is carefully crafted to give
// us only printable characters, starting with `!` (0n33).
// Similar statements are used elsewhere throughout this test.
auto wstr = std::wstring(1, static_cast<wchar_t>((i) % 93) + 33);
hostSm.ProcessString(wstr);
hostSm.ProcessString(L"\r\n");
}
// Conpty doesn't have a scrollback, it's view's origin is always 0,0
const auto secondHostView = si.GetViewport();
VERIFY_ARE_EQUAL(0, secondHostView.Top());
VERIFY_ARE_EQUAL(TerminalViewHeight, secondHostView.BottomExclusive());
VERIFY_SUCCEEDED(renderer.PaintFrame());
const auto secondTermView = term->GetViewport();
// If we've printed more lines than the height of the buffer, then we're
// expecting the viewport to have moved down. Otherwise, the terminal's
// viewport will stay at 0,0.
const auto expectedTerminalViewBottom = std::max(std::min(::base::saturated_cast<short>(printedRows + 1),
term->GetBufferHeight()),
term->GetViewport().Height());
VERIFY_ARE_EQUAL(expectedTerminalViewBottom, secondTermView.BottomExclusive());
VERIFY_ARE_EQUAL(expectedTerminalViewBottom - initialTermView.Height(), secondTermView.Top());
auto verifyTermData = [&expectedTerminalViewBottom, &printedRows, this, &initialTerminalBufferHeight](TextBuffer& termTb, const int resizeDy = 0) {
// Some number of lines of text were lost from the scrollback. The
// number of lines lost will be determined by whichever of the initial
// or current buffer is smaller.
const auto numLostRows = std::max(0,
printedRows - std::min(term->GetTextBuffer().GetSize().Height(), initialTerminalBufferHeight) + 1);
const auto rowsWithText = std::min(::base::saturated_cast<short>(printedRows),
expectedTerminalViewBottom) -
1 + std::min(resizeDy, 0);
for (short row = 0; row < rowsWithText; row++)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
auto iter = termTb.GetCellDataAt({ 0, row });
const wchar_t expectedChar = static_cast<wchar_t>((row + numLostRows) % 93) + 33;
auto expectedString = std::wstring(1, expectedChar);
if (iter->Chars() != expectedString)
{
Log::Comment(NoThrowString().Format(L"row [%d] was mismatched", row));
}
VERIFY_ARE_EQUAL(expectedString, (iter++)->Chars());
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
}
};
auto verifyHostData = [&si, &initialHostView, &printedRows](TextBuffer& hostTb, const int resizeDy = 0) {
const auto hostView = si.GetViewport();
// In the host, there are two regions we're interested in:
// 1. the first section of the buffer with the output in it. Before
// we're resized, this will be filled with one character on each row.
// 2. The second area below the first that's empty (filled with spaces).
// Initially, this is only one row.
// After we resize, different things will happen.
// * If we decrease the height of the buffer, the characters in the
// buffer will all move _up_ the same number of rows. We'll want to
// only check the first initialView+dy rows for characters.
// * If we increase the height, rows will be added at the bottom. We'll
// want to check the initial viewport height for the original
// characters, but then we'll want to look for more blank rows at the
// bottom. The characters in the initial viewport won't have moved.
const short originalViewHeight = ::base::saturated_cast<short>(resizeDy < 0 ?
initialHostView.Height() + resizeDy :
initialHostView.Height());
const auto rowsWithText = std::min(originalViewHeight - 1, printedRows);
const bool scrolled = printedRows > initialHostView.Height();
// The last row of the viewport should be empty
// The second last row will have '0'+50
// The third last row will have '0'+49
// ...
// The <height> last row will have '0'+(50-height+1)
const auto firstChar = static_cast<wchar_t>(scrolled ?
(printedRows - originalViewHeight + 1) :
0);
short row = 0;
// Don't include the last row of the viewport in this check, since it'll
// be blank. We'll check it in the below loop.
for (; row < rowsWithText; row++)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
auto iter = hostTb.GetCellDataAt({ 0, row });
const auto expectedChar = static_cast<wchar_t>(((firstChar + row) % 93) + 33);
auto expectedString = std::wstring(1, static_cast<wchar_t>(expectedChar));
if (iter->Chars() != expectedString)
{
Log::Comment(NoThrowString().Format(L"row [%d] was mismatched", row));
}
VERIFY_ARE_EQUAL(expectedString, (iter++)->Chars(), NoThrowString().Format(L"%s", expectedString.data()));
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
}
// Check that the remaining rows in the viewport are empty.
for (; row < hostView.Height(); row++)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
auto iter = hostTb.GetCellDataAt({ 0, row });
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
}
};
verifyHostData(*hostTb);
verifyTermData(*termTb);
const COORD newViewportSize{
::base::saturated_cast<short>(TerminalViewWidth + dx),
::base::saturated_cast<short>(TerminalViewHeight + dy)
};
Log::Comment(NoThrowString().Format(L"Resize the Terminal and conpty here"));
auto resizeResult = term->UserResize(newViewportSize);
VERIFY_SUCCEEDED(resizeResult);
_resizeConpty(newViewportSize.X, newViewportSize.Y);
// After we resize, make sure to get the new textBuffers
hostTb = &si.GetTextBuffer();
termTb = term->_buffer.get();
// Conpty's doesn't have a scrollback, it's view's origin is always 0,0
const auto thirdHostView = si.GetViewport();
VERIFY_ARE_EQUAL(0, thirdHostView.Top());
VERIFY_ARE_EQUAL(newViewportSize.Y, thirdHostView.BottomExclusive());
// The Terminal should be stuck to the top of the viewport, unless dy<0,
// rows=50. In that set of cases, we _didn't_ pin the top of the Terminal to
// the old top, we actually shifted it down (because the output was at the
// bottom of the window, not empty lines).
const auto thirdTermView = term->GetViewport();
if (dy < 0 && (printedRows > initialTermView.Height() && printedRows < initialTerminalBufferHeight))
{
VERIFY_ARE_EQUAL(secondTermView.Top() - dy, thirdTermView.Top());
VERIFY_ARE_EQUAL(expectedTerminalViewBottom, thirdTermView.BottomExclusive());
}
else
{
VERIFY_ARE_EQUAL(secondTermView.Top(), thirdTermView.Top());
VERIFY_ARE_EQUAL(expectedTerminalViewBottom + dy, thirdTermView.BottomExclusive());
}
verifyHostData(*hostTb, dy);
// Note that at this point, nothing should have changed with the Terminal.
verifyTermData(*termTb, dy);
Log::Comment(NoThrowString().Format(L"Paint a frame to update the Terminal"));
VERIFY_SUCCEEDED(renderer.PaintFrame());
// Conpty's doesn't have a scrollback, it's view's origin is always 0,0
const auto fourthHostView = si.GetViewport();
VERIFY_ARE_EQUAL(0, fourthHostView.Top());
VERIFY_ARE_EQUAL(newViewportSize.Y, fourthHostView.BottomExclusive());
// The Terminal should be stuck to the top of the viewport, unless dy<0,
// rows=50. In that set of cases, we _didn't_ pin the top of the Terminal to
// the old top, we actually shifted it down (because the output was at the
// bottom of the window, not empty lines).
const auto fourthTermView = term->GetViewport();
if (dy < 0 && (printedRows > initialTermView.Height() && printedRows < initialTerminalBufferHeight))
{
VERIFY_ARE_EQUAL(secondTermView.Top() - dy, thirdTermView.Top());
VERIFY_ARE_EQUAL(expectedTerminalViewBottom, thirdTermView.BottomExclusive());
}
else
{
VERIFY_ARE_EQUAL(secondTermView.Top(), thirdTermView.Top());
VERIFY_ARE_EQUAL(expectedTerminalViewBottom + dy, thirdTermView.BottomExclusive());
}
verifyHostData(*hostTb, dy);
verifyTermData(*termTb, dy);
}
void ConptyRoundtripTests::PassthroughClearScrollback()
{
Log::Comment(NoThrowString().Format(
@@ -684,6 +912,7 @@ void ConptyRoundtripTests::PassthroughClearScrollback()
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& termTb = *term->_buffer;
_flushFirstFrame();

View File

@@ -228,17 +228,20 @@ void RenderTracing::TraceLastText(const COORD lastTextPos) const
void RenderTracing::TraceMoveCursor(const COORD lastTextPos, const COORD cursor) const
{
#ifndef UNIT_TESTING
const auto lastTextStr = _CoordToString(lastTextPos);
const auto lastText = lastTextStr.c_str();
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
const auto lastTextStr = _CoordToString(lastTextPos);
const auto lastText = lastTextStr.c_str();
const auto cursorStr = _CoordToString(cursor);
const auto cursorPos = cursorStr.c_str();
const auto cursorStr = _CoordToString(cursor);
const auto cursorPos = cursorStr.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceMoveCursor",
TraceLoggingString(lastText),
TraceLoggingString(cursorPos),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceMoveCursor",
TraceLoggingString(lastText),
TraceLoggingString(cursorPos),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
#else
UNREFERENCED_PARAMETER(lastTextPos);
UNREFERENCED_PARAMETER(cursor);
@@ -248,11 +251,14 @@ void RenderTracing::TraceMoveCursor(const COORD lastTextPos, const COORD cursor)
void RenderTracing::TraceWrapped() const
{
#ifndef UNIT_TESTING
const auto* const msg = "Wrapped instead of \\r\\n";
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceWrapped",
TraceLoggingString(msg),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
const auto* const msg = "Wrapped instead of \\r\\n";
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceWrapped",
TraceLoggingString(msg),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
#else
#endif UNIT_TESTING
}
@@ -260,12 +266,15 @@ void RenderTracing::TraceWrapped() const
void RenderTracing::TracePaintCursor(const COORD coordCursor) const
{
#ifndef UNIT_TESTING
const auto cursorPosString = _CoordToString(coordCursor);
const auto cursorPos = cursorPosString.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TracePaintCursor",
TraceLoggingString(cursorPos),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
const auto cursorPosString = _CoordToString(coordCursor);
const auto cursorPos = cursorPosString.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TracePaintCursor",
TraceLoggingString(cursorPos),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
#else
UNREFERENCED_PARAMETER(coordCursor);
#endif UNIT_TESTING