Reimplement TextBuffer::Reflow (#15701)

Subjectively speaking, this commit makes 3 improvements:
* Most importantly, it now would work with arbitrary Unicode text.
  (No more `IsGlyphFullWidth` or DBCS handling during reflow.)
* Due to the simpler implementation it hopefully makes review of
  future changes and maintenance simpler. (~3x less LOC.)
* It improves perf. by 1-2 orders of magnitude.
  (At 120x9001 with a full buffer I get 60ms -> 2ms.)

Unfortunately, I'm not confident that the new code replicates the old
code exactly, because I failed to understand it. During development
I simply tried to match its behavior with what I think reflow should do.

Closes #797
Closes #3088
Closes #4968
Closes #6546
Closes #6901
Closes #15964
Closes MSFT:19446208

Related to #5800 and #8000

## Validation Steps Performed
* Unit tests 
* Feature tests 
* Reflow with a scrollback 
* Reflowing the cursor cell causes a forced line-wrap 
  (Even at the end of the buffer. )
* `color 8f` and reflowing retains the background color 
* Enter alt buffer, Resize window, Exit alt buffer 
This commit is contained in:
Leonard Hecker
2023-09-26 02:28:51 +02:00
committed by GitHub
parent c7f30a86d7
commit 74748394c1
20 changed files with 477 additions and 715 deletions

View File

@@ -749,7 +749,6 @@ gfx
GGI
GHIJK
GHIJKL
GHIJKLM
gitfilters
gitmodules
gle
@@ -1000,7 +999,6 @@ listptrsize
lld
llx
LMENU
LMNOP
lnk
lnkd
lnkfile
@@ -1223,7 +1221,6 @@ nonspace
NOOWNERZORDER
Nop
NOPAINT
NOPQRST
noprofile
NOREDRAW
NOREMOVE
@@ -1498,7 +1495,6 @@ pwsz
pythonw
Qaabbcc
qos
QRSTU
QUERYOPEN
QUESTIONMARK
quickedit
@@ -1861,7 +1857,6 @@ testname
TESTNULL
testpass
testpasses
testtimeout
TEXCOORD
texel
TExpected
@@ -2019,7 +2014,6 @@ utext
UText
UTEXT
utr
UVWX
UVWXY
UVWXYZ
uwa
@@ -2078,7 +2072,6 @@ VTRGBTo
vtseq
vtterm
vttest
VWX
waitable
WANSUNG
WANTARROWS
@@ -2274,6 +2267,7 @@ YCast
YCENTER
YCount
YDPI
YLimit
YOffset
YSubstantial
YVIRTUALSCREEN

View File

@@ -152,11 +152,15 @@ til::CoordType CharToColumnMapper::GetTrailingColumnAt(ptrdiff_t offset) noexcep
return col;
}
// If given a pointer inside the ROW's text buffer, this function will return the corresponding column.
// This function in particular returns the glyph's first column.
til::CoordType CharToColumnMapper::GetLeadingColumnAt(const wchar_t* str) noexcept
{
return GetLeadingColumnAt(str - _chars);
}
// If given a pointer inside the ROW's text buffer, this function will return the corresponding column.
// This function in particular returns the glyph's last column (this matters for wide glyphs).
til::CoordType CharToColumnMapper::GetTrailingColumnAt(const wchar_t* str) noexcept
{
return GetTrailingColumnAt(str - _chars);
@@ -364,11 +368,16 @@ void ROW::TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& a
void ROW::CopyFrom(const ROW& source)
{
RowCopyTextFromState state{ .source = source };
CopyTextFrom(state);
TransferAttributes(source.Attributes(), _columnCount);
_lineRendition = source._lineRendition;
_wrapForced = source._wrapForced;
RowCopyTextFromState state{
.source = source,
.sourceColumnLimit = source.GetReadableColumnCount(),
};
CopyTextFrom(state);
TransferAttributes(source.Attributes(), _columnCount);
}
// Returns the previous possible cursor position, preceding the given column.
@@ -382,7 +391,17 @@ til::CoordType ROW::NavigateToPrevious(til::CoordType column) const noexcept
// Returns the row width if column is beyond the width of the row.
til::CoordType ROW::NavigateToNext(til::CoordType column) const noexcept
{
return _adjustForward(_clampedColumn(column + 1));
return _adjustForward(_clampedColumnInclusive(column + 1));
}
// Returns the starting column of the glyph at the given column.
// In other words, if you have 3 wide glyphs
// AA BB CC
// 01 23 45 <-- column
// then `AdjustToGlyphStart(3)` returns 2.
til::CoordType ROW::AdjustToGlyphStart(til::CoordType column) const noexcept
{
return _adjustBackward(_clampedColumn(column));
}
// Routine Description:
@@ -719,11 +738,12 @@ try
if (sourceColBeg < sourceColLimit)
{
charOffsets = source._charOffsets.subspan(sourceColBeg, static_cast<size_t>(sourceColLimit) - sourceColBeg + 1);
const auto charsOffset = charOffsets.front() & CharOffsetsMask;
const auto beg = size_t{ charOffsets.front() } & CharOffsetsMask;
const auto end = size_t{ charOffsets.back() } & CharOffsetsMask;
// We _are_ using span. But C++ decided that string_view and span aren't convertible.
// _chars is a std::span for performance and because it refers to raw, shared memory.
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
chars = { source._chars.data() + charsOffset, source._chars.size() - charsOffset };
chars = { source._chars.data() + beg, end - beg };
}
WriteHelper h{ *this, state.columnBegin, state.columnLimit, chars };
@@ -939,6 +959,16 @@ til::CoordType ROW::MeasureLeft() const noexcept
til::CoordType ROW::MeasureRight() const noexcept
{
if (_wrapForced)
{
auto width = _columnCount;
if (_doubleBytePadded)
{
width--;
}
return width;
}
const auto text = GetText();
const auto beg = text.begin();
const auto end = text.end();

View File

@@ -136,6 +136,7 @@ public:
til::CoordType NavigateToPrevious(til::CoordType column) const noexcept;
til::CoordType NavigateToNext(til::CoordType column) const noexcept;
til::CoordType AdjustToGlyphStart(til::CoordType column) const noexcept;
void ClearCell(til::CoordType column);
OutputCellIterator WriteCells(OutputCellIterator it, til::CoordType columnBegin, std::optional<bool> wrap = std::nullopt, std::optional<til::CoordType> limitRight = std::nullopt);

View File

@@ -806,9 +806,9 @@ void TextBuffer::IncrementCircularBuffer(const TextAttribute& fillAttributes)
// - The viewport
//Return value:
// - Coordinate position (relative to the text buffer)
til::point TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Console::Types::Viewport> viewOptional) const
til::point TextBuffer::GetLastNonSpaceCharacter(const Viewport* viewOptional) const
{
const auto viewport = viewOptional.has_value() ? viewOptional.value() : GetSize();
const auto viewport = viewOptional ? *viewOptional : GetSize();
til::point coordEndOfText;
// Search the given viewport by starting at the bottom.
@@ -1070,46 +1070,40 @@ void TextBuffer::Reset() noexcept
// - newSize - new size of screen.
// Return Value:
// - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed.
[[nodiscard]] NTSTATUS TextBuffer::ResizeTraditional(til::size newSize) noexcept
void TextBuffer::ResizeTraditional(til::size newSize)
{
// Guard against resizing the text buffer to 0 columns/rows, which would break being able to insert text.
newSize.width = std::max(newSize.width, 1);
newSize.height = std::max(newSize.height, 1);
try
TextBuffer newBuffer{ newSize, _currentAttributes, 0, false, _renderer };
const auto cursorRow = GetCursor().GetPosition().y;
const auto copyableRows = std::min<til::CoordType>(_height, newSize.height);
til::CoordType srcRow = 0;
til::CoordType dstRow = 0;
if (cursorRow >= newSize.height)
{
TextBuffer newBuffer{ newSize, _currentAttributes, 0, false, _renderer };
const auto cursorRow = GetCursor().GetPosition().y;
const auto copyableRows = std::min<til::CoordType>(_height, newSize.height);
til::CoordType srcRow = 0;
til::CoordType dstRow = 0;
if (cursorRow >= newSize.height)
{
srcRow = cursorRow - newSize.height + 1;
}
for (; dstRow < copyableRows; ++dstRow, ++srcRow)
{
newBuffer.GetMutableRowByOffset(dstRow).CopyFrom(GetRowByOffset(srcRow));
}
// NOTE: Keep this in sync with _reserve().
_buffer = std::move(newBuffer._buffer);
_bufferEnd = newBuffer._bufferEnd;
_commitWatermark = newBuffer._commitWatermark;
_initialAttributes = newBuffer._initialAttributes;
_bufferRowStride = newBuffer._bufferRowStride;
_bufferOffsetChars = newBuffer._bufferOffsetChars;
_bufferOffsetCharOffsets = newBuffer._bufferOffsetCharOffsets;
_width = newBuffer._width;
_height = newBuffer._height;
_SetFirstRowIndex(0);
srcRow = cursorRow - newSize.height + 1;
}
CATCH_RETURN();
return S_OK;
for (; dstRow < copyableRows; ++dstRow, ++srcRow)
{
newBuffer.GetMutableRowByOffset(dstRow).CopyFrom(GetRowByOffset(srcRow));
}
// NOTE: Keep this in sync with _reserve().
_buffer = std::move(newBuffer._buffer);
_bufferEnd = newBuffer._bufferEnd;
_commitWatermark = newBuffer._commitWatermark;
_initialAttributes = newBuffer._initialAttributes;
_bufferRowStride = newBuffer._bufferRowStride;
_bufferOffsetChars = newBuffer._bufferOffsetChars;
_bufferOffsetCharOffsets = newBuffer._bufferOffsetCharOffsets;
_width = newBuffer._width;
_height = newBuffer._height;
_SetFirstRowIndex(0);
}
void TextBuffer::SetAsActiveBuffer(const bool isActiveBuffer) noexcept
@@ -2412,204 +2406,181 @@ void TextBuffer::_AppendRTFText(std::ostringstream& contentBuilder, const std::w
// the new buffer. The rows's new value is placed back into this parameter.
// 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,
const std::optional<Viewport> lastCharacterViewport,
std::optional<std::reference_wrapper<PositionInformation>> positionInfo)
try
void TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer, const Viewport* lastCharacterViewport, PositionInformation* positionInfo)
{
const auto& oldCursor = oldBuffer.GetCursor();
auto& newCursor = newBuffer.GetCursor();
// We need to save the old cursor position so that we can
// place the new cursor back on the equivalent character in
// the new buffer.
const auto cOldCursorPos = oldCursor.GetPosition();
const auto cOldLastChar = oldBuffer.GetLastNonSpaceCharacter(lastCharacterViewport);
til::point oldCursorPos = oldCursor.GetPosition();
til::point newCursorPos;
const auto cOldRowsTotal = cOldLastChar.y + 1;
// BODGY: We use oldCursorPos in two critical places below:
// * To compute an oldHeight that includes at a minimum the cursor row
// * For REFLOW_JANK_CURSOR_WRAP (see comment below)
// Both of these would break the reflow algorithm, but the latter of the two in particular
// would cause the main copy loop below to deadlock. In other words, these two lines
// protect this function against yet-unknown bugs in other parts of the code base.
oldCursorPos.x = std::clamp(oldCursorPos.x, 0, oldBuffer._width - 1);
oldCursorPos.y = std::clamp(oldCursorPos.y, 0, oldBuffer._height - 1);
til::point cNewCursorPos;
auto fFoundCursorPos = false;
auto foundOldMutable = false;
auto foundOldVisible = false;
// Loop through all the rows of the old buffer and reprint them into the new buffer
til::CoordType iOldRow = 0;
for (; iOldRow < cOldRowsTotal; iOldRow++)
const auto lastRowWithText = oldBuffer.GetLastNonSpaceCharacter(lastCharacterViewport).y;
auto mutableViewportTop = positionInfo ? positionInfo->mutableViewportTop : til::CoordTypeMax;
auto visibleViewportTop = positionInfo ? positionInfo->visibleViewportTop : til::CoordTypeMax;
til::CoordType oldY = 0;
til::CoordType newY = 0;
til::CoordType newX = 0;
til::CoordType newWidth = newBuffer.GetSize().Width();
til::CoordType newYLimit = til::CoordTypeMax;
const auto oldHeight = std::max(lastRowWithText, oldCursorPos.y) + 1;
const auto newHeight = newBuffer.GetSize().Height();
const auto newWidthU16 = gsl::narrow_cast<uint16_t>(newWidth);
// Copy oldBuffer into newBuffer until oldBuffer has been fully consumed.
for (; oldY < oldHeight && newY < newYLimit; ++oldY)
{
// Fetch the row and its "right" which is the last printable character.
const auto& row = oldBuffer.GetRowByOffset(iOldRow);
const auto cOldColsTotal = oldBuffer.GetLineWidth(iOldRow);
auto iRight = row.MeasureRight();
const auto& oldRow = oldBuffer.GetRowByOffset(oldY);
// If we're starting a new row, try and preserve the line rendition
// from the row in the original buffer.
const auto newBufferPos = newCursor.GetPosition();
if (newBufferPos.x == 0)
// A pair of double height rows should optimally wrap as a union (i.e. after wrapping there should be 4 lines).
// But for this initial implementation I chose the alternative approach: Just truncate them.
if (oldRow.GetLineRendition() != LineRendition::SingleWidth)
{
auto& newRow = newBuffer.GetMutableRowByOffset(newBufferPos.y);
newRow.SetLineRendition(row.GetLineRendition());
}
// There is a special case here. If the row has a "wrap"
// flag on it, but the right isn't equal to the width (one
// index past the final valid index in the row) then there
// were a bunch trailing of spaces in the row.
// (But the measuring functions for each row Left/Right do
// not count spaces as "displayable" so they're not
// included.)
// As such, adjust the "right" to be the width of the row
// to capture all these spaces
if (row.WasWrapForced())
{
iRight = cOldColsTotal;
// And a combined special case.
// If we wrapped off the end of the row by adding a
// piece of padding because of a double byte LEADING
// character, then remove one from the "right" to
// leave this padding out of the copy process.
if (row.WasDoubleBytePadded())
// Since rows with a non-standard line rendition should be truncated it's important
// that we pretend as if the previous row ended in a newline, even if it didn't.
// This is what this if does: It newlines.
if (newX)
{
iRight--;
}
}
// Loop through every character in the current row (up to
// the "right" boundary, which is one past the final valid
// character)
til::CoordType iOldCol = 0;
const auto copyRight = iRight;
for (; iOldCol < copyRight; iOldCol++)
{
if (iOldCol == cOldCursorPos.x && iOldRow == cOldCursorPos.y)
{
cNewCursorPos = newCursor.GetPosition();
fFoundCursorPos = true;
newX = 0;
newY++;
}
// TODO: MSFT: 19446208 - this should just use an iterator and the inserter...
const auto glyph = row.GlyphAt(iOldCol);
const auto dbcsAttr = row.DbcsAttrAt(iOldCol);
const auto textAttr = row.GetAttrByColumn(iOldCol);
auto& newRow = newBuffer.GetMutableRowByOffset(newY);
newBuffer.InsertCharacter(glyph, dbcsAttr, textAttr);
}
// GH#32: Copy the attributes from the rest of the row into this new buffer.
// From where we are in the old buffer, to the end of the row, copy the
// remaining attributes.
// - if the old buffer is smaller than the new buffer, then just copy
// what we have, as it was. We already copied all _text_ with colors,
// but it's possible for someone to just put some color into the
// buffer to the right of that without any text (as just spaces). The
// buffer looks weird to the user when we resize and it starts losing
// those colors, so we need to copy them over too... as long as there
// is space. The last attr in the row will be extended to the end of
// the row in the new buffer.
// - if the old buffer is WIDER, than we might have wrapped onto a new
// line. Use the cursor's position's Y so that we know where the new
// row is, and start writing at the cursor position. Again, the attr
// in the last column of the old row will be extended to the end of the
// row that the text was flowed onto.
// - if the text in the old buffer didn't actually fill the whole
// line in the new buffer, then we didn't wrap. That's fine. just
// copy attributes from the old row till the end of the new row, and
// move on.
const auto newRowY = newCursor.GetPosition().y;
auto& newRow = newBuffer.GetMutableRowByOffset(newRowY);
auto newAttrColumn = newCursor.GetPosition().x;
const auto newWidth = newBuffer.GetLineWidth(newRowY);
// Stop when we get to the end of the buffer width, or the new position
// for inserting an attr would be past the right of the new buffer.
for (auto copyAttrCol = iOldCol;
copyAttrCol < cOldColsTotal && newAttrColumn < newWidth;
copyAttrCol++, newAttrColumn++)
{
// TODO: MSFT: 19446208 - this should just use an iterator and the inserter...
const auto textAttr = row.GetAttrByColumn(copyAttrCol);
newRow.SetAttrToEnd(newAttrColumn, textAttr);
}
// If we found the old row that the caller was interested in, set the
// out value of that parameter to the cursor's current Y position (the
// new location of the _end_ of that row in the buffer).
if (positionInfo.has_value())
{
if (!foundOldMutable)
// See the comment marked with "REFLOW_RESET".
if (newY >= newHeight)
{
if (iOldRow >= positionInfo.value().get().mutableViewportTop)
newRow.Reset(newBuffer._initialAttributes);
}
newRow.CopyFrom(oldRow);
newRow.SetWrapForced(false);
if (oldY == oldCursorPos.y)
{
newCursorPos = { newRow.AdjustToGlyphStart(oldCursorPos.x), newY };
}
if (oldY >= mutableViewportTop)
{
positionInfo->mutableViewportTop = newY;
mutableViewportTop = til::CoordTypeMax;
}
if (oldY >= visibleViewportTop)
{
positionInfo->visibleViewportTop = newY;
visibleViewportTop = til::CoordTypeMax;
}
newY++;
continue;
}
// Rows don't store any information for what column the last written character is in.
// We simply truncate all trailing whitespace in this implementation.
auto oldRowLimit = oldRow.MeasureRight();
if (oldY == oldCursorPos.y)
{
// REFLOW_JANK_CURSOR_WRAP:
// Pretending as if there's always at least whitespace in front of the cursor has the benefit that
// * the cursor retains its distance from any preceding text.
// * when a client application starts writing on this new, empty line,
// enlarging the buffer unwraps the text onto the preceding line.
oldRowLimit = std::max(oldRowLimit, oldCursorPos.x + 1);
}
til::CoordType oldX = 0;
// Copy oldRow into newBuffer until oldRow has been fully consumed.
// We use a do-while loop to ensure that line wrapping occurs and
// that attributes are copied over even for seemingly empty rows.
do
{
// This if condition handles line wrapping.
// Only if we write past the last column we should wrap and as such this if
// condition is in front of the text insertion code instead of behind it.
// A SetWrapForced of false implies an explicit newline, which is the default.
if (newX >= newWidth)
{
newBuffer.GetMutableRowByOffset(newY).SetWrapForced(true);
newX = 0;
newY++;
}
// REFLOW_RESET:
// If we shrink the buffer vertically, for instance from 100 rows to 90 rows, we will write 10 rows in the
// new buffer twice. We need to reset them before copying text, or otherwise we'll see the previous contents.
// We don't need to be smart about this. Reset() is fast and shrinking doesn't occur often.
if (newY >= newHeight && newX == 0)
{
// We need to ensure not to overwrite the row the cursor is on.
if (newY >= newYLimit)
{
positionInfo.value().get().mutableViewportTop = newCursor.GetPosition().y;
foundOldMutable = true;
break;
}
newBuffer.GetMutableRowByOffset(newY).Reset(newBuffer._initialAttributes);
}
if (!foundOldVisible)
auto& newRow = newBuffer.GetMutableRowByOffset(newY);
RowCopyTextFromState state{
.source = oldRow,
.columnBegin = newX,
.columnLimit = til::CoordTypeMax,
.sourceColumnBegin = oldX,
.sourceColumnLimit = oldRowLimit,
};
newRow.CopyTextFrom(state);
const auto& oldAttr = oldRow.Attributes();
auto& newAttr = newRow.Attributes();
const auto attributes = oldAttr.slice(gsl::narrow_cast<uint16_t>(oldX), oldAttr.size());
newAttr.replace(gsl::narrow_cast<uint16_t>(newX), newAttr.size(), attributes);
newAttr.resize_trailing_extent(newWidthU16);
if (oldY == oldCursorPos.y && oldCursorPos.x >= oldX)
{
if (iOldRow >= positionInfo.value().get().visibleViewportTop)
{
positionInfo.value().get().visibleViewportTop = newCursor.GetPosition().y;
foundOldVisible = true;
}
// In theory AdjustToGlyphStart ensures we don't put the cursor on a trailing wide glyph.
// In practice I don't think that this can possibly happen. Better safe than sorry.
newCursorPos = { newRow.AdjustToGlyphStart(oldCursorPos.x - oldX + newX), newY };
// If there's so much text past the old cursor position that it doesn't fit into new buffer,
// then the new cursor position will be "lost", because it's overwritten by unrelated text.
// We have two choices how can handle this:
// * If the new cursor is at an y < 0, just put the cursor at (0,0)
// * Stop writing into the new buffer before we overwrite the new cursor position
// This implements the second option. There's no fundamental reason why this is better.
newYLimit = newY + newHeight;
}
if (oldY >= mutableViewportTop)
{
positionInfo->mutableViewportTop = newY;
mutableViewportTop = til::CoordTypeMax;
}
if (oldY >= visibleViewportTop)
{
positionInfo->visibleViewportTop = newY;
visibleViewportTop = til::CoordTypeMax;
}
}
// If we didn't have a full row to copy, insert a new
// line into the new buffer.
// Only do so if we were not forced to wrap. If we did
// force a word wrap, then the existing line break was
// only because we ran out of space.
if (iRight < cOldColsTotal && !row.WasWrapForced())
oldX = state.sourceColumnEnd;
newX = state.columnEnd;
} while (oldX < oldRowLimit);
// If the row had an explicit newline we also need to newline. :)
if (!oldRow.WasWrapForced())
{
if (!fFoundCursorPos && (iRight == cOldCursorPos.x && iOldRow == cOldCursorPos.y))
{
cNewCursorPos = newCursor.GetPosition();
fFoundCursorPos = true;
}
// Only do this if it's not the final line in the buffer.
// On the final line, we want the cursor to sit
// where it is done printing for the cursor
// adjustment to follow.
if (iOldRow < cOldRowsTotal - 1)
{
newBuffer.NewlineCursor();
}
else
{
// If we are on the final line of the buffer, we have one more check.
// We got into this code path because we are at the right most column of a row in the old buffer
// that had a hard return (no wrap was forced).
// However, as we're inserting, the old row might have just barely fit into the new buffer and
// caused a new soft return (wrap was forced) putting the cursor at x=0 on the line just below.
// We need to preserve the memory of the hard return at this point by inserting one additional
// hard newline, otherwise we've lost that information.
// We only do this when the cursor has just barely poured over onto the next line so the hard return
// isn't covered by the soft one.
// e.g.
// The old line was:
// |aaaaaaaaaaaaaaaaaaa | with no wrap which means there was a newline after that final a.
// The cursor was here ^
// And the new line will be:
// |aaaaaaaaaaaaaaaaaaa| and show a wrap at the end
// | |
// ^ and the cursor is now there.
// If we leave it like this, we've lost the newline information.
// So we insert one more newline so a continued reflow of this buffer by resizing larger will
// continue to look as the original output intended with the newline data.
// After this fix, it looks like this:
// |aaaaaaaaaaaaaaaaaaa| no wrap at the end (preserved hard newline)
// | |
// ^ and the cursor is now here.
const auto coordNewCursor = newCursor.GetPosition();
if (coordNewCursor.x == 0 && coordNewCursor.y > 0)
{
if (newBuffer.GetRowByOffset(coordNewCursor.y - 1).WasWrapForced())
{
newBuffer.NewlineCursor();
}
}
}
newX = 0;
newY++;
}
}
@@ -2617,85 +2588,40 @@ try
// printable character. This is to fix the `color 2f` scenario, where you
// change the buffer colors then resize and everything below the last
// printable char gets reset. See GH #12567
auto newRowY = newCursor.GetPosition().y + 1;
const auto newHeight = newBuffer.GetSize().Height();
const auto oldHeight = oldBuffer._estimateOffsetOfLastCommittedRow() + 1;
for (;
iOldRow < oldHeight && newRowY < newHeight;
iOldRow++)
const auto initializedRowsEnd = oldBuffer._estimateOffsetOfLastCommittedRow() + 1;
for (; oldY < initializedRowsEnd && newY < newHeight; oldY++, newY++)
{
const auto& row = oldBuffer.GetRowByOffset(iOldRow);
// Optimization: Since all these rows are below the last printable char,
// we can reasonably assume that they are filled with just spaces.
// That's convenient, we can just copy the attr row from the old buffer
// into the new one, and resize the row to match. We'll rely on the
// behavior of ATTR_ROW::Resize to trim down when narrower, or extend
// the last attr when wider.
auto& newRow = newBuffer.GetMutableRowByOffset(newRowY);
const auto newWidth = newBuffer.GetLineWidth(newRowY);
newRow.TransferAttributes(row.Attributes(), newWidth);
newRowY++;
auto& oldRow = oldBuffer.GetRowByOffset(oldY);
auto& newRow = newBuffer.GetMutableRowByOffset(newY);
auto& newAttr = newRow.Attributes();
newAttr = oldRow.Attributes();
newAttr.resize_trailing_extent(newWidthU16);
}
// Since we didn't use IncrementCircularBuffer() we need to compute the proper
// _firstRow offset now, in a way that replicates IncrementCircularBuffer().
// We need to do the same for newCursorPos.y for basically the same reason.
if (newY > newHeight)
{
newBuffer._firstRow = newY % newHeight;
// _firstRow maps from API coordinates that always start at 0,0 in the top left corner of the
// terminal's scrollback, to the underlying buffer Y coordinate via `(y + _firstRow) % height`.
// Here, we need to un-map the `newCursorPos.y` from the underlying Y coordinate to the API coordinate
// and so we do `(y - _firstRow) % height`, but we add `+ newHeight` to avoid getting negative results.
newCursorPos.y = (newCursorPos.y - newBuffer._firstRow + newHeight) % newHeight;
}
// Finish copying remaining parameters from the old text buffer to the new one
newBuffer.CopyProperties(oldBuffer);
newBuffer.CopyHyperlinkMaps(oldBuffer);
// If we found where to put the cursor while placing characters into the buffer,
// just put the cursor there. Otherwise we have to advance manually.
if (fFoundCursorPos)
{
newCursor.SetPosition(cNewCursorPos);
}
else
{
// Advance the cursor to the same offset as before
// get the number of newlines and spaces between the old end of text and the old cursor,
// then advance that many newlines and chars
auto iNewlines = cOldCursorPos.y - cOldLastChar.y;
const auto iIncrements = cOldCursorPos.x - cOldLastChar.x;
const auto cNewLastChar = newBuffer.GetLastNonSpaceCharacter();
// If the last row of the new buffer wrapped, there's going to be one less newline needed,
// because the cursor is already on the next line
if (newBuffer.GetRowByOffset(cNewLastChar.y).WasWrapForced())
{
iNewlines = std::max(iNewlines - 1, 0);
}
else
{
// if this buffer didn't wrap, but the old one DID, then the d(columns) of the
// old buffer will be one more than in this buffer, so new need one LESS.
if (oldBuffer.GetRowByOffset(cOldLastChar.y).WasWrapForced())
{
iNewlines = std::max(iNewlines - 1, 0);
}
}
for (auto r = 0; r < iNewlines; r++)
{
newBuffer.NewlineCursor();
}
for (auto c = 0; c < iIncrements - 1; c++)
{
newBuffer.IncrementCursor();
}
}
// Save old cursor size before we delete it
const auto ulSize = oldCursor.GetSize();
// Set size back to real size as it will be taking over the rendering duties.
newCursor.SetSize(ulSize);
assert(newCursorPos.x >= 0 && newCursorPos.x < newWidth);
assert(newCursorPos.y >= 0 && newCursorPos.y < newHeight);
newCursor.SetSize(oldCursor.GetSize());
newCursor.SetPosition(newCursorPos);
newBuffer._marks = oldBuffer._marks;
newBuffer._trimMarksOutsideBuffer();
return S_OK;
}
CATCH_RETURN()
// Method Description:
// - Adds or updates a hyperlink in our hyperlink table
@@ -2916,14 +2842,10 @@ void TextBuffer::AddMark(const ScrollMark& m)
void TextBuffer::_trimMarksOutsideBuffer()
{
const auto height = GetSize().Height();
_marks.erase(std::remove_if(_marks.begin(),
_marks.end(),
[height](const auto& m) {
return (m.start.y < 0) ||
(m.start.y >= height);
}),
_marks.end());
const til::CoordType height = _height;
std::erase_if(_marks, [height](const auto& m) {
return (m.start.y < 0) || (m.start.y >= height);
});
}
std::wstring_view TextBuffer::CurrentCommand() const

