diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 822a2a5b8796..0fd6e1943657 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -119,37 +119,49 @@ bool ROW::IsValid() const noexcept return _valid; } -void ROW::Discard() noexcept +void ROW::Discard(const TextAttribute& attr) noexcept { _charsHeap.reset(); + _chars = { _charsBuffer, _columnCount }; + // Constructing and then moving objects into place isn't free. + // Modifying the existing object is _much_ faster. + *_attr.runs().unsafe_shrink_to_size(1) = til::rle_pair{ attr, _columnCount }; + _lineRendition = LineRendition::SingleWidth; + _wrapForced = false; + _doubleBytePadded = false; _valid = false; } -void ROW::Reset(const TextAttribute& attr) noexcept +void ROW::Initialize() noexcept { - _init(attr); - std::fill_n(_chars.begin(), _columnCount, UNICODE_SPACE); + // The usage of _charsBuffer instead of _chars is quite intentional, + // as it allows _reset() to first call Initialize() and then Discard(). + std::fill_n(_charsBuffer, _columnCount, UNICODE_SPACE); std::iota(_charOffsets.begin(), _charOffsets.end(), uint16_t{ 0 }); + _valid = true; } -void ROW::Reset(const ROW& whitespaceRow, const TextAttribute& attr) noexcept +void ROW::Initialize(const ROW& whitespaceRow) noexcept { - _init(attr); - memcpy(_chars.data(), whitespaceRow._chars.data(), std::min(whitespaceRow._chars.size(), _chars.size()) * 2); - memcpy(_charOffsets.data(), whitespaceRow._charOffsets.data(), std::min(whitespaceRow._charOffsets.size(), _charOffsets.size()) * 2); + assert(!_valid); + FAIL_FAST_IF(whitespaceRow.size() != size()); + // The usage of _charsBuffer instead of _chars is quite intentional, + // as it allows _reset() to first call Initialize() and then Discard(). + memcpy(_charsBuffer, whitespaceRow._charsBuffer, _columnCount * 2); + memcpy(_charOffsets.data(), whitespaceRow._charOffsets.data(), _charOffsets.size() * 2); + _valid = true; } -void ROW::_init(const TextAttribute& attr) noexcept +// This is somewhat of a weapon of last resort to get the ROW back into a state that doesn't break +// callers too much. It discards the row (= releases the memory) but still ensures that it at least +// contains proper whitespace text to avoid any invalid text from being visible or returned anywhere. +void ROW::_reset() noexcept { - _charsHeap.reset(); - _chars = { _charsBuffer, _columnCount }; - // Constructing and then moving objects into place isn't free. - // Modifying the existing object is _much_ faster. - *_attr.runs().unsafe_shrink_to_size(1) = til::rle_pair{ attr, _columnCount }; - _lineRendition = LineRendition::SingleWidth; - _wrapForced = false; - _doubleBytePadded = false; - _valid = true; + static constexpr TextAttribute emptyAttributes{}; + // By first calling Initialize() and then Discard() we end up in a state + // where _valid is false, but the contents are still sort of valid. + Initialize(); + Discard(emptyAttributes); } void ROW::TransferAttributes(const til::small_rle& attr, til::CoordType newWidth) @@ -374,7 +386,7 @@ catch (...) // Due to this function writing _charOffsets first, then calling _resizeChars (which may throw) and only then finally // filling in _chars, we might end up in a situation were _charOffsets contains offsets outside of the _chars array. // --> Restore this row to a known "okay"-state. - Reset(TextAttribute{}); + _reset(); throw; } @@ -425,7 +437,7 @@ try } catch (...) { - Reset(TextAttribute{}); + _reset(); throw; } @@ -555,7 +567,7 @@ try } catch (...) { - Reset(TextAttribute{}); + _reset(); throw; } diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index 7baa8bd0ba60..817f4d63058c 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -77,9 +77,9 @@ class ROW final LineRendition GetLineRendition() const noexcept; bool IsValid() const noexcept; - void Discard() noexcept; - void Reset(const TextAttribute& attr) noexcept; - void Reset(const ROW& whitespaceRow, const TextAttribute& attr) noexcept; + void Discard(const TextAttribute& attr) noexcept; + void Initialize() noexcept; + void Initialize(const ROW& whitespaceRow) noexcept; void TransferAttributes(const til::small_rle& attr, til::CoordType newWidth); til::CoordType NavigateToPrevious(til::CoordType column) const noexcept; @@ -182,7 +182,7 @@ class ROW final uint16_t _uncheckedCharOffset(size_t col) const noexcept; bool _uncheckedIsTrailer(size_t col) const noexcept; - void _init(const TextAttribute& attr) noexcept; + void _reset() noexcept; void _resizeChars(uint16_t colEndDirty, uint16_t chBegDirty, size_t chEndDirty, uint16_t chEndDirtyOld); // These fields are a bit "wasteful", but it makes all this a bit more robust against diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 0620987c94e8..374d0615c07a 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -77,11 +77,16 @@ til::CoordType TextBuffer::TotalRowCount() const noexcept // Return Value: // - const reference to the requested row. Asserts if out of bounds. const ROW& TextBuffer::GetRowByOffset(const til::CoordType index) const noexcept +{ + const auto& row = _getRowByOffset(index); + return row.IsValid() ? row : _whitespaceRow; +} + +const ROW& TextBuffer::_getRowByOffset(const til::CoordType index) const noexcept { // Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows. const auto offsetIndex = gsl::narrow_cast(_firstRow + index) % _storage.size(); - const auto& row = til::at(_storage, offsetIndex); - return row.IsValid() ? row : _whitespaceRow; + return til::at(_storage, offsetIndex); } // Routine Description: @@ -91,20 +96,28 @@ const ROW& TextBuffer::GetRowByOffset(const til::CoordType index) const noexcept // - Number of rows down from the first row of the buffer. // Return Value: // - reference to the requested row. Asserts if out of bounds. -ROW& TextBuffer::GetRowByOffset(const til::CoordType index) noexcept +ROW& TextBuffer::GetMutableRowByOffset(const til::CoordType index) noexcept { - // Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows. - const auto offsetIndex = gsl::narrow_cast(_firstRow + index) % _storage.size(); - auto& row = til::at(_storage, offsetIndex); - + auto& row = _getRowByOffset(index); if (!row.IsValid()) { - row.Reset(_whitespaceRow, _currentAttributes); + row.Initialize(_whitespaceRow); } - return row; } +ROW& TextBuffer::_getRowByOffset(const til::CoordType index) noexcept +{ + // Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows. + const auto offsetIndex = gsl::narrow_cast(_firstRow + index) % _storage.size(); + return til::at(_storage, offsetIndex); +} + +void TextBuffer::DiscardRowByOffset(const til::CoordType index, const TextAttribute& attr) noexcept +{ + _getRowByOffset(index).Discard(attr); +} + // Routine Description: // - Retrieves read-only text iterator at the given buffer location // Arguments: @@ -196,7 +209,7 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut { // To figure out if the sequence is valid, we have to look at the character that comes before the current one const auto coordPrevPosition = _GetPreviousFromCursor(); - auto& prevRow = GetRowByOffset(coordPrevPosition.y); + auto& prevRow = GetMutableRowByOffset(coordPrevPosition.y); DbcsAttribute prevDbcsAttr = DbcsAttribute::Single; try { @@ -292,7 +305,7 @@ bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute if (cursorPosition.x == lineWidth - 1) { // set that we're wrapping for double byte reasons - auto& row = GetRowByOffset(cursorPosition.y); + auto& row = GetMutableRowByOffset(cursorPosition.y); row.SetDoubleBytePadded(true); // then move the cursor forward and onto the next row @@ -315,7 +328,7 @@ void TextBuffer::ConsumeGrapheme(std::wstring_view& chars) noexcept // The return value indicates to the caller whether the cursor should be moved to the next line. void TextBuffer::WriteLine(til::CoordType row, bool wrapAtEOL, const TextAttribute& attributes, RowWriteState& state) { - auto& r = GetRowByOffset(row); + auto& r = GetMutableRowByOffset(row); r.ReplaceText(state); r.ReplaceAttributes(state.columnBegin, state.columnEnd, attributes); @@ -401,7 +414,7 @@ OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt, } // Get the row and write the cells - auto& row = GetRowByOffset(target.y); + auto& row = GetMutableRowByOffset(target.y); const auto newIt = row.WriteCells(givenIt, target.x, wrap, limitRight); // Take the cell distance written and notify that it needs to be repainted. @@ -435,7 +448,7 @@ bool TextBuffer::InsertCharacter(const std::wstring_view chars, const auto iCol = GetCursor().GetPosition().x; // column logical and array positions are equal. // Get the row associated with the given logical position - auto& Row = GetRowByOffset(iRow); + auto& Row = GetMutableRowByOffset(iRow); // Store character and double byte data try @@ -509,7 +522,7 @@ void TextBuffer::_AdjustWrapOnCurrentRow(const bool fSet) noexcept const auto uiCurrentRowOffset = GetCursor().GetPosition().y; // Set the wrap status as appropriate - GetRowByOffset(uiCurrentRowOffset).SetWrapForced(fSet); + GetMutableRowByOffset(uiCurrentRowOffset).SetWrapForced(fSet); } //Routine Description: @@ -600,7 +613,7 @@ bool TextBuffer::IncrementCircularBuffer(const bool inVtMode) // the current background color, but with no meta attributes set. fillAttributes.SetStandardErase(); } - GetRowByOffset(0).Discard(); + DiscardRowByOffset(0, fillAttributes); { // Now proceed to increment. // Incrementing it will cause the next line down to become the new "top" of the window (the new "0" in logical coordinates) @@ -737,7 +750,7 @@ TextBuffer::VirtualAllocation TextBuffer::_allocateBuffer(til::size sz, const Te data += rowStride; } - whitespaceRow.Reset(attributes); + whitespaceRow.Initialize(); return { std::move(buffer), allocSize }; } @@ -872,7 +885,7 @@ void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition) { const auto cursorPosition = GetCursor().GetPosition(); const auto rowIndex = cursorPosition.y; - auto& row = GetRowByOffset(rowIndex); + auto& row = GetMutableRowByOffset(rowIndex); if (row.GetLineRendition() != lineRendition) { row.SetLineRendition(lineRendition); @@ -899,7 +912,7 @@ void TextBuffer::ResetLineRenditionRange(const til::CoordType startRow, const ti { for (auto row = startRow; row < endRow; row++) { - GetRowByOffset(row).SetLineRendition(LineRendition::SingleWidth); + GetMutableRowByOffset(row).SetLineRendition(LineRendition::SingleWidth); } } @@ -945,13 +958,15 @@ til::point TextBuffer::BufferToScreenPosition(const til::point position) const n // and the default current color attributes void TextBuffer::Reset() { + VirtualAlloc(_buffer.ptr.get(), _buffer.size, MEM_RESET, PAGE_READWRITE); + + _whitespaceRow.Discard(_currentAttributes); + _whitespaceRow.Initialize(); + for (auto& row : _storage) { - row.Discard(); + row.Discard(_currentAttributes); } - - VirtualAlloc(_buffer.ptr.get(), _buffer.size, MEM_RESET, PAGE_READWRITE); - _whitespaceRow.Reset(_currentAttributes); } // Routine Description: @@ -1088,17 +1103,6 @@ void TextBuffer::TriggerNewTextNotification(const std::wstring_view newText) } } -// Routine Description: -// - Retrieves the first row from the underlying buffer. -// Arguments: -// - -// Return Value: -// - reference to the first row. -ROW& TextBuffer::_GetFirstRow() noexcept -{ - return GetRowByOffset(0); -} - // Method Description: // - get delimiter class for buffer cell position // - used for double click selection and uia word navigation @@ -2371,7 +2375,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, const auto newBufferPos = newCursor.GetPosition(); if (newBufferPos.x == 0) { - auto& newRow = newBuffer.GetRowByOffset(newBufferPos.y); + auto& newRow = newBuffer.GetMutableRowByOffset(newBufferPos.y); newRow.SetLineRendition(row.GetLineRendition()); } @@ -2449,7 +2453,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, // 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.GetRowByOffset(newRowY); + 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 @@ -2574,7 +2578,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, // 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.GetRowByOffset(newRowY); + auto& newRow = newBuffer.GetMutableRowByOffset(newRowY); const auto newWidth = newBuffer.GetLineWidth(newRowY); newRow.TransferAttributes(row.Attributes(), newWidth); diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 3ef22d4edf4b..2d417ac70b8d 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -78,8 +78,9 @@ class TextBuffer final void CopyProperties(const TextBuffer& OtherBuffer) noexcept; // row manipulation - const ROW& GetRowByOffset(const til::CoordType index) const noexcept; - ROW& GetRowByOffset(const til::CoordType index) noexcept; + const ROW& GetRowByOffset(til::CoordType index) const noexcept; + ROW& GetMutableRowByOffset(til::CoordType index) noexcept; + void DiscardRowByOffset(til::CoordType index, const TextAttribute& attr) noexcept; TextBufferCellIterator GetCellDataAt(const til::point at) const; TextBufferCellIterator GetCellLineDataAt(const til::point at) const; @@ -227,6 +228,8 @@ class TextBuffer final static VirtualAllocation _allocateBuffer(til::size sz, const TextAttribute& attributes, ROW& whitespaceRow, std::vector& rows); + const ROW& _getRowByOffset(til::CoordType index) const noexcept; + ROW& _getRowByOffset(til::CoordType index) noexcept; void _UpdateSize(); void _SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept; til::point _GetPreviousFromCursor() const noexcept; @@ -235,7 +238,6 @@ class TextBuffer final // Assist with maintaining proper buffer state for Double Byte character sequences bool _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute); bool _AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute); - ROW& _GetFirstRow() noexcept; void _ExpandTextRow(til::inclusive_rect& selectionRow) const; DelimiterClass _GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const noexcept; til::point _GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const noexcept; diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index d6f1e87e80e3..af2af197d37f 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -475,7 +475,7 @@ using Microsoft::Console::VirtualTerminal::StateMachine; // since you just backspaced yourself back up into the previous row, unset the wrap // flag on the prev row if it was set - textBuffer.GetRowByOffset(CursorPosition.y).SetWrapForced(false); + textBuffer.GetMutableRowByOffset(CursorPosition.y).SetWrapForced(false); } } else if (IS_CONTROL_CHAR(LastChar)) @@ -546,7 +546,7 @@ using Microsoft::Console::VirtualTerminal::StateMachine; // since you just backspaced yourself back up into the previous row, unset the wrap flag // on the prev row if it was set - textBuffer.GetRowByOffset(CursorPosition.y).SetWrapForced(false); + textBuffer.GetMutableRowByOffset(CursorPosition.y).SetWrapForced(false); Status = AdjustCursorPosition(screenInfo, CursorPosition, dwFlags & WC_KEEP_CURSOR_VISIBLE, psScrollY); } @@ -582,7 +582,7 @@ using Microsoft::Console::VirtualTerminal::StateMachine; CursorPosition.y = cursor.GetPosition().y + 1; // since you just tabbed yourself past the end of the row, set the wrap - textBuffer.GetRowByOffset(cursor.GetPosition().y).SetWrapForced(true); + textBuffer.GetMutableRowByOffset(cursor.GetPosition().y).SetWrapForced(true); } else { @@ -629,7 +629,7 @@ using Microsoft::Console::VirtualTerminal::StateMachine; { // since we explicitly just moved down a row, clear the wrap status on the row we just came from - textBuffer.GetRowByOffset(cursor.GetPosition().y).SetWrapForced(false); + textBuffer.GetMutableRowByOffset(cursor.GetPosition().y).SetWrapForced(false); } Status = AdjustCursorPosition(screenInfo, CursorPosition, (dwFlags & WC_KEEP_CURSOR_VISIBLE) != 0, psScrollY); @@ -644,7 +644,7 @@ using Microsoft::Console::VirtualTerminal::StateMachine; fWrapAtEOL) { const auto TargetPoint = cursor.GetPosition(); - auto& Row = textBuffer.GetRowByOffset(TargetPoint.y); + auto& Row = textBuffer.GetMutableRowByOffset(TargetPoint.y); try { diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index ea44bd400db1..add3d070e3af 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -877,7 +877,7 @@ void AdaptDispatch::_SelectiveEraseRect(TextBuffer& textBuffer, const til::rect& { for (auto row = eraseRect.top; row < eraseRect.bottom; row++) { - auto& rowBuffer = textBuffer.GetRowByOffset(row); + auto& rowBuffer = textBuffer.GetMutableRowByOffset(row); for (auto col = eraseRect.left; col < eraseRect.right; col++) { // Only unprotected cells are affected. @@ -979,7 +979,7 @@ void AdaptDispatch::_ChangeRectAttributes(TextBuffer& textBuffer, const til::rec { for (auto row = changeRect.top; row < changeRect.bottom; row++) { - auto& rowBuffer = textBuffer.GetRowByOffset(row); + auto& rowBuffer = textBuffer.GetMutableRowByOffset(row); for (auto col = changeRect.left; col < changeRect.right; col++) { auto attr = rowBuffer.GetAttrByColumn(col); @@ -2389,7 +2389,7 @@ void AdaptDispatch::_DoLineFeed(TextBuffer& textBuffer, const bool withReturn, c // If the line was forced to wrap, set the wrap status. // When explicitly moving down a row, clear the wrap status. - textBuffer.GetRowByOffset(currentPosition.y).SetWrapForced(wrapForced); + textBuffer.GetMutableRowByOffset(currentPosition.y).SetWrapForced(wrapForced); // If a carriage return was requested, we move to the leftmost column or // the left margin, depending on whether we started within the margins. @@ -2436,7 +2436,7 @@ void AdaptDispatch::_DoLineFeed(TextBuffer& textBuffer, const bool withReturn, c { auto eraseAttributes = textBuffer.GetCurrentAttributes(); eraseAttributes.SetStandardErase(); - textBuffer.GetRowByOffset(newPosition.y).Discard(); + textBuffer.DiscardRowByOffset(newPosition.y, eraseAttributes); } } else