mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-06 06:09:50 +00:00
Compare commits
47 Commits
dev/miniks
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1470abe1fe | ||
|
|
f1ff8345c6 | ||
|
|
4849dab5dc | ||
|
|
58aa64da55 | ||
|
|
658295eab0 | ||
|
|
1448c52228 | ||
|
|
bb3d9102e7 | ||
|
|
7a177100e1 | ||
|
|
25c593f2ea | ||
|
|
e7c55117a7 | ||
|
|
e6c2746900 | ||
|
|
8ca2a4abf8 | ||
|
|
cc7b2d34e1 | ||
|
|
2ef2d88d0a | ||
|
|
0f283b4723 | ||
|
|
0c91a9b4d4 | ||
|
|
1d87f66792 | ||
|
|
e65371393d | ||
|
|
de9911df02 | ||
|
|
2e815c8954 | ||
|
|
097b62c896 | ||
|
|
edea9a335c | ||
|
|
95807154b8 | ||
|
|
74a528357c | ||
|
|
1788cb1e45 | ||
|
|
5a72af9822 | ||
|
|
96642deb39 | ||
|
|
b3de042c46 | ||
|
|
e0d251c349 | ||
|
|
40b4966782 | ||
|
|
0a98cceddb | ||
|
|
9b6554b10f | ||
|
|
7fd5d515b2 | ||
|
|
86623f57d5 | ||
|
|
0755fd73e1 | ||
|
|
c040a82e62 | ||
|
|
e000388baf | ||
|
|
bfde821b2f | ||
|
|
4a7f2e4f7b | ||
|
|
ce3138c685 | ||
|
|
edeb346325 | ||
|
|
7f341a25a5 | ||
|
|
416be4656f | ||
|
|
aae6ce60a4 | ||
|
|
1a2654d291 | ||
|
|
9aec69467c | ||
|
|
b5c8c854cc |
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user