View File

@@ -163,7 +163,7 @@ public:
// Scroll needs access to this to quickly rotate around the buffer.
void IncrementCircularBuffer(const TextAttribute& fillAttributes = {});
til::point GetLastNonSpaceCharacter(std::optional<const Microsoft::Console::Types::Viewport> viewOptional = std::nullopt) const;
til::point GetLastNonSpaceCharacter(const Microsoft::Console::Types::Viewport* viewOptional = nullptr) const;
Cursor& GetCursor() noexcept;
const Cursor& GetCursor() const noexcept;
@@ -194,7 +194,7 @@ public:
void Reset() noexcept;
[[nodiscard]] HRESULT ResizeTraditional(const til::size newSize) noexcept;
void ResizeTraditional(const til::size newSize);
void SetAsActiveBuffer(const bool isActiveBuffer) noexcept;
bool IsActiveBuffer() const noexcept;
@@ -262,10 +262,7 @@ public:
til::CoordType visibleViewportTop{ 0 };
};
static HRESULT Reflow(TextBuffer& oldBuffer,
TextBuffer& newBuffer,
const std::optional<Microsoft::Console::Types::Viewport> lastCharacterViewport,
std::optional<std::reference_wrapper<PositionInformation>> positionInfo);
static void Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer, const Microsoft::Console::Types::Viewport* lastCharacterViewport = nullptr, PositionInformation* positionInfo = nullptr);
std::vector<til::point_span> SearchText(const std::wstring_view& needle, bool caseInsensitive) const;
std::vector<til::point_span> SearchText(const std::wstring_view& needle, bool caseInsensitive, til::CoordType rowBeg, til::CoordType rowEnd) const;

