From c183d126491572f0bae1d8520582ccba44e054ea Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 14 Jun 2023 21:34:42 +0200 Subject: [PATCH] Move AdaptDispatch::_FillRect into TextBuffer (#15541) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit makes 2 changes: * Expose dirty-range information from `ROW::CopyTextFrom` This will allow us to call `TriggerRedraw`, which is an aspect I haven't previously considered as something this API needs. * Add a `FillRect` API to `TextBuffer` and refactor `AdeptDispatch` to use that API. Even if we determine that the new text APIs are unfit (for instance too difficult to use), this will make it simpler to write efficient implementations right inside `TextBuffer`. Since the new `FillRect` API lacks bounds checks the way `WriteLine` has them, it breaks `AdaptDispatch::_EraseAll` which failed to adjust the bottom parameter after scrolling the contents. This would result in more rows being erased than intended. ## Validation Steps Performed * `chcp 65001` * Launch `pwsh` * ``"`e[29483`$x"`` fills the viewport with cats ✅ * `ResizeTraditional` still doesn't work any worse than it used to ✅ --- src/buffer/out/Row.cpp | 57 +++++++++++---------- src/buffer/out/Row.hpp | 29 ++++++++++- src/buffer/out/textBuffer.cpp | 71 ++++++++++++++++++++++++-- src/buffer/out/textBuffer.hpp | 2 + src/host/outputStream.cpp | 2 +- src/terminal/adapter/adaptDispatch.cpp | 60 +++++++++------------- src/terminal/adapter/adaptDispatch.hpp | 2 +- 7 files changed, 152 insertions(+), 71 deletions(-) diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 737c59d4107..f96a72aca78 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -146,8 +146,8 @@ void ROW::TransferAttributes(const til::small_rle& a void ROW::CopyFrom(const ROW& source) { - til::CoordType begin = 0; - CopyTextFrom(0, til::CoordTypeMax, source, begin, til::CoordTypeMax); + RowCopyTextFromState state{ .source = source }; + CopyTextFrom(state); TransferAttributes(source.Attributes(), _columnCount); _lineRendition = source._lineRendition; _wrapForced = source._wrapForced; @@ -451,46 +451,51 @@ catch (...) charsConsumed = ch - chBeg; } -til::CoordType ROW::CopyTextFrom(til::CoordType columnBegin, til::CoordType columnLimit, const ROW& other, til::CoordType& otherBegin, til::CoordType otherLimit) +void ROW::CopyTextFrom(RowCopyTextFromState& state) try { - const auto otherColBeg = other._clampedColumnInclusive(otherBegin); - const auto otherColLimit = other._clampedColumnInclusive(otherLimit); - std::span charOffsets; + auto& source = state.source; + const auto sourceColBeg = source._clampedColumnInclusive(state.sourceColumnBegin); + const auto sourceColLimit = source._clampedColumnInclusive(state.sourceColumnLimit); + std::span charOffsets; std::wstring_view chars; - if (otherColBeg < otherColLimit) + if (sourceColBeg < sourceColLimit) { - charOffsets = other._charOffsets.subspan(otherColBeg, static_cast(otherColLimit) - otherColBeg + 1); + charOffsets = source._charOffsets.subspan(sourceColBeg, static_cast(sourceColLimit) - sourceColBeg + 1); const auto charsOffset = charOffsets.front() & 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 = { other._chars.data() + charsOffset, other._chars.size() - charsOffset }; + chars = { source._chars.data() + charsOffset, source._chars.size() - charsOffset }; } - WriteHelper h{ *this, columnBegin, columnLimit, chars }; - // If we were to copy text from ourselves, we'd overwrite - // our _charOffsets and break Finish() which reads from it. - if (!h.IsValid() || this == &other) - { - assert(false); // You probably shouldn't call this function in the first place. - return h.colBeg; - } - // Any valid charOffsets array is at least 2 elements long (the 1st element is the start offset and the 2nd - // element is the length of the first glyph) and begins/ends with a non-trailer offset. We don't really - // need to test for the end offset, since `WriteHelper::WriteWithOffsets` already takes care of that. - if (charOffsets.size() < 2 || WI_IsFlagSet(charOffsets.front(), CharOffsetsTrailer)) + WriteHelper h{ *this, state.columnBegin, state.columnLimit, chars }; + + if (!h.IsValid() || + // If we were to copy text from ourselves, we'd overwrite + // our _charOffsets and break Finish() which reads from it. + this == &state.source || + // Any valid charOffsets array is at least 2 elements long (the 1st element is the start offset and the 2nd + // element is the length of the first glyph) and begins/ends with a non-trailer offset. We don't really + // need to test for the end offset, since `WriteHelper::WriteWithOffsets` already takes care of that. + charOffsets.size() < 2 || WI_IsFlagSet(charOffsets.front(), CharOffsetsTrailer)) { - assert(false); - otherBegin = other.size(); - return h.colBeg; + state.columnEnd = h.colBeg; + state.columnBeginDirty = h.colBeg; + state.columnEndDirty = h.colBeg; + state.sourceColumnEnd = source._columnCount; + return; } + h.CopyTextFrom(charOffsets); h.Finish(); - otherBegin += h.colEnd - h.colBeg; - return h.colEndDirty; + // state.columnEnd is computed identical to ROW::ReplaceText. Check it out for more information. + state.columnEnd = h.charsConsumed == chars.size() ? h.colEnd : h.colLimit; + state.columnBeginDirty = h.colBegDirty; + state.columnEndDirty = h.colEndDirty; + state.sourceColumnEnd = sourceColBeg + h.colEnd - h.colBeg; } catch (...) { diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index b677f565d36..d3be9dbaf22 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -26,6 +26,7 @@ Revision History: #include "OutputCell.hpp" #include "OutputCellIterator.hpp" +class ROW; class TextBuffer; enum class DelimiterClass @@ -43,7 +44,7 @@ struct RowWriteState // The column at which to start writing. til::CoordType columnBegin = 0; // IN // The first column which should not be written to anymore. - til::CoordType columnLimit = 0; // IN + til::CoordType columnLimit = til::CoordTypeMax; // IN // The column 1 past the last glyph that was successfully written into the row. If you need to call // ReplaceAttributes() to colorize the written range, etc., this is the columnEnd parameter you want. @@ -57,6 +58,30 @@ struct RowWriteState til::CoordType columnEndDirty = 0; // OUT }; +struct RowCopyTextFromState +{ + // The row to copy text from. + const ROW& source; // IN + // The column at which to start writing. + til::CoordType columnBegin = 0; // IN + // The first column which should not be written to anymore. + til::CoordType columnLimit = til::CoordTypeMax; // IN + // The column at which to start reading from source. + til::CoordType sourceColumnBegin = 0; // IN + // The first column which should not be read from anymore. + til::CoordType sourceColumnLimit = til::CoordTypeMax; // IN + + til::CoordType columnEnd = 0; // OUT + // The first column that got modified by this write operation. In case that the first glyph we write overwrites + // the trailing half of a wide glyph, leadingSpaces will be 1 and this value will be 1 less than colBeg. + til::CoordType columnBeginDirty = 0; // OUT + // This is 1 past the last column that was modified and will be 1 past columnEnd if we overwrote + // the leading half of a wide glyph and had to fill the trailing half with whitespace. + til::CoordType columnEndDirty = 0; // OUT + // This is 1 past the last column that was read from. + til::CoordType sourceColumnEnd = 0; // OUT +}; + class ROW final { public: @@ -109,7 +134,7 @@ class ROW final void ReplaceAttributes(til::CoordType beginIndex, til::CoordType endIndex, const TextAttribute& newAttr); void ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars); void ReplaceText(RowWriteState& state); - til::CoordType CopyTextFrom(til::CoordType columnBegin, til::CoordType columnLimit, const ROW& other, til::CoordType& otherBegin, til::CoordType otherLimit); + void CopyTextFrom(RowCopyTextFromState& state); til::small_rle& Attributes() noexcept; const til::small_rle& Attributes() const noexcept; diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 164c10a1376..d4d62facb8c 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -196,7 +196,15 @@ ROW& TextBuffer::GetRowByOffset(const til::CoordType index) // Returns a row filled with whitespace and the current attributes, for you to freely use. ROW& TextBuffer::GetScratchpadRow() { - return _getRowByOffsetDirect(0); + return GetScratchpadRow(_currentAttributes); +} + +// Returns a row filled with whitespace and the given attributes, for you to freely use. +ROW& TextBuffer::GetScratchpadRow(const TextAttribute& attributes) +{ + auto& r = _getRowByOffsetDirect(0); + r.Reset(attributes); + return r; } #pragma warning(pop) @@ -429,10 +437,8 @@ void TextBuffer::ConsumeGrapheme(std::wstring_view& chars) noexcept chars = til::utf16_pop(chars); } -// This function is intended for writing regular "lines" of text and only the `state.text` and`state.columnBegin` -// fields are being used, whereas `state.columnLimit` is automatically overwritten by the line width of the given row. -// This allows this function to automatically set the wrap-forced field of the row, which is also the return value. -// The return value indicates to the caller whether the cursor should be moved to the next line. +// This function is intended for writing regular "lines" of text as it'll set the wrap flag on the given row. +// You can continue calling the function on the same row as long as state.columnEnd < state.columnLimit. void TextBuffer::WriteLine(til::CoordType row, bool wrapAtEOL, const TextAttribute& attributes, RowWriteState& state) { auto& r = GetRowByOffset(row); @@ -448,6 +454,61 @@ void TextBuffer::WriteLine(til::CoordType row, bool wrapAtEOL, const TextAttribu TriggerRedraw(Viewport::FromExclusive({ state.columnBeginDirty, row, state.columnEndDirty, row + 1 })); } +// Fills an area of the buffer with a given fill character(s) and attributes. +void TextBuffer::FillRect(const til::rect& rect, const std::wstring_view& fill, const TextAttribute& attributes) +{ + if (!rect || fill.empty()) + { + return; + } + + auto& scratchpad = GetScratchpadRow(attributes); + + // The scratchpad row gets reset to whitespace by default, so there's no need to + // initialize it again. Filling with whitespace is the most common operation by far. + if (fill != L" ") + { + RowWriteState state{ + .columnLimit = rect.right, + .columnEnd = rect.left, + }; + + // Fill the scratchpad row with consecutive copies of "fill" up to the amount we need. + // + // We don't just create a single string with N copies of "fill" and write that at once, + // because that might join neighboring combining marks unintentionally. + // + // Building the buffer this way is very wasteful and slow, but it's still 3x + // faster than what we had before and no one complained about that either. + // It's seldom used code and probably not worth optimizing for. + while (state.columnEnd < rect.right) + { + state.columnBegin = state.columnEnd; + state.text = fill; + scratchpad.ReplaceText(state); + } + } + + // Fill the given rows with copies of the scratchpad row. That's a little + // slower when filling just a single row, but will be much faster for >1 rows. + { + RowCopyTextFromState state{ + .source = scratchpad, + .columnBegin = rect.left, + .columnLimit = rect.right, + .sourceColumnBegin = rect.left, + }; + + for (auto y = rect.top; y < rect.bottom; ++y) + { + auto& r = GetRowByOffset(y); + r.CopyTextFrom(state); + r.ReplaceAttributes(rect.left, rect.right, attributes); + TriggerRedraw(Viewport::FromExclusive({ state.columnBeginDirty, y, state.columnEndDirty, y + 1 })); + } + } +} + // Routine Description: // - Writes cells to the output buffer. Writes at the cursor. // Arguments: diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 173f8616ffd..2843a2407e9 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -85,6 +85,7 @@ class TextBuffer final // row manipulation ROW& GetScratchpadRow(); + ROW& GetScratchpadRow(const TextAttribute& attributes); const ROW& GetRowByOffset(til::CoordType index) const; ROW& GetRowByOffset(til::CoordType index); @@ -98,6 +99,7 @@ class TextBuffer final // Text insertion functions static void ConsumeGrapheme(std::wstring_view& chars) noexcept; void WriteLine(til::CoordType row, bool wrapAtEOL, const TextAttribute& attributes, RowWriteState& state); + void FillRect(const til::rect& rect, const std::wstring_view& fill, const TextAttribute& attributes); OutputCellIterator Write(const OutputCellIterator givenIt); diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 202b0b53a3b..01145bd413a 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -406,7 +406,7 @@ bool ConhostInternalGetSet::IsVtInputEnabled() const void ConhostInternalGetSet::NotifyAccessibilityChange(const til::rect& changedRect) { auto& screenInfo = _io.GetActiveOutputBuffer(); - if (screenInfo.HasAccessibilityEventing()) + if (screenInfo.HasAccessibilityEventing() && changedRect) { screenInfo.NotifyAccessibilityEventing( changedRect.left, diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 2209050da46..180b9276ba8 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -15,6 +15,8 @@ using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Render; using namespace Microsoft::Console::VirtualTerminal; +static constexpr std::wstring_view whitespace{ L" " }; + AdaptDispatch::AdaptDispatch(ITerminalApi& api, Renderer& renderer, RenderSettings& renderSettings, TerminalInput& terminalInput) : _api{ api }, _renderer{ renderer }, @@ -634,7 +636,7 @@ void AdaptDispatch::_ScrollRectVertically(TextBuffer& textBuffer, const til::rec eraseRect.top = delta > 0 ? scrollRect.top : (scrollRect.bottom - absoluteDelta); eraseRect.bottom = eraseRect.top + absoluteDelta; const auto eraseAttributes = _GetEraseAttributes(textBuffer); - _FillRect(textBuffer, eraseRect, L' ', eraseAttributes); + _FillRect(textBuffer, eraseRect, whitespace, eraseAttributes); // Also reset the line rendition for the erased rows. textBuffer.ResetLineRenditionRange(eraseRect.top, eraseRect.bottom); @@ -682,7 +684,7 @@ void AdaptDispatch::_ScrollRectHorizontally(TextBuffer& textBuffer, const til::r eraseRect.left = delta > 0 ? scrollRect.left : (scrollRect.right - absoluteDelta); eraseRect.right = eraseRect.left + absoluteDelta; const auto eraseAttributes = _GetEraseAttributes(textBuffer); - _FillRect(textBuffer, eraseRect, L' ', eraseAttributes); + _FillRect(textBuffer, eraseRect, whitespace, eraseAttributes); } // Routine Description: @@ -746,19 +748,10 @@ bool AdaptDispatch::DeleteCharacter(const VTInt count) // - fillAttrs - Attributes to be written to the buffer. // Return Value: // - -void AdaptDispatch::_FillRect(TextBuffer& textBuffer, const til::rect& fillRect, const wchar_t fillChar, const TextAttribute fillAttrs) +void AdaptDispatch::_FillRect(TextBuffer& textBuffer, const til::rect& fillRect, const std::wstring_view& fillChar, const TextAttribute& fillAttrs) const { - if (fillRect.left < fillRect.right && fillRect.top < fillRect.bottom) - { - const auto fillWidth = gsl::narrow_cast(fillRect.right - fillRect.left); - const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillWidth }; - const auto col = fillRect.left; - for (auto row = fillRect.top; row < fillRect.bottom; row++) - { - textBuffer.WriteLine(fillData, { col, row }, false); - } - _api.NotifyAccessibilityChange(fillRect); - } + textBuffer.FillRect(fillRect, fillChar, fillAttrs); + _api.NotifyAccessibilityChange(fillRect); } // Routine Description: @@ -781,7 +774,7 @@ bool AdaptDispatch::EraseCharacters(const VTInt numChars) textBuffer.GetCursor().ResetDelayEOLWrap(); const auto eraseAttributes = _GetEraseAttributes(textBuffer); - _FillRect(textBuffer, { startCol, row, endCol, row + 1 }, L' ', eraseAttributes); + _FillRect(textBuffer, { startCol, row, endCol, row + 1 }, whitespace, eraseAttributes); return true; } @@ -836,14 +829,14 @@ bool AdaptDispatch::EraseInDisplay(const DispatchTypes::EraseType eraseType) if (eraseType == DispatchTypes::EraseType::FromBeginning) { textBuffer.ResetLineRenditionRange(viewport.top, row); - _FillRect(textBuffer, { 0, viewport.top, bufferWidth, row }, L' ', eraseAttributes); - _FillRect(textBuffer, { 0, row, col + 1, row + 1 }, L' ', eraseAttributes); + _FillRect(textBuffer, { 0, viewport.top, bufferWidth, row }, whitespace, eraseAttributes); + _FillRect(textBuffer, { 0, row, col + 1, row + 1 }, whitespace, eraseAttributes); } if (eraseType == DispatchTypes::EraseType::ToEnd) { textBuffer.ResetLineRenditionRange(col > 0 ? row + 1 : row, viewport.bottom); - _FillRect(textBuffer, { col, row, bufferWidth, row + 1 }, L' ', eraseAttributes); - _FillRect(textBuffer, { 0, row + 1, bufferWidth, viewport.bottom }, L' ', eraseAttributes); + _FillRect(textBuffer, { col, row, bufferWidth, row + 1 }, whitespace, eraseAttributes); + _FillRect(textBuffer, { 0, row + 1, bufferWidth, viewport.bottom }, whitespace, eraseAttributes); } return true; @@ -868,13 +861,13 @@ bool AdaptDispatch::EraseInLine(const DispatchTypes::EraseType eraseType) switch (eraseType) { case DispatchTypes::EraseType::FromBeginning: - _FillRect(textBuffer, { 0, row, col + 1, row + 1 }, L' ', eraseAttributes); + _FillRect(textBuffer, { 0, row, col + 1, row + 1 }, whitespace, eraseAttributes); return true; case DispatchTypes::EraseType::ToEnd: - _FillRect(textBuffer, { col, row, textBuffer.GetLineWidth(row), row + 1 }, L' ', eraseAttributes); + _FillRect(textBuffer, { col, row, textBuffer.GetLineWidth(row), row + 1 }, whitespace, eraseAttributes); return true; case DispatchTypes::EraseType::All: - _FillRect(textBuffer, { 0, row, textBuffer.GetLineWidth(row), row + 1 }, L' ', eraseAttributes); + _FillRect(textBuffer, { 0, row, textBuffer.GetLineWidth(row), row + 1 }, whitespace, eraseAttributes); return true; default: return false; @@ -1270,7 +1263,7 @@ bool AdaptDispatch::CopyRectangularArea(const VTInt top, const VTInt left, const bool AdaptDispatch::FillRectangularArea(const VTParameter ch, const VTInt top, const VTInt left, const VTInt bottom, const VTInt right) { auto& textBuffer = _api.GetTextBuffer(); - auto fillRect = _CalculateRectArea(top, left, bottom, right, textBuffer.GetSize().Dimensions()); + const auto fillRect = _CalculateRectArea(top, left, bottom, right, textBuffer.GetSize().Dimensions()); // The standard only allows for characters in the range of the GL and GR // character set tables, but we also support additional Unicode characters @@ -1282,14 +1275,8 @@ bool AdaptDispatch::FillRectangularArea(const VTParameter ch, const VTInt top, c if (glChar || grChar || unicodeChar) { const auto fillChar = _termOutput.TranslateKey(gsl::narrow_cast(charValue)); - const auto fillAttributes = textBuffer.GetCurrentAttributes(); - if (IsGlyphFullWidth(fillChar)) - { - // If the fill char is full width, we need to halve the width of the - // fill area, otherwise it'll occupy twice as much space as expected. - fillRect.right = fillRect.left + fillRect.width() / 2; - } - _FillRect(textBuffer, fillRect, fillChar, fillAttributes); + const auto& fillAttributes = textBuffer.GetCurrentAttributes(); + _FillRect(textBuffer, fillRect, { &fillChar, 1 }, fillAttributes); } return true; @@ -1310,7 +1297,7 @@ bool AdaptDispatch::EraseRectangularArea(const VTInt top, const VTInt left, cons auto& textBuffer = _api.GetTextBuffer(); const auto eraseRect = _CalculateRectArea(top, left, bottom, right, textBuffer.GetSize().Dimensions()); const auto eraseAttributes = _GetEraseAttributes(textBuffer); - _FillRect(textBuffer, eraseRect, L' ', eraseAttributes); + _FillRect(textBuffer, eraseRect, whitespace, eraseAttributes); return true; } @@ -3111,7 +3098,7 @@ bool AdaptDispatch::ScreenAlignmentPattern() const auto bufferWidth = textBuffer.GetSize().Dimensions().width; // Fill the screen with the letter E using the default attributes. - _FillRect(textBuffer, { 0, viewport.top, bufferWidth, viewport.bottom }, L'E', {}); + _FillRect(textBuffer, { 0, viewport.top, bufferWidth, viewport.bottom }, L"E", {}); // Reset the line rendition for all of these rows. textBuffer.ResetLineRenditionRange(viewport.top, viewport.bottom); // Reset the meta/extended attributes (but leave the colors unchanged). @@ -3154,7 +3141,7 @@ bool AdaptDispatch::_EraseScrollback() // Scroll the viewport content to the top of the buffer. textBuffer.ScrollRows(top, height, -top); // Clear everything after the viewport. - _FillRect(textBuffer, { 0, height, bufferSize.width, bufferSize.height }, L' ', {}); + _FillRect(textBuffer, { 0, height, bufferSize.width, bufferSize.height }, whitespace, {}); // Also reset the line rendition for all of the cleared rows. textBuffer.ResetLineRenditionRange(height, bufferSize.height); // Move the viewport @@ -3201,7 +3188,7 @@ bool AdaptDispatch::_EraseAll() // start of the buffer, then we shouldn't move down at all. const auto lastChar = textBuffer.GetLastNonSpaceCharacter(); auto newViewportTop = lastChar == til::point{} ? 0 : lastChar.y + 1; - const auto newViewportBottom = newViewportTop + viewportHeight; + auto newViewportBottom = newViewportTop + viewportHeight; const auto delta = newViewportBottom - (bufferSize.Height()); if (delta > 0) { @@ -3211,6 +3198,7 @@ bool AdaptDispatch::_EraseAll() } _api.NotifyBufferRotation(delta); newViewportTop -= delta; + newViewportBottom -= delta; // We don't want to trigger a scroll in pty mode, because we're going to // pass through the ED sequence anyway, and this will just result in the // buffer being scrolled up by two pages instead of one. @@ -3227,7 +3215,7 @@ bool AdaptDispatch::_EraseAll() // Erase all the rows in the current viewport. const auto eraseAttributes = _GetEraseAttributes(textBuffer); - _FillRect(textBuffer, { 0, newViewportTop, bufferSize.Width(), newViewportBottom }, L' ', eraseAttributes); + _FillRect(textBuffer, { 0, newViewportTop, bufferSize.Width(), newViewportBottom }, whitespace, eraseAttributes); // Also reset the line rendition for the erased rows. textBuffer.ResetLineRenditionRange(newViewportTop, newViewportBottom); diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 5af3479dc07..674cafc4506 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -212,7 +212,7 @@ namespace Microsoft::Console::VirtualTerminal std::pair _GetHorizontalMargins(const til::CoordType bufferWidth) noexcept; bool _CursorMovePosition(const Offset rowOffset, const Offset colOffset, const bool clampInMargins); void _ApplyCursorMovementFlags(Cursor& cursor) noexcept; - void _FillRect(TextBuffer& textBuffer, const til::rect& fillRect, const wchar_t fillChar, const TextAttribute fillAttrs); + void _FillRect(TextBuffer& textBuffer, const til::rect& fillRect, const std::wstring_view& fillChar, const TextAttribute& fillAttrs) const; void _SelectiveEraseRect(TextBuffer& textBuffer, const til::rect& eraseRect); void _ChangeRectAttributes(TextBuffer& textBuffer, const til::rect& changeRect, const ChangeOps& changeOps); void _ChangeRectOrStreamAttributes(const til::rect& changeArea, const ChangeOps& changeOps);