From 5bdb5e6caaab525addd0344e142eb8a9c64ba369 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Fri, 30 Jul 2021 12:34:39 -0500 Subject: [PATCH] Break a huge amount of the console Writing CHAR_INFO doesn't work RowImage is half-baked Row.hpp contains eight reimplementations of the same slgotihm move shit around RowImage.split is fucking madness look for TODO(DH) everywhere however, termbench performance doubles for non-colored text. --- src/buffer/out/AttrRow.hpp | 6 +- src/buffer/out/OutputCellRect.cpp | 13 -- src/buffer/out/OutputCellRect.hpp | 3 - src/buffer/out/Row.cpp | 3 + src/buffer/out/Row.hpp | 239 ++++++++++++++++++--------- src/buffer/out/lib/bufferout.vcxproj | 2 - src/buffer/out/textBuffer.cpp | 170 +++++++------------ src/buffer/out/textBuffer.hpp | 39 ++--- src/host/CommandListPopup.cpp | 6 +- src/host/_output.cpp | 57 ++++++- src/host/_stream.cpp | 31 ++-- src/host/conareainfo.cpp | 5 +- src/host/conareainfo.h | 2 +- src/host/conimeinfo.cpp | 206 +++++++++-------------- src/host/conimeinfo.h | 16 +- src/host/directio.cpp | 5 +- src/host/output.cpp | 5 +- src/host/screenInfo.cpp | 47 ++---- src/host/screenInfo.hpp | 10 +- src/inc/til/rle.h | 16 ++ 20 files changed, 422 insertions(+), 459 deletions(-) diff --git a/src/buffer/out/AttrRow.hpp b/src/buffer/out/AttrRow.hpp index 49756f608..d6b462924 100644 --- a/src/buffer/out/AttrRow.hpp +++ b/src/buffer/out/AttrRow.hpp @@ -31,6 +31,8 @@ public: using const_iterator = rle_vector::const_iterator; ATTR_ROW(uint16_t width, TextAttribute attr); + ATTR_ROW(rle_vector&& v) : + _data(std::move(v)) {} ~ATTR_ROW() = default; @@ -57,11 +59,11 @@ public: friend bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept; friend class ROW; + rle_vector _data; + private: void Reset(const TextAttribute attr); - rle_vector _data; - #ifdef UNIT_TESTING friend class CommonState; #endif diff --git a/src/buffer/out/OutputCellRect.cpp b/src/buffer/out/OutputCellRect.cpp index 9dd0f74df..9d40c7983 100644 --- a/src/buffer/out/OutputCellRect.cpp +++ b/src/buffer/out/OutputCellRect.cpp @@ -42,19 +42,6 @@ gsl::span OutputCellRect::GetRow(const size_t row) return gsl::span(_FindRowOffset(row), _cols); } -// Routine Description: -// - Gets a read-only iterator view over a single row of the rectangle. -// Arguments: -// - row - The Y position or row index in the buffer. -// Return Value: -// - Read-only iterator of OutputCells -OutputCellIterator OutputCellRect::GetRowIter(const size_t row) const -{ - const gsl::span view(_FindRowOffset(row), _cols); - - return OutputCellIterator(view); -} - // Routine Description: // - Internal helper to find the pointer to the specific row offset in the giant // contiguous block of memory allocated for this rectangle. diff --git a/src/buffer/out/OutputCellRect.hpp b/src/buffer/out/OutputCellRect.hpp index ebdb4883e..9cf94a786 100644 --- a/src/buffer/out/OutputCellRect.hpp +++ b/src/buffer/out/OutputCellRect.hpp @@ -21,10 +21,8 @@ Revision History: #pragma once -#include "DbcsAttribute.hpp" #include "TextAttribute.hpp" #include "OutputCell.hpp" -#include "OutputCellIterator.hpp" class OutputCellRect final { @@ -33,7 +31,6 @@ public: OutputCellRect(const size_t rows, const size_t cols); gsl::span GetRow(const size_t row); - OutputCellIterator GetRowIter(const size_t row) const; size_t Height() const noexcept; size_t Width() const noexcept; diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 881336fdb..f6817f0cc 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -91,6 +91,8 @@ void ROW::ClearColumn(const size_t column) WriteGlyphAtMeasured(column, 1, L" "); } +#if 0 +TODO (DH) // Routine Description: // - writes cell data to the row // Arguments: @@ -203,3 +205,4 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co return it; } +#endif diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index aa6e68770..ebc8087cc 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -23,13 +23,144 @@ Revision History: #include "AttrRow.hpp" #include "LineRendition.hpp" #include "OutputCell.hpp" -#include "OutputCellIterator.hpp" #include "unicode.hpp" #pragma warning(push) #pragma warning(disable : 4267) class TextBuffer; +using RowMeasurementBuffer = til::small_rle; + +struct DamageRanges +{ + size_t dataOffset; + size_t dataLength; + uint16_t firstColumn; + uint16_t lastColumnExclusive; +}; + +template +static DamageRanges DamageRangesForColumnInMeasurementBuffer(const TRuns& cwid, size_t col) +{ + size_t currentCol{ 0 }; + size_t currentWchar{ 0 }; + auto it{ cwid.runs().cbegin() }; + while (it != cwid.runs().cend()) + { + // Each compressed pair tells us how many columns x N wchar_t + const auto colsCoveredByRun{ it->value * it->length }; + if (currentCol + colsCoveredByRun > col) + { + // We want to break out of the loop to manually handle this run, because + // we've determined that it is the run that covers the column of interest. + break; + } + currentCol += colsCoveredByRun; + currentWchar += it->length; + it++; + } + + if (it == cwid.runs().cend()) + { + // this is an interesting case- somebody requested a column we cannot answer for. + // The string might actually have data, and the caller might be interested in where that data is. + // Ideally, we would return the index of the first char out-of-bounds, and the length of the remaining data as a single unit. + // We can't answer for how much space it takes up, though. + __debugbreak(); + return { 0, 0, 0u, 0u }; + //return { currentWchar, _data.size() - currentWchar, 0u, 0u }; + } + // currentWchar is how many wchar_t we are into the string before processing this run + // currentCol is how many columns we've covered before processing this run + + // We are *guaranteed* that the hit is in this run -- no need to check it->length + // col-currentCol is how many columns are left unaccounted for (how far into this run we need to go) + const auto colsLeftToCountInCurrentRun{ col - currentCol }; + currentWchar += colsLeftToCountInCurrentRun / it->value; // one wch per column unit -- rounds down (correct behavior) + + size_t lenInWchars{ 1 }; // the first hit takes up one wchar + + // We use this to determine if we have exhausted every column this run can cough up. + // colsLeftToCountInCurrentRun is 0-indexed, but colsConsumedByRun is 1-indexed (index 0 consumes N columns, etc.) + // therefore, we reindex colsLeftToCountInCurrentRun and compare it to colsConsumedByRun + const auto colsConsumedFromRun{ colsLeftToCountInCurrentRun + it->value }; + const auto colsCoveredByRun{ it->value * it->length }; + // If we *have* consumed every column this run can provide, we must check the run after it: + // if it contributes "0" columns, it is actually a set of trailing code units. + if (colsConsumedFromRun >= colsCoveredByRun && it != cwid.runs().cend()) + { + const auto nextRunIt{ it + 1 }; + if (nextRunIt != cwid.runs().cend() && nextRunIt->value == 0) + { + // we were at the boundary of a column run, so if the next one is 0 it tells us that each + // wchar after it is a trailer + lenInWchars += nextRunIt->length; + } + } + + return { + currentWchar, // wchar start + lenInWchars, // wchar size + gsl::narrow_cast(col - (colsLeftToCountInCurrentRun % it->value)), // Column damage to the left (where we overlapped the right of a wide glyph) + gsl::narrow_cast(col - (colsLeftToCountInCurrentRun % it->value) + it->value), // Column damage to the right (where we overlapped the left of a wide glyph) + }; +} + +class ROW; +struct RowImage +{ + std::wstring _data; + RowMeasurementBuffer _cwid; + ATTR_ROW _attrRow; + uint16_t _width; + friend class ROW; + friend class TextBuffer; + RowImage() : + _data{}, _cwid{}, _attrRow{ {} }, _width{ 0 } {} + RowImage(const std::wstring& data, const RowMeasurementBuffer& cwid, ATTR_ROW attrRow, uint16_t width): + _data{data}, _cwid{cwid}, _attrRow{std::move(attrRow)}, _width{width} {} + + // exclusive + std::tuple split(uint16_t col) const + { + if (col >= _width) + { + return { *this, RowImage{} }; + } + else if (col == 0) + { + return { RowImage{}, *this }; + } + auto yes_more_fucking_damage_ranges = DamageRangesForColumnInMeasurementBuffer(_cwid, col); + // here's a dumb decision: when you split along a wide char, you get spaces over its damage on the left side. + // X X XX X X X + // ^ split + // X X S <- left + // XX X X X <- right + auto chopped_off = col == yes_more_fucking_damage_ranges.lastColumnExclusive ? 0 : /*didn't get whole thing*/ yes_more_fucking_damage_ranges.lastColumnExclusive - col; + auto chopped_on = col == yes_more_fucking_damage_ranges.lastColumnExclusive ? yes_more_fucking_damage_ranges.dataLength : 0; + auto lwid = _cwid.slice(0, yes_more_fucking_damage_ranges.dataOffset + chopped_on); + for (auto z{ chopped_off }; z > 0; --z) + { + lwid.append(1); + } + RowImage left{ + _data.substr(0, yes_more_fucking_damage_ranges.dataOffset + chopped_on) + std::wstring(chopped_off, UNICODE_SPACE), + lwid, + _attrRow._data.slice(0, yes_more_fucking_damage_ranges.lastColumnExclusive), + col + }; + RowImage right{ + _data.substr(yes_more_fucking_damage_ranges.dataOffset + chopped_on), + _cwid.slice(yes_more_fucking_damage_ranges.dataOffset + chopped_on, _data.length()), + // we use first col here to catch the overlap + _attrRow._data.slice(yes_more_fucking_damage_ranges.firstColumn, _width), + ::base::ClampSub(_width, col), + }; + return { left, right }; + } +}; + enum class DelimiterClass { ControlChar, @@ -62,92 +193,15 @@ public: void ClearColumn(const size_t column); std::wstring GetText() const { return _data._data; } - OutputCellIterator WriteCells(OutputCellIterator it, const size_t index, const std::optional wrap = std::nullopt, std::optional limitRight = std::nullopt); - #ifdef UNIT_TESTING friend constexpr bool operator==(const ROW& a, const ROW& b) noexcept; friend class RowTests; #endif - struct DamageRanges - { - size_t dataOffset; - size_t dataLength; - uint16_t firstColumn; - uint16_t lastColumnExclusive; - }; - - template - static DamageRanges DamageRangesForColumnInMeasurementBuffer(const TRuns& cwid, size_t col) - { - size_t currentCol{ 0 }; - size_t currentWchar{ 0 }; - auto it{ cwid.runs().cbegin() }; - while (it != cwid.runs().cend()) - { - // Each compressed pair tells us how many columns x N wchar_t - const auto colsCoveredByRun{ it->value * it->length }; - if (currentCol + colsCoveredByRun > col) - { - // We want to break out of the loop to manually handle this run, because - // we've determined that it is the run that covers the column of interest. - break; - } - currentCol += colsCoveredByRun; - currentWchar += it->length; - it++; - } - - if (it == cwid.runs().cend()) - { - // this is an interesting case- somebody requested a column we cannot answer for. - // The string might actually have data, and the caller might be interested in where that data is. - // Ideally, we would return the index of the first char out-of-bounds, and the length of the remaining data as a single unit. - // We can't answer for how much space it takes up, though. - __debugbreak(); - return { 0, 0, 0u, 0u }; - //return { currentWchar, _data.size() - currentWchar, 0u, 0u }; - } - // currentWchar is how many wchar_t we are into the string before processing this run - // currentCol is how many columns we've covered before processing this run - - // We are *guaranteed* that the hit is in this run -- no need to check it->length - // col-currentCol is how many columns are left unaccounted for (how far into this run we need to go) - const auto colsLeftToCountInCurrentRun{ col - currentCol }; - currentWchar += colsLeftToCountInCurrentRun / it->value; // one wch per column unit -- rounds down (correct behavior) - - size_t lenInWchars{ 1 }; // the first hit takes up one wchar - - // We use this to determine if we have exhausted every column this run can cough up. - // colsLeftToCountInCurrentRun is 0-indexed, but colsConsumedByRun is 1-indexed (index 0 consumes N columns, etc.) - // therefore, we reindex colsLeftToCountInCurrentRun and compare it to colsConsumedByRun - const auto colsConsumedFromRun{ colsLeftToCountInCurrentRun + it->value }; - const auto colsCoveredByRun{ it->value * it->length }; - // If we *have* consumed every column this run can provide, we must check the run after it: - // if it contributes "0" columns, it is actually a set of trailing code units. - if (colsConsumedFromRun >= colsCoveredByRun && it != cwid.runs().cend()) - { - const auto nextRunIt{ it + 1 }; - if (nextRunIt != cwid.runs().cend() && nextRunIt->value == 0) - { - // we were at the boundary of a column run, so if the next one is 0 it tells us that each - // wchar after it is a trailer - lenInWchars += nextRunIt->length; - } - } - - return { - currentWchar, // wchar start - lenInWchars, // wchar size - gsl::narrow_cast(col - (colsLeftToCountInCurrentRun % it->value)), // Column damage to the left (where we overlapped the right of a wide glyph) - gsl::narrow_cast(col - (colsLeftToCountInCurrentRun % it->value) + it->value), // Column damage to the right (where we overlapped the left of a wide glyph) - }; - } - struct RowData { std::wstring _data; - til::small_rle _cwid; + RowMeasurementBuffer _cwid; ATTR_ROW _attrRow; unsigned short _width; @@ -184,8 +238,9 @@ public: return damage; } - void _strikeDamageAndAdjust(size_t col, size_t ncols, DamageRanges& range) + void _strikeDamageAndAdjust(size_t col, size_t ncols, size_t incomingCodeUnitCount, DamageRanges& range) { + (void)incomingCodeUnitCount; const bool damaged{ range.firstColumn < col || col + ncols < range.lastColumnExclusive }; if (damaged) { @@ -295,7 +350,7 @@ public: } } - std::tuple WriteStringAtMeasured(uint16_t col, uint16_t colCount, const std::wstring_view& string, const til::small_rle& measurements) + std::tuple WriteStringAtMeasured(uint16_t col, uint16_t colCount, const std::wstring_view& string, const RowMeasurementBuffer& measurements) { size_t incomingLastColumn{ std::min(_data._width - col, colCount) }; auto incomingLastColumnOffsets{ DamageRangesForColumnInMeasurementBuffer(measurements, incomingLastColumn - 1 /*inclusive*/) }; @@ -386,7 +441,7 @@ public: // If we are filling over the left/right halves of a character // This is a bit wasteful since it can grow/shrink the buffers and we're about // to do it again, but I was trying to be expedient. - _data._strikeDamageAndAdjust(col, columnsRequired, damage); + _data._strikeDamageAndAdjust(col, columnsRequired, charsFitOrRemain, damage); const auto [begin, len, min, max] = damage; _data._data.replace(begin, len, charsFitOrRemain, ch); @@ -440,6 +495,26 @@ public: } } + RowImage Dump(uint16_t left, uint16_t size) + { + auto [begin, len, min, max] = _data._damageForColumns(left, size); + return RowImage{ + _data._data.substr(begin, len), + _data._cwid.slice(begin, begin + len), + ATTR_ROW{ _data._attrRow._data.slice(min, max) }, + ::base::MakeClampedNum(max) - min + }; + } + + void Reinsert(uint16_t left, const RowImage& ri) + { + auto damage{ _data._damageForColumns(left, ri._width) }; + _data._strikeDamageAndAdjust(left, ri._width, ri._data.size(), damage); + _data._data.replace(damage.dataOffset, damage.dataLength, ri._data); + _data._cwid.replace(damage.dataOffset, damage.dataOffset + damage.dataLength, ri._cwid.runs()); + _data._attrRow._data.replace(damage.firstColumn, damage.lastColumnExclusive, ri._attrRow._data.runs()); + } + uint16_t _maxc{}; size_t MeasureRight() const { diff --git a/src/buffer/out/lib/bufferout.vcxproj b/src/buffer/out/lib/bufferout.vcxproj index 6ede80b0f..c15f11d84 100644 --- a/src/buffer/out/lib/bufferout.vcxproj +++ b/src/buffer/out/lib/bufferout.vcxproj @@ -13,7 +13,6 @@ - @@ -33,7 +32,6 @@ - diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index e953ab88e..e4adcd93a 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -183,90 +183,6 @@ TextBufferCellIterator TextBuffer::GetCellDataAt(const COORD at, const Viewport return TextBufferCellIterator(*this, at, limit); } -// Routine Description: -// - Writes cells to the output buffer. Writes at the cursor. -// Arguments: -// - givenIt - Iterator representing output cell data to write -// Return Value: -// - The final position of the iterator -OutputCellIterator TextBuffer::Write(const OutputCellIterator givenIt) -{ - const auto& cursor = GetCursor(); - const auto target = cursor.GetPosition(); - - const auto finalIt = Write(givenIt, target); - - return finalIt; -} - -// Routine Description: -// - Writes cells to the output buffer. -// Arguments: -// - givenIt - Iterator representing output cell data to write -// - target - the row/column to start writing the text to -// - wrap - change the wrap flag if we hit the end of the row while writing and there's still more data -// Return Value: -// - The final position of the iterator -OutputCellIterator TextBuffer::Write(const OutputCellIterator givenIt, - const COORD target, - const std::optional wrap) -{ - // Make mutable copy so we can walk. - auto it = givenIt; - - // Make mutable target so we can walk down lines. - auto lineTarget = target; - - // Get size of the text buffer so we can stay in bounds. - const auto size = GetSize(); - - // While there's still data in the iterator and we're still targeting in bounds... - while (it && size.IsInBounds(lineTarget)) - { - // Attempt to write as much data as possible onto this line. - // NOTE: if wrap = true/false, we want to set the line's wrap to true/false (respectively) if we reach the end of the line - it = WriteLine(it, lineTarget, wrap); - - // Move to the next line down. - lineTarget.X = 0; - ++lineTarget.Y; - } - - return it; -} - -// Routine Description: -// - Writes one line of text to the output buffer. -// Arguments: -// - givenIt - The iterator that will dereference into cell data to insert -// - target - Coordinate targeted within output buffer -// - wrap - change the wrap flag if we hit the end of the row while writing and there's still more data in the iterator. -// - limitRight - Optionally restrict the right boundary for writing (e.g. stop writing earlier than the end of line) -// Return Value: -// - The iterator, but advanced to where we stopped writing. Use to find input consumed length or cells written length. -OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt, - const COORD target, - const std::optional wrap, - std::optional limitRight) -{ - // If we're not in bounds, exit early. - if (!GetSize().IsInBounds(target)) - { - return givenIt; - } - - // Get the row and write the cells - ROW& row = GetRowByOffset(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. - const auto written = newIt.GetCellDistance(givenIt); - const Viewport paint = Viewport::FromDimensions(target, { gsl::narrow(written), 1 }); - _NotifyPaint(paint); - - return newIt; -} - //Routine Description: // - Finds the current row in the buffer (as indicated by the cursor position) // and specifies that we have forced a line wrap on that row @@ -629,7 +545,7 @@ void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition) fillAttrs.SetStandardErase(); const size_t fillOffset = GetLineWidth(rowIndex); const size_t fillLength = GetSize().Width() - fillOffset; - FillWithCharacterAndAttributeLinear(til::point{fillOffset, ::base::saturated_cast(cursorPosition.Y)}, fillLength, UNICODE_SPACE, fillAttrs); + FillWithCharacterAndAttributeLinear(til::point{ fillOffset, ::base::saturated_cast(cursorPosition.Y) }, fillLength, UNICODE_SPACE, fillAttrs); // We also need to make sure the cursor is clamped within the new width. GetCursor().SetPosition(ClampPositionWithinLine(cursorPosition)); } @@ -1939,10 +1855,14 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, } } +#if 0 OutputCellIterator it{ oldBuffer.GetCellDataAt({ 0, iOldRow }, Viewport::FromDimensions({ 0, iOldRow }, iRight, 1)) }; auto preCurPos{ newBuffer.GetCursor().GetPosition() }; const auto last = newBuffer.Write(it, preCurPos, true); // true - wrap if we don't fit! const auto distance = last.GetCellDistance(it); +#endif + auto preCurPos{ newBuffer.GetCursor().GetPosition() }; + const auto distance = 5; if (iOldRow == cOldCursorPos.Y) { @@ -2318,17 +2238,18 @@ PointTree TextBuffer::GetPatterns(const size_t firstRow, const size_t lastRow) c return result; } -void TextBuffer::FillWithAttributeRectangular(const til::rectangle& region, const TextAttribute& attribute) +TextBuffer::WriteResult TextBuffer::FillWithAttributeRectangular(const til::rectangle& region, const TextAttribute& attribute) { - _WriteRectangular( + return _WriteRectangular( region, - [&](WriteResult&, auto&& at, auto&& size) { + [&](WriteResult& result, auto&& at, auto&& size) { auto& row = GetRowByOffset(at.y()); row.GetAttrRow().Replace(::base::saturated_cast(at.x()), ::base::ClampAdd(::base::saturated_cast(at.x()), size), attribute); + result.columnsWritten += size; }); } -size_t TextBuffer::FillWithAttributeLinear(til::point at, size_t count, const TextAttribute& attribute) +TextBuffer::WriteResult TextBuffer::FillWithAttributeLinear(til::point at, size_t count, const TextAttribute& attribute) { auto out = _WriteLinear( at, @@ -2338,20 +2259,20 @@ size_t TextBuffer::FillWithAttributeLinear(til::point at, size_t count, const Te auto& row = GetRowByOffset(at.y()); row.GetAttrRow().Replace(::base::saturated_cast(at.x()), ::base::ClampAdd(::base::saturated_cast(at.x()), size), attribute); - result.colsConsumed += w.RawValue(); + result.columnsWritten += w.RawValue(); const Viewport paint = Viewport::FromDimensions(at, { w.Cast(), 1 }); _NotifyPaint(paint); return (count -= w.RawValue()) > 0; // keep going if data remains }); - return out.colsConsumed; + return out; } // TODO(DH): make this a member so that IsGlyphFullWidth can be a member ref -static std::tuple, uint16_t> _MeasureStringAndCountColumns(std::wstring_view string) //const noexcept +static std::tuple _MeasureStringAndCountColumns(std::wstring_view string) //const noexcept { - til::small_rle measurements; + RowMeasurementBuffer measurements; uint16_t colCount{}; while (!string.empty()) { @@ -2374,7 +2295,7 @@ struct no_attributes }; template -size_t TextBuffer::_WriteMeasuredStringLinearWithAttributes(const til::point& at, std::wstring_view string, til::small_rle measurements, [[maybe_unused]] const T& attributes) +TextBuffer::WriteResult TextBuffer::_WriteMeasuredStringLinearWithAttributes(const til::point& at, std::wstring_view string, RowMeasurementBuffer measurements, [[maybe_unused]] const T& attributes) { uint16_t colCount = std::accumulate( measurements.runs().cbegin(), @@ -2391,34 +2312,52 @@ size_t TextBuffer::_WriteMeasuredStringLinearWithAttributes(const til::point& at { row.GetAttrRow().Replace(::base::saturated_cast(at.x()), ::base::ClampAdd(::base::saturated_cast(at.x()), consumedDestCols), attributes); } +#if 0 + else if constexpr (std::is_same::value) + { + // user has passed us a packed representation of attributes + // It is a programming error to pass one whose total length does + // not match the measurement buffer + const auto attrRunSlice{ attributes.slice(0, consumedSourceCols) }; + attrRunSlice.resize_trailing_extent(consumedDestCols); // this may be very wrong. TODO(DH) + // intent: I believe that this will take the attribute from the cell before the one we had to cut off + // and then extend it to cover our empty column? + row.GetAttrRow()._data.replace(::base::saturated_cast(at.x()), ::base::ClampAdd(::base::saturated_cast(at.x()), consumedSourceCols), attrRunSlice.runs()); + } +#endif const Viewport paint = Viewport::FromDimensions(at, { ::base::saturated_cast(consumedDestCols), 1 }); _NotifyPaint(paint); colCount -= consumedSourceCols; - result.charsConsumed += consumedWchar; - result.colsConsumed += consumedDestCols; + result.charactersRead += consumedWchar; + result.charactersWritten += consumedWchar; + result.columnsWritten += consumedDestCols; + result.columnsRead += consumedSourceCols; + // unchecked substr + //string = std::wstring_view{ &*string.begin() + consumedWchar, string.size() - consumedWchar }; + //string = til::unchecked_substr(string, consumedWchar); string = string.substr(consumedWchar); measurements = measurements.slice(gsl::narrow_cast(consumedWchar), measurements.size()); return !string.empty(); }); - return out.colsConsumed; + return out; } -size_t TextBuffer::WriteMeasuredStringLinear(const til::point& at, const std::wstring_view& string, til::small_rle measurements) +TextBuffer::WriteResult TextBuffer::WriteMeasuredStringLinear(const til::point& at, const std::wstring_view& string, RowMeasurementBuffer measurements) { return _WriteMeasuredStringLinearWithAttributes(at, string, std::move(measurements), _currentAttributes); } -size_t TextBuffer::WriteStringLinearWithAttributes(const til::point &at, const std::wstring_view& string, const TextAttribute& attr) +TextBuffer::WriteResult TextBuffer::WriteStringLinearWithAttributes(const til::point& at, const std::wstring_view& string, const TextAttribute& attr) { auto [measurements, colCount] = _MeasureStringAndCountColumns(string); return _WriteMeasuredStringLinearWithAttributes(at, string, std::move(measurements), attr); } -size_t TextBuffer::WriteStringLinearKeepAttributes(const til::point &at, const std::wstring_view& string) +TextBuffer::WriteResult TextBuffer::WriteStringLinearKeepAttributes(const til::point& at, const std::wstring_view& string) { auto [measurements, colCount] = _MeasureStringAndCountColumns(string); return _WriteMeasuredStringLinearWithAttributes(at, string, std::move(measurements), no_attributes{}); @@ -2443,7 +2382,7 @@ TextBuffer::WriteResult TextBuffer::_FillWithCharacterAndAttributeRectangular(co } template -TextBuffer::WriteResult TextBuffer::_FillWithCharacterAndAttributeLinear(const til::point &at, size_t count, const wchar_t character, [[maybe_unused]] const T& attr) +TextBuffer::WriteResult TextBuffer::_FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, [[maybe_unused]] const T& attr) { const uint8_t width{ IsGlyphFullWidth(character) ? uint8_t{ 2u } : uint8_t{ 1u } }; return _WriteLinear( @@ -2459,8 +2398,8 @@ TextBuffer::WriteResult TextBuffer::_FillWithCharacterAndAttributeLinear(const t row.GetAttrRow().Replace(::base::saturated_cast(at.x()), ::base::ClampAdd(::base::saturated_cast(at.x()), w), attr); } - result.charsConsumed += consumedWchars; - result.colsConsumed += w.RawValue(); + result.charactersWritten += consumedWchars; + result.columnsWritten += w.RawValue(); const Viewport paint = Viewport::FromDimensions(at, { w.Cast(), 1 }); _NotifyPaint(paint); @@ -2469,26 +2408,31 @@ TextBuffer::WriteResult TextBuffer::_FillWithCharacterAndAttributeLinear(const t }); } -size_t TextBuffer::FillWithCharacterRectangular(const til::rectangle& region, const wchar_t character) +TextBuffer::WriteResult TextBuffer::FillWithCharacterRectangular(const til::rectangle& region, const wchar_t character) { - return _FillWithCharacterAndAttributeRectangular(region, character, no_attributes{}).colsConsumed; + return _FillWithCharacterAndAttributeRectangular(region, character, no_attributes{}); } -void TextBuffer::FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const TextAttribute& attr) +TextBuffer::WriteResult TextBuffer::FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const TextAttribute& attr) { - _FillWithCharacterAndAttributeRectangular(region, character, attr); + return _FillWithCharacterAndAttributeRectangular(region, character, attr); } -size_t TextBuffer::FillWithCharacterLinear(const til::point& at, size_t count, const wchar_t character) +TextBuffer::WriteResult TextBuffer::FillWithCharacterLinear(const til::point& at, size_t count, const wchar_t character) { - const auto out = _FillWithCharacterAndAttributeLinear(at, count, character, no_attributes{}); - return out.charsConsumed; + return _FillWithCharacterAndAttributeLinear(at, count, character, no_attributes{}); } -size_t TextBuffer::FillWithCharacterAndAttributeLinear(const til::point &at, size_t count, const wchar_t character, const TextAttribute& attr) +TextBuffer::WriteResult TextBuffer::FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const TextAttribute& attr) { - const auto out = _FillWithCharacterAndAttributeLinear(at, count, character, attr); - return out.charsConsumed; + return _FillWithCharacterAndAttributeLinear(at, count, character, attr); +} + +TextBuffer::WriteResult TextBuffer::WriteRowImage(const til::point at, const RowImage& ri) +{ + auto& row{ GetRowByOffset(at.y()) }; + row.Reinsert(at.x(), ri); + return {}; } template diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index ebb82908b..68d9aef4d 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -85,17 +85,6 @@ public: TextBufferTextIterator GetTextDataAt(const COORD at, const Microsoft::Console::Types::Viewport limit) const; // Text insertion functions - OutputCellIterator Write(const OutputCellIterator givenIt); - - OutputCellIterator Write(const OutputCellIterator givenIt, - const COORD target, - const std::optional wrap = true); - - OutputCellIterator WriteLine(const OutputCellIterator givenIt, - const COORD target, - const std::optional setWrap = std::nullopt, - const std::optional limitRight = std::nullopt); - bool IncrementCursor(); bool NewlineCursor(); @@ -196,23 +185,27 @@ public: struct WriteResult { - size_t charsConsumed; - size_t colsConsumed; + size_t charactersWritten; + size_t charactersRead; + size_t columnsWritten; + size_t columnsRead; }; - size_t WriteMeasuredStringLinear(const til::point& at, const std::wstring_view& string, til::small_rle measurements); + WriteResult WriteMeasuredStringLinear(const til::point& at, const std::wstring_view& string, RowMeasurementBuffer measurements); - size_t WriteStringLinearWithAttributes(const til::point& at, const std::wstring_view& string, const TextAttribute& attr); - size_t WriteStringLinearKeepAttributes(const til::point& at, const std::wstring_view& string); + WriteResult WriteStringLinearWithAttributes(const til::point& at, const std::wstring_view& string, const TextAttribute& attr); + WriteResult WriteStringLinearKeepAttributes(const til::point& at, const std::wstring_view& string); - size_t FillWithCharacterRectangular(const til::rectangle& region, const wchar_t character); - size_t FillWithCharacterLinear(const til::point& at, size_t count, const wchar_t character); + WriteResult FillWithCharacterRectangular(const til::rectangle& region, const wchar_t character); + WriteResult FillWithCharacterLinear(const til::point& at, size_t count, const wchar_t character); - void FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const TextAttribute& attr); - size_t FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const TextAttribute& attr); + WriteResult FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const TextAttribute& attr); + WriteResult FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const TextAttribute& attr); - void FillWithAttributeRectangular(const til::rectangle& region, const TextAttribute& attribute); - size_t FillWithAttributeLinear(const til::point at, size_t count, const TextAttribute& attribute); + WriteResult FillWithAttributeRectangular(const til::rectangle& region, const TextAttribute& attribute); + WriteResult FillWithAttributeLinear(const til::point at, size_t count, const TextAttribute& attribute); + + WriteResult WriteRowImage(const til::point at, const RowImage& ri); private: void _UpdateSize(); @@ -266,7 +259,7 @@ private: WriteResult _FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const T& attr); template - size_t _WriteMeasuredStringLinearWithAttributes(const til::point& at, std::wstring_view string, til::small_rle measurements, const T& attributes); + WriteResult _WriteMeasuredStringLinearWithAttributes(const til::point& at, std::wstring_view string, RowMeasurementBuffer measurements, const T& attributes); std::unordered_map _idsAndPatterns; size_t _currentPatternId; diff --git a/src/host/CommandListPopup.cpp b/src/host/CommandListPopup.cpp index 42c70e373..bbe0bca9b 100644 --- a/src/host/CommandListPopup.cpp +++ b/src/host/CommandListPopup.cpp @@ -429,7 +429,7 @@ void CommandListPopup::_drawList() TextAttribute inverted = _attributes; inverted.Invert(); - _screenInfo.GetTextBuffer().FillWithAttributeRectangular(til::rectangle{WriteCoord, til::size{ Width(), 1 }}, inverted); + _screenInfo.GetTextBuffer().FillWithAttributeRectangular(til::rectangle{ WriteCoord, til::size{ Width(), 1 } }, inverted); } WriteCoord.Y += 1; @@ -526,11 +526,11 @@ void CommandListPopup::_updateHighlight(const SHORT OldCurrentCommand, const SHO WriteCoord.X = _region.Left + 1i16; WriteCoord.Y = _region.Top + 1i16 + OldCurrentCommand - TopIndex; - _screenInfo.GetTextBuffer().FillWithAttributeRectangular(til::rectangle{WriteCoord, til::size{ Width(), 1 }}, _attributes); + _screenInfo.GetTextBuffer().FillWithAttributeRectangular(til::rectangle{ WriteCoord, til::size{ Width(), 1 } }, _attributes); // highlight new command WriteCoord.Y = _region.Top + 1i16 + NewCurrentCommand - TopIndex; TextAttribute inverted = _attributes; inverted.Invert(); - _screenInfo.GetTextBuffer().FillWithAttributeRectangular(til::rectangle{WriteCoord, til::size{ Width(), 1 }}, inverted); + _screenInfo.GetTextBuffer().FillWithAttributeRectangular(til::rectangle{ WriteCoord, til::size{ Width(), 1 } }, inverted); } diff --git a/src/host/_output.cpp b/src/host/_output.cpp index 02e210351..04f4989a8 100644 --- a/src/host/_output.cpp +++ b/src/host/_output.cpp @@ -89,10 +89,48 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region) return E_INVALIDARG; } - const OutputCellIterator it(attrs); - const auto done = screenInfo.Write(it, target); + //const OutputCellIterator it(attrs); + //const auto done = screenInfo.Write(it, target); + __debugbreak(); +// TODO(DH) restore writing runs of attributes +//////////////// +#if 0 + static rle_container rle_encode(const basic_container_view& from) + { + if (from.empty()) + { + return {}; + } - used = done.GetCellDistance(it); + rle_container to; + value_type value = from.front(); + size_type length = 0; + + for (auto v : from) + { + if (v != value) + { + to.emplace_back(value, length); + value = v; + length = 0; + } + + length++; + } + + if (length) + { + to.emplace_back(value, length); + } + + return to; + } +#endif + + //////////////// + + used = 0; + //used = done.GetCellDistance(it); return S_OK; } @@ -131,7 +169,8 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region) try { - used = screenInfo.GetTextBuffer().WriteStringLinearKeepAttributes(target, chars); + const auto result = screenInfo.GetTextBuffer().WriteStringLinearKeepAttributes(target, chars); + used = result.charactersWritten; } CATCH_RETURN(); @@ -214,7 +253,8 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region) try { - cellsModified = screenBuffer.GetTextBuffer().FillWithAttributeLinear(startingCoordinate, lengthToWrite, TextAttribute{ attribute }); + const auto result = screenBuffer.GetTextBuffer().FillWithAttributeLinear(startingCoordinate, lengthToWrite, TextAttribute{ attribute }); + cellsModified = result.columnsWritten; if (screenBuffer.HasAccessibilityEventing()) { @@ -271,12 +311,11 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region) HRESULT hr = S_OK; try { - const OutputCellIterator it(character, lengthToWrite); - // when writing to the buffer, specifically unset wrap if we get to the last column. // a fill operation should UNSET wrap in that scenario. See GH #1126 for more details. - const auto done = screenInfo.Write(it, startingCoordinate, false); - cellsModified = done.GetInputDistance(it); + // TODO(DH) maintain above fix to clear wrap ^^^ + const auto result = screenInfo.GetTextBuffer().FillWithCharacterLinear(startingCoordinate, lengthToWrite, character); + cellsModified = result.columnsWritten; // Notify accessibility if (screenInfo.HasAccessibilityEventing()) diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index 7eff96375..5d3736b73 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -342,7 +342,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100; SHORT XPosition; std::wstring local; local.reserve(LOCAL_BUFFER_SIZE); - til::small_rle measurements{ gsl::narrow_cast(LOCAL_BUFFER_SIZE), 1u }; + RowMeasurementBuffer measurements; size_t TempNumSpaces = 0; const bool fUnprocessed = WI_IsFlagClear(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT); const bool fWrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT); @@ -400,7 +400,6 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100; // WCL-NOTE: is initialized from pwchRealUnicode. const wchar_t RealUnicodeChar = *pwchRealUnicode; const auto cbuf{ Utf16Parser::ParseNext(std::wstring_view{ pwchRealUnicode, (BufferSize / sizeof(WCHAR)) - static_cast(pwchRealUnicode - base) }) }; - uint16_t lo{ gsl::narrow_cast(local.size()) }; if (IS_GLYPH_CHAR(RealUnicodeChar) || fUnprocessed) { // WCL-NOTE: This operates on a single code unit instead of a whole codepoint. It will mis-measure surrogate pairs. @@ -409,7 +408,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100; if (XPosition < (coordScreenBufferSize.X - 1)) { local.append(cbuf); - measurements.replace(lo, lo + 1, { uint8_t{ 2u }, 1u }); + measurements.append(uint8_t{ 2u }); // cursor adjusted by 2 because the char is double width XPosition += 2; @@ -424,14 +423,14 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100; else { local.append(cbuf); - measurements.replace(lo, lo + 1, { uint8_t{ 1u }, 1u }); + measurements.append(uint8_t{ 1u }); XPosition++; i += cbuf.size(); pwchBuffer += cbuf.size(); } - if (cbuf.size() > 1) + for (auto z = cbuf.size(); z > 1; --z) { - measurements.replace(lo + 1, gsl::narrow_cast(lo + 1 + cbuf.size() - 1), typename decltype(measurements)::rle_type{ uint8_t{ 0u }, gsl::narrow_cast(cbuf.size() - 1) }); + measurements.append(uint8_t{ 0 }); } } else @@ -561,7 +560,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100; //OutputCellIterator it(std::wstring_view{ local }, Attributes); //const auto itEnd = screenInfo.Write(it); measurements.resize_trailing_extent(gsl::narrow_cast(local.size())); - auto vcolsTaken = screenInfo.WriteMeasuredStringLinear(local, measurements); + auto result = screenInfo.GetTextBuffer().WriteMeasuredStringLinear(CursorPosition, local, measurements); local.clear(); measurements.resize_trailing_extent(0); @@ -573,7 +572,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100; // The number of "spaces" or "cells" we have consumed needs to be reported and stored for later // when/if we need to erase the command line. - TempNumSpaces += vcolsTaken; //itEnd.GetCellDistance(it); + TempNumSpaces += result.columnsWritten; //itEnd.GetCellDistance(it); // WCL-NOTE: We are using the "estimated" X position delta instead of the actual delta from // WCL-NOTE: the iterator. It is not clear why. If they differ, the cursor ends up in the // WCL-NOTE: wrong place (typically inside another character). @@ -711,7 +710,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100; { try { - screenInfo.Write(OutputCellIterator(UNICODE_SPACE, Attributes, 1), CursorPosition); + screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(CursorPosition, 1, UNICODE_SPACE, Attributes); Status = STATUS_SUCCESS; } CATCH_LOG(); @@ -729,7 +728,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100; { try { - screenInfo.Write(OutputCellIterator(UNICODE_SPACE, Attributes, 1), CursorPosition); + screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(CursorPosition, 1, UNICODE_SPACE, Attributes); Status = STATUS_SUCCESS; } CATCH_LOG(); @@ -751,7 +750,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100; { try { - screenInfo.Write(OutputCellIterator(UNICODE_SPACE, Attributes, 1), cursor.GetPosition()); + screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(cursor.GetPosition(), 1, UNICODE_SPACE, Attributes); } CATCH_LOG(); } @@ -806,9 +805,8 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100; try { - const OutputCellIterator it(UNICODE_SPACE, Attributes, NumChars); - const auto done = screenInfo.Write(it, cursor.GetPosition()); - NumChars = done.GetCellDistance(it); + const auto result = screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(cursor.GetPosition(), NumChars, UNICODE_SPACE, Attributes); + NumChars = result.columnsWritten; } CATCH_LOG(); @@ -865,15 +863,12 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100; // If we're on top of a trailing cell, clear it and the previous cell. if (Row.DbcsAttrAt(TargetPoint.X).IsTrailing()) { - // Space to clear for 2 cells. - OutputCellIterator it(UNICODE_SPACE, 2); - // Back target point up one. auto writeTarget = TargetPoint; writeTarget.X--; // Write 2 clear cells. - screenInfo.Write(it, writeTarget); + screenInfo.GetTextBuffer().FillWithCharacterLinear(writeTarget, 2, UNICODE_SPACE); } } catch (...) diff --git a/src/host/conareainfo.cpp b/src/host/conareainfo.cpp index b336c0d78..88aed056a 100644 --- a/src/host/conareainfo.cpp +++ b/src/host/conareainfo.cpp @@ -109,11 +109,10 @@ void ConversionAreaInfo::SetAttributes(const TextAttribute& attr) // Arguments: // - text - Text to insert into the conversion area buffer // - column - Column to start at (X position) -void ConversionAreaInfo::WriteText(const std::vector& text, +void ConversionAreaInfo::WriteText(const RowImage& text, const SHORT column) { - gsl::span view(text.data(), text.size()); - _screenBuffer->Write(view, { column, 0 }); + _screenBuffer->GetTextBuffer().WriteRowImage(til::point{ column, 0 }, text); } // Routine Description: diff --git a/src/host/conareainfo.h b/src/host/conareainfo.h index 4808da09f..ab32c271e 100644 --- a/src/host/conareainfo.h +++ b/src/host/conareainfo.h @@ -61,7 +61,7 @@ public: void SetWindowInfo(const SMALL_RECT view) noexcept; void Paint() const noexcept; - void WriteText(const std::vector& text, const SHORT column); + void WriteText(const RowImage& text, const SHORT column); void SetAttributes(const TextAttribute& attr); const TextBuffer& GetTextBuffer() const noexcept; diff --git a/src/host/conimeinfo.cpp b/src/host/conimeinfo.cpp index 7140418f8..04338c281 100644 --- a/src/host/conimeinfo.cpp +++ b/src/host/conimeinfo.cpp @@ -217,76 +217,60 @@ TextAttribute ConsoleImeInfo::s_RetrieveAttributeAt(const size_t pos, // - colorArray - Array of color values provided by the IME. // Return Value: // - Vector of OutputCells where each one represents one cell of the output buffer. -std::vector ConsoleImeInfo::s_ConvertToCells(const std::wstring_view text, - const gsl::span attributes, - const gsl::span colorArray) +RowImage ConsoleImeInfo::s_ConvertToCells(std::wstring_view text, + const gsl::span attributes, + const gsl::span colorArray) { + RowImage e; std::vector cells; - // - Convert incoming wchar_t stream into UTF-16 units. - const auto glyphs = Utf16Parser::Parse(text); - - // - Walk through all of the grouped up text, match up the correct attribute to it, and make a new cell. - size_t attributesUsed = 0; - for (const auto& parsedGlyph : glyphs) + size_t attributesUsed{}; + while (!text.empty()) { - const std::wstring_view glyph{ parsedGlyph.data(), parsedGlyph.size() }; - // Collect up attributes that apply to this glyph range. - auto drawingAttr = s_RetrieveAttributeAt(attributesUsed, attributes, colorArray); - attributesUsed++; + const auto glyph{ Utf16Parser::ParseNext(text) }; + text = text.substr(glyph.size()); + const uint8_t width{ gsl::narrow_cast(IsGlyphFullWidth(glyph) ? 2u : 1u) }; - // The IME gave us an attribute for every glyph position in a surrogate pair. - // But the only important information will be the cursor position. - // Check all additional attributes to see if the cursor resides on top of them. - for (size_t i = 1; i < glyph.size(); i++) - { - TextAttribute additionalAttr = s_RetrieveAttributeAt(attributesUsed, attributes, colorArray); - attributesUsed++; - if (additionalAttr.IsLeftVerticalDisplayed()) - { - drawingAttr.SetLeftVerticalDisplayed(true); - } - if (additionalAttr.IsRightVerticalDisplayed()) - { - drawingAttr.SetRightVerticalDisplayed(true); - } + // add glyph + e._data.append(glyph); + + // register its width + e._cwid.append(width); + for (auto z{ glyph.size() }; z > 1; --z) + { // add a trailing 0-width for every surrogate pair trail + e._cwid.append(0); } - // We have to determine if the glyph range is 1 column or two. - // If it's full width, it's two, and we need to make sure we don't draw the cursor - // right down the middle of the character. - // Otherwise it's one column and we'll push it in with the default empty DbcsAttribute. - DbcsAttribute dbcsAttr; - if (IsGlyphFullWidth(glyph)) + auto drawingAttr = s_RetrieveAttributeAt(attributesUsed++, attributes, colorArray); + if (width == 1 || !(drawingAttr.IsLeftVerticalDisplayed() || drawingAttr.IsRightVerticalDisplayed())) { - auto leftHalfAttr = drawingAttr; - auto rightHalfAttr = drawingAttr; - - // Don't draw lines in the middle of full width glyphs. - // If we need a right vertical, don't apply it to the left side of the character - if (leftHalfAttr.IsRightVerticalDisplayed()) + for (auto z{ width }; z > 0; --z) // add an attribute for each cell covered { - leftHalfAttr.SetRightVerticalDisplayed(false); + e._attrRow._data.append(drawingAttr); } - - dbcsAttr.SetLeading(); - cells.emplace_back(glyph, dbcsAttr, leftHalfAttr); - dbcsAttr.SetTrailing(); - - // If we need a left vertical, don't apply it to the right side of the character - if (rightHalfAttr.IsLeftVerticalDisplayed()) - { - rightHalfAttr.SetLeftVerticalDisplayed(false); - } - cells.emplace_back(glyph, dbcsAttr, rightHalfAttr); } else { - cells.emplace_back(glyph, dbcsAttr, drawingAttr); - } - } + // the width was >1 and the attribute contained the left or the right cursor indicator; split them into [ ___ ] (left, middle, right) + auto first = drawingAttr; + first.SetRightVerticalDisplayed(false); + auto middle = drawingAttr; + middle.SetLeftVerticalDisplayed(false); + middle.SetRightVerticalDisplayed(false); + auto last = drawingAttr; + last.SetLeftVerticalDisplayed(false); - return cells; + e._attrRow._data.append(first); + for (auto z{ width }; z > 2; --z) + { + e._attrRow._data.append(middle); + } + e._attrRow._data.append(last); + } + + e._width += width; + } + return e; } // Routine Description: @@ -308,81 +292,55 @@ std::vector ConsoleImeInfo::s_ConvertToCells(const std::wstring_view // However, if text couldn't fit in our line (full-width character starting at the very last cell) // then we will give back the same begin and update the position for the next call to try again. // If the viewport is deemed too small, we'll skip past it and advance begin past the entire full-width character. -std::vector::const_iterator ConsoleImeInfo::_WriteConversionArea(const std::vector::const_iterator begin, - const std::vector::const_iterator end, - COORD& pos, - const Microsoft::Console::Types::Viewport view, - SCREEN_INFORMATION& screenInfo) +void ConsoleImeInfo::_WriteConversionArea(const RowImage& cells, + COORD pos, + const Microsoft::Console::Types::Viewport view, + SCREEN_INFORMATION& screenInfo) { - // The position in the viewport where we will start inserting cells for this conversion area - // NOTE: We might exit early if there's not enough space to fit here, so we take a copy of - // the original and increment it up front. - const auto insertionPos = pos; - - // Advance the cursor position to set up the next call for success (insert the next conversion area - // at the beginning of the following line) - pos.X = view.Left(); - pos.Y++; - // The index of the last column in the viewport. (view is inclusive) const auto finalViewColumn = view.RightInclusive(); - // The maximum number of cells we can insert into a line. - const auto lineWidth = finalViewColumn - insertionPos.X + 1; // +1 because view was inclusive - - // The iterator to the beginning position to form our line - const auto lineBegin = begin; - - // The total number of cells we could insert. - const auto size = end - begin; - FAIL_FAST_IF(size <= 0); // It's a programming error to have <= 0 cells to insert. - - // The end is the smaller of the remaining number of cells or the amount of line cells we can write before - // hitting the right edge of the viewport - auto lineEnd = lineBegin + std::min(size, (ptrdiff_t)lineWidth); - - // We must attempt to compensate for ending on a leading byte. We can't split a full-width character across lines. - // As such, if the last item is a leading byte, back the end up by one. - FAIL_FAST_IF(lineEnd <= lineBegin); // We should have at least 1 space we can back up. - - // Get the last cell in the run and if it's a leading byte, move the end position back one so we don't - // try to insert it. - const auto lastCell = lineEnd - 1; - if (lastCell->DbcsAttr().IsLeading()) + auto remain{ cells }; + do { - lineEnd--; - } + // The maximum number of cells we can insert into a line. + const auto lineWidth = finalViewColumn - pos.X + 1; // +1 because view was inclusive - // Copy out the substring into a vector. - const std::vector lineVec(lineBegin, lineEnd); + auto [draw, left] = cells.split(::base::saturated_cast(lineWidth)); - // Add a conversion area to the internal state to hold this line. - THROW_IF_FAILED(_AddConversionArea()); + // Add a conversion area to the internal state to hold this line. + THROW_IF_FAILED(_AddConversionArea()); - // Get the added conversion area. - auto& area = ConvAreaCompStr.back(); + // Get the added conversion area. + auto& area = ConvAreaCompStr.back(); - // Write our text into the conversion area. - area.WriteText(lineVec, insertionPos.X); + area.WriteText(draw, pos.X); - // Set the viewport and positioning parameters for the conversion area to describe to the renderer - // the appropriate location to overlay this conversion area on top of the main screen buffer inside the viewport. - const SMALL_RECT region{ insertionPos.X, 0, gsl::narrow(insertionPos.X + lineVec.size() - 1), 0 }; - area.SetWindowInfo(region); - area.SetViewPos({ 0 - view.Left(), insertionPos.Y - view.Top() }); + // Set the viewport and positioning parameters for the conversion area to describe to the renderer + // the appropriate location to overlay this conversion area on top of the main screen buffer inside the viewport. + const SMALL_RECT region{ pos.X, 0, gsl::narrow(pos.X + draw._width - 1), 0 }; + area.SetWindowInfo(region); + area.SetViewPos({ 0 - view.Left(), pos.Y - view.Top() }); - // Make it visible and paint it. - area.SetHidden(false); - area.Paint(); + // Make it visible and paint it. + area.SetHidden(false); + area.Paint(); - // Notify accessibility that we have updated the text in this display region within the viewport. - if (screenInfo.HasAccessibilityEventing()) - { - screenInfo.NotifyAccessibilityEventing(insertionPos.X, insertionPos.Y, gsl::narrow(insertionPos.X + lineVec.size() - 1), insertionPos.Y); - } + // Notify accessibility that we have updated the text in this display region within the viewport. + if (screenInfo.HasAccessibilityEventing()) + { + screenInfo.NotifyAccessibilityEventing(pos.X, pos.Y, gsl::narrow(pos.X + draw._width - 1), pos.Y); + } - // Hand back the iterator representing the end of what we used to be fed into the beginning of the next call. - return lineEnd; + if (remain._data.empty()) + break; + + // Advance the cursor position to set up the next call for success (insert the next conversion area + // at the beginning of the following line) + pos.X = view.Left(); + pos.Y++; + remain = std::move(left); + } while (true); } // Routine Description: @@ -432,17 +390,7 @@ void ConsoleImeInfo::_WriteUndeterminedChars(const std::wstring_view text, const auto view = screenInfo.GetViewport(); // Set cursor position relative to viewport - // Set up our iterators. We will walk through the entire set of cells from beginning to end. - // The first time, we will give the iterators as the whole span and the begin - // will be moved forward by the conversion area write to set up the next call. - auto begin = cells.cbegin(); - const auto end = cells.cend(); - - // Write over and over updating the beginning iterator until we reach the end. - do - { - begin = _WriteConversionArea(begin, end, pos, view, screenInfo); - } while (begin < end); + _WriteConversionArea(cells, pos, view, screenInfo); } // Routine Description: diff --git a/src/host/conimeinfo.h b/src/host/conimeinfo.h index 2a3016ff0..11a167788 100644 --- a/src/host/conimeinfo.h +++ b/src/host/conimeinfo.h @@ -22,6 +22,7 @@ Revision History: #include "../buffer/out/OutputCell.hpp" #include "../buffer/out/TextAttribute.hpp" #include "../renderer/inc/FontInfo.hpp" +#include "../buffer/out/Row.hpp" #include "../types/inc/viewport.hpp" #include "conareainfo.h" @@ -73,15 +74,14 @@ private: const gsl::span attributes, const gsl::span colorArray); - static std::vector s_ConvertToCells(const std::wstring_view text, - const gsl::span attributes, - const gsl::span colorArray); + static RowImage s_ConvertToCells(std::wstring_view text, + const gsl::span attributes, + const gsl::span colorArray); - std::vector::const_iterator _WriteConversionArea(const std::vector::const_iterator begin, - const std::vector::const_iterator end, - COORD& pos, - const Microsoft::Console::Types::Viewport view, - SCREEN_INFORMATION& screenInfo); + void _WriteConversionArea(const RowImage& cells, + COORD pos, + const Microsoft::Console::Types::Viewport view, + SCREEN_INFORMATION& screenInfo); bool _isSavedCursorVisible; diff --git a/src/host/directio.cpp b/src/host/directio.cpp index 760a7ecf7..74aa855c3 100644 --- a/src/host/directio.cpp +++ b/src/host/directio.cpp @@ -1010,8 +1010,9 @@ void EventsToUnicode(_Inout_ std::deque>& inEvents, const auto charInfos = gsl::span(subspan.data(), subspan.size()); // Make the iterator and write to the target position. - OutputCellIterator it(charInfos); - storageBuffer.Write(it, target); + __debugbreak(); + //OutputCellIterator it(charInfos); + //storageBuffer.Write(it, target); } // Since we've managed to write part of the request, return the clamped part that we actually used. diff --git a/src/host/output.cpp b/src/host/output.cpp index eb710f98b..f875c05b3 100644 --- a/src/host/output.cpp +++ b/src/host/output.cpp @@ -104,8 +104,9 @@ static void _CopyRectangle(SCREEN_INFORMATION& screenInfo, do { - const auto data = OutputCell(*screenInfo.GetCellDataAt(sourcePos)); - screenInfo.Write(OutputCellIterator({ &data, 1 }), targetPos); + __debugbreak(); + //const auto data = OutputCell(*screenInfo.GetCellDataAt(sourcePos)); + //screenInfo.Write(OutputCellIterator({ &data, 1 }), targetPos); source.WalkInBounds(sourcePos, walkDirection); } while (target.WalkInBounds(targetPos, walkDirection)); diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index e9e0f7537..b20562072 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -2334,42 +2334,8 @@ OutputCellRect SCREEN_INFORMATION::ReadRect(const Viewport viewport) const return result; } -size_t SCREEN_INFORMATION::WriteMeasuredStringLinear(std::wstring_view string, const til::small_rle& measurements) -{ - return _textBuffer->WriteMeasuredStringLinear(_textBuffer->GetCursor().GetPosition(), string, measurements); -} - -// Routine Description: -// - Writes cells to the output buffer at the cursor position. -// Arguments: -// - it - Iterator representing output cell data to write. -// Return Value: -// - the iterator at its final position -// Note: -// - will throw exception on error. -OutputCellIterator SCREEN_INFORMATION::Write(const OutputCellIterator it) -{ - return _textBuffer->Write(it); -} - -// Routine Description: -// - Writes cells to the output buffer. -// Arguments: -// - it - Iterator representing output cell data to write. -// - target - The position to start writing at -// - wrap - change the wrap flag if we hit the end of the row while writing and there's still more data -// Return Value: -// - the iterator at its final position -// Note: -// - will throw exception on error. -OutputCellIterator SCREEN_INFORMATION::Write(const OutputCellIterator it, - const COORD target, - const std::optional wrap) -{ - // NOTE: if wrap = true/false, we want to set the line's wrap to true/false (respectively) if we reach the end of the line - return _textBuffer->Write(it, target, wrap); -} - +#if 0 +TODO(DH) // Routine Description: // - This routine writes a rectangular region into the screen buffer. // Arguments: @@ -2393,6 +2359,7 @@ OutputCellIterator SCREEN_INFORMATION::WriteRect(const OutputCellIterator it, return iter; } +#endif // Routine Description: // - This routine writes a rectangular region into the screen buffer. @@ -2406,6 +2373,10 @@ OutputCellIterator SCREEN_INFORMATION::WriteRect(const OutputCellIterator it, void SCREEN_INFORMATION::WriteRect(const OutputCellRect& data, const COORD location) { + (void)data; + (void)location; + __debugbreak(); +#if 0 for (size_t i = 0; i < data.Height(); i++) { const auto iter = data.GetRowIter(i); @@ -2414,8 +2385,10 @@ void SCREEN_INFORMATION::WriteRect(const OutputCellRect& data, point.X = location.X; point.Y = location.Y + static_cast(i); - _textBuffer->WriteLine(iter, point); + __debugbreak(); + //_textBuffer->WriteLine(iter, point); } +#endif } // Routine Description: diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp index 90ffea98c..870452952 100644 --- a/src/host/screenInfo.hpp +++ b/src/host/screenInfo.hpp @@ -135,15 +135,7 @@ public: TextBufferTextIterator GetTextLineDataAt(const COORD at) const; TextBufferTextIterator GetTextDataAt(const COORD at, const Microsoft::Console::Types::Viewport limit) const; - size_t WriteMeasuredStringLinear(std::wstring_view string, const til::small_rle& measurements); - OutputCellIterator Write(const OutputCellIterator it); - - OutputCellIterator Write(const OutputCellIterator it, - const COORD target, - const std::optional wrap = true); - - OutputCellIterator WriteRect(const OutputCellIterator it, - const Microsoft::Console::Types::Viewport viewport); + size_t WriteMeasuredStringLinear(std::wstring_view string, const RowMeasurementBuffer& measurements); void WriteRect(const OutputCellRect& data, const COORD location); diff --git a/src/inc/til/rle.h b/src/inc/til/rle.h index 10857ac7c..f84caf438 100644 --- a/src/inc/til/rle.h +++ b/src/inc/til/rle.h @@ -520,6 +520,22 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" _total_length = new_size; } + void append(const value_type& value) + { + ++_total_length; + if (!_runs.empty()) + { + auto& back{ _runs.back() }; + if (back.value == value) + { + ++back.length; + return; + } + } + // Either runs was empty, or we have to append a new run. + _runs.emplace_back(value, S{ 1 }); + } + constexpr bool operator==(const basic_rle& other) const noexcept { return _total_length == other._total_length && _runs == other._runs;