View File

@@ -46,8 +46,6 @@ namespace
std::vector<TestBuffer> buffers;
};
static constexpr auto true_due_to_exact_wrap_bug{ true };
static const TestCase testCases[] = {
TestCase{
L"No reflow required",
@@ -61,7 +59,7 @@ namespace
{ L"EFG ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
{ 0, 1 }, // cursor on $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
@@ -72,7 +70,7 @@ namespace
{ L"EFG ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
{ 0, 1 }, // cursor on $
},
TestBuffer{
{ 4, 5 },
@@ -83,7 +81,7 @@ namespace
{ L"EFG ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
{ 0, 1 }, // cursor on $
},
},
},
@@ -99,40 +97,40 @@ namespace
{ L" ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
{ 0, 1 }, // cursor on $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
{
{ L"ABCDE", true },
{ L"F$ ", false }, // [BUG] EXACT WRAP. $ should be alone on next line.
{ L" ", false },
{ L"F ", false },
{ L"$ ", false },
{ L" ", false },
{ L" ", false },
},
{ 1, 1 } // cursor on $
{ 0, 2 }, // cursor on $
},
TestBuffer{
{ 6, 5 }, // grow width back to original
{
{ L"ABCDEF", true_due_to_exact_wrap_bug },
{ L"ABCDEF", false },
{ L"$ ", false },
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
{ 0, 1 }, // cursor on $
},
TestBuffer{
{ 7, 5 }, // grow width wider than original
{
{ L"ABCDEF$", true_due_to_exact_wrap_bug }, // EXACT WRAP BUG: $ should be alone on next line
{ L" ", false },
{ L"ABCDEF ", false },
{ L"$ ", false },
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 6, 0 } // cursor on $
{ 0, 1 }, // cursor on $
},
},
},
@@ -148,7 +146,7 @@ namespace
{ L" ", false },
{ L" ", false },
},
{ 1, 1 } // cursor on $
{ 1, 1 }, // cursor on $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
@@ -159,7 +157,7 @@ namespace
{ L" ", false },
{ L" ", false },
},
{ 2, 1 } // cursor on $
{ 2, 1 }, // cursor on $
},
TestBuffer{
{ 6, 5 }, // grow width back to original
@@ -170,7 +168,7 @@ namespace
{ L" ", false },
{ L" ", false },
},
{ 1, 1 } // cursor on $
{ 1, 1 }, // cursor on $
},
TestBuffer{
{ 7, 5 }, // grow width wider than original
@@ -181,7 +179,7 @@ namespace
{ L" ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
{ 0, 1 }, // cursor on $
},
},
},
@@ -197,29 +195,29 @@ namespace
{ L"EFG ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
{ 0, 1 }, // cursor on $
},
TestBuffer{
{ 7, 5 }, // reduce width by 1
{
{ L"AB $", true },
{ L" CD", true_due_to_exact_wrap_bug },
{ L" ", false },
{ L" CD", false }, // CD ends with a newline -> .wrap = false
{ L"EFG ", false },
{ L" ", false },
{ L" ", false },
},
{ 6, 0 } // cursor on $
{ 6, 0 }, // cursor on $
},
TestBuffer{
{ 8, 5 },
{
{ L"AB $ ", true },
{ L" CD ", false }, // Goes to false because we hit the end of ..CD
{ L"EFG ", false }, // [BUG] EFG moves up due to exact wrap bug above
{ L" CD ", false },
{ L"EFG ", false },
{ L" ", false },
{ L" ", false },
},
{ 6, 0 } // cursor on $
{ 6, 0 }, // cursor on $
},
},
},
@@ -236,19 +234,19 @@ namespace
{ L" ", false },
{ L" ", false },
},
{ 2, 1 } // cursor on $
{ 2, 1 }, // cursor on $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
{
//--012345--
{ L"カタ ", true }, // KA TA [FORCED SPACER]
{ L"カナ$", true_due_to_exact_wrap_bug }, // KA NA
{ L"カナ$", false }, // KA NA
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 4, 1 } // cursor on $
{ 4, 1 }, // cursor on $
},
TestBuffer{
{ 6, 5 }, // grow width back to original
@@ -260,7 +258,7 @@ namespace
{ L" ", false },
{ L" ", false },
},
{ 2, 1 } // cursor on $
{ 2, 1 }, // cursor on $
},
TestBuffer{
{ 7, 5 }, // grow width wider than original (by one; no visible change!)
@@ -272,7 +270,7 @@ namespace
{ L" ", false },
{ L" ", false },
},
{ 2, 1 } // cursor on $
{ 2, 1 }, // cursor on $
},
TestBuffer{
{ 8, 5 }, // grow width enough to fit second DBCS
@@ -284,7 +282,7 @@ namespace
{ L" ", false },
{ L" ", false },
},
{ 0, 1 } // cursor on $
{ 0, 1 }, // cursor on $
},
},
},
@@ -300,41 +298,29 @@ namespace
{ L"MNOPQR", false },
{ L"STUVWX", false },
},
{ 0, 1 } // cursor on $
{ 0, 1 }, // cursor on $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
{
{ L"F$ ", false },
{ L"GHIJK", true }, // [BUG] We should see GHIJK\n L\n MNOPQ\n R\n
{ L"LMNOP", true }, // The wrapping here is irregular
{ L"QRSTU", true },
{ L"VWX ", false },
{ L"$ ", false },
{ L"GHIJK", true },
{ L"L ", false },
{ L"MNOPQ", true },
{ L"R ", false },
},
{ 1, 1 } // [BUG] cursor moves to 1,1 instead of sticking with the $
{ 0, 0 },
},
TestBuffer{
{ 6, 5 }, // going back to 6,5, the data lost has been destroyed
{
//{ L"F$ ", false }, // [BUG] this line is erroneously destroyed too!
{ L"GHIJKL", true },
{ L"MNOPQR", true },
{ L"STUVWX", true },
{ L"$ ", false },
{ L"GHIJKL", false },
{ L"MNOPQR", false },
{ L" ", false },
{ L" ", false },
{ L" ", false }, // [BUG] this line is added
},
{ 1, 1 }, // [BUG] cursor does not follow [H], it sticks at 1,1
},
TestBuffer{
{ 7, 5 }, // a number of errors are carried forward from the previous buffer
{
{ L"GHIJKLM", true },
{ L"NOPQRST", true },
{ L"UVWX ", false }, // [BUG] This line loses wrap for some reason
{ L" ", false },
{ L" ", false },
},
{ 0, 1 }, // [BUG] The cursor moves to 0, 1 now, sticking with the [N] from before
{ 0, 0 },
},
},
},
@@ -353,18 +339,18 @@ namespace
{ L" ", false },
{ L" ", false },
},
{ 1, 1 } // cursor *after* $
{ 1, 1 }, // cursor *after* $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
{
{ L"ABCDE", true },
{ L"F$ ", false }, // [BUG] EXACT WRAP. $ should be alone on next line.
{ L" ", false },
{ L"F ", false },
{ L"$ ", false },
{ L" ", false },
{ L" ", false },
},
{ 2, 1 } // cursor follows space after $ to next line
{ 1, 2 }, // cursor follows space after $ to next line
},
},
},
@@ -380,7 +366,7 @@ namespace
{ L"STUVWX", true },
{ L"YZ0 $ ", false },
},
{ 5, 4 } // cursor *after* $
{ 5, 4 }, // cursor *after* $
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
@@ -391,7 +377,7 @@ namespace
{ L"UVWXY", true },
{ L"Z0 $ ", false },
},
{ 4, 4 } // cursor follows space after $ to newly introduced bottom line
{ 4, 4 }, // cursor follows space after $ to newly introduced bottom line
},
},
},
@@ -402,40 +388,36 @@ namespace
{ 6, 5 },
{
{ L"ABCDEF", false },
// v cursor
{ L"$ ", false },
// ^ cursor
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 5, 1 } // cursor in space far after $
{ 5, 1 }, // The cursor is 5 columns to the right of the $ (last column).
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
{
{ L"ABCDE", true },
{ L"F$ ", true }, // [BUG] This line is marked wrapped, and I do not know why
// v cursor
{ L" ", false },
// ^ cursor
{ L"F ", false },
// The reflow implementation marks a wrapped cursor as a forced row-wrap (= the row is padded with whitespace), so that when
// the buffer is enlarged again, we restore the original cursor position correctly. That's why it says .cursor={5,1} below.
{ L"$ ", true },
{ L" ", false },
{ L" ", false },
},
{ 1, 2 } // cursor stays same linear distance from $
{ 0, 3 }, // $ is now at 0,2 and the cursor used to be 5 columns to the right. -> 0,3
},
TestBuffer{
{ 6, 5 }, // grow back to original size
{
{ L"ABCDEF", true_due_to_exact_wrap_bug },
// v cursor [BUG] cursor does not retain linear distance from $
{ L"ABCDEF", false },
{ L"$ ", false },
// ^ cursor
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 4, 1 } // cursor stays same linear distance from $
{ 5, 1 },
},
},
},
@@ -446,39 +428,37 @@ namespace
{ 6, 5 },
{
{ L"ABCDEF", false },
// v cursor
{ L"$ ", false },
// ^ cursor
{ L"BLAH ", false },
{ L"BLAH ", false },
{ L" ", false },
},
{ 5, 1 } // cursor in space far after $
{ 5, 1 }, // The cursor is 5 columns to the right of the $ (last column).
},
TestBuffer{
{ 5, 5 }, // reduce width by 1
{
{ L"ABCDE", true },
{ L"F$ ", false },
{ L"BLAH ", false },
{ L"BLAH ", true }, // [BUG] this line wraps, no idea why
// v cursor [BUG] cursor erroneously moved to end of all content
{ L"F ", false },
// The reflow implementation pads the row with the cursor with whitespace.
// Search for "REFLOW_JANK_CURSOR_WRAP" to find the corresponding code.
{ L"$ ", true },
{ L" ", false },
// ^ cursor
{ L"BLAH ", false },
{ L"BLAH ", false },
},
{ 0, 4 } },
{ 0, 2 },
},
TestBuffer{
{ 6, 5 }, // grow back to original size
{
{ L"ABCDEF", true },
{ L"F ", false },
{ L"$ ", false },
{ L"BLAH ", false },
// v cursor [BUG] cursor is pulled up to previous line because it was marked wrapped
{ L"BLAH ", false },
// ^ cursor
{ L" ", false },
},
{ 5, 3 } },
{ 5, 1 },
},
},
},
TestCase{
@@ -489,27 +469,24 @@ namespace
{ 6, 5 },
{
{ L"ABCDEF", false },
// v cursor
{ L"$ ", false },
// ^ cursor
{ L" ", false },
{ L" ", false },
{ L" ", false },
},
{ 5, 1 } // cursor in space far after $
{ 5, 1 }, // The cursor is 5 columns to the right of the $ (last column).
},
TestBuffer{
{ 2, 5 }, // reduce width aggressively
{
{ L"CD", true },
{ L"EF", true },
{ L"EF", false },
{ L"$ ", true },
{ L" ", true },
// v cursor
{ L" ", false },
// ^ cursor
},
{ 1, 4 } },
{ 1, 4 },
},
},
},
TestCase{
@@ -519,27 +496,24 @@ namespace
{ 6, 5 },
{
{ L"ABCDEF", true },
// v cursor
{ L"$ ", true },
// ^ cursor
{ L" ", true },
{ L" ", true },
{ L" ", true },
},
{ 5, 1 } // cursor in space far after $
{ 5, 1 }, // cursor in space far after $
},
TestBuffer{
{ 2, 5 }, // reduce width aggressively
{
{ L"EF", true },
{ L"$ ", true },
{ L" ", true },
{ L" ", true },
// v cursor [BUG] cursor does not maintain linear distance from $
{ L" ", false },
// ^ cursor
{ L" ", true },
{ L" ", true },
{ L" ", true },
},
{ 1, 4 } },
{ 1, 0 },
},
},
},
TestCase{
@@ -549,14 +523,12 @@ namespace
{ 6, 5 },
{
{ L"ABCDEF", true },
// v cursor
{ L"$ ", true },
// ^ cursor
{ L" ", true },
{ L" ", true },
{ L" Q", true },
},
{ 5, 1 } // cursor in space far after $
{ 5, 1 }, // cursor in space far after $
},
TestBuffer{
{ 2, 5 }, // reduce width aggressively
@@ -564,12 +536,11 @@ namespace
{ L" ", true },
{ L" ", true },
{ L" ", true },
{ L" Q", true },
// v cursor [BUG] cursor jumps to end of world
{ L" ", false }, // POTENTIAL [BUG] a whole new blank line is added for the cursor
// ^ cursor
{ L" ", true },
{ L" ", true },
},
{ 1, 4 } },
{ 1, 0 },
},
},
},
TestCase{
@@ -579,27 +550,24 @@ namespace
{ 6, 5 },
{
{ L"ABCDEF", false },
// v cursor
{ L"$ ", false },
// ^ cursor
{ L" ", false },
{ L" ", true },
{ L" Q", true },
},
{ 5, 1 } // cursor in space far after $
{ 5, 1 }, // cursor in space far after $
},
TestBuffer{
{ 2, 5 }, // reduce width aggressively
{
{ L" ", true },
{ L" ", true },
{ L" ", true },
{ L" Q", true },
// v cursor [BUG] cursor jumps to different place than fully wrapped case
{ L" ", false },
// ^ cursor
{ L" ", false },
{ L" ", true },
{ L" ", true },
{ L" ", true },
},
{ 0, 4 } },
{ 1, 0 },
},
},
},
TestCase{
@@ -614,24 +582,21 @@ namespace
{ L"$ ", false },
{ L" Q", true },
{ L" ", true },
// v cursor
{ L" ", true },
// ^ cursor
},
{ 5, 4 } // cursor at end of buffer
{ 5, 4 }, // cursor at end of buffer
},
TestBuffer{
{ 2, 5 }, // reduce width aggressively
{
{ L" ", false },
{ L" ", true },
{ L" ", true },
{ L" ", true },
{ L" ", true },
{ L" Q", true },
{ L" ", false },
// v cursor [BUG] cursor loses linear distance from Q; is this important?
{ L" ", false },
// ^ cursor
},
{ 0, 4 } },
{ 1, 0 },
},
},
},
};
@@ -761,7 +726,7 @@ class ReflowTests
static std::unique_ptr<TextBuffer> _textBufferByReflowingTextBuffer(TextBuffer& originalBuffer, const til::size newSize)
{
auto buffer = std::make_unique<TextBuffer>(newSize, TextAttribute{ 0x7 }, 0, false, renderer);
TextBuffer::Reflow(originalBuffer, *buffer, std::nullopt, std::nullopt);
TextBuffer::Reflow(originalBuffer, *buffer);
return buffer;
}

