Skip to content

Commit bc48eda

Browse files
lheckerDHowett
authored andcommitted
Reset _wrapForced when erasing scrollback (#16610)
#15541 changed `AdaptDispatch::_FillRect` which caused it to not affect the `ROW::_wrapForced` flag anymore. This change in behavior was not noticeable as `TextBuffer::GetLastNonSpaceCharacter` had a bug where rows of only whitespace text would always be treated as empty. This would then affect `AdaptDispatch::_EraseAll` to accidentally correctly guess the last row with text despite the `_FillRect` change. #15701 then fixed `GetLastNonSpaceCharacter` indirectly by fixing `ROW::MeasureRight` which now made the previous change apparent. `_EraseAll` would now guess the last row of text incorrectly, because it would find the rows that `_FillRect` cleared but still had `_wrapForced` set to `true`. This PR fixes the issue by replacing the `_FillRect` usage to clear rows with direct calls to `ROW::Reset()`. In the future this could be extended by also `MEM_DECOMMIT`ing the now unused underlying memory. Closes #16603 ## Validation Steps Performed * Enter WSL and resize the window to <40 columns * Execute ```sh cd /bin ls -la printf "\e[3J" ls -la printf "\e[3J" printf "\e[2J" ``` * Only one viewport-height-many lines of whitespace exist between the current prompt line and the previous scrollback contents ✅ (cherry picked from commit 5f71cf3) Service-Card-Id: 91707937 Service-Version: 1.19
1 parent 29895e1 commit bc48eda

File tree

4 files changed

+51
-23
lines changed

4 files changed

+51
-23
lines changed

src/buffer/out/textBuffer.cpp

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ void TextBuffer::_reserve(til::size screenBufferSize, const TextAttribute& defau
126126
// The compiler doesn't understand the likelihood of our branches. (PGO does, but that's imperfect.)
127127
__declspec(noinline) void TextBuffer::_commit(const std::byte* row)
128128
{
129+
assert(row >= _commitWatermark);
130+
129131
const auto rowEnd = row + _bufferRowStride;
130132
const auto remaining = gsl::narrow_cast<uintptr_t>(_bufferEnd - _commitWatermark);
131133
const auto minimum = gsl::narrow_cast<uintptr_t>(rowEnd - _commitWatermark);
@@ -146,7 +148,7 @@ void TextBuffer::_decommit() noexcept
146148
_commitWatermark = _buffer.get();
147149
}
148150

149-
// Constructs ROWs up to (excluding) the ROW pointed to by `until`.
151+
// Constructs ROWs between [_commitWatermark,until).
150152
void TextBuffer::_construct(const std::byte* until) noexcept
151153
{
152154
for (; _commitWatermark < until; _commitWatermark += _bufferRowStride)
@@ -158,8 +160,7 @@ void TextBuffer::_construct(const std::byte* until) noexcept
158160
}
159161
}
160162

161-
// Destroys all previously constructed ROWs.
162-
// Be careful! This doesn't reset any of the members, in particular the _commitWatermark.
163+
// Destructs ROWs between [_buffer,_commitWatermark).
163164
void TextBuffer::_destroy() const noexcept
164165
{
165166
for (auto it = _buffer.get(); it < _commitWatermark; it += _bufferRowStride)
@@ -168,9 +169,8 @@ void TextBuffer::_destroy() const noexcept
168169
}
169170
}
170171

171-
// This function is "direct" because it trusts the caller to properly wrap the "offset"
172-
// parameter modulo the _height of the buffer, etc. But keep in mind that a offset=0
173-
// is the GetScratchpadRow() and not the GetRowByOffset(0). That one is offset=1.
172+
// This function is "direct" because it trusts the caller to properly
173+
// wrap the "offset" parameter modulo the _height of the buffer.
174174
ROW& TextBuffer::_getRowByOffsetDirect(size_t offset)
175175
{
176176
const auto row = _buffer.get() + _bufferRowStride * offset;
@@ -184,6 +184,7 @@ ROW& TextBuffer::_getRowByOffsetDirect(size_t offset)
184184
return *reinterpret_cast<ROW*>(row);
185185
}
186186

187+
// See GetRowByOffset().
187188
ROW& TextBuffer::_getRow(til::CoordType y) const
188189
{
189190
// Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows.
@@ -197,6 +198,7 @@ ROW& TextBuffer::_getRow(til::CoordType y) const
197198
}
198199

