diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 50b4d3c347df..793c49f07ba4 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -82,10 +82,6 @@ ROW::ROW(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, c _attr{ rowWidth, fillAttribute }, _columnCount{ rowWidth } { - if (_chars.data()) - { - _init(); - } } void ROW::SetWrapForced(const bool wrap) noexcept @@ -118,13 +114,12 @@ LineRendition ROW::GetLineRendition() const noexcept return _lineRendition; } -// Routine Description: -// - Sets all properties of the ROW to default values -// Arguments: -// - Attr - The default attribute (color) to fill -// Return Value: -// - -void ROW::Reset(const TextAttribute& attr) +bool ROW::IsInitialized() const noexcept +{ + return _initialized; +} + +void ROW::Reset(const TextAttribute& attr) noexcept { _charsHeap.reset(); _chars = { _charsBuffer, _columnCount }; @@ -134,13 +129,37 @@ void ROW::Reset(const TextAttribute& attr) _lineRendition = LineRendition::SingleWidth; _wrapForced = false; _doubleBytePadded = false; - _init(); + _initialized = false; } -void ROW::_init() noexcept +void ROW::Initialize() noexcept { - std::fill_n(_chars.begin(), _columnCount, UNICODE_SPACE); + // The usage of _charsBuffer instead of _chars is quite intentional, as it + // allows _safeReset() to first call Initialize() and then Discard(), ending up + // with a ROW that is both not initialized and also consists only of whitespace. + std::fill_n(_charsBuffer, _columnCount, UNICODE_SPACE); std::iota(_charOffsets.begin(), _charOffsets.end(), uint16_t{ 0 }); + _initialized = true; +} + +void ROW::Initialize(const ROW& whitespaceRow) noexcept +{ + assert(!_initialized); + FAIL_FAST_IF(_columnCount != whitespaceRow._columnCount); + memcpy(_charsBuffer, whitespaceRow._charsBuffer, CalculateBufferStride(_columnCount)); + _initialized = true; +} + +// 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::_safeReset() noexcept +{ + static constexpr TextAttribute emptyAttributes{}; + // By first calling Initialize() and then Discard() we end up in a state + // where _initialized is false, but the contents are still sort of valid. + Initialize(); + Reset(emptyAttributes); } void ROW::TransferAttributes(const til::small_rle& attr, til::CoordType newWidth) @@ -365,7 +384,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{}); + _safeReset(); throw; } @@ -416,7 +435,7 @@ try } catch (...) { - Reset(TextAttribute{}); + _safeReset(); throw; } @@ -546,7 +565,7 @@ try } catch (...) { - Reset(TextAttribute{}); + _safeReset(); throw; } diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index d2afd5bf8b53..cd959961139a 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -60,6 +60,27 @@ struct RowWriteState class ROW final { public: + // The implicit agreement between ROW and TextBuffer is that each ROW gets a + // `columns` sized array of `wchar_t` as its `charsBuffer` argument and a + // `columns + 1` sized array of `uint16_t` as its `charOffsetsBuffer` argument. + // The former is used as a scratch buffer fitting any BMP codepoints into the + // ROW without the need for extra heap allocations and the latter stores the + // column-to-start-of-chararacter association. It has 1 more entry than needed + // because that one serves as the past-the-end _chars pointer. The expectation + // is that these two arrays are given to ROW laid out in memory back-to-back. + // + // This method exists to make this agreement explicit and serve as a reminder. + static constexpr size_t CalculateBufferStride(size_t columns) noexcept + { + return columns * 4 + 2; + } + + // This function returns the size of the first part (the `columns` sized `wchar_t` array). + static constexpr size_t CalculateCharsBufferSize(size_t columns) noexcept + { + return columns * 2; + } + ROW() = default; ROW(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute); @@ -76,7 +97,10 @@ class ROW final void SetLineRendition(const LineRendition lineRendition) noexcept; LineRendition GetLineRendition() const noexcept; - void Reset(const TextAttribute& attr); + bool IsInitialized() const noexcept; + void Reset(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; @@ -179,7 +203,7 @@ class ROW final uint16_t _uncheckedCharOffset(size_t col) const noexcept; bool _uncheckedIsTrailer(size_t col) const noexcept; - void _init() noexcept; + void _safeReset() 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 @@ -233,6 +257,7 @@ class ROW final bool _wrapForced = false; // Occurs when the user runs out of text to support a double byte character and we're forced to the next line bool _doubleBytePadded = false; + bool _initialized = false; }; #ifdef UNIT_TESTING diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 4eb6d5e0689a..fee78e6615f0 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -42,7 +42,7 @@ TextBuffer::TextBuffer(til::size screenBufferSize, // Guard against resizing the text buffer to 0 columns/rows, which would break being able to insert text. screenBufferSize.width = std::max(screenBufferSize.width, 1); screenBufferSize.height = std::max(screenBufferSize.height, 1); - _charBuffer = _allocateBuffer(screenBufferSize, _currentAttributes, _storage); + _buffer = _allocateBuffer(screenBufferSize, _currentAttributes, _whitespaceRow, _storage); _UpdateSize(); } @@ -69,6 +69,20 @@ til::CoordType TextBuffer::TotalRowCount() const noexcept return gsl::narrow_cast(_storage.size()); } +const ROW& TextBuffer::_getRowByOffsetImpl(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(); + return til::at(_storage, offsetIndex); +} + +ROW& TextBuffer::_getRowByOffsetImpl(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); +} + // Routine Description: // - Retrieves a row from the buffer by its offset from the first row of the text buffer (what corresponds to // the top row of the screen buffer) @@ -78,9 +92,8 @@ til::CoordType TextBuffer::TotalRowCount() const noexcept // - const reference to the requested row. Asserts if out of bounds. 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(); - return til::at(_storage, offsetIndex); + const auto& row = _getRowByOffsetImpl(index); + return row.IsInitialized() ? row : _whitespaceRow; } // Routine Description: @@ -90,11 +103,19 @@ 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(); - return til::at(_storage, offsetIndex); + auto& row = _getRowByOffsetImpl(index); + if (!row.IsInitialized()) + { + row.Initialize(_whitespaceRow); + } + return row; +} + +void TextBuffer::ResetRowByOffset(const til::CoordType index, const TextAttribute& attr) noexcept +{ + _getRowByOffsetImpl(index).Reset(attr); } // Routine Description: @@ -188,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 { @@ -284,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 @@ -307,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); @@ -393,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. @@ -427,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 @@ -501,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: @@ -592,7 +613,7 @@ bool TextBuffer::IncrementCircularBuffer(const bool inVtMode) // the current background color, but with no meta attributes set. fillAttributes.SetStandardErase(); } - GetRowByOffset(0).Reset(fillAttributes); + ResetRowByOffset(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) @@ -693,35 +714,42 @@ const Viewport TextBuffer::GetSize() const noexcept return _size; } -wil::unique_virtualalloc_ptr TextBuffer::_allocateBuffer(til::size sz, const TextAttribute& attributes, std::vector& rows) +TextBuffer::VirtualAllocation TextBuffer::_allocateBuffer(til::size sz, const TextAttribute& attributes, ROW& whitespaceRow, std::vector& rows) { const auto w = gsl::narrow(sz.width); const auto h = gsl::narrow(sz.height); - const auto charsBytes = w * sizeof(wchar_t); - // The ROW::_indices array stores 1 more item than the buffer is wide. - // That extra column stores the past-the-end _chars pointer. - const auto indicesBytes = w * sizeof(uint16_t) + sizeof(uint16_t); - const auto rowStride = charsBytes + indicesBytes; // 65535*65535 cells would result in a charsAreaSize of 8GiB. // --> Use uint64_t so that we can safely do our calculations even on x86. - const auto allocSize = gsl::narrow(::base::strict_cast(rowStride) * ::base::strict_cast(h)); + const auto charsBytes = ROW::CalculateCharsBufferSize(w); + const auto rowStride = ROW::CalculateBufferStride(w); + const auto rowCount = ::base::strict_cast(h) + 1; + const auto allocSize = gsl::narrow(rowCount * rowStride); auto buffer = wil::unique_virtualalloc_ptr{ static_cast(VirtualAlloc(nullptr, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) }; THROW_IF_NULL_ALLOC(buffer); - auto data = std::span{ buffer.get(), allocSize }.begin(); + const auto data = std::span{ buffer.get(), allocSize }; + auto it = data.begin(); + const auto end = data.end(); + + { + const auto chars = til::bit_cast(&*it); + const auto indices = til::bit_cast(&*(it + charsBytes)); + whitespaceRow = { chars, indices, w, attributes }; + it += rowStride; + } - rows.resize(h); - for (auto& row : rows) + while (it < end) { - const auto chars = til::bit_cast(&*data); - const auto indices = til::bit_cast(&*(data + charsBytes)); - row = { chars, indices, w, attributes }; - data += rowStride; + const auto chars = til::bit_cast(&*it); + const auto indices = til::bit_cast(&*(it + charsBytes)); + rows.emplace_back(chars, indices, w, attributes); + it += rowStride; } - return buffer; + whitespaceRow.Initialize(); + return { std::move(buffer), allocSize }; } void TextBuffer::_UpdateSize() @@ -855,7 +883,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); @@ -882,7 +910,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); } } @@ -928,11 +956,14 @@ til::point TextBuffer::BufferToScreenPosition(const til::point position) const n // and the default current color attributes void TextBuffer::Reset() { - const auto attr = GetCurrentAttributes(); + VirtualAlloc(_buffer.ptr.get(), _buffer.size, MEM_RESET, PAGE_READWRITE); + + _whitespaceRow.Reset(_currentAttributes); + _whitespaceRow.Initialize(); for (auto& row : _storage) { - row.Reset(attr); + row.Reset(_currentAttributes); } } @@ -957,8 +988,9 @@ void TextBuffer::Reset() } const auto TopRowIndex = gsl::narrow_cast(_firstRow + TopRow) % _storage.size(); + ROW whitespaceRow; std::vector newStorage; - auto newBuffer = _allocateBuffer(newSize, _currentAttributes, newStorage); + auto newBuffer = _allocateBuffer(newSize, _currentAttributes, whitespaceRow, newStorage); // This basically imitates a std::rotate_copy(first, mid, last), but uses ROW::CopyRangeFrom() to do the copying. { @@ -994,7 +1026,8 @@ void TextBuffer::Reset() } } - _charBuffer = std::move(newBuffer); + _buffer = std::move(newBuffer); + _whitespaceRow = std::move(whitespaceRow); _storage = std::move(newStorage); _SetFirstRowIndex(0); @@ -1068,17 +1101,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 @@ -2351,7 +2373,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()); } @@ -2429,7 +2451,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 @@ -2554,7 +2576,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 0826ec8d7e91..e2dbf4500193 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 ResetRowByOffset(til::CoordType index, const TextAttribute& attr) noexcept; TextBufferCellIterator GetCellDataAt(const til::point at) const; TextBufferCellIterator GetCellLineDataAt(const til::point at) const; @@ -219,8 +220,16 @@ class TextBuffer final interval_tree::IntervalTree GetPatterns(const til::CoordType firstRow, const til::CoordType lastRow) const; private: - static wil::unique_virtualalloc_ptr _allocateBuffer(til::size sz, const TextAttribute& attributes, std::vector& rows); + struct VirtualAllocation + { + wil::unique_virtualalloc_ptr ptr; + size_t size = 0; + }; + + static VirtualAllocation _allocateBuffer(til::size sz, const TextAttribute& attributes, ROW& whitespaceRow, std::vector& rows); + const ROW& _getRowByOffsetImpl(til::CoordType index) const noexcept; + ROW& _getRowByOffsetImpl(til::CoordType index) noexcept; void _UpdateSize(); void _SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept; til::point _GetPreviousFromCursor() const noexcept; @@ -229,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; @@ -249,7 +257,8 @@ class TextBuffer final std::unordered_map _idsAndPatterns; size_t _currentPatternId = 0; - wil::unique_virtualalloc_ptr _charBuffer; + VirtualAllocation _buffer; + ROW _whitespaceRow; std::vector _storage; TextAttribute _currentAttributes; til::CoordType _firstRow = 0; // indexes top row (not necessarily 0) 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 d17d3b740a86..3bc73c0232dd 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).Reset(eraseAttributes); + textBuffer.ResetRowByOffset(newPosition.y, eraseAttributes); } } else