View File

@@ -229,6 +229,7 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept
// nothing to do (the viewportSize is the same as our current size), or an
// appropriate HRESULT for failing to resize.
[[nodiscard]] HRESULT Terminal::UserResize(const til::size viewportSize) noexcept
try
{
const auto oldDimensions = _GetMutableViewport().Dimensions();
if (viewportSize == oldDimensions)
@@ -236,97 +237,59 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept
return S_FALSE;
}
// Shortcut: if we're in the alt buffer, just resize the
// alt buffer and put off resizing the main buffer till we switch back. Fortunately, this is easy. We don't need to
// worry about the viewport and scrollback at all! The alt buffer never has
// any scrollback, so we just need to resize it and presto, we're done.
if (_inAltBuffer())
{
// stash this resize for the future.
// _deferredResize will indicate to UseMainScreenBuffer() that it needs to reflow the main buffer.
// Deferring the reflow of the main buffer has the benefit that it avoids destroying the state
// of the text buffer any more than necessary. For ConPTY in particular a reflow is destructive,
// because it "forgets" text that wraps beyond the top of its viewport when shrinking it.
_deferredResize = viewportSize;
_altBuffer->GetCursor().StartDeferDrawing();
// we're capturing `this` here because when we exit, we want to EndDefer on the (newly created) active buffer.
auto endDefer = wil::scope_exit([this]() noexcept { _altBuffer->GetCursor().EndDeferDrawing(); });
// GH#3494: We don't need to reflow the alt buffer. Apps that use the alt buffer will
// redraw themselves. This prevents graphical artifacts and is consistent with VTE.
_altBuffer->ResizeTraditional(viewportSize);
// GH#3494: We don't need to reflow the alt buffer. Apps that use the
// alt buffer will redraw themselves. This prevents graphical artifacts.
//
// This is consistent with VTE
RETURN_IF_FAILED(_altBuffer->ResizeTraditional(viewportSize));
// Since the _mutableViewport is no longer the size of the actual
// viewport, then update our _altBufferSize tracker we're using to help
// us out here.
_altBufferSize = viewportSize;
_altBuffer->TriggerRedrawAll();
return S_OK;
}
const auto dx = viewportSize.width - oldDimensions.width;
const auto newBufferHeight = std::clamp(viewportSize.height + _scrollbackLines, 0, SHRT_MAX);
til::size bufferSize{ viewportSize.width, newBufferHeight };
// This will be used to determine where the viewport should be in the new buffer.
const auto oldViewportTop = _mutableViewport.Top();
auto newViewportTop = oldViewportTop;
auto newVisibleTop = _VisibleStartIndex();
const auto newBufferHeight = std::clamp(viewportSize.height + _scrollbackLines, 1, SHRT_MAX);
const til::size bufferSize{ viewportSize.width, newBufferHeight };
// If the original buffer had _no_ scroll offset, then we should be at the
// bottom in the new buffer as well. Track that case now.
const auto originalOffsetWasZero = _scrollOffset == 0;
// skip any drawing updates that might occur until we swap _buffer with the new buffer or if we exit early.
_mainBuffer->GetCursor().StartDeferDrawing();
// we're capturing `this` here because when we exit, we want to EndDefer on the (newly created) active buffer.
auto endDefer = wil::scope_exit([this]() noexcept { _mainBuffer->GetCursor().EndDeferDrawing(); });
// GH#3848 - We'll initialize the new buffer with the default attributes,
// but after the resize, we'll want to make sure that the new buffer's
// current attributes (the ones used for printing new text) match the
// old buffer's.
auto newTextBuffer = std::make_unique<TextBuffer>(bufferSize,
TextAttribute{},
0,
_mainBuffer->IsActiveBuffer(),
_mainBuffer->GetRenderer());
// First allocate a new text buffer to take the place of the current one.
std::unique_ptr<TextBuffer> newTextBuffer;
try
{
// GH#3848 - Stash away the current attributes the old text buffer is
// using. We'll initialize the new buffer with the default attributes,
// but after the resize, we'll want to make sure that the new buffer's
// current attributes (the ones used for printing new text) match the
// old buffer's.
const auto& oldBufferAttributes = _mainBuffer->GetCurrentAttributes();
newTextBuffer = std::make_unique<TextBuffer>(bufferSize,
TextAttribute{},
0, // temporarily set size to 0 so it won't render.
_mainBuffer->IsActiveBuffer(),
_mainBuffer->GetRenderer());
// Build a PositionInformation to track the position of both the top of
// the mutable viewport and the top of the visible viewport in the new
// buffer.
// * the new value of mutableViewportTop will be used to figure out
// where we should place the mutable viewport in the new buffer. This
// requires a bit of trickiness to remain consistent with conpty's
// buffer (as seen below).
// * the new value of visibleViewportTop will be used to calculate the
// new scrollOffset in the new buffer, so that the visible lines on
// the screen remain roughly the same.
TextBuffer::PositionInformation positionInfo{
.mutableViewportTop = _mutableViewport.Top(),
.visibleViewportTop = _VisibleStartIndex(),
};
// start defer drawing on the new buffer
newTextBuffer->GetCursor().StartDeferDrawing();
TextBuffer::Reflow(*_mainBuffer.get(), *newTextBuffer.get(), &_mutableViewport, &positionInfo);
// Build a PositionInformation to track the position of both the top of
// the mutable viewport and the top of the visible viewport in the new
// buffer.
// * the new value of mutableViewportTop will be used to figure out
// where we should place the mutable viewport in the new buffer. This
// requires a bit of trickiness to remain consistent with conpty's
// buffer (as seen below).
// * the new value of visibleViewportTop will be used to calculate the
// new scrollOffset in the new buffer, so that the visible lines on
// the screen remain roughly the same.
TextBuffer::PositionInformation oldRows{ 0 };
oldRows.mutableViewportTop = oldViewportTop;
oldRows.visibleViewportTop = newVisibleTop;
const std::optional oldViewStart{ oldViewportTop };
RETURN_IF_FAILED(TextBuffer::Reflow(*_mainBuffer.get(),
*newTextBuffer.get(),
_mutableViewport,
{ oldRows }));
newViewportTop = oldRows.mutableViewportTop;
newVisibleTop = oldRows.visibleViewportTop;
// Restore the active text attributes
newTextBuffer->SetCurrentAttributes(oldBufferAttributes);
}
CATCH_RETURN();
// Restore the active text attributes
newTextBuffer->SetCurrentAttributes(_mainBuffer->GetCurrentAttributes());
// 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
@@ -369,7 +332,7 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept
const auto maxRow = std::max(newLastChar.y, newCursorPos.y);
const auto proposedTopFromLastLine = maxRow - viewportSize.height + 1;
const auto proposedTopFromScrollback = newViewportTop;
const auto proposedTopFromScrollback = positionInfo.mutableViewportTop;
auto proposedTop = std::max(proposedTopFromLastLine,
proposedTopFromScrollback);
@@ -392,17 +355,13 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept
const auto proposedViewFromTop = Viewport::FromDimensions({ 0, proposedTopFromScrollback }, viewportSize);
if (maxRow < proposedViewFromTop.BottomInclusive())
{
if (dx < 0 && proposedTop > 0)
if (viewportSize.width < oldDimensions.width && proposedTop > 0)
{
try
const auto& row = newTextBuffer->GetRowByOffset(proposedTop - 1);
if (row.WasWrapForced())
{
const auto& row = newTextBuffer->GetRowByOffset(::base::ClampSub(proposedTop, 1));
if (row.WasWrapForced())
{
proposedTop--;
}
proposedTop--;
}
CATCH_LOG();
}
}
}
@@ -435,7 +394,7 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept
// GH#3494: Maintain scrollbar position during resize
// Make sure that we don't scroll past the mutableViewport at the bottom of the buffer
newVisibleTop = std::min(newVisibleTop, _mutableViewport.Top());
auto newVisibleTop = std::min(positionInfo.visibleViewportTop, _mutableViewport.Top());
// Make sure we don't scroll past the top of the scrollback
newVisibleTop = std::max(newVisibleTop, 0);
@@ -443,16 +402,11 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept
// before, and shouldn't be now either.
_scrollOffset = originalOffsetWasZero ? 0 : static_cast<int>(::base::ClampSub(_mutableViewport.Top(), newVisibleTop));
// GH#5029 - make sure to InvalidateAll here, so that we'll paint the entire visible viewport.
try
{
_activeBuffer().TriggerRedrawAll();
_NotifyScrollEvent();
}
CATCH_LOG();
_mainBuffer->TriggerRedrawAll();
_NotifyScrollEvent();
return S_OK;
}
CATCH_RETURN()
void Terminal::Write(std::wstring_view stringView)
{
@@ -1073,14 +1027,12 @@ const TerminalInput& Terminal::_getTerminalInput() const noexcept
// _VisibleStartIndex is the first visible line of the buffer
int Terminal::_VisibleStartIndex() const noexcept
{
return _inAltBuffer() ? ViewStartIndex() :
std::max(0, ViewStartIndex() - _scrollOffset);
return _inAltBuffer() ? 0 : std::max(0, _mutableViewport.Top() - _scrollOffset);
}
int Terminal::_VisibleEndIndex() const noexcept
{
return _inAltBuffer() ? ViewEndIndex() :
std::max(0, ViewEndIndex() - _scrollOffset);
return _inAltBuffer() ? _altBufferSize.height - 1 : std::max(0, _mutableViewport.BottomInclusive() - _scrollOffset);
}
Viewport Terminal::_GetVisibleViewport() const noexcept

View File

@@ -248,33 +248,19 @@ void Terminal::UseAlternateScreenBuffer(const TextAttribute& attrs)
}
void Terminal::UseMainScreenBuffer()
{
// Short-circuit: do nothing.
if (!_inAltBuffer())
// To make UserResize() work as if we're back in the main buffer, we first need to unset
// _altBuffer, which is used throughout this class as an indicator via _inAltBuffer().
//
// We delay destroying the alt buffer instance to get a valid altBuffer->GetCursor() reference below.
const auto altBuffer = std::exchange(_altBuffer, nullptr);
if (!altBuffer)
{
return;
}
ClearSelection();
// Copy our cursor state back to the main buffer's cursor
{
// Update the alt buffer's cursor style, visibility, and position to match our own.
const auto& myCursor = _altBuffer->GetCursor();
auto& tgtCursor = _mainBuffer->GetCursor();
tgtCursor.SetStyle(myCursor.GetSize(), myCursor.GetType());
tgtCursor.SetIsVisible(myCursor.IsVisible());
tgtCursor.SetBlinkingAllowed(myCursor.IsBlinkingAllowed());
// The new position should match the viewport-relative position of the main buffer.
// This is the equal and opposite effect of what we did in UseAlternateScreenBuffer
auto tgtCursorPos = myCursor.GetPosition();
tgtCursorPos.y += _mutableViewport.Top();
tgtCursor.SetPosition(tgtCursorPos);
}
_mainBuffer->SetAsActiveBuffer(true);
// destroy the alt buffer
_altBuffer = nullptr;
if (_deferredResize.has_value())
{
@@ -282,6 +268,24 @@ void Terminal::UseMainScreenBuffer()
_deferredResize = std::nullopt;
}
// After exiting the alt buffer, the main buffer should adopt the current cursor position and style.
// This is the equal and opposite effect of what we did in UseAlternateScreenBuffer and matches xterm.
//
// We have to do this after the call to UserResize() to ensure that the TextBuffer sizes match up.
// Otherwise the cursor position may be temporarily out of bounds and some code may choke on that.
{
const auto& altCursor = altBuffer->GetCursor();
auto& mainCursor = _mainBuffer->GetCursor();
mainCursor.SetStyle(altCursor.GetSize(), altCursor.GetType());
mainCursor.SetIsVisible(altCursor.IsVisible());
mainCursor.SetBlinkingAllowed(altCursor.IsBlinkingAllowed());
auto tgtCursorPos = altCursor.GetPosition();
tgtCursorPos.y += _mutableViewport.Top();
mainCursor.SetPosition(tgtCursorPos);
}
// update all the hyperlinks on the screen
_updateUrlDetection();
@@ -293,11 +297,7 @@ void Terminal::UseMainScreenBuffer()
_NotifyScrollEvent();
// redraw the screen
try
{
_activeBuffer().TriggerRedrawAll();
}
CATCH_LOG();
_activeBuffer().TriggerRedrawAll();
}
// NOTE: This is the version of AddMark that comes from VT