199200
// We add 1 to the row offset, because row "0" is the one returned by GetScratchpadRow().
201+
// See GetScratchpadRow() for more explanation.
200202
#pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3).
201203
return const_cast<TextBuffer*>(this)->_getRowByOffsetDirect(gsl::narrow_cast<size_t>(offset) + 1);
202204
}
@@ -238,6 +240,9 @@ ROW& TextBuffer::GetScratchpadRow()
238240
// Returns a row filled with whitespace and the given attributes, for you to freely use.
239241
ROW& TextBuffer::GetScratchpadRow(const TextAttribute& attributes)
240242
{
243+
// The scratchpad row is mapped to the underlying index 0, whereas all regular rows are mapped to
244+
// index 1 and up. We do it this way instead of the other way around (scratchpad row at index _height),
245+
// because that would force us to MEM_COMMIT the entire buffer whenever this function is called.
241246
auto& r = _getRowByOffsetDirect(0);
242247
r.Reset(attributes);
243248
return r;
@@ -902,15 +907,14 @@ til::point TextBuffer::GetLastNonSpaceCharacter(const Viewport* viewOptional) co
902907

903908
// If the X coordinate turns out to be -1, the row was empty, we need to search backwards for the real end of text.
904909
const auto viewportTop = viewport.Top();
905-
auto fDoBackUp = (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop); // this row is empty, and we're not at the top
906-
while (fDoBackUp)
910+
911+
// while (this row is empty, and we're not at the top)
912+
while (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop)
907913
{
908914
coordEndOfText.y--;
909915
const auto& backupRow = GetRowByOffset(coordEndOfText.y);
910916
// We need to back up to the previous row if this line is empty, AND there are more rows
911-
912917
coordEndOfText.x = backupRow.MeasureRight() - 1;
913-
fDoBackUp = (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop);
914918
}
915919

916920
// don't allow negative results
@@ -1146,6 +1150,39 @@ void TextBuffer::Reset() noexcept
11461150
_initialAttributes = _currentAttributes;
11471151
}
11481152

1153+
void TextBuffer::ClearScrollback(const til::CoordType start, const til::CoordType height)
1154+
{
1155+
if (start <= 0)
1156+
{
1157+
return;
1158+
}
1159+
1160+
if (height <= 0)
1161+
{
1162+
_decommit();
1163+
return;
1164+
}
1165+
1166+
// Our goal is to move the viewport to the absolute start of the underlying memory buffer so that we can
1167+
// MEM_DECOMMIT the remaining memory. _firstRow is used to make the TextBuffer behave like a circular buffer.
1168+
// The start parameter is relative to the _firstRow. The trick to get the content to the absolute start
1169+
// is to simply add _firstRow ourselves and then reset it to 0. This causes ScrollRows() to write into
1170+
// the absolute start while reading from relative coordinates. This works because GetRowByOffset()
1171+
// operates modulo the buffer height and so the possibly-too-large startAbsolute won't be an issue.
1172+
const auto startAbsolute = _firstRow + start;
1173+
_firstRow = 0;
1174+
ScrollRows(startAbsolute, height, -startAbsolute);
1175+
1176+
const auto end = _estimateOffsetOfLastCommittedRow();
1177+
for (auto y = height; y <= end; ++y)
1178+
{
1179+
GetMutableRowByOffset(y).Reset(_initialAttributes);
1180+
}
1181+
1182+
ScrollMarks(-start);
1183+
ClearMarksInRange(til::point{ 0, height }, til::point{ _width, _height });
1184+
}
1185+
11491186
// Routine Description:
11501187
// - This is the legacy screen resize with minimal changes
11511188
// Arguments:

src/buffer/out/textBuffer.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ class TextBuffer final
194194
til::point BufferToScreenPosition(const til::point position) const;
195195

196196
void Reset() noexcept;
197+
void ClearScrollback(const til::CoordType start, const til::CoordType height);
197198

198199
void ResizeTraditional(const til::size newSize);
199200

src/host/ut_host/ScreenBufferTests.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4515,6 +4515,7 @@ void ScreenBufferTests::EraseScrollbackTests()
45154515
auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer();
45164516
auto& stateMachine = si.GetStateMachine();
45174517
const auto& cursor = si.GetTextBuffer().GetCursor();
4518+
const auto initialAttributes = si.GetAttributes();
45184519
WI_SetFlag(si.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING);
45194520

45204521
const auto bufferWidth = si.GetBufferSize().Width();
@@ -4571,7 +4572,7 @@ void ScreenBufferTests::EraseScrollbackTests()
45714572
}
45724573

45734574
Log::Comment(L"The rest of the buffer should be cleared with default attributes.");
4574-
VERIFY_IS_TRUE(_ValidateLinesContain(viewportLine, bufferHeight, L' ', TextAttribute{}));
4575+
VERIFY_IS_TRUE(_ValidateLinesContain(viewportLine, bufferHeight, L' ', initialAttributes));
45754576
}
45764577

45774578
void ScreenBufferTests::EraseTests()

src/terminal/adapter/adaptDispatch.cpp

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3200,18 +3200,7 @@ bool AdaptDispatch::_EraseScrollback()
32003200
auto& cursor = textBuffer.GetCursor();
32013201
const auto row = cursor.GetPosition().y;
32023202

3203-
// Clear all the marks below the new viewport position.
3204-
textBuffer.ClearMarksInRange(til::point{ 0, height },
3205-
til::point{ bufferSize.width, bufferSize.height });
3206-
// Then scroll all the remaining marks up. This will trim ones that are now "outside" the buffer
3207-
textBuffer.ScrollMarks(-top);
3208-
3209-
// Scroll the viewport content to the top of the buffer.
3210-
textBuffer.ScrollRows(top, height, -top);
3211-
// Clear everything after the viewport.
3212-
_FillRect(textBuffer, { 0, height, bufferSize.width, bufferSize.height }, whitespace, {});
3213-
// Also reset the line rendition for all of the cleared rows.
3214-
textBuffer.ResetLineRenditionRange(height, bufferSize.height);
3203+
textBuffer.ClearScrollback(top, height);
32153204
// Move the viewport
32163205
_api.SetViewportPosition({ viewport.left, 0 });
32173206
// Move the cursor to the same relative location.

0 commit comments

Comments
 (0)