View File

@@ -462,40 +462,6 @@ void VtIo::SendCloseEvent()
}
}
// Method Description:
// - Tell the vt renderer to begin a resize operation. During a resize
// operation, the vt renderer should _not_ request to be repainted during a
// text buffer circling event. Any callers of this method should make sure to
// call EndResize to make sure the renderer returns to normal behavior.
// See GH#1795 for context on this method.
// Arguments:
// - <none>
// Return Value:
// - <none>
void VtIo::BeginResize()
{
if (_pVtRenderEngine)
{
_pVtRenderEngine->BeginResizeRequest();
}
}
// Method Description:
// - Tell the vt renderer to end a resize operation.
// See BeginResize for more details.
// See GH#1795 for context on this method.
// Arguments:
// - <none>
// Return Value:
// - <none>
void VtIo::EndResize()
{
if (_pVtRenderEngine)
{
_pVtRenderEngine->EndResizeRequest();
}
}
// The name of this method is an analogy to TCP_CORK. It instructs
// the VT renderer to stop flushing its buffer to the output pipe.
// Don't forget to uncork it!

View File

@@ -40,9 +40,6 @@ namespace Microsoft::Console::VirtualTerminal
void CloseInput();
void CloseOutput();
void BeginResize();
void EndResize();
void CorkRenderer(bool corked) const noexcept;
#ifdef UNIT_TESTING

View File

@@ -1387,6 +1387,7 @@ bool SCREEN_INFORMATION::IsMaximizedY() const
// Return Value:
// - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed.
[[nodiscard]] NTSTATUS SCREEN_INFORMATION::ResizeWithReflow(const til::size coordNewScreenSize)
try
{
if ((USHORT)coordNewScreenSize.width >= SHORT_MAX || (USHORT)coordNewScreenSize.height >= SHORT_MAX)
{
@@ -1394,26 +1395,14 @@ bool SCREEN_INFORMATION::IsMaximizedY() const
return STATUS_INVALID_PARAMETER;
}
// First allocate a new text buffer to take the place of the current one.
std::unique_ptr<TextBuffer> newTextBuffer;
// GH#3848 - Stash away the current attributes the old text buffer is using.
// We'll initialize the new buffer with the default attributes, but after
// the resize, we'll want to make sure that the new buffer's current
// GH#3848 - We'll initialize the new buffer with the default attributes,
// but after the resize, we'll want to make sure that the new buffer's current
// attributes (the ones used for printing new text) match the old buffer's.
const auto oldPrimaryAttributes = _textBuffer->GetCurrentAttributes();
try
{
newTextBuffer = std::make_unique<TextBuffer>(coordNewScreenSize,
TextAttribute{},
0, // temporarily set size to 0 so it won't render.
_textBuffer->IsActiveBuffer(),
_textBuffer->GetRenderer());
}
catch (...)
{
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
}
auto newTextBuffer = std::make_unique<TextBuffer>(coordNewScreenSize,
TextAttribute{},
0, // temporarily set size to 0 so it won't render.
_textBuffer->IsActiveBuffer(),
_textBuffer->GetRenderer());
// Save cursor's relative height versus the viewport
const auto sCursorHeightInViewportBefore = _textBuffer->GetCursor().GetPosition().y - _viewport.Top();
@@ -1426,38 +1415,35 @@ bool SCREEN_INFORMATION::IsMaximizedY() const
// we're capturing _textBuffer by reference here because when we exit, we want to EndDefer on the current active buffer.
auto endDefer = wil::scope_exit([&]() noexcept { _textBuffer->GetCursor().EndDeferDrawing(); });
auto hr = TextBuffer::Reflow(*_textBuffer.get(), *newTextBuffer.get(), std::nullopt, std::nullopt);
TextBuffer::Reflow(*_textBuffer.get(), *newTextBuffer.get());
if (SUCCEEDED(hr))
{
// Since the reflow doesn't preserve the virtual bottom, we try and
// estimate where it ought to be by making it the same distance from
// the cursor row as it was before the resize. However, we also need
// to make sure it is far enough down to include the last non-space
// row, and it shouldn't be less than the height of the viewport,
// otherwise the top of the virtual viewport would end up negative.
const auto cursorRow = newTextBuffer->GetCursor().GetPosition().y;
const auto lastNonSpaceRow = newTextBuffer->GetLastNonSpaceCharacter().y;
const auto estimatedBottom = cursorRow + cursorDistanceFromBottom;
const auto viewportBottom = _viewport.Height() - 1;
_virtualBottom = std::max({ lastNonSpaceRow, estimatedBottom, viewportBottom });
// Since the reflow doesn't preserve the virtual bottom, we try and
// estimate where it ought to be by making it the same distance from
// the cursor row as it was before the resize. However, we also need
// to make sure it is far enough down to include the last non-space
// row, and it shouldn't be less than the height of the viewport,
// otherwise the top of the virtual viewport would end up negative.
const auto cursorRow = newTextBuffer->GetCursor().GetPosition().y;
const auto lastNonSpaceRow = newTextBuffer->GetLastNonSpaceCharacter().y;
const auto estimatedBottom = cursorRow + cursorDistanceFromBottom;
const auto viewportBottom = _viewport.Height() - 1;
_virtualBottom = std::max({ lastNonSpaceRow, estimatedBottom, viewportBottom });
// We can't let it extend past the bottom of the buffer either.
_virtualBottom = std::min(_virtualBottom, newTextBuffer->GetSize().BottomInclusive());
// We can't let it extend past the bottom of the buffer either.
_virtualBottom = std::min(_virtualBottom, newTextBuffer->GetSize().BottomInclusive());
// Adjust the viewport so the cursor doesn't wildly fly off up or down.
const auto sCursorHeightInViewportAfter = cursorRow - _viewport.Top();
til::point coordCursorHeightDiff;
coordCursorHeightDiff.y = sCursorHeightInViewportAfter - sCursorHeightInViewportBefore;
LOG_IF_FAILED(SetViewportOrigin(false, coordCursorHeightDiff, false));
// Adjust the viewport so the cursor doesn't wildly fly off up or down.
const auto sCursorHeightInViewportAfter = cursorRow - _viewport.Top();
til::point coordCursorHeightDiff;
coordCursorHeightDiff.y = sCursorHeightInViewportAfter - sCursorHeightInViewportBefore;
LOG_IF_FAILED(SetViewportOrigin(false, coordCursorHeightDiff, false));
newTextBuffer->SetCurrentAttributes(oldPrimaryAttributes);
newTextBuffer->SetCurrentAttributes(_textBuffer->GetCurrentAttributes());
_textBuffer.swap(newTextBuffer);
}
return NTSTATUS_FROM_HRESULT(hr);
_textBuffer = std::move(newTextBuffer);
return STATUS_SUCCESS;
}
NT_CATCH_RETURN()
//
// Routine Description:
@@ -1467,11 +1453,14 @@ bool SCREEN_INFORMATION::IsMaximizedY() const
// Return Value:
// - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed.
[[nodiscard]] NTSTATUS SCREEN_INFORMATION::ResizeTraditional(const til::size coordNewScreenSize)
try
{
_textBuffer->GetCursor().StartDeferDrawing();
auto endDefer = wil::scope_exit([&]() noexcept { _textBuffer->GetCursor().EndDeferDrawing(); });
return NTSTATUS_FROM_HRESULT(_textBuffer->ResizeTraditional(coordNewScreenSize));
_textBuffer->ResizeTraditional(coordNewScreenSize);
return STATUS_SUCCESS;
}
NT_CATCH_RETURN()
//
// Routine Description:
@@ -1493,19 +1482,6 @@ bool SCREEN_INFORMATION::IsMaximizedY() const
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto status = STATUS_SUCCESS;
// If we're in conpty mode, suppress any immediate painting we might do
// during the resize.
if (gci.IsInVtIoMode())
{
gci.GetVtIo()->BeginResize();
}
auto endResize = wil::scope_exit([&] {
if (gci.IsInVtIoMode())
{
gci.GetVtIo()->EndResize();
}
});
// cancel any active selection before resizing or it will not necessarily line up with the new buffer positions
Selection::Instance().ClearSelection();

View File

@@ -607,7 +607,7 @@ class ApiRoutinesTests
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
VERIFY_SUCCEEDED(si.GetTextBuffer().ResizeTraditional({ 5, 5 }), L"Make the buffer small so this doesn't take forever.");
si.GetTextBuffer().ResizeTraditional({ 5, 5 });
// Tests are run both with and without the DECSTBM margins set. This should not alter
// the results, since ScrollConsoleScreenBuffer should not be affected by VT margins.

View File

@@ -2307,7 +2307,7 @@ void ScreenBufferTests::GetWordBoundary()
// Make the buffer as big as our test text.
const til::size newBufferSize = { gsl::narrow<til::CoordType>(length), 10 };
VERIFY_SUCCEEDED(si.GetTextBuffer().ResizeTraditional(newBufferSize));
si.GetTextBuffer().ResizeTraditional(newBufferSize);
const OutputCellIterator it(text, si.GetAttributes());
si.Write(it, { 0, 0 });
@@ -2383,7 +2383,7 @@ void ScreenBufferTests::GetWordBoundaryTrimZeros(const bool on)
// Make the buffer as big as our test text.
const til::size newBufferSize = { gsl::narrow<til::CoordType>(length), 10 };
VERIFY_SUCCEEDED(si.GetTextBuffer().ResizeTraditional(newBufferSize));
si.GetTextBuffer().ResizeTraditional(newBufferSize);
const OutputCellIterator it(text, si.GetAttributes());
si.Write(it, { 0, 0 });

View File

@@ -1755,7 +1755,7 @@ void TextBufferTests::ResizeTraditional()
auto expectedSpace = UNICODE_SPACE;
std::wstring_view expectedSpaceView(&expectedSpace, 1);
VERIFY_SUCCEEDED(buffer.ResizeTraditional(newSize));
buffer.ResizeTraditional(newSize);
Log::Comment(L"Verify every cell in the X dimension is still the same as when filled and the new Y row is just empty default cells.");
{
@@ -1821,7 +1821,7 @@ void TextBufferTests::ResizeTraditionalRotationPreservesHighUnicode()
_buffer->_SetFirstRowIndex(pos.y);
// Perform resize to rotate the rows around
VERIFY_NT_SUCCESS(_buffer->ResizeTraditional(bufferSize));
_buffer->ResizeTraditional(bufferSize);
// Retrieve the text at the old and new positions.
const auto shouldBeEmptyText = *_buffer->GetTextDataAt(pos);
@@ -1893,7 +1893,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeRowRemoval()
// Perform resize to trim off the row of the buffer that included the emoji
til::size trimmedBufferSize{ bufferSize.width, bufferSize.height - 1 };
VERIFY_NT_SUCCESS(_buffer->ResizeTraditional(trimmedBufferSize));
_buffer->ResizeTraditional(trimmedBufferSize);
}
// This tests that columns removed from the buffer while resizing traditionally will also drop the high unicode
@@ -1923,7 +1923,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeColumnRemoval()
// Perform resize to trim off the column of the buffer that included the emoji
til::size trimmedBufferSize{ bufferSize.width - 1, bufferSize.height };
VERIFY_NT_SUCCESS(_buffer->ResizeTraditional(trimmedBufferSize));
_buffer->ResizeTraditional(trimmedBufferSize);
}
void TextBufferTests::TestBurrito()

View File

@@ -426,7 +426,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
// Returns the range [start_index, end_index) as a new vector.
// It works just like std::string::substr(), but with absolute indices.
[[nodiscard]] basic_rle slice(size_type start_index, size_type end_index) const noexcept
[[nodiscard]] basic_rle slice(size_type start_index, size_type end_index) const
{
if (end_index > _total_length)
{
@@ -446,14 +446,14 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
// --> It's safe to subtract 1 from end_index
rle_scanner scanner(_runs.begin(), _runs.end());
auto [begin_run, start_run_pos] = scanner.scan(start_index);
auto [end_run, end_run_pos] = scanner.scan(end_index - 1);
const auto [begin_run, start_run_pos] = scanner.scan(start_index);
const auto [end_run, end_run_pos] = scanner.scan(end_index - 1);
container slice{ begin_run, end_run + 1 };
slice.back().length = end_run_pos + 1;
slice.front().length -= start_run_pos;
return { std::move(slice), static_cast<size_type>(end_index - start_index) };
return { std::move(slice), gsl::narrow_cast<size_type>(end_index - start_index) };
}
// Replace the range [start_index, end_index) with the given value.
@@ -463,7 +463,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
_check_indices(start_index, end_index);
const rle_type replacement{ value, static_cast<size_type>(end_index - start_index) };
const rle_type replacement{ value, gsl::narrow_cast<size_type>(end_index - start_index) };
_replace_unchecked(start_index, end_index, { &replacement, 1 });
}
@@ -651,7 +651,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
size_type total = 0;
};
basic_rle(container&& runs, size_type size) :
basic_rle(container&& runs, size_type size) noexcept :
_runs(std::forward<container>(runs)),
_total_length(size)
{

View File

@@ -106,19 +106,11 @@ CATCH_RETURN();
// - S_OK
[[nodiscard]] HRESULT VtEngine::InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept
{
// If we're in the middle of a resize request, don't try to immediately start a frame.
if (_inResizeRequest)
{
*pForcePaint = false;
}
else
{
*pForcePaint = true;
*pForcePaint = true;
// Keep track of the fact that we circled, we'll need to do some work on
// end paint to specifically handle this.
_circled = circled;
}
// Keep track of the fact that we circled, we'll need to do some work on
// end paint to specifically handle this.
_circled = circled;
_trace.TraceTriggerCircling(*pForcePaint);
return S_OK;

View File

@@ -49,7 +49,6 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
_terminalOwner{ nullptr },
_newBottomLine{ false },
_deferredCursorPos{ INVALID_COORDS },
_inResizeRequest{ false },
_trace{},
_bufferLine{},
_buffer{},
@@ -459,34 +458,6 @@ HRESULT VtEngine::RequestCursor() noexcept
return S_OK;
}
// Method Description:
// - Tell the vt renderer to begin a resize operation. During a resize
// operation, the vt renderer should _not_ request to be repainted during a
// text buffer circling event. Any callers of this method should make sure to
// call EndResize to make sure the renderer returns to normal behavior.
// See GH#1795 for context on this method.
// Arguments:
// - <none>
// Return Value:
// - <none>
void VtEngine::BeginResizeRequest()
{
_inResizeRequest = true;
}
// Method Description:
// - Tell the vt renderer to end a resize operation.
// See BeginResize for more details.
// See GH#1795 for context on this method.
// Arguments:
// - <none>
// Return Value:
// - <none>
void VtEngine::EndResizeRequest()
{
_inResizeRequest = false;
}
// Method Description:
// - Configure the renderer for the resize quirk. This changes the behavior of
// conpty to _not_ InvalidateAll the entire viewport on a resize operation.

View File

@@ -132,7 +132,6 @@ namespace Microsoft::Console::Render
Microsoft::Console::VirtualTerminal::VtIo* _terminalOwner;
Microsoft::Console::VirtualTerminal::RenderTracing _trace;
bool _inResizeRequest{ false };
std::optional<til::CoordType> _wrappedRow{ std::nullopt };

View File

@@ -2106,26 +2106,26 @@ public:
auto& stateMachine = *_testGetSet->_stateMachine;
Log::Comment(L"Default tabs stops in 80-column mode");
VERIFY_SUCCEEDED(textBuffer.ResizeTraditional({ 80, 600 }));
textBuffer.ResizeTraditional({ 80, 600 });
_pDispatch->RequestPresentationStateReport(DispatchTypes::PresentationReportFormat::TabulationStopReport);
_testGetSet->ValidateInputEvent(L"\033P2$u9/17/25/33/41/49/57/65/73\033\\");
Log::Comment(L"Default tabs stops in 132-column mode");
VERIFY_SUCCEEDED(textBuffer.ResizeTraditional({ 132, 600 }));
textBuffer.ResizeTraditional({ 132, 600 });
_pDispatch->RequestPresentationStateReport(DispatchTypes::PresentationReportFormat::TabulationStopReport);
_testGetSet->ValidateInputEvent(L"\033P2$u9/17/25/33/41/49/57/65/73/81/89/97/105/113/121/129\033\\");
Log::Comment(L"Custom tab stops in 80 columns");
VERIFY_SUCCEEDED(textBuffer.ResizeTraditional({ 80, 600 }));
textBuffer.ResizeTraditional({ 80, 600 });
_testGetSet->_stateMachine->ProcessString(L"\033P2$t30/60/120/240\033\\");
_pDispatch->RequestPresentationStateReport(DispatchTypes::PresentationReportFormat::TabulationStopReport);
_testGetSet->ValidateInputEvent(L"\033P2$u30/60\033\\");
Log::Comment(L"After expanding width to 132 columns");
VERIFY_SUCCEEDED(textBuffer.ResizeTraditional({ 132, 600 }));
textBuffer.ResizeTraditional({ 132, 600 });
_pDispatch->RequestPresentationStateReport(DispatchTypes::PresentationReportFormat::TabulationStopReport);
_testGetSet->ValidateInputEvent(L"\033P2$u30/60/120\033\\");
VERIFY_SUCCEEDED(textBuffer.ResizeTraditional({ 80, 600 }));
textBuffer.ResizeTraditional({ 80, 600 });
Log::Comment(L"Out of order tab stops");
stateMachine.ProcessString(L"\033P2$t44/22/66\033\\");

View File

@@ -1335,7 +1335,7 @@ til::point UiaTextRangeBase::_getDocumentEnd() const
{
const auto optimizedBufferSize{ _getOptimizedBufferSize() };
const auto& buffer{ _pData->GetTextBuffer() };
const auto lastCharPos{ buffer.GetLastNonSpaceCharacter(optimizedBufferSize) };
const auto lastCharPos{ buffer.GetLastNonSpaceCharacter(&optimizedBufferSize) };
const auto cursorPos{ buffer.GetCursor().GetPosition() };
return { optimizedBufferSize.Left(), std::max(lastCharPos.y, cursorPos.y) + 1 };
}