From 1761d6061fb65661aa9711632ccc33f542bd68b1 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 29 Jul 2022 17:43:15 +0200 Subject: [PATCH 01/19] Rewrite ROW to be Unicode capable --- .github/actions/spelling/allow/apis.txt | 9 +- .github/actions/spelling/expect/expect.txt | 2 + src/buffer/out/AttrRow.cpp | 134 ---- src/buffer/out/AttrRow.hpp | 68 -- src/buffer/out/CharRow.cpp | 281 -------- src/buffer/out/CharRow.hpp | 115 ---- src/buffer/out/CharRowCell.cpp | 73 -- src/buffer/out/CharRowCell.hpp | 65 -- src/buffer/out/CharRowCellReference.cpp | 136 ---- src/buffer/out/CharRowCellReference.hpp | 63 -- src/buffer/out/DbcsAttribute.hpp | 135 +--- src/buffer/out/LineRendition.hpp | 2 +- src/buffer/out/OutputCell.hpp | 2 +- src/buffer/out/OutputCellIterator.cpp | 43 +- src/buffer/out/OutputCellView.cpp | 15 +- src/buffer/out/OutputCellView.hpp | 2 +- src/buffer/out/Row.cpp | 626 +++++++++++++++--- src/buffer/out/Row.hpp | 132 ++-- src/buffer/out/UnicodeStorage.cpp | 98 --- src/buffer/out/UnicodeStorage.hpp | 66 -- src/buffer/out/lib/bufferout.vcxproj | 10 - src/buffer/out/search.cpp | 1 - src/buffer/out/textBuffer.cpp | 278 ++++---- src/buffer/out/textBuffer.hpp | 58 +- src/buffer/out/textBufferCellIterator.cpp | 20 +- src/buffer/out/textBufferCellIterator.hpp | 9 +- src/buffer/out/textBufferTextIterator.cpp | 1 - src/buffer/out/ut_textbuffer/ReflowTests.cpp | 65 +- .../TextBuffer.Unit.Tests.vcxproj | 3 +- .../out/ut_textbuffer/UnicodeStorageTests.cpp | 51 -- src/cascadia/TerminalControl/ControlCore.cpp | 13 +- .../TerminalCore/terminalrenderdata.cpp | 6 +- .../ConptyRoundtripTests.cpp | 6 +- src/host/_output.cpp | 2 - src/host/_stream.cpp | 3 +- src/host/conimeinfo.cpp | 8 +- src/host/consoleInformation.cpp | 2 +- src/host/output.cpp | 12 +- src/host/screenInfo.cpp | 10 +- src/host/selectionInput.cpp | 12 +- src/host/ut_host/OutputCellIteratorTests.cpp | 14 +- src/host/ut_host/ScreenBufferTests.cpp | 98 +-- src/host/ut_host/SelectionTests.cpp | 62 -- src/host/ut_host/TextBufferIteratorTests.cpp | 13 +- src/host/ut_host/TextBufferTests.cpp | 219 +++--- src/inc/test/CommonState.hpp | 131 ++-- .../UiaTextRangeTests.cpp | 33 +- src/renderer/base/renderer.cpp | 2 +- src/renderer/base/renderer.hpp | 1 - 49 files changed, 1116 insertions(+), 2094 deletions(-) delete mode 100644 src/buffer/out/AttrRow.cpp delete mode 100644 src/buffer/out/AttrRow.hpp delete mode 100644 src/buffer/out/CharRow.cpp delete mode 100644 src/buffer/out/CharRow.hpp delete mode 100644 src/buffer/out/CharRowCell.cpp delete mode 100644 src/buffer/out/CharRowCell.hpp delete mode 100644 src/buffer/out/CharRowCellReference.cpp delete mode 100644 src/buffer/out/CharRowCellReference.hpp delete mode 100644 src/buffer/out/UnicodeStorage.cpp delete mode 100644 src/buffer/out/UnicodeStorage.hpp delete mode 100644 src/buffer/out/ut_textbuffer/UnicodeStorageTests.cpp diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 4a8161c6aac..76d94179a08 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -32,10 +32,10 @@ DERR dlldata DNE DONTADDTORECENT -DWMWA -DWORDLONG DWMSBT DWMWA +DWMWA +DWORDLONG endfor ENDSESSION enumset @@ -108,8 +108,8 @@ memchr memicmp MENUCOMMAND MENUDATA -MENUITEMINFOW MENUINFO +MENUITEMINFOW mmeapi MOUSELEAVE mov @@ -155,8 +155,8 @@ rcx REGCLS RETURNCMD rfind -roundf ROOTOWNER +roundf RSHIFT SACL schandle @@ -208,6 +208,7 @@ UPDATEINIFILE userenv USEROBJECTFLAGS Viewbox +virtualalloc wcsstr wcstoui winmain diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 065cfd84a61..52596dbb1ea 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -2782,6 +2782,8 @@ xutr xvalue XVIRTUALSCREEN XWalk +xwwyzz +xxyyzz yact YAML YCast diff --git a/src/buffer/out/AttrRow.cpp b/src/buffer/out/AttrRow.cpp deleted file mode 100644 index 25def986e1a..00000000000 --- a/src/buffer/out/AttrRow.cpp +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "AttrRow.hpp" - -// Routine Description: -// - constructor -// Arguments: -// - cchRowWidth - the length of the default text attribute -// - attr - the default text attribute -// Return Value: -// - constructed object -ATTR_ROW::ATTR_ROW(const til::CoordType width, const TextAttribute attr) : - _data(gsl::narrow_cast(width), attr) {} - -// Routine Description: -// - Sets all properties of the ATTR_ROW to default values -// Arguments: -// - attr - The default text attributes to use on text in this row. -void ATTR_ROW::Reset(const TextAttribute attr) -{ - _data.replace(0, _data.size(), attr); -} - -// Routine Description: -// - Takes an existing row of attributes, and changes the length so that it fills the NewWidth. -// If the new size is bigger, then the last attr is extended to fill the NewWidth. -// If the new size is smaller, the runs are cut off to fit. -// Arguments: -// - oldWidth - The original width of the row. -// - newWidth - The new width of the row. -// Return Value: -// - , throws exceptions on failures. -void ATTR_ROW::Resize(const til::CoordType newWidth) -{ - _data.resize_trailing_extent(gsl::narrow(newWidth)); -} - -// Routine Description: -// - returns a copy of the TextAttribute at the specified column -// Arguments: -// - column - the column to get the attribute for -// Return Value: -// - the text attribute at column -// Note: -// - will throw on error -TextAttribute ATTR_ROW::GetAttrByColumn(const til::CoordType column) const -{ - return _data.at(gsl::narrow(column)); -} - -// Routine Description: -// - Finds the hyperlink IDs present in this row and returns them -// Return value: -// - The hyperlink IDs present in this row -std::vector ATTR_ROW::GetHyperlinks() const -{ - std::vector ids; - for (const auto& run : _data.runs()) - { - if (run.value.IsHyperlink()) - { - ids.emplace_back(run.value.GetHyperlinkId()); - } - } - return ids; -} - -// Routine Description: -// - Sets the attributes (colors) of all character positions from the given position through the end of the row. -// Arguments: -// - iStart - Starting index position within the row -// - attr - Attribute (color) to fill remaining characters with -// Return Value: -// - -bool ATTR_ROW::SetAttrToEnd(const til::CoordType beginIndex, const TextAttribute attr) -{ - _data.replace(gsl::narrow(beginIndex), _data.size(), attr); - return true; -} - -// Method Description: -// - Replaces all runs in the row with the given toBeReplacedAttr with the new -// attribute replaceWith. -// Arguments: -// - toBeReplacedAttr - the attribute to replace in this row. -// - replaceWith - the new value for the matching runs' attributes. -// Return Value: -// - -void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith) -{ - _data.replace_values(toBeReplacedAttr, replaceWith); -} - -// Routine Description: -// - Takes an attribute, and merges it into this row from beginIndex (inclusive) to endIndex (exclusive). -// - For example, if the current row was [{4, BLUE}], the merge arguments were -// { beginIndex = 1, endIndex = 3, newAttr = RED }, then the row would modified to be -// [{ 1, BLUE}, {2, RED}, {1, BLUE}]. -// Arguments: -// - beginIndex, endIndex: The [beginIndex, endIndex) range that's to be replaced with newAttr. -// - newAttr: The attribute to merge into this row. -// Return Value: -// - -void ATTR_ROW::Replace(const til::CoordType beginIndex, const til::CoordType endIndex, const TextAttribute& newAttr) -{ - _data.replace(gsl::narrow(beginIndex), gsl::narrow(endIndex), newAttr); -} - -ATTR_ROW::const_iterator ATTR_ROW::begin() const noexcept -{ - return _data.begin(); -} - -ATTR_ROW::const_iterator ATTR_ROW::end() const noexcept -{ - return _data.end(); -} - -ATTR_ROW::const_iterator ATTR_ROW::cbegin() const noexcept -{ - return _data.cbegin(); -} - -ATTR_ROW::const_iterator ATTR_ROW::cend() const noexcept -{ - return _data.cend(); -} - -bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept -{ - return a._data == b._data; -} diff --git a/src/buffer/out/AttrRow.hpp b/src/buffer/out/AttrRow.hpp deleted file mode 100644 index 5a6de23c4d6..00000000000 --- a/src/buffer/out/AttrRow.hpp +++ /dev/null @@ -1,68 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- AttrRow.hpp - -Abstract: -- contains data structure for the attributes of one row of screen buffer - -Author(s): -- Michael Niksa (miniksa) 10-Apr-2014 -- Paul Campbell (paulcam) 10-Apr-2014 - -Revision History: -- From components of output.h/.c - by Therese Stowell (ThereseS) 1990-1991 -- Pulled into its own file from textBuffer.hpp/cpp (AustDi, 2017) ---*/ - -#pragma once - -#include "til/rle.h" -#include "TextAttribute.hpp" - -class ATTR_ROW final -{ - using rle_vector = til::small_rle; - -public: - using const_iterator = rle_vector::const_iterator; - - ATTR_ROW(til::CoordType width, TextAttribute attr); - - ~ATTR_ROW() = default; - - ATTR_ROW(const ATTR_ROW&) = default; - ATTR_ROW& operator=(const ATTR_ROW&) = default; - ATTR_ROW(ATTR_ROW&&) - noexcept = default; - ATTR_ROW& operator=(ATTR_ROW&&) noexcept = default; - - TextAttribute GetAttrByColumn(til::CoordType column) const; - std::vector GetHyperlinks() const; - - bool SetAttrToEnd(til::CoordType beginIndex, TextAttribute attr); - void ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith); - void Resize(til::CoordType newWidth); - void Replace(til::CoordType beginIndex, til::CoordType endIndex, const TextAttribute& newAttr); - - const_iterator begin() const noexcept; - const_iterator end() const noexcept; - - const_iterator cbegin() const noexcept; - const_iterator cend() const noexcept; - - friend bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept; - friend class ROW; - -private: - void Reset(const TextAttribute attr); - - rle_vector _data; - -#ifdef UNIT_TESTING - friend class CommonState; -#endif -}; diff --git a/src/buffer/out/CharRow.cpp b/src/buffer/out/CharRow.cpp deleted file mode 100644 index 704adacf218..00000000000 --- a/src/buffer/out/CharRow.cpp +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "CharRow.hpp" -#include "unicode.hpp" -#include "Row.hpp" - -// Routine Description: -// - constructor -// Arguments: -// - rowWidth - the size (in wchar_t) of the char and attribute rows -// - pParent - the parent ROW -// Return Value: -// - instantiated object -// Note: will through if unable to allocate char/attribute buffers -#pragma warning(push) -#pragma warning(disable : 26447) // small_vector's constructor says it can throw but it should not given how we use it. This suppresses this error for the AuditMode build. -CharRow::CharRow(til::CoordType rowWidth, ROW* const pParent) noexcept : - _data(rowWidth, value_type()), - _pParent{ FAIL_FAST_IF_NULL(pParent) } -{ -} -#pragma warning(pop) - -// Routine Description: -// - gets the size of the row, in glyph cells -// Arguments: -// - -// Return Value: -// - the size of the row -til::CoordType CharRow::size() const noexcept -{ - return gsl::narrow_cast(_data.size()); -} - -// Routine Description: -// - Sets all properties of the CharRowBase to default values -// Arguments: -// - sRowWidth - The width of the row. -// Return Value: -// - -void CharRow::Reset() noexcept -{ - for (auto& cell : _data) - { - cell.Reset(); - } -} - -// Routine Description: -// - resizes the width of the CharRowBase -// Arguments: -// - newSize - the new width of the character and attributes rows -// Return Value: -// - S_OK on success, otherwise relevant error code -[[nodiscard]] HRESULT CharRow::Resize(const til::CoordType newSize) noexcept -{ - try - { - const value_type insertVals; - _data.resize(newSize, insertVals); - } - CATCH_RETURN(); - - return S_OK; -} - -typename CharRow::iterator CharRow::begin() noexcept -{ - return _data.begin(); -} - -typename CharRow::const_iterator CharRow::cbegin() const noexcept -{ - return _data.cbegin(); -} - -typename CharRow::iterator CharRow::end() noexcept -{ - return _data.end(); -} - -typename CharRow::const_iterator CharRow::cend() const noexcept -{ - return _data.cend(); -} - -// Routine Description: -// - Inspects the current internal string to find the left edge of it -// Arguments: -// - -// Return Value: -// - The calculated left boundary of the internal string. -til::CoordType CharRow::MeasureLeft() const noexcept -{ - auto it = _data.cbegin(); - while (it != _data.cend() && it->IsSpace()) - { - ++it; - } - return gsl::narrow_cast(it - _data.cbegin()); -} - -// Routine Description: -// - Inspects the current internal string to find the right edge of it -// Arguments: -// - -// Return Value: -// - The calculated right boundary of the internal string. -til::CoordType CharRow::MeasureRight() const -{ - auto it = _data.crbegin(); - while (it != _data.crend() && it->IsSpace()) - { - ++it; - } - return gsl::narrow_cast(_data.crend() - it); -} - -void CharRow::ClearCell(const til::CoordType column) -{ - _data.at(column).Reset(); -} - -// Routine Description: -// - Tells you whether or not this row contains any valid text. -// Arguments: -// - -// Return Value: -// - True if there is valid text in this row. False otherwise. -bool CharRow::ContainsText() const noexcept -{ - for (const auto& cell : _data) - { - if (!cell.IsSpace()) - { - return true; - } - } - return false; -} - -// Routine Description: -// - gets the attribute at the specified column -// Arguments: -// - column - the column to get the attribute for -// Return Value: -// - the attribute -// Note: will throw exception if column is out of bounds -const DbcsAttribute& CharRow::DbcsAttrAt(const til::CoordType column) const -{ - return _data.at(column).DbcsAttr(); -} - -// Routine Description: -// - gets the attribute at the specified column -// Arguments: -// - column - the column to get the attribute for -// Return Value: -// - the attribute -// Note: will throw exception if column is out of bounds -DbcsAttribute& CharRow::DbcsAttrAt(const til::CoordType column) -{ - return _data.at(column).DbcsAttr(); -} - -// Routine Description: -// - resets text data at column -// Arguments: -// - column - column index to clear text data from -// Return Value: -// - -// Note: will throw exception if column is out of bounds -void CharRow::ClearGlyph(const til::CoordType column) -{ - _data.at(column).EraseChars(); -} - -// Routine Description: -// - returns text data at column as a const reference. -// Arguments: -// - column - column to get text data for -// Return Value: -// - text data at column -// - Note: will throw exception if column is out of bounds -const CharRow::reference CharRow::GlyphAt(const til::CoordType column) const -{ - THROW_HR_IF(E_INVALIDARG, column < 0 || column >= gsl::narrow_cast(_data.size())); - return { const_cast(*this), column }; -} - -// Routine Description: -// - returns text data at column as a reference. -// Arguments: -// - column - column to get text data for -// Return Value: -// - text data at column -// - Note: will throw exception if column is out of bounds -CharRow::reference CharRow::GlyphAt(const til::CoordType column) -{ - THROW_HR_IF(E_INVALIDARG, column < 0 || column >= gsl::narrow_cast(_data.size())); - return { *this, column }; -} - -std::wstring CharRow::GetText() const -{ - std::wstring wstr; - wstr.reserve(_data.size()); - - for (til::CoordType i = 0; i < gsl::narrow_cast(_data.size()); ++i) - { - const auto glyph = GlyphAt(i); - if (!DbcsAttrAt(i).IsTrailing()) - { - for (const auto wch : glyph) - { - wstr.push_back(wch); - } - } - } - return wstr; -} - -// Method Description: -// - get delimiter class for a position in the char row -// - used for double click selection and uia word navigation -// Arguments: -// - column: column to get text data for -// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar -// Return Value: -// - the delimiter class for the given char -const DelimiterClass CharRow::DelimiterClassAt(const til::CoordType column, const std::wstring_view wordDelimiters) const -{ - THROW_HR_IF(E_INVALIDARG, column < 0 || column >= gsl::narrow_cast(_data.size())); - - const auto glyph = *GlyphAt(column).begin(); - if (glyph <= UNICODE_SPACE) - { - return DelimiterClass::ControlChar; - } - else if (wordDelimiters.find(glyph) != std::wstring_view::npos) - { - return DelimiterClass::DelimiterChar; - } - else - { - return DelimiterClass::RegularChar; - } -} - -UnicodeStorage& CharRow::GetUnicodeStorage() noexcept -{ - return _pParent->GetUnicodeStorage(); -} - -const UnicodeStorage& CharRow::GetUnicodeStorage() const noexcept -{ - return _pParent->GetUnicodeStorage(); -} - -// Routine Description: -// - calculates the key used by the given column of the char row to store glyph data in UnicodeStorage -// Arguments: -// - column - the column to generate the key for -// Return Value: -// - the til::point key for data access from UnicodeStorage for the column -til::point CharRow::GetStorageKey(const til::CoordType column) const noexcept -{ - return { column, _pParent->GetId() }; -} - -// Routine Description: -// - Updates the pointer to the parent row (which might change if we shuffle the rows around) -// Arguments: -// - pParent - Pointer to the parent row -void CharRow::UpdateParent(ROW* const pParent) -{ - _pParent = FAIL_FAST_IF_NULL(pParent); -} diff --git a/src/buffer/out/CharRow.hpp b/src/buffer/out/CharRow.hpp deleted file mode 100644 index 7e18cb6fb3e..00000000000 --- a/src/buffer/out/CharRow.hpp +++ /dev/null @@ -1,115 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- CharRow.hpp - -Abstract: -- contains data structure for UCS2 encoded character data of a row - -Author(s): -- Michael Niksa (miniksa) 10-Apr-2014 -- Paul Campbell (paulcam) 10-Apr-2014 - -Revision History: -- From components of output.h/.c - by Therese Stowell (ThereseS) 1990-1991 -- Pulled into its own file from textBuffer.hpp/cpp (AustDi, 2017) ---*/ - -#pragma once - -#include "DbcsAttribute.hpp" -#include "CharRowCellReference.hpp" -#include "CharRowCell.hpp" -#include "UnicodeStorage.hpp" - -class ROW; - -enum class DelimiterClass -{ - ControlChar, - DelimiterChar, - RegularChar -}; - -// the characters of one row of screen buffer -// we keep the following values so that we don't write -// more pixels to the screen than we have to: -// left is initialized to screenbuffer width. right is -// initialized to zero. -// -// [ foo.bar 12-12-61 ] -// ^ ^ ^ ^ -// | | | | -// Chars Left Right end of Chars buffer -class CharRow final -{ -public: - using glyph_type = typename wchar_t; - using value_type = typename CharRowCell; - using iterator = typename boost::container::small_vector_base::iterator; - using const_iterator = typename boost::container::small_vector_base::const_iterator; - using const_reverse_iterator = typename boost::container::small_vector_base::const_reverse_iterator; - using reference = typename CharRowCellReference; - - CharRow(til::CoordType rowWidth, ROW* const pParent) noexcept; - - til::CoordType size() const noexcept; - [[nodiscard]] HRESULT Resize(const til::CoordType newSize) noexcept; - til::CoordType MeasureLeft() const noexcept; - til::CoordType MeasureRight() const; - bool ContainsText() const noexcept; - const DbcsAttribute& DbcsAttrAt(const til::CoordType column) const; - DbcsAttribute& DbcsAttrAt(const til::CoordType column); - void ClearGlyph(const til::CoordType column); - - const DelimiterClass DelimiterClassAt(const til::CoordType column, const std::wstring_view wordDelimiters) const; - - // working with glyphs - const reference GlyphAt(const til::CoordType column) const; - reference GlyphAt(const til::CoordType column); - - // iterators - iterator begin() noexcept; - const_iterator cbegin() const noexcept; - const_iterator begin() const noexcept { return cbegin(); } - - iterator end() noexcept; - const_iterator cend() const noexcept; - const_iterator end() const noexcept { return cend(); } - - UnicodeStorage& GetUnicodeStorage() noexcept; - const UnicodeStorage& GetUnicodeStorage() const noexcept; - til::point GetStorageKey(const til::CoordType column) const noexcept; - - void UpdateParent(ROW* const pParent); - - friend CharRowCellReference; - friend class ROW; - -private: - void Reset() noexcept; - void ClearCell(const til::CoordType column); - std::wstring GetText() const; - -protected: - // storage for glyph data and dbcs attributes - boost::container::small_vector _data; - - // ROW that this CharRow belongs to - ROW* _pParent; -}; - -template -void OverwriteColumns(InputIt1 startChars, InputIt1 endChars, InputIt2 startAttrs, CharRow::iterator outIt) -{ - std::transform(startChars, - endChars, - startAttrs, - outIt, - [](const wchar_t wch, const DbcsAttribute attr) { - return CharRow::value_type{ wch, attr }; - }); -} diff --git a/src/buffer/out/CharRowCell.cpp b/src/buffer/out/CharRowCell.cpp deleted file mode 100644 index 2ada05d3c50..00000000000 --- a/src/buffer/out/CharRowCell.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -#include "precomp.h" - -#include "CharRowCell.hpp" -#include "unicode.hpp" - -// default glyph value, used for resetting the character data portion of a cell -static constexpr wchar_t DefaultValue = UNICODE_SPACE; - -// Routine Description: -// - "erases" the glyph. really sets it back to the default "empty" value -void CharRowCell::EraseChars() noexcept -{ - if (_attr.IsGlyphStored()) - { - _attr.SetGlyphStored(false); - } - _wch = DefaultValue; -} - -// Routine Description: -// - resets this object back to the defaults it would have from the default constructor -void CharRowCell::Reset() noexcept -{ - _attr.Reset(); - _wch = DefaultValue; -} - -// Routine Description: -// - checks if cell contains a space glyph -// Return Value: -// - true if cell contains a space glyph, false otherwise -bool CharRowCell::IsSpace() const noexcept -{ - return !_attr.IsGlyphStored() && _wch == UNICODE_SPACE; -} - -// Routine Description: -// - Access the DbcsAttribute for the cell -// Return Value: -// - ref to the cells' DbcsAttribute -DbcsAttribute& CharRowCell::DbcsAttr() noexcept -{ - return _attr; -} - -// Routine Description: -// - Access the DbcsAttribute for the cell -// Return Value: -// - ref to the cells' DbcsAttribute -const DbcsAttribute& CharRowCell::DbcsAttr() const noexcept -{ - return _attr; -} - -// Routine Description: -// - Access the cell's wchar field. this does not access any char data through UnicodeStorage. -// Return Value: -// - the cell's wchar field -wchar_t& CharRowCell::Char() noexcept -{ - return _wch; -} - -// Routine Description: -// - Access the cell's wchar field. this does not access any char data through UnicodeStorage. -// Return Value: -// - the cell's wchar field -const wchar_t& CharRowCell::Char() const noexcept -{ - return _wch; -} diff --git a/src/buffer/out/CharRowCell.hpp b/src/buffer/out/CharRowCell.hpp deleted file mode 100644 index 2e68a51efb6..00000000000 --- a/src/buffer/out/CharRowCell.hpp +++ /dev/null @@ -1,65 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- CharRowCell.hpp - -Abstract: -- data structure for one cell of a char row. contains the char data for one - coordinate position in the output buffer (leading/trailing information and - the char itself. - -Author(s): -- Austin Diviness (AustDi) 02-May-2018 ---*/ - -#pragma once - -#include "DbcsAttribute.hpp" -#include "unicode.hpp" - -#if (defined(_M_IX86) || defined(_M_AMD64)) -// currently CharRowCell's fields use 3 bytes of memory, leaving the 4th byte in unused. this leads -// to a rather large amount of useless memory allocated. so instead, pack CharRowCell by bytes instead of words. -#pragma pack(push, 1) -#endif - -class CharRowCell final -{ -public: - CharRowCell() noexcept = default; - CharRowCell(const wchar_t wch, const DbcsAttribute attr) noexcept - : - _wch(wch), - _attr(attr) - { - } - - void EraseChars() noexcept; - void Reset() noexcept; - - bool IsSpace() const noexcept; - - DbcsAttribute& DbcsAttr() noexcept; - const DbcsAttribute& DbcsAttr() const noexcept; - - wchar_t& Char() noexcept; - const wchar_t& Char() const noexcept; - - friend constexpr bool operator==(const CharRowCell& a, const CharRowCell& b) noexcept; - -private: - wchar_t _wch{ UNICODE_SPACE }; - DbcsAttribute _attr{}; -}; - -#if (defined(_M_IX86) || defined(_M_AMD64)) -#pragma pack(pop) -#endif - -constexpr bool operator==(const CharRowCell& a, const CharRowCell& b) noexcept -{ - return (a._wch == b._wch && - a._attr == b._attr); -} diff --git a/src/buffer/out/CharRowCellReference.cpp b/src/buffer/out/CharRowCellReference.cpp deleted file mode 100644 index ff5e5da8f14..00000000000 --- a/src/buffer/out/CharRowCellReference.cpp +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "UnicodeStorage.hpp" -#include "CharRow.hpp" - -// Routine Description: -// - assignment operator. will store extended glyph data in a separate storage location -// Arguments: -// - chars - the glyph data to store -void CharRowCellReference::operator=(const std::wstring_view chars) -{ - THROW_HR_IF(E_INVALIDARG, chars.empty()); - if (chars.size() == 1) - { - _cellData().Char() = chars.front(); - _cellData().DbcsAttr().SetGlyphStored(false); - } - else - { - auto& storage = _parent.GetUnicodeStorage(); - const auto key = _parent.GetStorageKey(_index); - storage.StoreGlyph(key, { chars.cbegin(), chars.cend() }); - _cellData().DbcsAttr().SetGlyphStored(true); - } -} - -// Routine Description: -// - implicit conversion to vector operator. -// Return Value: -// - std::vector of the glyph data in the referenced cell -CharRowCellReference::operator std::wstring_view() const -{ - return _glyphData(); -} - -// Routine Description: -// - The CharRowCell this object "references" -// Return Value: -// - ref to the CharRowCell -CharRowCell& CharRowCellReference::_cellData() -{ - return _parent._data.at(_index); -} - -// Routine Description: -// - The CharRowCell this object "references" -// Return Value: -// - ref to the CharRowCell -const CharRowCell& CharRowCellReference::_cellData() const -{ - return _parent._data.at(_index); -} - -// Routine Description: -// - the glyph data of the referenced cell -// Return Value: -// - the glyph data -std::wstring_view CharRowCellReference::_glyphData() const -{ - if (_cellData().DbcsAttr().IsGlyphStored()) - { - const auto& text = _parent.GetUnicodeStorage().GetText(_parent.GetStorageKey(_index)); - - return { text.data(), text.size() }; - } - else - { - return { &_cellData().Char(), 1 }; - } -} - -// Routine Description: -// - gets read-only iterator to the beginning of the glyph data -// Return Value: -// - iterator of the glyph data -CharRowCellReference::const_iterator CharRowCellReference::begin() const -{ - if (_cellData().DbcsAttr().IsGlyphStored()) - { - return _parent.GetUnicodeStorage().GetText(_parent.GetStorageKey(_index)).data(); - } - else - { - return &_cellData().Char(); - } -} - -// Routine Description: -// - get read-only iterator to the end of the glyph data -// Return Value: -// - end iterator of the glyph data -#pragma warning(push) -#pragma warning(disable : 26481) -// TODO GH 2672: eliminate using pointers raw as begin/end markers in this class -CharRowCellReference::const_iterator CharRowCellReference::end() const -{ - if (_cellData().DbcsAttr().IsGlyphStored()) - { - const auto& chars = _parent.GetUnicodeStorage().GetText(_parent.GetStorageKey(_index)); - return chars.data() + chars.size(); - } - else - { - return &_cellData().Char() + 1; - } -} -#pragma warning(pop) - -bool operator==(const CharRowCellReference& ref, const std::vector& glyph) -{ - const auto& dbcsAttr = ref._cellData().DbcsAttr(); - if (glyph.size() == 1 && dbcsAttr.IsGlyphStored()) - { - return false; - } - else if (glyph.size() > 1 && !dbcsAttr.IsGlyphStored()) - { - return false; - } - else if (glyph.size() == 1 && !dbcsAttr.IsGlyphStored()) - { - return ref._cellData().Char() == glyph.front(); - } - else - { - const auto& chars = ref._parent.GetUnicodeStorage().GetText(ref._parent.GetStorageKey(ref._index)); - return chars == glyph; - } -} - -bool operator==(const std::vector& glyph, const CharRowCellReference& ref) -{ - return ref == glyph; -} diff --git a/src/buffer/out/CharRowCellReference.hpp b/src/buffer/out/CharRowCellReference.hpp deleted file mode 100644 index ecbae5809e5..00000000000 --- a/src/buffer/out/CharRowCellReference.hpp +++ /dev/null @@ -1,63 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- CharRowCellReference.hpp - -Abstract: -- reference class for the glyph data of a char row cell - -Author(s): -- Austin Diviness (AustDi) 02-May-2018 ---*/ - -#pragma once - -#include "DbcsAttribute.hpp" -#include "CharRowCell.hpp" -#include - -class CharRow; - -class CharRowCellReference final -{ -public: - using const_iterator = const wchar_t*; - - CharRowCellReference(CharRow& parent, const til::CoordType index) noexcept : - _parent{ parent }, - _index{ index } - { - } - - ~CharRowCellReference() = default; - CharRowCellReference(const CharRowCellReference&) noexcept = default; - CharRowCellReference(CharRowCellReference&&) noexcept = default; - - void operator=(const CharRowCellReference&) = delete; - void operator=(CharRowCellReference&&) = delete; - - void operator=(const std::wstring_view chars); - operator std::wstring_view() const; - - const_iterator begin() const; - const_iterator end() const; - - friend bool operator==(const CharRowCellReference& ref, const std::vector& glyph); - friend bool operator==(const std::vector& glyph, const CharRowCellReference& ref); - -private: - // what char row the object belongs to - CharRow& _parent; - // the index of the cell in the parent char row - til::CoordType _index; - - CharRowCell& _cellData(); - const CharRowCell& _cellData() const; - - std::wstring_view _glyphData() const; -}; - -bool operator==(const CharRowCellReference& ref, const std::vector& glyph); -bool operator==(const std::vector& glyph, const CharRowCellReference& ref); diff --git a/src/buffer/out/DbcsAttribute.hpp b/src/buffer/out/DbcsAttribute.hpp index 442505052c3..4e5ca88be56 100644 --- a/src/buffer/out/DbcsAttribute.hpp +++ b/src/buffer/out/DbcsAttribute.hpp @@ -16,129 +16,22 @@ Revision History: #pragma once -class DbcsAttribute final +enum class DbcsAttribute : uint8_t { -public: - enum class Attribute : BYTE - { - Single = 0x00, - Leading = 0x01, - Trailing = 0x02 - }; - - DbcsAttribute() noexcept : - _attribute{ Attribute::Single }, - _glyphStored{ false } - { - } - - DbcsAttribute(const Attribute attribute) noexcept : - _attribute{ attribute }, - _glyphStored{ false } - { - } - - constexpr bool IsSingle() const noexcept - { - return _attribute == Attribute::Single; - } - - constexpr bool IsLeading() const noexcept - { - return _attribute == Attribute::Leading; - } - - constexpr bool IsTrailing() const noexcept - { - return _attribute == Attribute::Trailing; - } - - constexpr bool IsDbcs() const noexcept - { - return IsLeading() || IsTrailing(); - } - - constexpr bool IsGlyphStored() const noexcept - { - return _glyphStored; - } - - void SetGlyphStored(const bool stored) noexcept - { - _glyphStored = stored; - } - - void SetSingle() noexcept - { - _attribute = Attribute::Single; - } - - void SetLeading() noexcept - { - _attribute = Attribute::Leading; - } - - void SetTrailing() noexcept - { - _attribute = Attribute::Trailing; - } - - void Reset() noexcept - { - SetSingle(); - SetGlyphStored(false); - } - - WORD GeneratePublicApiAttributeFormat() const noexcept - { - WORD publicAttribute = 0; - if (IsLeading()) - { - WI_SetFlag(publicAttribute, COMMON_LVB_LEADING_BYTE); - } - if (IsTrailing()) - { - WI_SetFlag(publicAttribute, COMMON_LVB_TRAILING_BYTE); - } - return publicAttribute; - } - - static DbcsAttribute FromPublicApiAttributeFormat(WORD publicAttribute) - { - // it's not valid to be both a leading and trailing byte - if (WI_AreAllFlagsSet(publicAttribute, COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE)) - { - THROW_HR(E_INVALIDARG); - } - - DbcsAttribute attr; - if (WI_IsFlagSet(publicAttribute, COMMON_LVB_LEADING_BYTE)) - { - attr.SetLeading(); - } - else if (WI_IsFlagSet(publicAttribute, COMMON_LVB_TRAILING_BYTE)) - { - attr.SetTrailing(); - } - return attr; - } - - friend constexpr bool operator==(const DbcsAttribute& a, const DbcsAttribute& b) noexcept; - -private: - Attribute _attribute : 2; - bool _glyphStored : 1; - -#ifdef UNIT_TESTING - friend class TextBufferTests; -#endif + Single, + Leading, + Trailing, }; -constexpr bool operator==(const DbcsAttribute& a, const DbcsAttribute& b) noexcept +constexpr WORD GeneratePublicApiAttributeFormat(DbcsAttribute attribute) noexcept { - return a._attribute == b._attribute; + switch (attribute) + { + case DbcsAttribute::Leading: + return COMMON_LVB_LEADING_BYTE; + case DbcsAttribute::Trailing: + return COMMON_LVB_TRAILING_BYTE; + default: + return 0; + } } - -static_assert(sizeof(DbcsAttribute) == sizeof(BYTE), "DbcsAttribute should be one byte big. if this changes then it needs " - "either an implicit conversion to a BYTE or an update to all places " - "that assume it's a byte big"); diff --git a/src/buffer/out/LineRendition.hpp b/src/buffer/out/LineRendition.hpp index db7a5489ca7..397c9ee95ab 100644 --- a/src/buffer/out/LineRendition.hpp +++ b/src/buffer/out/LineRendition.hpp @@ -13,7 +13,7 @@ Module Name: #pragma once -enum class LineRendition +enum class LineRendition : uint8_t { SingleWidth, DoubleWidth, diff --git a/src/buffer/out/OutputCell.hpp b/src/buffer/out/OutputCell.hpp index da9482e5d93..5dd51267051 100644 --- a/src/buffer/out/OutputCell.hpp +++ b/src/buffer/out/OutputCell.hpp @@ -80,7 +80,7 @@ class OutputCell final // basic_string contains a small storage internally so we don't need // to worry about heap allocation for short strings. std::wstring _text; - DbcsAttribute _dbcsAttribute; + DbcsAttribute _dbcsAttribute = DbcsAttribute::Single; TextAttribute _textAttribute; TextAttributeBehavior _behavior; diff --git a/src/buffer/out/OutputCellIterator.cpp b/src/buffer/out/OutputCellIterator.cpp index 1b585cd28c5..4f2c9555be0 100644 --- a/src/buffer/out/OutputCellIterator.cpp +++ b/src/buffer/out/OutputCellIterator.cpp @@ -240,13 +240,10 @@ OutputCellIterator& OutputCellIterator::operator++() { if (!_TryMoveTrailing()) { - if (_currentView.DbcsAttr().IsTrailing()) + if (_currentView.DbcsAttr() == DbcsAttribute::Trailing) { - auto dbcsAttr = _currentView.DbcsAttr(); - dbcsAttr.SetLeading(); - _currentView = OutputCellView(_currentView.Chars(), - dbcsAttr, + DbcsAttribute::Leading, _currentView.TextAttr(), _currentView.TextAttrBehavior()); } @@ -336,13 +333,10 @@ const OutputCellView* OutputCellIterator::operator->() const noexcept // - False if this wasn't applicable and the caller should update the view. bool OutputCellIterator::_TryMoveTrailing() noexcept { - if (_currentView.DbcsAttr().IsLeading()) + if (_currentView.DbcsAttr() == DbcsAttribute::Leading) { - auto dbcsAttr = _currentView.DbcsAttr(); - dbcsAttr.SetTrailing(); - _currentView = OutputCellView(_currentView.Chars(), - dbcsAttr, + DbcsAttribute::Trailing, _currentView.TextAttr(), _currentView.TextAttrBehavior()); return true; @@ -399,12 +393,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, const TextAttributeBehavior behavior) { const auto glyph = Utf16Parser::ParseNext(view); - DbcsAttribute dbcsAttr; - if (IsGlyphFullWidth(glyph)) - { - dbcsAttr.SetLeading(); - } - + const auto dbcsAttr = IsGlyphFullWidth(glyph) ? DbcsAttribute::Leading : DbcsAttribute::Single; return OutputCellView(glyph, dbcsAttr, attr, behavior); } @@ -420,13 +409,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch) noexcept { const auto glyph = std::wstring_view(&wch, 1); - - DbcsAttribute dbcsAttr; - if (IsGlyphFullWidth(wch)) - { - dbcsAttr.SetLeading(); - } - + const auto dbcsAttr = IsGlyphFullWidth(wch) ? DbcsAttribute::Leading : DbcsAttribute::Single; return OutputCellView(glyph, dbcsAttr, InvalidTextAttribute, TextAttributeBehavior::Current); } @@ -457,13 +440,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const TextAttribute& attr) noe OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch, const TextAttribute& attr) noexcept { const auto glyph = std::wstring_view(&wch, 1); - - DbcsAttribute dbcsAttr; - if (IsGlyphFullWidth(wch)) - { - dbcsAttr.SetLeading(); - } - + const auto dbcsAttr = IsGlyphFullWidth(wch) ? DbcsAttribute::Leading : DbcsAttribute::Single; return OutputCellView(glyph, dbcsAttr, attr, TextAttributeBehavior::Stored); } @@ -498,14 +475,14 @@ OutputCellView OutputCellIterator::s_GenerateView(const CHAR_INFO& charInfo) noe { const auto glyph = std::wstring_view(&charInfo.Char.UnicodeChar, 1); - DbcsAttribute dbcsAttr; + DbcsAttribute dbcsAttr = DbcsAttribute::Single; if (WI_IsFlagSet(charInfo.Attributes, COMMON_LVB_LEADING_BYTE)) { - dbcsAttr.SetLeading(); + dbcsAttr = DbcsAttribute::Leading; } else if (WI_IsFlagSet(charInfo.Attributes, COMMON_LVB_TRAILING_BYTE)) { - dbcsAttr.SetTrailing(); + dbcsAttr = DbcsAttribute::Trailing; } const TextAttribute textAttr(charInfo.Attributes); diff --git a/src/buffer/out/OutputCellView.cpp b/src/buffer/out/OutputCellView.cpp index 5b454fe396f..0b47a23bf2e 100644 --- a/src/buffer/out/OutputCellView.cpp +++ b/src/buffer/out/OutputCellView.cpp @@ -40,20 +40,7 @@ OutputCellView::OutputCellView(const std::wstring_view view, // - Count of column cells on the screen til::CoordType OutputCellView::Columns() const noexcept { - if (DbcsAttr().IsSingle()) - { - return 1; - } - else if (DbcsAttr().IsLeading()) - { - return 2; - } - else if (DbcsAttr().IsTrailing()) - { - return 1; - } - - return 1; + return DbcsAttr() == DbcsAttribute::Leading ? 2 : 1; } // Routine Description: diff --git a/src/buffer/out/OutputCellView.hpp b/src/buffer/out/OutputCellView.hpp index 65d0ffac074..e32d866bbbb 100644 --- a/src/buffer/out/OutputCellView.hpp +++ b/src/buffer/out/OutputCellView.hpp @@ -56,7 +56,7 @@ class OutputCellView private: std::wstring_view _view; - DbcsAttribute _dbcsAttr; + DbcsAttribute _dbcsAttr = DbcsAttribute::Single; TextAttribute _textAttr; TextAttributeBehavior _behavior; }; diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 362dffe9124..3be6de8d887 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -3,29 +3,129 @@ #include "precomp.h" #include "Row.hpp" -#include "CharRow.hpp" + #include "textBuffer.hpp" -#include "../types/inc/convert.hpp" + +// The STL is missing a std::iota_n analogue for std::iota, so I made my own. +template +constexpr OutIt iota_n(OutIt dest, Diff count, T val) +{ + for (; count; --count, ++dest, ++val) + { + *dest = val; + } + return dest; +} + +// ROW::ReplaceCharacters needs to calculate `val + count` after +// calling iota_n() and this function achieves both things at once. +template +constexpr OutIt iota_n_mut(OutIt dest, Diff count, T& val) +{ + for (; count; --count, ++dest, ++val) + { + *dest = val; + } + return dest; +} + +// Same as std::fill, but purpose-built for very small `last - first` +// where a trivial loop outperforms vectorization. +template +constexpr FwdIt fill_small(FwdIt first, FwdIt last, const T val) +{ + for (; first != last; ++first) + { + *first = val; + } + return first; +} + +// Same as std::fill_n, but purpose-built for very small `count` +// where a trivial loop outperforms vectorization. +template +constexpr OutIt fill_n_small(OutIt dest, Diff count, const T val) +{ + for (; count; --count, ++dest) + { + *dest = val; + } + return dest; +} + +// Same as std::copy_n, but purpose-built for very short `count` +// where a trivial loop outperforms vectorization. +template +constexpr OutIt copy_n_small(InIt first, Diff count, OutIt dest) +{ + for (; count; --count, ++dest, ++first) + { + *dest = *first; + } + return dest; +} // Routine Description: // - constructor // Arguments: -// - rowId - the row index in the text buffer // - rowWidth - the width of the row, cell elements // - fillAttribute - the default text attribute -// - pParent - the text buffer that this row belongs to // Return Value: // - constructed object -ROW::ROW(const til::CoordType rowId, const til::CoordType rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent) : - _id{ rowId }, - _rowWidth{ rowWidth }, - _charRow{ rowWidth, this }, - _attrRow{ rowWidth, fillAttribute }, - _lineRendition{ LineRendition::SingleWidth }, - _wrapForced{ false }, - _doubleBytePadded{ false }, - _pParent{ pParent } +ROW::ROW(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute) : + _charsBuffer{ charsBuffer }, + _chars{ charsBuffer, rowWidth }, + _charOffsets{ charOffsetsBuffer, ::base::strict_cast(rowWidth) + 1u }, + _attr{ rowWidth, fillAttribute }, + _columnCount{ rowWidth } { + if (_chars.data()) + { + _init(); + } +} + +void swap(ROW& lhs, ROW& rhs) noexcept +{ + std::swap(lhs._charsBuffer, rhs._charsBuffer); + std::swap(lhs._charsHeap, rhs._charsHeap); + std::swap(lhs._chars, rhs._chars); + std::swap(lhs._charOffsets, rhs._charOffsets); + std::swap(lhs._attr, rhs._attr); + std::swap(lhs._columnCount, rhs._columnCount); + std::swap(lhs._lineRendition, rhs._lineRendition); + std::swap(lhs._wrapForced, rhs._wrapForced); + std::swap(lhs._doubleBytePadded, rhs._doubleBytePadded); +} + +void ROW::SetWrapForced(const bool wrap) noexcept +{ + _wrapForced = wrap; +} + +bool ROW::WasWrapForced() const noexcept +{ + return _wrapForced; +} + +void ROW::SetDoubleBytePadded(const bool doubleBytePadded) noexcept +{ + _doubleBytePadded = doubleBytePadded; +} + +bool ROW::WasDoubleBytePadded() const noexcept +{ + return _doubleBytePadded; +} + +LineRendition ROW::GetLineRendition() const noexcept +{ + return _lineRendition; +} + +void ROW::SetLineRendition(const LineRendition lineRendition) noexcept +{ + _lineRendition = lineRendition; } // Routine Description: @@ -34,22 +134,15 @@ ROW::ROW(const til::CoordType rowId, const til::CoordType rowWidth, const TextAt // - Attr - The default attribute (color) to fill // Return Value: // - -bool ROW::Reset(const TextAttribute Attr) +void ROW::Reset(const TextAttribute& attr) { + _charsHeap.reset(); + _chars = { _charsBuffer, _columnCount }; + _attr = { _columnCount, attr }; _lineRendition = LineRendition::SingleWidth; _wrapForced = false; _doubleBytePadded = false; - _charRow.Reset(); - try - { - _attrRow.Reset(Attr); - } - catch (...) - { - LOG_CAUGHT_EXCEPTION(); - return false; - } - return true; + _init(); } // Routine Description: @@ -58,18 +151,75 @@ bool ROW::Reset(const TextAttribute Attr) // - width - the new width, in cells // Return Value: // - S_OK if successful, otherwise relevant error -[[nodiscard]] HRESULT ROW::Resize(const til::CoordType width) +void ROW::Resize(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute) { - RETURN_IF_FAILED(_charRow.Resize(width)); - try + // A default-constructed ROW has no cols/chars to copy. + // It can be detected by the lack of a _charsBuffer (among others). + // + // Otherwise, this block figures out how much we can copy into the new `rowWidth`. + uint16_t colsToCopy = 0; + uint16_t charsToCopy = 0; + if (_charsBuffer) { - _attrRow.Resize(width); + colsToCopy = std::min(rowWidth, _columnCount); + // Safety: colsToCopy is [0, _columnCount]. + charsToCopy = _uncheckedCharOffset(colsToCopy); + // Safety: colsToCopy is [0, _columnCount] due to colsToCopy != 0. + for (; colsToCopy != 0 && _uncheckedIsTrailer(colsToCopy); --colsToCopy) + { + } } - CATCH_RETURN(); - _rowWidth = width; + // If we grow the row width, we have to append a bunch of whitespace. + // `trailingWhitespace` stores that amount. + // Safety: The preceding block left colsToCopy in the range [0, rowWidth]. + const uint16_t trailingWhitespace = rowWidth - colsToCopy; - return S_OK; + // Allocate memory for the new `_chars` array. + // Use the provided charsBuffer if possible, otherwise allocate a `_charsHeap`. + std::unique_ptr charsHeap; + std::span chars{ charsBuffer, rowWidth }; + const std::span charOffsets{ charOffsetsBuffer, ::base::strict_cast(rowWidth) + 1u }; + if (const uint16_t charsCapacity = charsToCopy + trailingWhitespace; charsCapacity > rowWidth) + { + charsHeap = std::make_unique_for_overwrite(charsCapacity); + chars = { charsHeap.get(), charsCapacity }; + } + + // Copy chars and charOffsets over. + { + const auto it = std::copy_n(_chars.begin(), charsToCopy, chars.begin()); + std::fill_n(it, trailingWhitespace, L' '); + } + { + const auto it = std::copy_n(_charOffsets.begin(), colsToCopy, charOffsets.begin()); + // The _charOffsets array is 1 wider than newWidth indicates. + // This is because the extra column contains the past-the-end index into _chars. + iota_n(it, trailingWhitespace + 1u, charsToCopy); + } + + _charsBuffer = charsBuffer; + _charsHeap = std::move(charsHeap); + _chars = chars; + _charOffsets = charOffsets; + _columnCount = rowWidth; + + // .resize_trailing_extent() doesn't work if the vector is empty, + // since there's no trailing item that could be extended. + if (_attr.empty()) + { + _attr = { rowWidth, fillAttribute }; + } + else + { + _attr.resize_trailing_extent(rowWidth); + } +} + +void ROW::TransferAttributes(const til::small_rle& attr, til::CoordType newWidth) +{ + _attr = attr; + _attr.resize_trailing_extent(gsl::narrow(newWidth)); } // Routine Description: @@ -78,20 +228,10 @@ bool ROW::Reset(const TextAttribute Attr) // - column - 0-indexed column index // Return Value: // - -void ROW::ClearColumn(const til::CoordType column) -{ - THROW_HR_IF(E_INVALIDARG, column >= _charRow.size()); - _charRow.ClearCell(column); -} - -UnicodeStorage& ROW::GetUnicodeStorage() noexcept -{ - return _pParent->GetUnicodeStorage(); -} - -const UnicodeStorage& ROW::GetUnicodeStorage() const noexcept +void ROW::ClearCell(const til::CoordType column) { - return _pParent->GetUnicodeStorage(); + static constexpr std::wstring_view space{ L" " }; + ReplaceCharacters(column, 1, space); } // Routine Description: @@ -103,17 +243,17 @@ const UnicodeStorage& ROW::GetUnicodeStorage() const noexcept // - limitRight - right inclusive column ID for the last write in this row. (optional, will just write to the end of row if nullopt) // Return Value: // - iterator to first cell that was not written to this row. -OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType index, const std::optional wrap, std::optional limitRight) +OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType columnBegin, const std::optional wrap, std::optional limitRight) { - THROW_HR_IF(E_INVALIDARG, index >= _charRow.size()); - THROW_HR_IF(E_INVALIDARG, limitRight.value_or(0) >= _charRow.size()); + THROW_HR_IF(E_INVALIDARG, columnBegin >= size()); + THROW_HR_IF(E_INVALIDARG, limitRight.value_or(0) >= size()); // If we're given a right-side column limit, use it. Otherwise, the write limit is the final column index available in the char row. - const auto finalColumnInRow = limitRight.value_or(_charRow.size() - 1); + const auto finalColumnInRow = limitRight.value_or(size() - 1); auto currentColor = it->TextAttr(); uint16_t colorUses = 0; - auto colorStarts = gsl::narrow_cast(index); + auto colorStarts = gsl::narrow_cast(columnBegin); auto currentIndex = colorStarts; while (it && currentIndex <= finalColumnInRow) @@ -131,7 +271,7 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType i { // Otherwise, commit this color into the run and save off the new one. // Now commit the new color runs into the attr row. - _attrRow.Replace(colorStarts, currentIndex, currentColor); + _attr.replace(colorStarts, currentIndex, currentColor); currentColor = it->TextAttr(); colorUses = 1; colorStarts = currentIndex; @@ -141,31 +281,41 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType i // Fill the text if the behavior isn't set to saying there's only a color stored in this iterator. if (it->TextAttrBehavior() != TextAttributeBehavior::StoredOnly) { + const auto fillingFirstColumn = currentIndex == 0; const auto fillingLastColumn = currentIndex == finalColumnInRow; + const auto attr = it->DbcsAttr(); + const auto& chars = it->Chars(); - // TODO: MSFT: 19452170 - We need to ensure when writing any trailing byte that the one to the left - // is a matching leading byte. Likewise, if we're writing a leading byte, we need to make sure we still have space in this loop - // for the trailing byte coming up before writing it. - - // If we're trying to fill the first cell with a trailing byte, pad it out instead by clearing it. - // Don't increment iterator. We'll advance the index and try again with this value on the next round through the loop. - if (currentIndex == 0 && it->DbcsAttr().IsTrailing()) - { - _charRow.ClearCell(currentIndex); - } - // If we're trying to fill the last cell with a leading byte, pad it out instead by clearing it. - // Don't increment iterator. We'll exit because we couldn't write a lead at the end of a line. - else if (fillingLastColumn && it->DbcsAttr().IsLeading()) + switch (attr) { - _charRow.ClearCell(currentIndex); - SetDoubleBytePadded(true); - } - // Otherwise, copy the data given and increment the iterator. - else - { - _charRow.DbcsAttrAt(currentIndex) = it->DbcsAttr(); - _charRow.GlyphAt(currentIndex) = it->Chars(); - ++it; + case DbcsAttribute::Leading: + if (fillingLastColumn) + { + // If we're trying to fill the last cell with a leading byte, pad it out instead by clearing it. + // Don't increment iterator. We'll exit because we couldn't write a lead at the end of a line. + ClearCell(currentIndex); + SetDoubleBytePadded(true); + } + else + { + ReplaceCharacters(currentIndex, 2, chars); + } + break; + case DbcsAttribute::Trailing: + if (fillingFirstColumn) + { + ClearCell(currentIndex); + } + else + { + ReplaceCharacters(currentIndex - 1, 2, chars); + } + break; + case DbcsAttribute::Single: + ReplaceCharacters(currentIndex, 1, chars); + break; + default: + break; } // If we're asked to (un)set the wrap status and we just filled the last column with some text... @@ -179,20 +329,340 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType i SetWrapForced(*wrap); } } - else - { - ++it; - } // Move to the next cell for the next time through the loop. + ++it; ++currentIndex; } // Now commit the final color into the attr row if (colorUses) { - _attrRow.Replace(colorStarts, currentIndex, currentColor); + _attr.replace(colorStarts, currentIndex, currentColor); } return it; } + +bool ROW::SetAttrToEnd(const til::CoordType columnBegin, const TextAttribute attr) +{ + _attr.replace(clampedColumnInclusive(columnBegin), _attr.size(), attr); + return true; +} + +void ROW::ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars) +{ + const auto colBeg = clampedUint16(columnBegin); + const auto colEnd = clampedUint16(columnBegin + width); + + if (colBeg >= colEnd || colEnd > _columnCount || chars.empty()) + { + return; + } + + // Algorithm explanation + // + // Task: + // Replace the characters in cells [colBeg, colEnd) with a single cell `width`-wide consisting of `chars`. + // + // Problem: + // Imagine that we have the following ROW contents: + // "xxyyzz" + // xx, yy, zz are 2 cell wide glyphs. We want to insert a 2 cell wide glyph ww at colBeg 1: + // ^^ + // ww + // An incorrect result would be: + // "xwwyzz" + // The half cut off x and y glyph wouldn't make much sense, so we need to fill them with whitespace: + // " ww zz" + // + // Solution: + // Given the range we want to replace [colBeg, colEnd), we "extend" it to encompass leading (preceding) + // and trailing wide glyphs we partially overwrite resulting in the range [colExtBeg, colExtEnd), where + // colExtBeg <= colBeg and colExtEnd >= colEnd. In other words, the to be replaced range has been "extended". + // The amount of leading whitespace we need to insert is thus colBeg - colExtBeg + // and the amount of trailing whitespace colExtEnd - colEnd. + + // Safety: + // * colBeg is now [0, _columnCount] + // * colEnd is now [colBeg, _columnCount] + + // Extend range downwards (leading whitespace) + uint16_t colExtBeg = colBeg; + // Safety: colExtBeg is [0, _columnCount], because colBeg is. + const uint16_t chExtBeg = _uncheckedCharOffset(colExtBeg); + // Safety: colExtBeg remains [0, _columnCount] due to colExtBeg != 0. + for (; colExtBeg != 0 && _uncheckedIsTrailer(colExtBeg); --colExtBeg) + { + } + + // Extend range upwards (trailing whitespace) + uint16_t colExtEnd = colEnd; + // Safety: colExtEnd cannot be incremented past _columnCount, because the last + // _charOffset at index _columnCount will never get the CharOffsetsTrailer flag. + for (; _uncheckedIsTrailer(colExtEnd); ++colExtEnd) + { + } + // Safety: After the previous loop colExtEnd is [0, _columnCount]. + const uint16_t chExtEnd = _uncheckedCharOffset(colExtEnd); + + const uint16_t leadingSpaces = colBeg - colExtBeg; + const uint16_t trailingSpaces = colExtEnd - colEnd; + const size_t chExtEndNew = chars.size() + leadingSpaces + trailingSpaces + chExtBeg; + + if (chExtEndNew != chExtEnd) + { + _resizeChars(colExtEnd, chExtBeg, chExtEnd, chExtEndNew); + } + + // Add leading/trailing whitespace and copy chars + { + auto it = _chars.begin() + chExtBeg; + it = fill_n_small(it, leadingSpaces, L' '); + it = copy_n_small(chars.begin(), chars.size(), it); + it = fill_n_small(it, trailingSpaces, L' '); + } + // Update char offsets with leading/trailing whitespace and the chars columns. + { + auto chPos = chExtBeg; + auto it = _charOffsets.begin() + colExtBeg; + + it = iota_n_mut(it, leadingSpaces, chPos); + + *it++ = chPos; + it = fill_small(it, _charOffsets.begin() + colEnd, gsl::narrow_cast(chPos | CharOffsetsTrailer)); + chPos = gsl::narrow_cast(chPos + chars.size()); + + it = iota_n_mut(it, trailingSpaces, chPos); + } +} + +const til::small_rle& ROW::Attributes() const noexcept +{ + return _attr; +} + +TextAttribute ROW::GetAttrByColumn(const til::CoordType column) const +{ + return _attr.at(clampedUint16(column)); +} + +std::vector ROW::GetHyperlinks() const +{ + std::vector ids; + for (const auto& run : _attr.runs()) + { + if (run.value.IsHyperlink()) + { + ids.emplace_back(run.value.GetHyperlinkId()); + } + } + return ids; +} + +uint16_t ROW::size() const noexcept +{ + return _columnCount; +} + +til::CoordType ROW::MeasureLeft() const noexcept +{ + const auto text = GetText(); + const auto beg = text.begin(); + const auto end = text.end(); + auto it = beg; + + for (; it != end; ++it) + { + if (*it != L' ') + { + break; + } + } + + return gsl::narrow_cast(it - beg); +} + +til::CoordType ROW::MeasureRight() const noexcept +{ + const auto text = GetText(); + const auto beg = text.begin(); + const auto end = text.end(); + auto it = end; + + for (; it != beg; --it) + { + // it[-1] is safe as `it` is always greater than `beg` (loop invariant). + if (til::at(it, -1) != L' ') + { + break; + } + } + + // We're supposed to return the measurement in cells and not characters + // and therefore simply calculating `it - beg` would be wrong. + // + // An example: The row is 10 cells wide and `it` points to the second character. + // `it - beg` would return 1, but it's possible it's actually 1 wide glyph and 8 whitespace. + return gsl::narrow_cast(_columnCount - (end - it)); +} + +bool ROW::ContainsText() const noexcept +{ + const auto text = GetText(); + const auto beg = text.begin(); + const auto end = text.end(); + auto it = beg; + + for (; it != end; ++it) + { + if (*it != L' ') + { + return true; + } + } + + return false; +} + +std::wstring_view ROW::GlyphAt(til::CoordType column) const noexcept +{ + auto col = clampedColumn(column); + + // Safety: col is [0, _columnCount). + const auto beg = _uncheckedCharOffset(col); + // Safety: col cannot be incremented past _columnCount, because the last + // _charOffset at index _columnCount will never get the CharOffsetsTrailer flag. + while (_uncheckedIsTrailer(++col)) + { + } + // Safety: col is now [1, _columnCount]. + const auto end = _uncheckedCharOffset(col); + + return { _chars.begin() + beg, _chars.begin() + end }; +} + +DbcsAttribute ROW::DbcsAttrAt(til::CoordType column) const noexcept +{ + const auto col = clampedColumn(column); + + auto attr = DbcsAttribute::Single; + // Safety: col is [0, _columnCount). + if (_uncheckedIsTrailer(col)) + { + attr = DbcsAttribute::Trailing; + } + // Safety: col+1 is [1, _columnCount]. + else if (_uncheckedIsTrailer(::base::strict_cast(col) + 1u)) + { + attr = DbcsAttribute::Leading; + } + + return { attr }; +} + +std::wstring_view ROW::GetText() const noexcept +{ + return { _chars.data(), _charSize() }; +} + +DelimiterClass ROW::DelimiterClassAt(til::CoordType column, const std::wstring_view& wordDelimiters) const noexcept +{ + const auto col = clampedColumn(column); + // Safety: col is [0, _columnCount). + const auto glyph = _uncheckedChar(_uncheckedCharOffset(col)); + + if (glyph <= L' ') + { + return DelimiterClass::ControlChar; + } + else if (wordDelimiters.find(glyph) != std::wstring_view::npos) + { + return DelimiterClass::DelimiterChar; + } + else + { + return DelimiterClass::RegularChar; + } +} + +template +constexpr uint16_t ROW::clampedUint16(T v) noexcept +{ + return static_cast(std::max(T{ 0 }, std::min(T{ 65535 }, v))); +} + +template +constexpr uint16_t ROW::clampedColumn(T v) const noexcept +{ + return static_cast(std::max(T{ 0 }, std::min(_columnCount - 1u, v))); +} + +template +constexpr uint16_t ROW::clampedColumnInclusive(T v) const noexcept +{ + return static_cast(std::max(T{ 0 }, std::min(_columnCount, v))); +} + +// Safety: col must be [0, _charSize()]. +wchar_t ROW::_uncheckedChar(size_t off) const noexcept +{ + return til::at(_chars, off); +} + +uint16_t ROW::_charSize() const noexcept +{ + // Safety: _charOffsets is an array of `_columnCount + 1` entries. + return til::at(_charOffsets, _columnCount); +} + +// Safety: col must be [0, _columnCount]. +uint16_t ROW::_uncheckedCharOffset(size_t col) const noexcept +{ + return til::at(_charOffsets, col) & CharOffsetsMask; +} + +// Safety: col must be [0, _columnCount]. +bool ROW::_uncheckedIsTrailer(size_t col) const noexcept +{ + return WI_IsFlagSet(til::at(_charOffsets, col), CharOffsetsTrailer); +} + +void ROW::_init() noexcept +{ + std::fill_n(_chars.begin(), _columnCount, UNICODE_SPACE); + iota_n(_charOffsets.begin(), _columnCount + 1u, uint16_t{ 0 }); +} + +void ROW::_resizeChars(uint16_t colExtEnd, uint16_t chExtBeg, uint16_t chExtEnd, size_t chExtEndNew) +{ + const auto diff = chExtEndNew - chExtEnd; + const auto currentLength = _charSize(); + const auto newLength = currentLength + diff; + + if (newLength <= _chars.size()) + { + std::copy_n(_chars.begin() + chExtEnd, currentLength - chExtEnd, _chars.begin() + chExtEndNew); + } + else + { + const auto minCapacity = std::min(UINT16_MAX, _chars.size() + (_chars.size() >> 1)); + const auto newCapacity = gsl::narrow(std::max(newLength, minCapacity)); + + auto charsHeap = std::make_unique_for_overwrite(newCapacity); + const std::span chars{ charsHeap.get(), newCapacity }; + + std::copy_n(_chars.begin(), chExtBeg, chars.begin()); + std::copy_n(_chars.begin() + chExtEnd, currentLength - chExtEnd, chars.begin() + chExtEndNew); + + _charsHeap = std::move(charsHeap); + _chars = chars; + } + + auto it = _charOffsets.begin() + colExtEnd; + const auto end = _charOffsets.end(); + for (; it != end; ++it) + { + *it = gsl::narrow_cast(*it + diff); + } +} diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index 5c2040078dd..f289837e5a5 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -20,50 +20,66 @@ Revision History: #pragma once -#include "AttrRow.hpp" +#include + +#include "til/rle.h" #include "LineRendition.hpp" #include "OutputCell.hpp" #include "OutputCellIterator.hpp" -#include "CharRow.hpp" -#include "UnicodeStorage.hpp" class TextBuffer; +enum class DelimiterClass +{ + ControlChar, + DelimiterChar, + RegularChar +}; + class ROW final { public: - ROW(const til::CoordType rowId, const til::CoordType rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent); - - til::CoordType size() const noexcept { return _rowWidth; } - - void SetWrapForced(const bool wrap) noexcept { _wrapForced = wrap; } - bool WasWrapForced() const noexcept { return _wrapForced; } - - void SetDoubleBytePadded(const bool doubleBytePadded) noexcept { _doubleBytePadded = doubleBytePadded; } - bool WasDoubleBytePadded() const noexcept { return _doubleBytePadded; } - - const CharRow& GetCharRow() const noexcept { return _charRow; } - CharRow& GetCharRow() noexcept { return _charRow; } - - const ATTR_ROW& GetAttrRow() const noexcept { return _attrRow; } - ATTR_ROW& GetAttrRow() noexcept { return _attrRow; } - - LineRendition GetLineRendition() const noexcept { return _lineRendition; } - void SetLineRendition(const LineRendition lineRendition) noexcept { _lineRendition = lineRendition; } - - til::CoordType GetId() const noexcept { return _id; } - void SetId(const til::CoordType id) noexcept { _id = id; } - - bool Reset(const TextAttribute Attr); - [[nodiscard]] HRESULT Resize(const til::CoordType width); - - void ClearColumn(const til::CoordType column); - std::wstring GetText() const { return _charRow.GetText(); } - - UnicodeStorage& GetUnicodeStorage() noexcept; - const UnicodeStorage& GetUnicodeStorage() const noexcept; - - OutputCellIterator WriteCells(OutputCellIterator it, const til::CoordType index, const std::optional wrap = std::nullopt, std::optional limitRight = std::nullopt); + ROW() = default; + ROW(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute); + + ROW(const ROW& other) = delete; + ROW& operator=(const ROW& other) = delete; + + explicit ROW(ROW&& other) = default; + ROW& operator=(ROW&& other) = default; + + friend void swap(ROW& lhs, ROW& rhs) noexcept; + + void SetWrapForced(const bool wrap) noexcept; + bool WasWrapForced() const noexcept; + void SetDoubleBytePadded(const bool doubleBytePadded) noexcept; + bool WasDoubleBytePadded() const noexcept; + LineRendition GetLineRendition() const noexcept; + void SetLineRendition(const LineRendition lineRendition) noexcept; + + void Reset(const TextAttribute& attr); + void Resize(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute); + void TransferAttributes(const til::small_rle& attr, til::CoordType newWidth); + + void ClearCell(til::CoordType column); + OutputCellIterator WriteCells(OutputCellIterator it, til::CoordType columnBegin, std::optional wrap = std::nullopt, std::optional limitRight = std::nullopt); + bool SetAttrToEnd(til::CoordType columnBegin, TextAttribute attr); + void ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars); + + const til::small_rle& Attributes() const noexcept; + TextAttribute GetAttrByColumn(til::CoordType column) const; + std::vector GetHyperlinks() const; + uint16_t size() const noexcept; + til::CoordType MeasureLeft() const noexcept; + til::CoordType MeasureRight() const noexcept; + bool ContainsText() const noexcept; + std::wstring_view GlyphAt(til::CoordType column) const noexcept; + DbcsAttribute DbcsAttrAt(til::CoordType column) const noexcept; + std::wstring_view GetText() const noexcept; + DelimiterClass DelimiterClassAt(til::CoordType column, const std::wstring_view& wordDelimiters) const noexcept; + + auto AttrBegin() const noexcept { return _attr.begin(); } + auto AttrEnd() const noexcept { return _attr.end(); } #ifdef UNIT_TESTING friend constexpr bool operator==(const ROW& a, const ROW& b) noexcept; @@ -71,23 +87,49 @@ class ROW final #endif private: - CharRow _charRow; - ATTR_ROW _attrRow; - LineRendition _lineRendition; - til::CoordType _id; - til::CoordType _rowWidth; + static constexpr uint16_t CharOffsetsTrailer = 0x8000; + static constexpr uint16_t CharOffsetsMask = 0x7fff; + + template + static constexpr uint16_t clampedUint16(T v) noexcept; + template + constexpr uint16_t clampedColumn(T v) const noexcept; + template + constexpr uint16_t clampedColumnInclusive(T v) const noexcept; + + wchar_t _uncheckedChar(size_t off) const noexcept; + uint16_t _charSize() const noexcept; + uint16_t _uncheckedCharOffset(size_t col) const noexcept; + bool _uncheckedIsTrailer(size_t col) const noexcept; + + void _init() noexcept; + void _resizeChars(uint16_t colExtEnd, uint16_t chExtBeg, uint16_t chExtEnd, size_t chExtEndNew); + + // These fields are a bit "wasteful", but it makes all this a bit more robust against + // programming errors during initial development (which is when this comment was written). + // * _charsHeap as unique_ptr + // It can be stored in _chars and delete[] manually called if `_chars != _charsBuffer` + // * _chars as std::span + // The size may never exceed an uint16_t anyways + // * _charOffsets as std::span + // The length is already stored in _columns + wchar_t* _charsBuffer = nullptr; + std::unique_ptr _charsHeap; + std::span _chars; + std::span _charOffsets; + til::small_rle _attr; + uint16_t _columnCount = 0; + LineRendition _lineRendition = LineRendition::SingleWidth; // Occurs when the user runs out of text in a given row and we're forced to wrap the cursor to the next line - bool _wrapForced; + 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; - TextBuffer* _pParent; // non ownership pointer + bool _doubleBytePadded = false; }; #ifdef UNIT_TESTING constexpr bool operator==(const ROW& a, const ROW& b) noexcept { // comparison is only used in the tests; this should suffice. - return (a._pParent == b._pParent && - a._id == b._id); + return a._charsBuffer == b._charsBuffer; } #endif diff --git a/src/buffer/out/UnicodeStorage.cpp b/src/buffer/out/UnicodeStorage.cpp deleted file mode 100644 index b7c63bfb41e..00000000000 --- a/src/buffer/out/UnicodeStorage.cpp +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "UnicodeStorage.hpp" - -UnicodeStorage::UnicodeStorage() noexcept : - _map{} -{ -} - -// Routine Description: -// - fetches the text associated with key -// Arguments: -// - key - the key into the storage -// Return Value: -// - the glyph data associated with key -// Note: will throw exception if key is not stored yet -const UnicodeStorage::mapped_type& UnicodeStorage::GetText(const key_type key) const -{ - return _map.at(key); -} - -// Routine Description: -// - stores glyph data associated with key. -// Arguments: -// - key - the key into the storage -// - glyph - the glyph data to store -void UnicodeStorage::StoreGlyph(const key_type key, const mapped_type& glyph) -{ - _map.insert_or_assign(key, glyph); -} - -// Routine Description: -// - erases key and its associated data from the storage -// Arguments: -// - key - the key to remove -void UnicodeStorage::Erase(const key_type key) noexcept -{ - _map.erase(key); -} - -// Routine Description: -// - Remaps all of the stored items to new coordinate positions -// based on a bulk rearrangement of row IDs and potential row width resize. -// Arguments: -// - rowMap - A map of the old row IDs to the new row IDs. -// - width - The width of the new row. Remove any items that are beyond the row width. -// - Use nullopt if we're not resizing the width of the row, just renumbering the rows. -void UnicodeStorage::Remap(const std::unordered_map& rowMap, const std::optional width) -{ - // Make a temporary map to hold all the new row positioning - std::unordered_map newMap; - - // Walk through every stored item. - for (const auto& pair : _map) - { - // Extract the old coordinate position - const auto oldCoord = pair.first; - - // Only try to short-circuit based on width if we were told it changed - // by being given a new width value. - if (width.has_value()) - { - // Get the column ID - const auto oldColId = oldCoord.X; - - // If the column index is at/beyond the row width, don't bother copying it to the new map. - if (oldColId >= width.value()) - { - continue; - } - } - - // Get the row ID from the position as that's what we need to remap - const auto oldRowId = oldCoord.Y; - - // Use the mapping given to convert the old row ID to the new row ID - const auto mapIter = rowMap.find(oldRowId); - - // If there's no mapping to a new row, don't bother copying it to the new map. The row is gone. - if (mapIter == rowMap.end()) - { - continue; - } - - const auto newRowId = mapIter->second; - - // Generate a new coordinate with the same X as the old one, but a new Y value. - const auto newCoord = til::point{ oldCoord.X, newRowId }; - - // Put the adjusted coordinate into the map with the original value. - newMap.emplace(newCoord, pair.second); - } - - // Swap into the stored map, free the temporary when we exit. - _map.swap(newMap); -} diff --git a/src/buffer/out/UnicodeStorage.hpp b/src/buffer/out/UnicodeStorage.hpp deleted file mode 100644 index e7330131978..00000000000 --- a/src/buffer/out/UnicodeStorage.hpp +++ /dev/null @@ -1,66 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- UnicodeStorage.hpp - -Abstract: -- dynamic storage location for glyphs that can't normally fit in the output buffer - -Author(s): -- Austin Diviness (AustDi) 02-May-2018 ---*/ - -#pragma once - -#include -#include - -#include -#include - -// std::unordered_map needs help to know how to hash a til::point -namespace std -{ - template<> - struct hash - { - // Routine Description: - // - hashes a coord. coord will be hashed by storing the x and y values consecutively in the lower - // bits of a size_t. - // Arguments: - // - coord - the coord to hash - // Return Value: - // - the hashed coord - constexpr size_t operator()(const til::point coord) const noexcept - { - return til::hash(til::bit_cast(coord)); - } - }; -} - -class UnicodeStorage final -{ -public: - using key_type = typename til::point; - using mapped_type = typename std::vector; - - UnicodeStorage() noexcept; - - const mapped_type& GetText(const key_type key) const; - - void StoreGlyph(const key_type key, const mapped_type& glyph); - - void Erase(const key_type key) noexcept; - - void Remap(const std::unordered_map& rowMap, const std::optional width); - -private: - std::unordered_map _map; - -#ifdef UNIT_TESTING - friend class UnicodeStorageTests; - friend class TextBufferTests; -#endif -}; diff --git a/src/buffer/out/lib/bufferout.vcxproj b/src/buffer/out/lib/bufferout.vcxproj index f55f751d986..f7962921c2d 100644 --- a/src/buffer/out/lib/bufferout.vcxproj +++ b/src/buffer/out/lib/bufferout.vcxproj @@ -11,7 +11,6 @@ - @@ -24,16 +23,11 @@ - - - Create - - @@ -49,11 +43,7 @@ - - - - diff --git a/src/buffer/out/search.cpp b/src/buffer/out/search.cpp index 30731eec7cc..c442c4bd150 100644 --- a/src/buffer/out/search.cpp +++ b/src/buffer/out/search.cpp @@ -5,7 +5,6 @@ #include "search.h" -#include "CharRow.hpp" #include "textBuffer.hpp" #include "../types/inc/Utf16Parser.hpp" #include "../types/inc/GlyphWidth.hpp" diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index ac699ec4351..03c3ff3ed06 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -4,7 +4,8 @@ #include "precomp.h" #include "textBuffer.hpp" -#include "CharRow.hpp" + +#include #include "../renderer/base/renderer.hpp" #include "../types/inc/utils.hpp" @@ -12,7 +13,71 @@ #include "../../types/inc/Utf16Parser.hpp" #include "../../types/inc/GlyphWidth.hpp" -#pragma hdrstop +struct BufferAllocator +{ + BufferAllocator(til::size sz) + { + 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)); + + _buffer = wil::unique_virtualalloc_ptr{ static_cast(VirtualAlloc(nullptr, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) }; + THROW_IF_NULL_ALLOC(_buffer); + + _data = std::span{ _buffer.get(), allocSize }.begin(); + _rowStride = rowStride; + _indicesOffset = charsBytes; + _width = w; + _height = h; + } + + BufferAllocator& operator++() noexcept + { + _data += _rowStride; + return *this; + } + + wchar_t* chars() const noexcept + { + return til::bit_cast(&*_data); + } + + uint16_t* indices() const noexcept + { + return til::bit_cast(&*(_data + _indicesOffset)); + } + + uint16_t width() const noexcept + { + return _width; + } + + uint16_t height() const noexcept + { + return _height; + } + + wil::unique_virtualalloc_ptr&& take() noexcept + { + return std::move(_buffer); + } + +private: + wil::unique_virtualalloc_ptr _buffer; + std::span::iterator _data; + size_t _rowStride; + size_t _indicesOffset; + uint16_t _width; + uint16_t _height; +}; using namespace Microsoft::Console; using namespace Microsoft::Console::Types; @@ -35,24 +100,20 @@ TextBuffer::TextBuffer(const til::size screenBufferSize, const UINT cursorSize, const bool isActiveBuffer, Microsoft::Console::Render::Renderer& renderer) : - _firstRow{ 0 }, + _renderer{ renderer }, _currentAttributes{ defaultAttributes }, _cursor{ cursorSize, *this }, - _storage{}, - _unicodeStorage{}, - _isActiveBuffer{ isActiveBuffer }, - _renderer{ renderer }, - _size{}, - _currentHyperlinkId{ 1 }, - _currentPatternId{ 0 } + _isActiveBuffer{ isActiveBuffer } { - // initialize ROWs - _storage.reserve(gsl::narrow(screenBufferSize.Y)); - for (til::CoordType i = 0; i < screenBufferSize.Y; ++i) + BufferAllocator allocator{ screenBufferSize }; + + _storage.reserve(allocator.height()); + for (til::CoordType i = 0; i < screenBufferSize.Y; ++i, ++allocator) { - _storage.emplace_back(i, screenBufferSize.X, _currentAttributes, this); + _storage.emplace_back(allocator.chars(), allocator.indices(), allocator.width(), _currentAttributes); } + _charBuffer = allocator.take(); _UpdateSize(); } @@ -199,10 +260,10 @@ 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); - DbcsAttribute prevDbcsAttr; + DbcsAttribute prevDbcsAttr = DbcsAttribute::Single; try { - prevDbcsAttr = prevRow.GetCharRow().DbcsAttrAt(coordPrevPosition.X); + prevDbcsAttr = prevRow.DbcsAttrAt(coordPrevPosition.X); } catch (...) { @@ -229,21 +290,21 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut // T T Fail, uncorrectable. New trailing byte must have had leading before it. // Check for only failing portions of the matrix: - if (prevDbcsAttr.IsSingle() && dbcsAttribute.IsTrailing()) + if (prevDbcsAttr == DbcsAttribute::Single && dbcsAttribute == DbcsAttribute::Trailing) { // N, T failing case (uncorrectable) fValidSequence = false; } - else if (prevDbcsAttr.IsLeading()) + else if (prevDbcsAttr == DbcsAttribute::Leading) { - if (dbcsAttribute.IsSingle() || dbcsAttribute.IsLeading()) + if (dbcsAttribute == DbcsAttribute::Single || dbcsAttribute == DbcsAttribute::Leading) { // L, N and L, L failing cases (correctable) fValidSequence = false; fCorrectableByErase = true; } } - else if (prevDbcsAttr.IsTrailing() && dbcsAttribute.IsTrailing()) + else if (prevDbcsAttr == DbcsAttribute::Trailing && dbcsAttribute == DbcsAttribute::Trailing) { // T, T failing case (uncorrectable) fValidSequence = false; @@ -255,7 +316,7 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut // Erase previous character into an N type. try { - prevRow.ClearColumn(coordPrevPosition.X); + prevRow.ClearCell(coordPrevPosition.X); } catch (...) { @@ -289,7 +350,7 @@ bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute auto fSuccess = true; // Now compensate if we don't have enough space for the upcoming double byte sequence // We only need to compensate for leading bytes - if (dbcsAttribute.IsLeading()) + if (dbcsAttribute == DbcsAttribute::Leading) { const auto cursorPosition = GetCursor().GetPosition(); const auto lineWidth = GetLineWidth(cursorPosition.Y); @@ -418,12 +479,20 @@ bool TextBuffer::InsertCharacter(const std::wstring_view chars, auto& Row = GetRowByOffset(iRow); // Store character and double byte data - auto& charRow = Row.GetCharRow(); - try { - charRow.GlyphAt(iCol) = chars; - charRow.DbcsAttrAt(iCol) = dbcsAttribute; + switch (dbcsAttribute) + { + case DbcsAttribute::Leading: + Row.ReplaceCharacters(iCol, 2, chars); + break; + case DbcsAttribute::Trailing: + Row.ReplaceCharacters(iCol - 1, 2, chars); + break; + default: + Row.ReplaceCharacters(iCol, 1, chars); + break; + } } catch (...) { @@ -432,7 +501,7 @@ bool TextBuffer::InsertCharacter(const std::wstring_view chars, } // Store color data - fSuccess = Row.GetAttrRow().SetAttrToEnd(iCol, attr); + fSuccess = Row.SetAttrToEnd(iCol, attr); if (fSuccess) { // Advance the cursor @@ -572,8 +641,7 @@ bool TextBuffer::IncrementCircularBuffer(const bool inVtMode) // the current background color, but with no meta attributes set. fillAttributes.SetStandardErase(); } - const auto fSuccess = _storage.at(_firstRow).Reset(fillAttributes); - if (fSuccess) + GetRowByOffset(0).Reset(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) @@ -585,7 +653,7 @@ bool TextBuffer::IncrementCircularBuffer(const bool inVtMode) _firstRow = 0; } } - return fSuccess; + return true; } //Routine Description: @@ -610,7 +678,7 @@ til::point TextBuffer::GetLastNonSpaceCharacter(std::optional viewportTop); } @@ -779,10 +847,6 @@ void TextBuffer::ScrollRows(const til::CoordType firstRow, const til::CoordType // - end std::rotate(_storage.begin() + firstRow, _storage.begin() + firstRow + size, _storage.begin() + firstRow + size + delta); } - - // Renumber the IDs now that we've rearranged where the rows sit within the buffer. - // Refreshing should also delegate to the UnicodeStorage to re-key all the stored unicode sequences (where applicable). - _RefreshRowIDs(std::nullopt); } Cursor& TextBuffer::GetCursor() noexcept @@ -898,10 +962,10 @@ void TextBuffer::Reset() // - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed. [[nodiscard]] NTSTATUS TextBuffer::ResizeTraditional(const til::size newSize) noexcept { - RETURN_HR_IF(E_INVALIDARG, newSize.X < 0 || newSize.Y < 0); - try { + BufferAllocator allocator{ newSize }; + const auto currentSize = GetSize().Dimensions(); const auto attributes = GetCurrentAttributes(); @@ -913,49 +977,32 @@ void TextBuffer::Reset() const auto TopRowIndex = (GetFirstRowIndex() + TopRow) % currentSize.Y; // rotate rows until the top row is at index 0 - for (auto i = 0; i < TopRowIndex; i++) - { - _storage.emplace_back(std::move(_storage.front())); - _storage.erase(_storage.begin()); - } - + std::rotate(_storage.begin(), _storage.begin() + TopRowIndex, _storage.end()); _SetFirstRowIndex(0); // realloc in the Y direction // remove rows if we're shrinking - while (_storage.size() > static_cast(newSize.Y)) - { - _storage.pop_back(); - } - // add rows if we're growing - while (_storage.size() < static_cast(newSize.Y)) - { - _storage.emplace_back(gsl::narrow_cast(_storage.size()), newSize.X, attributes, this); - } + _storage.resize(allocator.height()); // Now that we've tampered with the row placement, refresh all the row IDs. // Also take advantage of the row ID refresh loop to resize the rows in the X dimension // and cleanup the UnicodeStorage characters that might fall outside the resized buffer. - _RefreshRowIDs(newSize.X); + for (auto& it : _storage) + { + it.Resize(allocator.chars(), allocator.indices(), allocator.width(), attributes); + ++allocator; + } // Update the cached size value _UpdateSize(); + + _charBuffer = allocator.take(); } CATCH_RETURN(); return S_OK; } -const UnicodeStorage& TextBuffer::GetUnicodeStorage() const noexcept -{ - return _unicodeStorage; -} - -UnicodeStorage& TextBuffer::GetUnicodeStorage() noexcept -{ - return _unicodeStorage; -} - void TextBuffer::SetAsActiveBuffer(const bool isActiveBuffer) noexcept { _isActiveBuffer = isActiveBuffer; @@ -1019,42 +1066,6 @@ void TextBuffer::TriggerNewTextNotification(const std::wstring_view newText) } } -// Routine Description: -// - Method to help refresh all the Row IDs after manipulating the row -// by shuffling pointers around. -// - This will also update parent pointers that are stored in depth within the buffer -// (e.g. it will update CharRow parents pointing at Rows that might have been moved around) -// - Optionally takes a new row width if we're resizing to perform a resize operation and cleanup -// any high unicode (UnicodeStorage) runs while we're already looping through the rows. -// Arguments: -// - newRowWidth - Optional new value for the row width. -void TextBuffer::_RefreshRowIDs(std::optional newRowWidth) -{ - std::unordered_map rowMap; - til::CoordType i = 0; - for (auto& it : _storage) - { - // Build a map so we can update Unicode Storage - rowMap.emplace(it.GetId(), i); - - // Update the IDs - it.SetId(i++); - - // Also update the char row parent pointers as they can get shuffled up in the rotates. - it.GetCharRow().UpdateParent(&it); - - // Resize the rows in the X dimension if we have a new width - if (newRowWidth.has_value()) - { - // Realloc in the X direction - THROW_IF_FAILED(it.Resize(newRowWidth.value())); - } - } - - // Give the new mapping to Unicode Storage - _unicodeStorage.Remap(rowMap, newRowWidth); -} - // Routine Description: // - Retrieves the first row from the underlying buffer. // Arguments: @@ -1066,27 +1077,6 @@ ROW& TextBuffer::_GetFirstRow() noexcept return GetRowByOffset(0); } -// Routine Description: -// - Retrieves the row that comes before the given row. -// - Does not wrap around the screen buffer. -// Arguments: -// - The current row. -// Return Value: -// - reference to the previous row -// Note: -// - will throw exception if called with the first row of the text buffer -ROW& TextBuffer::_GetPrevRowNoWrap(const ROW& Row) -{ - auto prevRowIndex = Row.GetId() - 1; - if (prevRowIndex < 0) - { - prevRowIndex = TotalRowCount() - 1; - } - - THROW_HR_IF(E_FAIL, Row.GetId() == _firstRow); - return _storage.at(prevRowIndex); -} - // Method Description: // - get delimiter class for buffer cell position // - used for double click selection and uia word navigation @@ -1095,9 +1085,9 @@ ROW& TextBuffer::_GetPrevRowNoWrap(const ROW& Row) // - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar // Return Value: // - the delimiter class for the given char -DelimiterClass TextBuffer::_GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const +DelimiterClass TextBuffer::_GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const noexcept { - return GetRowByOffset(pos.Y).GetCharRow().DelimiterClassAt(pos.X, wordDelimiters); + return GetRowByOffset(pos.Y).DelimiterClassAt(pos.X, wordDelimiters); } // Method Description: @@ -1161,7 +1151,7 @@ til::point TextBuffer::GetWordStart(const til::point target, const std::wstring_ // - wordDelimiters - what characters are we considering for the separation of words // Return Value: // - The til::point for the first character on the current/previous READABLE "word" (inclusive) -til::point TextBuffer::_GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const +til::point TextBuffer::_GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const noexcept { auto result = target; const auto bufferSize = GetSize(); @@ -1206,7 +1196,7 @@ til::point TextBuffer::_GetWordStartForAccessibility(const til::point target, co // - wordDelimiters - what characters are we considering for the separation of words // Return Value: // - The til::point for the first character on the current word or delimiter run (stopped by the left margin) -til::point TextBuffer::_GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const +til::point TextBuffer::_GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept { auto result = target; const auto bufferSize = GetSize(); @@ -1327,7 +1317,7 @@ til::point TextBuffer::_GetWordEndForAccessibility(const til::point target, cons // - wordDelimiters - what characters are we considering for the separation of words // Return Value: // - The til::point for the last character of the current word or delimiter run (stopped by right margin) -til::point TextBuffer::_GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const +til::point TextBuffer::_GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept { const auto bufferSize = GetSize(); @@ -1362,7 +1352,7 @@ void TextBuffer::_PruneHyperlinks() // If the buffer does not contain the same reference, we can remove that hyperlink from our map // This way, obsolete hyperlink references are cleared from our hyperlink map instead of hanging around // Get all the hyperlink references in the row we're erasing - const auto hyperlinks = _storage.at(_firstRow).GetAttrRow().GetHyperlinks(); + const auto hyperlinks = GetRowByOffset(0).GetHyperlinks(); if (!hyperlinks.empty()) { @@ -1378,7 +1368,7 @@ void TextBuffer::_PruneHyperlinks() // to see if those references are anywhere else for (til::CoordType i = 1; i < total; ++i) { - const auto nextRowRefs = GetRowByOffset(i).GetAttrRow().GetHyperlinks(); + const auto nextRowRefs = GetRowByOffset(i).GetHyperlinks(); for (auto id : nextRowRefs) { if (firstRowRefs.find(id) != firstRowRefs.end()) @@ -1472,7 +1462,7 @@ til::point TextBuffer::GetGlyphStart(const til::point pos, std::optionalDbcsAttr().IsTrailing()) + if (resultPos != limit && GetCellDataAt(resultPos)->DbcsAttr() == DbcsAttribute::Trailing) { bufferSize.DecrementInBounds(resultPos, true); } @@ -1499,7 +1489,7 @@ til::point TextBuffer::GetGlyphEnd(const til::point pos, bool accessibilityMode, resultPos = limit; } - if (resultPos != limit && GetCellDataAt(resultPos)->DbcsAttr().IsLeading()) + if (resultPos != limit && GetCellDataAt(resultPos)->DbcsAttr() == DbcsAttribute::Leading) { bufferSize.IncrementInBounds(resultPos, true); } @@ -1557,7 +1547,7 @@ bool TextBuffer::MoveToNextGlyph(til::point& pos, bool allowExclusiveEnd, std::o const bool success{ ++iter }; // Move again if we're on a wide glyph - if (success && iter->DbcsAttr().IsTrailing()) + if (success && iter->DbcsAttr() == DbcsAttribute::Trailing) { ++iter; } @@ -1589,7 +1579,7 @@ bool TextBuffer::MoveToPreviousGlyph(til::point& pos, std::optional // try to move. If we can't, we're done. const auto success = bufferSize.DecrementInBounds(resultPos, true); - if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsLeading()) + if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr() == DbcsAttribute::Leading) { bufferSize.DecrementInBounds(resultPos, true); } @@ -1673,7 +1663,7 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const // expand left side of rect til::point targetPoint{ textRow.Left, textRow.Top }; - if (GetCellDataAt(targetPoint)->DbcsAttr().IsTrailing()) + if (GetCellDataAt(targetPoint)->DbcsAttr() == DbcsAttribute::Trailing) { if (targetPoint.X == bufferSize.Left()) { @@ -1688,7 +1678,7 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const // expand right side of rect targetPoint = { textRow.Right, textRow.Bottom }; - if (GetCellDataAt(targetPoint)->DbcsAttr().IsLeading()) + if (GetCellDataAt(targetPoint)->DbcsAttr() == DbcsAttribute::Leading) { if (targetPoint.X == bufferSize.RightInclusive()) { @@ -1758,7 +1748,7 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF, { const auto& cell = *it; - if (!cell.DbcsAttr().IsTrailing()) + if (cell.DbcsAttr() != DbcsAttribute::Trailing) { const auto chars = cell.Chars(); selectionText.append(chars); @@ -2263,12 +2253,11 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, // Fetch the row and its "right" which is the last printable character. const auto& row = oldBuffer.GetRowByOffset(iOldRow); const auto cOldColsTotal = oldBuffer.GetLineWidth(iOldRow); - const auto& charRow = row.GetCharRow(); - auto iRight = charRow.MeasureRight(); + auto iRight = row.MeasureRight(); // If we're starting a new row, try and preserve the line rendition // from the row in the original buffer. - const auto newBufferPos = newBuffer.GetCursor().GetPosition(); + const auto newBufferPos = newCursor.GetPosition(); if (newBufferPos.X == 0) { auto& newRow = newBuffer.GetRowByOffset(newBufferPos.Y); @@ -2315,9 +2304,9 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, try { // TODO: MSFT: 19446208 - this should just use an iterator and the inserter... - const auto glyph = row.GetCharRow().GlyphAt(iOldCol); - const auto dbcsAttr = row.GetCharRow().DbcsAttrAt(iOldCol); - const auto textAttr = row.GetAttrRow().GetAttrByColumn(iOldCol); + const auto glyph = row.GlyphAt(iOldCol); + const auto dbcsAttr = row.DbcsAttrAt(iOldCol); + const auto textAttr = row.GetAttrByColumn(iOldCol); if (!newBuffer.InsertCharacter(glyph, dbcsAttr, textAttr)) { @@ -2361,8 +2350,8 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, try { // TODO: MSFT: 19446208 - this should just use an iterator and the inserter... - const auto textAttr = row.GetAttrRow().GetAttrByColumn(copyAttrCol); - if (!newRow.GetAttrRow().SetAttrToEnd(newAttrColumn, textAttr)) + const auto textAttr = row.GetAttrByColumn(copyAttrCol); + if (!newRow.SetAttrToEnd(newAttrColumn, textAttr)) { break; } @@ -2476,8 +2465,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, // the last attr when wider. auto& newRow = newBuffer.GetRowByOffset(newRowY); const auto newWidth = newBuffer.GetLineWidth(newRowY); - newRow.GetAttrRow() = row.GetAttrRow(); - newRow.GetAttrRow().Resize(newWidth); + newRow.TransferAttributes(row.Attributes(), newWidth); newRowY++; } diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 61f82599acd..ddf56b8021e 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -54,7 +54,6 @@ filling in the last row, and updating the screen. #include "cursor.h" #include "Row.hpp" #include "TextAttribute.hpp" -#include "UnicodeStorage.hpp" #include "../types/inc/Viewport.hpp" #include "../buffer/out/textBufferCellIterator.hpp" @@ -140,9 +139,6 @@ class TextBuffer final [[nodiscard]] HRESULT ResizeTraditional(const til::size newSize) noexcept; - const UnicodeStorage& GetUnicodeStorage() const noexcept; - UnicodeStorage& GetUnicodeStorage() noexcept; - void SetAsActiveBuffer(const bool isActiveBuffer) noexcept; bool IsActiveBuffer() const noexcept; @@ -216,54 +212,42 @@ class TextBuffer final private: void _UpdateSize(); - Microsoft::Console::Types::Viewport _size; - std::vector _storage; - Cursor _cursor; - - til::CoordType _firstRow; // indexes top row (not necessarily 0) - - TextAttribute _currentAttributes; - - // storage location for glyphs that can't fit into the buffer normally - UnicodeStorage _unicodeStorage; - - bool _isActiveBuffer; - Microsoft::Console::Render::Renderer& _renderer; - - std::unordered_map _hyperlinkMap; - std::unordered_map _hyperlinkCustomIdMap; - uint16_t _currentHyperlinkId; - - void _RefreshRowIDs(std::optional newRowWidth); - void _SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept; - til::point _GetPreviousFromCursor() const noexcept; - void _SetWrapOnCurrentRow() noexcept; void _AdjustWrapOnCurrentRow(const bool fSet) noexcept; - // Assist with maintaining proper buffer state for Double Byte character sequences bool _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute); bool _AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute); - ROW& _GetFirstRow() noexcept; - ROW& _GetPrevRowNoWrap(const ROW& row); - void _ExpandTextRow(til::inclusive_rect& selectionRow) const; - - DelimiterClass _GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const; - til::point _GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const; - til::point _GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) 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; + til::point _GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept; til::point _GetWordEndForAccessibility(const til::point target, const std::wstring_view wordDelimiters, const til::point limit) const; - til::point _GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const; - + til::point _GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept; void _PruneHyperlinks(); static void _AppendRTFText(std::ostringstream& contentBuilder, const std::wstring_view& text); + Microsoft::Console::Render::Renderer& _renderer; + + std::unordered_map _hyperlinkMap; + std::unordered_map _hyperlinkCustomIdMap; + uint16_t _currentHyperlinkId = 1; + std::unordered_map _idsAndPatterns; - size_t _currentPatternId; + size_t _currentPatternId = 0; + + wil::unique_virtualalloc_ptr _charBuffer; + std::vector _storage; + TextAttribute _currentAttributes; + til::CoordType _firstRow = 0; // indexes top row (not necessarily 0) + + Cursor _cursor; + Microsoft::Console::Types::Viewport _size; + + bool _isActiveBuffer = false; #ifdef UNIT_TESTING friend class TextBufferTests; diff --git a/src/buffer/out/textBufferCellIterator.cpp b/src/buffer/out/textBufferCellIterator.cpp index a9343d74984..d09ef92e507 100644 --- a/src/buffer/out/textBufferCellIterator.cpp +++ b/src/buffer/out/textBufferCellIterator.cpp @@ -5,7 +5,6 @@ #include "textBufferCellIterator.hpp" -#include "CharRow.hpp" #include "textBuffer.hpp" #include "../types/inc/convert.hpp" #include "../types/inc/viewport.hpp" @@ -37,7 +36,7 @@ TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, til::po _bounds(limits), _exceeded(false), _view({}, {}, {}, TextAttributeBehavior::Stored), - _attrIter(s_GetRow(buffer, pos)->GetAttrRow().cbegin()) + _attrIter(s_GetRow(buffer, pos)->AttrBegin()) { // Throw if the bounds rectangle is not limited to the inside of the given buffer. THROW_HR_IF(E_INVALIDARG, !buffer.GetSize().IsInBounds(limits)); @@ -165,16 +164,15 @@ TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& move _attrIter += diff; _view.UpdateTextAttribute(*_attrIter); - const auto& charRow = _pRow->GetCharRow(); - _view.UpdateText(charRow.GlyphAt(newX)); - _view.UpdateDbcsAttribute(charRow.DbcsAttrAt(newX)); + _view.UpdateText(_pRow->GlyphAt(newX)); + _view.UpdateDbcsAttribute(_pRow->DbcsAttrAt(newX)); _pos.X = newX; } else { // cold path (_GenerateView is slow) _pRow = s_GetRow(_buffer, { newX, newY }); - _attrIter = _pRow->GetAttrRow().cbegin() + newX; + _attrIter = _pRow->AttrBegin() + newX; _pos.X = newX; _pos.Y = newY; _GenerateView(); @@ -289,12 +287,12 @@ ptrdiff_t TextBufferCellIterator::operator-(const TextBufferCellIterator& it) // - Sets the coordinate position that this iterator will inspect within the text buffer on dereference. // Arguments: // - newPos - The new coordinate position. -void TextBufferCellIterator::_SetPos(const til::point newPos) +void TextBufferCellIterator::_SetPos(const til::point newPos) noexcept { if (newPos.Y != _pos.Y) { _pRow = s_GetRow(_buffer, newPos); - _attrIter = _pRow->GetAttrRow().cbegin(); + _attrIter = _pRow->AttrBegin(); _pos.X = 0; } @@ -324,10 +322,10 @@ const ROW* TextBufferCellIterator::s_GetRow(const TextBuffer& buffer, const til: // Routine Description: // - Updates the internal view. Call after updating row, attribute, or positions. -void TextBufferCellIterator::_GenerateView() +void TextBufferCellIterator::_GenerateView() noexcept { - _view = OutputCellView(_pRow->GetCharRow().GlyphAt(_pos.X), - _pRow->GetCharRow().DbcsAttrAt(_pos.X), + _view = OutputCellView(_pRow->GlyphAt(_pos.X), + _pRow->DbcsAttrAt(_pos.X), *_attrIter, TextAttributeBehavior::Stored); } diff --git a/src/buffer/out/textBufferCellIterator.hpp b/src/buffer/out/textBufferCellIterator.hpp index ffeb0378602..198bdac6ce1 100644 --- a/src/buffer/out/textBufferCellIterator.hpp +++ b/src/buffer/out/textBufferCellIterator.hpp @@ -15,8 +15,7 @@ Author(s): #pragma once -#include "CharRow.hpp" -#include "AttrRow.hpp" +#include "Row.hpp" #include "OutputCellView.hpp" #include "../../types/inc/viewport.hpp" @@ -50,14 +49,14 @@ class TextBufferCellIterator til::point Pos() const noexcept; protected: - void _SetPos(const til::point newPos); - void _GenerateView(); + void _SetPos(const til::point newPos) noexcept; + void _GenerateView() noexcept; static const ROW* s_GetRow(const TextBuffer& buffer, const til::point pos) noexcept; + til::small_rle::const_iterator _attrIter; OutputCellView _view; const ROW* _pRow; - ATTR_ROW::const_iterator _attrIter; const TextBuffer& _buffer; const Microsoft::Console::Types::Viewport _bounds; bool _exceeded; diff --git a/src/buffer/out/textBufferTextIterator.cpp b/src/buffer/out/textBufferTextIterator.cpp index 9687e317584..36ac370af44 100644 --- a/src/buffer/out/textBufferTextIterator.cpp +++ b/src/buffer/out/textBufferTextIterator.cpp @@ -5,7 +5,6 @@ #include "textBufferTextIterator.hpp" -#include "CharRow.hpp" #include "Row.hpp" #pragma hdrstop diff --git a/src/buffer/out/ut_textbuffer/ReflowTests.cpp b/src/buffer/out/ut_textbuffer/ReflowTests.cpp index 94183b703e2..88eacc47362 100644 --- a/src/buffer/out/ut_textbuffer/ReflowTests.cpp +++ b/src/buffer/out/ut_textbuffer/ReflowTests.cpp @@ -737,34 +737,22 @@ class ReflowTests { auto buffer = std::make_unique(testBuffer.size, TextAttribute{ 0x7 }, 0, false, renderer); - til::CoordType i{}; + til::CoordType y = 0; for (const auto& testRow : testBuffer.rows) { - auto& row{ buffer->GetRowByOffset(i) }; + auto& row{ buffer->GetRowByOffset(y) }; - auto& charRow{ row.GetCharRow() }; row.SetWrapForced(testRow.wrap); - til::CoordType j{}; - for (auto it{ charRow.begin() }; it != charRow.end(); ++it) + til::CoordType x = 0; + for (const auto& ch : testRow.text) { - // Yes, we're about to manually create a buffer. It is unpleasant. - const auto ch{ til::at(testRow.text, j) }; - it->Char() = ch; - if (IsGlyphFullWidth(ch)) - { - it->DbcsAttr().SetLeading(); - it++; - it->Char() = ch; - it->DbcsAttr().SetTrailing(); - } - else - { - it->DbcsAttr().SetSingle(); - } - j++; + const til::CoordType width = IsGlyphFullWidth(ch) ? 2 : 1; + row.ReplaceCharacters(x, width, { &ch, 1 }); + x += width; } - i++; + + y++; } buffer->GetCursor().SetPosition(testBuffer.cursor); @@ -783,42 +771,39 @@ class ReflowTests VERIFY_ARE_EQUAL(testBuffer.cursor, buffer.GetCursor().GetPosition()); VERIFY_ARE_EQUAL(testBuffer.size, buffer.GetSize().Dimensions()); - til::CoordType i{}; + til::CoordType y = 0; for (const auto& testRow : testBuffer.rows) { NoThrowString indexString; - const auto& row{ buffer.GetRowByOffset(i) }; - - const auto& charRow{ row.GetCharRow() }; + const auto& row{ buffer.GetRowByOffset(y) }; - indexString.Format(L"[Row %d]", i); + indexString.Format(L"[Row %d]", y); VERIFY_ARE_EQUAL(testRow.wrap, row.WasWrapForced(), indexString); - til::CoordType j{}; - for (auto it{ charRow.begin() }; it != charRow.end(); ++it) + til::CoordType x = 0; + for (const auto& ch : testRow.text) { - indexString.Format(L"[Cell %d, %d; Text line index %d]", it - charRow.begin(), i, j); - // Yes, we're about to manually create a buffer. It is unpleasant. - const auto ch{ til::at(testRow.text, j) }; if (IsGlyphFullWidth(ch)) { // Char is full width in test buffer, so // ensure that real buffer is LEAD, TRAIL (ch) - VERIFY_IS_TRUE(it->DbcsAttr().IsLeading(), indexString); - VERIFY_ARE_EQUAL(ch, it->Char(), indexString); + VERIFY_IS_TRUE(row.DbcsAttrAt(x) == DbcsAttribute::Leading, indexString); + VERIFY_ARE_EQUAL(ch, row.GlyphAt(x).front(), indexString); + ++x; - it++; - VERIFY_IS_TRUE(it->DbcsAttr().IsTrailing(), indexString); + VERIFY_IS_TRUE(row.DbcsAttrAt(x) == DbcsAttribute::Trailing, indexString); + VERIFY_ARE_EQUAL(ch, row.GlyphAt(x).front(), indexString); + ++x; } else { - VERIFY_IS_TRUE(it->DbcsAttr().IsSingle(), indexString); + VERIFY_IS_TRUE(row.DbcsAttrAt(x) == DbcsAttribute::Single, indexString); + VERIFY_ARE_EQUAL(ch, row.GlyphAt(x).front(), indexString); + ++x; } - - VERIFY_ARE_EQUAL(ch, it->Char(), indexString); - j++; } - i++; + + y++; } } diff --git a/src/buffer/out/ut_textbuffer/TextBuffer.Unit.Tests.vcxproj b/src/buffer/out/ut_textbuffer/TextBuffer.Unit.Tests.vcxproj index 3ed68250f98..72a89ba1246 100644 --- a/src/buffer/out/ut_textbuffer/TextBuffer.Unit.Tests.vcxproj +++ b/src/buffer/out/ut_textbuffer/TextBuffer.Unit.Tests.vcxproj @@ -14,7 +14,6 @@ - Create @@ -42,4 +41,4 @@ - + \ No newline at end of file diff --git a/src/buffer/out/ut_textbuffer/UnicodeStorageTests.cpp b/src/buffer/out/ut_textbuffer/UnicodeStorageTests.cpp deleted file mode 100644 index b2f9cf824a6..00000000000 --- a/src/buffer/out/ut_textbuffer/UnicodeStorageTests.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "WexTestClass.h" -#include "../../inc/consoletaeftemplates.hpp" - -#include "../UnicodeStorage.hpp" - -using namespace WEX::Common; -using namespace WEX::Logging; -using namespace WEX::TestExecution; - -class UnicodeStorageTests -{ - TEST_CLASS(UnicodeStorageTests); - - TEST_METHOD(CanOverwriteEmoji) - { - UnicodeStorage storage; - const til::point coord{ 1, 3 }; - const std::vector newMoon{ 0xD83C, 0xDF11 }; - const std::vector fullMoon{ 0xD83C, 0xDF15 }; - - // store initial glyph - storage.StoreGlyph(coord, newMoon); - - // verify it was stored - auto findIt = storage._map.find(coord); - VERIFY_ARE_NOT_EQUAL(findIt, storage._map.end()); - const auto& newMoonGlyph = findIt->second; - VERIFY_ARE_EQUAL(newMoonGlyph.size(), newMoon.size()); - for (size_t i = 0; i < newMoon.size(); ++i) - { - VERIFY_ARE_EQUAL(newMoonGlyph.at(i), newMoon.at(i)); - } - - // overwrite it - storage.StoreGlyph(coord, fullMoon); - - // verify the glyph was overwritten - findIt = storage._map.find(coord); - VERIFY_ARE_NOT_EQUAL(findIt, storage._map.end()); - const auto& fullMoonGlyph = findIt->second; - VERIFY_ARE_EQUAL(fullMoonGlyph.size(), fullMoon.size()); - for (size_t i = 0; i < fullMoon.size(); ++i) - { - VERIFY_ARE_EQUAL(fullMoonGlyph.at(i), fullMoon.at(i)); - } - } -}; diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 2ed9f2d7efe..8a2eb60c919 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1751,26 +1751,25 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto& textBuffer = _terminal->GetTextBuffer(); - std::wstringstream ss; + std::wstring str; const auto lastRow = textBuffer.GetLastNonSpaceCharacter().Y; for (auto rowIndex = 0; rowIndex <= lastRow; rowIndex++) { const auto& row = textBuffer.GetRowByOffset(rowIndex); - auto rowText = row.GetText(); + const auto rowText = row.GetText(); const auto strEnd = rowText.find_last_not_of(UNICODE_SPACE); - if (strEnd != std::string::npos) + if (strEnd != decltype(rowText)::npos) { - rowText.erase(strEnd + 1); - ss << rowText; + str.append(rowText.substr(strEnd + 1)); } if (!row.WasWrapForced()) { - ss << UNICODE_CARRIAGERETURN << UNICODE_LINEFEED; + str.append(L"\r\n"); } } - return hstring(ss.str()); + return hstring{ str }; } // Helper to check if we're on Windows 11 or not. This is used to check if diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index 2e3e695498e..60b70b44763 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -72,9 +72,9 @@ CursorType Terminal::GetCursorStyle() const noexcept bool Terminal::IsCursorDoubleWidth() const { - const auto position = _activeBuffer().GetCursor().GetPosition(); - TextBufferTextIterator it(TextBufferCellIterator(_activeBuffer(), position)); - return IsGlyphFullWidth(*it); + const auto& buffer = _activeBuffer(); + const auto position = buffer.GetCursor().GetPosition(); + return buffer.GetRowByOffset(position.y).DbcsAttrAt(position.x) != DbcsAttribute::Single; } const std::vector Terminal::GetOverlays() const noexcept diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp index a497a826597..4e87ab14966 100644 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp @@ -3704,11 +3704,11 @@ void ConptyRoundtripTests::HyperlinkIdConsistency() auto verifyData = [](TextBuffer& tb) { // Check that all the linked cells still have the same ID - auto& attrRow = tb.GetRowByOffset(0).GetAttrRow(); - auto id = attrRow.GetAttrByColumn(0).GetHyperlinkId(); + auto& row = tb.GetRowByOffset(0); + auto id = row.GetAttrByColumn(0).GetHyperlinkId(); for (uint16_t i = 1; i < 4; ++i) { - VERIFY_ARE_EQUAL(id, attrRow.GetAttrByColumn(i).GetHyperlinkId()); + VERIFY_ARE_EQUAL(id, row.GetAttrByColumn(i).GetHyperlinkId()); } }; diff --git a/src/host/_output.cpp b/src/host/_output.cpp index 38b5a66f65e..1b6926e0d88 100644 --- a/src/host/_output.cpp +++ b/src/host/_output.cpp @@ -9,8 +9,6 @@ #include "handle.h" #include "misc.h" -#include "../buffer/out/CharRow.hpp" - #include "../interactivity/inc/ServiceLocator.hpp" #include "../types/inc/Viewport.hpp" #include "../types/inc/convert.hpp" diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index 899228a9dcf..d81a514cb48 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -859,12 +859,11 @@ using Microsoft::Console::VirtualTerminal::StateMachine; { const auto TargetPoint = cursor.GetPosition(); auto& Row = textBuffer.GetRowByOffset(TargetPoint.Y); - const auto& charRow = Row.GetCharRow(); try { // If we're on top of a trailing cell, clear it and the previous cell. - if (charRow.DbcsAttrAt(TargetPoint.X).IsTrailing()) + if (Row.DbcsAttrAt(TargetPoint.X) == DbcsAttribute::Trailing) { // Space to clear for 2 cells. OutputCellIterator it(UNICODE_SPACE, 2); diff --git a/src/host/conimeinfo.cpp b/src/host/conimeinfo.cpp index fbe226ea25d..0e914f751d1 100644 --- a/src/host/conimeinfo.cpp +++ b/src/host/conimeinfo.cpp @@ -256,7 +256,7 @@ std::vector ConsoleImeInfo::s_ConvertToCells(const std::wstring_view // 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; + DbcsAttribute dbcsAttr = DbcsAttribute::Single; if (IsGlyphFullWidth(glyph)) { auto leftHalfAttr = drawingAttr; @@ -269,9 +269,9 @@ std::vector ConsoleImeInfo::s_ConvertToCells(const std::wstring_view leftHalfAttr.SetRightVerticalDisplayed(false); } - dbcsAttr.SetLeading(); + dbcsAttr = DbcsAttribute::Leading; cells.emplace_back(glyph, dbcsAttr, leftHalfAttr); - dbcsAttr.SetTrailing(); + dbcsAttr = DbcsAttribute::Trailing; // If we need a left vertical, don't apply it to the right side of the character if (rightHalfAttr.IsLeftVerticalDisplayed()) @@ -346,7 +346,7 @@ std::vector::const_iterator ConsoleImeInfo::_WriteConversionArea(con // 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()) + if (lastCell->DbcsAttr() == DbcsAttribute::Leading) { lineEnd--; } diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index 21c7428d5a4..fb4405b7121 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -424,6 +424,6 @@ CHAR_INFO CONSOLE_INFORMATION::AsCharInfo(const OutputCellView& cell) const noex // (for mapping RGB values to the nearest table value) const auto& attr = cell.TextAttr(); ci.Attributes = attr.GetLegacyAttributes(); - ci.Attributes |= cell.DbcsAttr().GeneratePublicApiAttributeFormat(); + ci.Attributes |= GeneratePublicApiAttributeFormat(cell.DbcsAttr()); return ci; } diff --git a/src/host/output.cpp b/src/host/output.cpp index 0a9fceb03c1..cba292716aa 100644 --- a/src/host/output.cpp +++ b/src/host/output.cpp @@ -152,14 +152,14 @@ std::vector ReadOutputAttributes(const SCREEN_INFORMATION& screenInfo, // If the first thing we read is trailing, pad with a space. // OR If the last thing we read is leading, pad with a space. - if ((amountRead == 0 && it->DbcsAttr().IsTrailing()) || - (amountRead == (amountToRead - 1) && it->DbcsAttr().IsLeading())) + if ((amountRead == 0 && it->DbcsAttr() == DbcsAttribute::Trailing) || + (amountRead == (amountToRead - 1) && it->DbcsAttr() == DbcsAttribute::Leading)) { retVal.push_back(legacyAttributes); } else { - retVal.push_back(legacyAttributes | it->DbcsAttr().GeneratePublicApiAttributeFormat()); + retVal.push_back(legacyAttributes | GeneratePublicApiAttributeFormat(it->DbcsAttr())); } amountRead++; @@ -208,15 +208,15 @@ std::wstring ReadOutputStringW(const SCREEN_INFORMATION& screenInfo, { // If the first thing we read is trailing, pad with a space. // OR If the last thing we read is leading, pad with a space. - if ((amountRead == 0 && it->DbcsAttr().IsTrailing()) || - (amountRead == (amountToRead - 1) && it->DbcsAttr().IsLeading())) + if ((amountRead == 0 && it->DbcsAttr() == DbcsAttribute::Trailing) || + (amountRead == (amountToRead - 1) && it->DbcsAttr() == DbcsAttribute::Leading)) { retVal += UNICODE_SPACE; } else { // Otherwise, add anything that isn't a trailing cell. (Trailings are duplicate copies of the leading.) - if (!it->DbcsAttr().IsTrailing()) + if (it->DbcsAttr() != DbcsAttribute::Trailing) { retVal += it->Chars(); } diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index e0631290026..6cd58e1e852 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -9,7 +9,6 @@ #include "_output.h" #include "misc.h" #include "handle.h" -#include "../buffer/out/CharRow.hpp" #include #include "../interactivity/inc/ServiceLocator.hpp" @@ -2396,11 +2395,11 @@ OutputCellRect SCREEN_INFORMATION::ReadRect(const Viewport viewport) const } // if we're clipping a dbcs char then don't include it, add a space instead - if (span.begin()->DbcsAttr().IsTrailing()) + if (span.begin()->DbcsAttr() == DbcsAttribute::Trailing) { *span.begin() = paddingCell; } - if (span.rbegin()->DbcsAttr().IsLeading()) + if (span.rbegin()->DbcsAttr() == DbcsAttribute::Leading) { *span.rbegin() = paddingCell; } @@ -2658,7 +2657,7 @@ void SCREEN_INFORMATION::InitializeCursorRowAttributes() // the current background color, but with no meta attributes set. auto fillAttributes = GetAttributes(); fillAttributes.SetStandardErase(); - row.GetAttrRow().SetAttrToEnd(0, fillAttributes); + row.SetAttrToEnd(0, fillAttributes); // The row should also be single width to start with. row.SetLineRendition(LineRendition::SingleWidth); } @@ -2689,8 +2688,7 @@ bool SCREEN_INFORMATION::CursorIsDoubleWidth() const { const auto& buffer = GetTextBuffer(); const auto position = buffer.GetCursor().GetPosition(); - TextBufferTextIterator it(TextBufferCellIterator(buffer, position)); - return IsGlyphFullWidth(*it); + return buffer.GetRowByOffset(position.y).DbcsAttrAt(position.x) != DbcsAttribute::Single; } // Method Description: diff --git a/src/host/selectionInput.cpp b/src/host/selectionInput.cpp index 115911eb84d..f3f4a07c46c 100644 --- a/src/host/selectionInput.cpp +++ b/src/host/selectionInput.cpp @@ -360,7 +360,7 @@ bool Selection::HandleKeyboardLineSelectionEvent(const INPUT_KEY_INFO* const pIn try { const auto attr = gci.GetActiveOutputBuffer().GetCellDataAt(coordSelPoint)->DbcsAttr(); - if (attr.IsTrailing()) + if (attr == DbcsAttribute::Trailing) { bufferSize.IncrementInBounds(coordSelPoint); } @@ -583,7 +583,7 @@ bool Selection::HandleKeyboardLineSelectionEvent(const INPUT_KEY_INFO* const pIn try { const auto attr = gci.GetActiveOutputBuffer().GetCellDataAt(coordSelPoint)->DbcsAttr(); - if (attr.IsTrailing()) + if (attr == DbcsAttribute::Trailing) { // try to move off by highlighting the lead half too. auto fSuccess = bufferSize.DecrementInBounds(coordSelPoint); @@ -766,7 +766,7 @@ bool Selection::_HandleMarkModeSelectionNav(const INPUT_KEY_INFO* const pInputKe auto it = ScreenInfo.GetCellLineDataAt(cursorPos); // calculate next right - if (it->DbcsAttr().IsLeading()) + if (it->DbcsAttr() == DbcsAttribute::Leading) { iNextRightX = 2; } @@ -779,16 +779,16 @@ bool Selection::_HandleMarkModeSelectionNav(const INPUT_KEY_INFO* const pInputKe if (cursorPos.X > 0) { it--; - if (it->DbcsAttr().IsTrailing()) + if (it->DbcsAttr() == DbcsAttribute::Trailing) { iNextLeftX = 2; } - else if (it->DbcsAttr().IsLeading()) + else if (it->DbcsAttr() == DbcsAttribute::Leading) { if (cursorPos.X - 1 > 0) { it--; - if (it->DbcsAttr().IsTrailing()) + if (it->DbcsAttr() == DbcsAttribute::Trailing) { iNextLeftX = 3; } diff --git a/src/host/ut_host/OutputCellIteratorTests.cpp b/src/host/ut_host/OutputCellIteratorTests.cpp index 88e583b7435..cb078da228b 100644 --- a/src/host/ut_host/OutputCellIteratorTests.cpp +++ b/src/host/ut_host/OutputCellIteratorTests.cpp @@ -29,12 +29,12 @@ class OutputCellIteratorTests OutputCellIterator it(wch, limit); OutputCellView expectedLead({ &wch, 1 }, - DbcsAttribute(DbcsAttribute::Attribute::Leading), + DbcsAttribute::Leading, InvalidTextAttribute, TextAttributeBehavior::Current); OutputCellView expectedTrail({ &wch, 1 }, - DbcsAttribute(DbcsAttribute::Attribute::Trailing), + DbcsAttribute::Trailing, InvalidTextAttribute, TextAttributeBehavior::Current); @@ -284,7 +284,7 @@ class OutputCellIteratorTests for (const auto& wch : testText) { auto expected = OutputCellView({ &wch, 1 }, - DbcsAttribute(DbcsAttribute::Attribute::Leading), + DbcsAttribute::Leading, InvalidTextAttribute, TextAttributeBehavior::Current); @@ -293,7 +293,7 @@ class OutputCellIteratorTests it++; expected = OutputCellView({ &wch, 1 }, - DbcsAttribute(DbcsAttribute::Attribute::Trailing), + DbcsAttribute::Trailing, InvalidTextAttribute, TextAttributeBehavior::Current); @@ -341,7 +341,7 @@ class OutputCellIteratorTests for (const auto& wch : testText) { auto expected = OutputCellView({ &wch, 1 }, - DbcsAttribute(DbcsAttribute::Attribute::Leading), + DbcsAttribute::Leading, color, TextAttributeBehavior::Stored); @@ -350,7 +350,7 @@ class OutputCellIteratorTests it++; expected = OutputCellView({ &wch, 1 }, - DbcsAttribute(DbcsAttribute::Attribute::Trailing), + DbcsAttribute::Trailing, color, TextAttributeBehavior::Stored); @@ -496,7 +496,7 @@ class OutputCellIteratorTests const auto value = *it; it++; - if (value.DbcsAttr().IsLeading() || value.DbcsAttr().IsTrailing()) + if (value.DbcsAttr() == DbcsAttribute::Leading || value.DbcsAttr() == DbcsAttribute::Trailing) { VERIFY_IS_TRUE(it); it++; diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index 22c5df8c6a5..7f0fb8c967c 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -1461,8 +1461,7 @@ void ScreenBufferTests::VtScrollMarginsNewlineColor() { SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures); const auto& row = tbi.GetRowByOffset(y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; for (auto x = 0; x < viewport.RightInclusive(); x++) { const auto& attr = attrs[x]; @@ -1529,8 +1528,7 @@ void ScreenBufferTests::VtNewlinePastViewport() { SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures); const auto& row = tbi.GetRowByOffset(y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; for (auto x = 0; x < viewport.RightInclusive(); x++) { const auto& attr = attrs[x]; @@ -1539,8 +1537,7 @@ void ScreenBufferTests::VtNewlinePastViewport() } const auto& row = tbi.GetRowByOffset(viewport.BottomInclusive()); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; for (auto x = 0; x < viewport.RightInclusive(); x++) { const auto& attr = attrs[x]; @@ -1606,8 +1603,7 @@ void ScreenBufferTests::VtNewlinePastEndOfBuffer() { SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures); const auto& row = tbi.GetRowByOffset(y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; for (auto x = 0; x < viewport.RightInclusive(); x++) { const auto& attr = attrs[x]; @@ -1616,8 +1612,7 @@ void ScreenBufferTests::VtNewlinePastEndOfBuffer() } const auto& row = tbi.GetRowByOffset(viewport.BottomInclusive()); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; for (auto x = 0; x < viewport.RightInclusive(); x++) { const auto& attr = attrs[x]; @@ -2474,8 +2469,7 @@ void ScreenBufferTests::TestAltBufferVtDispatching() // Recall we didn't print an 'X' to the main buffer, so there's no // char to inspect the attributes of. const auto& altRow = alternate.GetTextBuffer().GetRowByOffset(altCursor.GetPosition().Y); - const auto altAttrRow = &altRow.GetAttrRow(); - const std::vector altAttrs{ altAttrRow->begin(), altAttrRow->end() }; + const std::vector altAttrs{ altRow.AttrBegin(), altRow.AttrEnd() }; const auto altAttrA = altAttrs[altCursor.GetPosition().X - 1]; VERIFY_ARE_EQUAL(expectedRgb, altAttrA); } @@ -2569,8 +2563,7 @@ void ScreenBufferTests::SetDefaultsIndividuallyBothDefault() VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition()); const auto& row = tbi.GetRowByOffset(0); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[0]; const auto attrB = attrs[1]; const auto attrC = attrs[2]; @@ -2660,8 +2653,7 @@ void ScreenBufferTests::SetDefaultsTogether() VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition()); const auto& row = tbi.GetRowByOffset(0); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[0]; const auto attrB = attrs[1]; const auto attrC = attrs[2]; @@ -2724,8 +2716,7 @@ void ScreenBufferTests::ReverseResetWithDefaultBackground() VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition()); const auto& row = tbi.GetRowByOffset(0); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[0]; const auto attrB = attrs[1]; const auto attrC = attrs[2]; @@ -2787,8 +2778,7 @@ void ScreenBufferTests::BackspaceDefaultAttrs() VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition()); const auto& row = tbi.GetRowByOffset(0); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[0]; const auto attrB = attrs[1]; @@ -2866,8 +2856,7 @@ void ScreenBufferTests::BackspaceDefaultAttrsWriteCharsLegacy() VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition()); const auto& row = tbi.GetRowByOffset(0); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[0]; const auto attrB = attrs[1]; @@ -2920,7 +2909,6 @@ void ScreenBufferTests::BackspaceDefaultAttrsInPrompt() const auto viewport = si.GetViewport(); const auto& row = tbi.GetRowByOffset(cursor.GetPosition().Y); - const auto attrRow = &row.GetAttrRow(); { SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures); @@ -2928,7 +2916,7 @@ void ScreenBufferTests::BackspaceDefaultAttrsInPrompt() L"Make sure the row contains what we're expecting before we start." L"It should entirely be filled with defaults")); - const std::vector initialAttrs{ attrRow->begin(), attrRow->end() }; + const std::vector initialAttrs{ row.AttrBegin(), row.AttrEnd() }; for (auto x = 0; x <= viewport.RightInclusive(); x++) { const auto& attr = initialAttrs[x]; @@ -2945,7 +2933,7 @@ void ScreenBufferTests::BackspaceDefaultAttrsInPrompt() til::point expectedCursor{ 1, 0 }; VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition()); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; for (auto x = 0; x <= viewport.RightInclusive(); x++) { const auto& attr = attrs[x]; @@ -2989,8 +2977,7 @@ void ScreenBufferTests::SetGlobalColorTable() VERIFY_ARE_EQUAL(expectedCursor, mainCursor.GetPosition()); { const auto& row = mainBuffer.GetTextBuffer().GetRowByOffset(mainCursor.GetPosition().Y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[0]; LOG_ATTR(attrA); VERIFY_ARE_EQUAL(originalRed, renderSettings.GetAttributeColors(attrA).second); @@ -3015,8 +3002,7 @@ void ScreenBufferTests::SetGlobalColorTable() VERIFY_ARE_EQUAL(expectedCursor, altCursor.GetPosition()); { const auto& row = altBuffer.GetTextBuffer().GetRowByOffset(altCursor.GetPosition().Y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[0]; LOG_ATTR(attrA); VERIFY_ARE_EQUAL(originalRed, renderSettings.GetAttributeColors(attrA).second); @@ -3030,8 +3016,7 @@ void ScreenBufferTests::SetGlobalColorTable() VERIFY_ARE_EQUAL(til::point(2, 0), altCursor.GetPosition()); { const auto& row = altBuffer.GetTextBuffer().GetRowByOffset(altCursor.GetPosition().Y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[0]; const auto attrB = attrs[1]; LOG_ATTR(attrA); @@ -3053,8 +3038,7 @@ void ScreenBufferTests::SetGlobalColorTable() VERIFY_ARE_EQUAL(til::point(2, 0), mainCursor.GetPosition()); { const auto& row = mainBuffer.GetTextBuffer().GetRowByOffset(mainCursor.GetPosition().Y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[0]; const auto attrB = attrs[1]; LOG_ATTR(attrA); @@ -3096,8 +3080,7 @@ void ScreenBufferTests::SetColorTableThreeDigits() VERIFY_ARE_EQUAL(expectedCursor, mainCursor.GetPosition()); { const auto& row = mainBuffer.GetTextBuffer().GetRowByOffset(mainCursor.GetPosition().Y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[0]; LOG_ATTR(attrA); VERIFY_ARE_EQUAL(originalRed, renderSettings.GetAttributeColors(attrA).second); @@ -3122,8 +3105,7 @@ void ScreenBufferTests::SetColorTableThreeDigits() VERIFY_ARE_EQUAL(expectedCursor, altCursor.GetPosition()); { const auto& row = altBuffer.GetTextBuffer().GetRowByOffset(altCursor.GetPosition().Y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[0]; LOG_ATTR(attrA); VERIFY_ARE_EQUAL(originalRed, renderSettings.GetAttributeColors(attrA).second); @@ -3140,8 +3122,7 @@ void ScreenBufferTests::SetColorTableThreeDigits() VERIFY_ARE_EQUAL(til::point(2, 0), altCursor.GetPosition()); { const auto& row = altBuffer.GetTextBuffer().GetRowByOffset(altCursor.GetPosition().Y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrB = attrs[1]; // TODO MSFT:20105972 - attrA and attrB should both be the same color now LOG_ATTR(attrB); @@ -3343,19 +3324,12 @@ void ScreenBufferTests::DeleteCharsNearEndOfLine() VERIFY_ARE_EQUAL(til::point(mainView.Width() - 1, 0), mainCursor.GetPosition()); - Log::Comment(NoThrowString().Format( - L"row_i=[%s]", - tbi.GetRowByOffset(0).GetText().c_str())); - - mainCursor.SetPosition({ mainView.Width() - static_cast(dx), 0 }); + mainCursor.SetPosition({ mainView.Width() - dx, 0 }); std::wstringstream ss; ss << L"\x1b[" << numCharsToDelete << L"P"; // Delete N chars stateMachine.ProcessString(ss.str()); - Log::Comment(NoThrowString().Format( - L"row_f=[%s]", - tbi.GetRowByOffset(0).GetText().c_str())); - VERIFY_ARE_EQUAL(til::point(mainView.Width() - static_cast(dx), 0), mainCursor.GetPosition()); + VERIFY_ARE_EQUAL(til::point(mainView.Width() - dx, 0), mainCursor.GetPosition()); auto iter = tbi.GetCellDataAt({ 0, 0 }); auto expectedNumSpaces = std::min(dx, numCharsToDelete); for (auto x = 0; x < mainView.Width() - expectedNumSpaces; x++) @@ -3411,14 +3385,11 @@ void ScreenBufferTests::DeleteCharsNearEndOfLineSimpleFirstCase() // Place the cursor on the 'D' mainCursor.SetPosition({ 3, 0 }); - Log::Comment(NoThrowString().Format(L"before=[%s]", tbi.GetRowByOffset(0).GetText().c_str())); // Delete 3 chars - [D, E, F] std::wstringstream ss; ss << L"\x1b[" << 3 << L"P"; stateMachine.ProcessString(ss.str()); - Log::Comment(NoThrowString().Format(L"after =[%s]", tbi.GetRowByOffset(0).GetText().c_str())); - // Cursor shouldn't have moved VERIFY_ARE_EQUAL(til::point(3, 0), mainCursor.GetPosition()); @@ -3472,15 +3443,11 @@ void ScreenBufferTests::DeleteCharsNearEndOfLineSimpleSecondCase() // Place the cursor on the 'C' mainCursor.SetPosition({ 2, 0 }); - Log::Comment(NoThrowString().Format(L"before=[%s]", tbi.GetRowByOffset(0).GetText().c_str())); - // Delete 4 chars - [C, D, E, F] std::wstringstream ss; ss << L"\x1b[" << 4 << L"P"; stateMachine.ProcessString(ss.str()); - Log::Comment(NoThrowString().Format(L"after =[%s]", tbi.GetRowByOffset(0).GetText().c_str())); - VERIFY_ARE_EQUAL(til::point(2, 0), mainCursor.GetPosition()); auto iter = tbi.GetCellDataAt({ 0, 0 }); @@ -3537,8 +3504,7 @@ void ScreenBufferTests::DontResetColorsAboveVirtualBottom() VERIFY_ARE_EQUAL(2, cursor.GetPosition().X); { const auto& row = tbi.GetRowByOffset(cursor.GetPosition().Y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[0]; const auto attrB = attrs[1]; LOG_ATTR(attrA); @@ -3568,8 +3534,7 @@ void ScreenBufferTests::DontResetColorsAboveVirtualBottom() VERIFY_ARE_EQUAL(3, cursor.GetPosition().X); { const auto& row = tbi.GetRowByOffset(cursor.GetPosition().Y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[0]; const auto attrB = attrs[1]; const auto attrC = attrs[1]; @@ -3876,8 +3841,6 @@ void ScreenBufferTests::InsertChars() auto before = si.GetTextBuffer().GetRowByOffset(insertLine).GetText(); stateMachine.ProcessString(L"\x1b[5@"); auto after = si.GetTextBuffer().GetRowByOffset(insertLine).GetText(); - Log::Comment(before.c_str(), L"Before"); - Log::Comment(after.c_str(), L" After"); // Verify cursor didn't move. VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from insert operation."); @@ -3917,8 +3880,6 @@ void ScreenBufferTests::InsertChars() before = si.GetTextBuffer().GetRowByOffset(insertLine).GetText(); stateMachine.ProcessString(L"\x1b[5@"); after = si.GetTextBuffer().GetRowByOffset(insertLine).GetText(); - Log::Comment(before.c_str(), L"Before"); - Log::Comment(after.c_str(), L" After"); // Verify cursor didn't move. VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from insert operation."); @@ -3955,8 +3916,6 @@ void ScreenBufferTests::InsertChars() before = si.GetTextBuffer().GetRowByOffset(insertLine).GetText(); stateMachine.ProcessString(L"\x1b[100@"); after = si.GetTextBuffer().GetRowByOffset(insertLine).GetText(); - Log::Comment(before.c_str(), L"Before"); - Log::Comment(after.c_str(), L" After"); // Verify cursor didn't move. VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from insert operation."); @@ -4036,8 +3995,6 @@ void ScreenBufferTests::DeleteChars() auto before = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText(); stateMachine.ProcessString(L"\x1b[5P"); auto after = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText(); - Log::Comment(before.c_str(), L"Before"); - Log::Comment(after.c_str(), L" After"); // Verify cursor didn't move. VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from delete operation."); @@ -4077,8 +4034,6 @@ void ScreenBufferTests::DeleteChars() before = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText(); stateMachine.ProcessString(L"\x1b[5P"); after = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText(); - Log::Comment(before.c_str(), L"Before"); - Log::Comment(after.c_str(), L" After"); // Verify cursor didn't move. VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from delete operation."); @@ -4115,8 +4070,6 @@ void ScreenBufferTests::DeleteChars() before = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText(); stateMachine.ProcessString(L"\x1b[100P"); after = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText(); - Log::Comment(before.c_str(), L"Before"); - Log::Comment(after.c_str(), L" After"); // Verify cursor didn't move. VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from delete operation."); @@ -6781,8 +6734,7 @@ void ScreenBufferTests::TestWriteConsoleVTQuirkMode() const auto verifyLastAttribute = [&](const TextAttribute& expected) { const auto& row = mainBuffer.GetTextBuffer().GetRowByOffset(cursor.GetPosition().Y); - const auto attrRow = &row.GetAttrRow(); - auto iter{ attrRow->begin() }; + auto iter{ row.AttrBegin() }; iter += cursor.GetPosition().X - 1; VERIFY_ARE_EQUAL(expected, *iter); }; diff --git a/src/host/ut_host/SelectionTests.cpp b/src/host/ut_host/SelectionTests.cpp index d8d7a6aaf8a..3b568a1c082 100644 --- a/src/host/ut_host/SelectionTests.cpp +++ b/src/host/ut_host/SelectionTests.cpp @@ -343,68 +343,6 @@ class SelectionTests VERIFY_ARE_EQUAL(srOriginal.Left + sDeltaLeft, srSelection.Left); VERIFY_ARE_EQUAL(srOriginal.Right + sDeltaRight, srSelection.Right); } - - TEST_METHOD(TestBisectSelection) - { - m_state->FillTextBufferBisect(); - - // From CommonState, this is what rows look like: - // positions of き are at 0, 27-28, 39-40, 67-68, 79 - // きABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789ききABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789き - // きABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789ききABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789き - // きABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789ききABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789き - // きABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789ききABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789き - - // 1a. Start position is trailing half and is at beginning of row - - // start from position Column 0, Row 2 - // selection is 5 characters long - // the left edge should move one to the right (+1) to not select the trailing byte - // right edge shouldn't move - TestBisectSelectionDelta(0, 2, 5, 1, 0); - - // 1b. Start position is trailing half and is elsewhere in the row - - // start from position Column 28, Row 2, which is the position of a trailing き in the mid row - // selection is 5 characters long - // the left edge should move one to the left (-1) to select the leading byte also - // right edge shouldn't move - TestBisectSelectionDelta(28, 2, 5, -1, 0); - - // 1c. Start position is trailing half and is beginning of buffer - - // start from position Column 0, Row 0 which is a trailing byte - // selection is 5 characters long - // the left edge should move one to the right (+1) to not select the trailing byte - // right edge shouldn't move - TestBisectSelectionDelta(0, 0, 5, 1, 0); - - // 2a. End position is leading half and is at end of row - - // start from position 10 before end of row (80 length row) - // row is 2 - // selection is 9 characters long - // the left edge shouldn't move - // the right edge should move one to the left (-1) to not select the leading byte - TestBisectSelectionDelta(70, 2, 9, 0, -1); - - // 2b. End position is leading half and is elsewhere in the row - - // start from 10 before trailing き in the mid row (pos 68 - 10 = 58) - // row is 2 - // selection is 10 characters long - // the left edge shouldn't move - // the right edge should not move, because it is already on the trailing byte - TestBisectSelectionDelta(58, 2, 10, 0, 0); - - // 2c. End position is leading half and is at end of buffer - // start from position 10 before end of row (80 length row) - // row is 300 (or 299 for the index) - // selection is 9 characters long - // the left edge shouldn't move - // the right edge should move one to the left (-1) to not select the leading byte - TestBisectSelectionDelta(70, 299, 9, 0, -1); - } }; class SelectionInputTests diff --git a/src/host/ut_host/TextBufferIteratorTests.cpp b/src/host/ut_host/TextBufferIteratorTests.cpp index e7e9a149d26..65c7773fb5b 100644 --- a/src/host/ut_host/TextBufferIteratorTests.cpp +++ b/src/host/ut_host/TextBufferIteratorTests.cpp @@ -11,7 +11,6 @@ #include "../buffer/out/textBuffer.hpp" #include "../buffer/out/textBufferCellIterator.hpp" #include "../buffer/out/textBufferTextIterator.hpp" -#include "../buffer/out/CharRow.hpp" #include "input.h" @@ -455,8 +454,8 @@ void TextBufferIteratorTests::AsCharInfoCell() const auto& row = outputBuffer._textBuffer->GetRowByOffset(it._pos.Y); - const auto wcharExpected = *row.GetCharRow().GlyphAt(it._pos.X).begin(); - const auto attrExpected = row.GetAttrRow().GetAttrByColumn(it._pos.X); + const auto wcharExpected = *row.GlyphAt(it._pos.X).begin(); + const auto attrExpected = row.GetAttrByColumn(it._pos.X); const auto cellActual = gci.AsCharInfo(*it); const auto wcharActual = cellActual.Char.UnicodeChar; @@ -475,7 +474,7 @@ void TextBufferIteratorTests::DereferenceOperatorText() const auto& row = outputBuffer._textBuffer->GetRowByOffset(it._pos.Y); - const auto wcharExpected = row.GetCharRow().GlyphAt(it._pos.X); + const auto wcharExpected = row.GlyphAt(it._pos.X); const auto wcharActual = *it; VERIFY_ARE_EQUAL(*wcharExpected.begin(), *wcharActual.begin()); @@ -490,9 +489,9 @@ void TextBufferIteratorTests::DereferenceOperatorCell() const auto& row = outputBuffer._textBuffer->GetRowByOffset(it._pos.Y); - const auto textExpected = (std::wstring_view)row.GetCharRow().GlyphAt(it._pos.X); - const auto dbcsExpected = row.GetCharRow().DbcsAttrAt(it._pos.X); - const auto attrExpected = row.GetAttrRow().GetAttrByColumn(it._pos.X); + const auto textExpected = (std::wstring_view)row.GlyphAt(it._pos.X); + const auto dbcsExpected = row.DbcsAttrAt(it._pos.X); + const auto attrExpected = row.GetAttrByColumn(it._pos.X); const auto cellActual = *it; const auto textActual = cellActual.Chars(); diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index eae3b069659..9647bff25c5 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -2,6 +2,9 @@ // Licensed under the MIT license. #include "precomp.h" + +#include + #include "WexTestClass.h" #include "../inc/consoletaeftemplates.hpp" @@ -9,7 +12,6 @@ #include "globals.h" #include "../buffer/out/textBuffer.hpp" -#include "../buffer/out/CharRow.hpp" #include "input.h" #include "_stream.h" @@ -73,8 +75,6 @@ class TextBufferTests til::CoordType GetBufferHeight(); - TEST_METHOD(TestBufferRowByOffset); - TEST_METHOD(TestWrapFlag); TEST_METHOD(TestWrapThroughWriteLine); @@ -87,8 +87,9 @@ class TextBufferTests const til::CoordType cLeft, const til::CoordType cRight); + TEST_METHOD(TestBoundaryMeasuresEmptyString); + TEST_METHOD(TestBoundaryMeasuresFullString); TEST_METHOD(TestBoundaryMeasuresRegularString); - TEST_METHOD(TestBoundaryMeasuresFloatingString); TEST_METHOD(TestCopyProperties); @@ -145,6 +146,7 @@ class TextBufferTests TEST_METHOD(ResizeTraditionalHighUnicodeColumnRemoval); TEST_METHOD(TestBurrito); + TEST_METHOD(TestOverwriteChars); TEST_METHOD(TestAppendRTFText); @@ -181,19 +183,6 @@ til::CoordType TextBufferTests::GetBufferHeight() return GetTbi().GetSize().Height(); } -void TextBufferTests::TestBufferRowByOffset() -{ - auto& textBuffer = GetTbi(); - auto csBufferHeight = GetBufferHeight(); - - VERIFY_IS_TRUE(csBufferHeight > 20); - - auto sId = csBufferHeight / 2 - 5; - - const auto& row = textBuffer.GetRowByOffset(sId); - VERIFY_ARE_EQUAL(row.GetId(), sId); -} - void TextBufferTests::TestWrapFlag() { auto& textBuffer = GetTbi(); @@ -310,12 +299,12 @@ void TextBufferTests::DoBoundaryTest(PCWCHAR const pwszInputString, { auto& textBuffer = GetTbi(); - auto& charRow = textBuffer._GetFirstRow().GetCharRow(); + auto& row = textBuffer._GetFirstRow(); // copy string into buffer for (til::CoordType i = 0; i < cLength; ++i) { - charRow.GlyphAt(i) = { &pwszInputString[i], 1 }; + row.ReplaceCharacters(i, 1, { &pwszInputString[i], 1 }); } // space pad the rest of the string @@ -323,14 +312,32 @@ void TextBufferTests::DoBoundaryTest(PCWCHAR const pwszInputString, { for (auto cStart = cLength; cStart < cMax; cStart++) { - charRow.ClearGlyph(cStart); + row.ClearCell(cStart); } } // left edge should be 0 since there are no leading spaces - VERIFY_ARE_EQUAL(charRow.MeasureLeft(), cLeft); + VERIFY_ARE_EQUAL(row.MeasureLeft(), cLeft); // right edge should be one past the index of the last character or the string length - VERIFY_ARE_EQUAL(charRow.MeasureRight(), cRight); + VERIFY_ARE_EQUAL(row.MeasureRight(), cRight); +} + +void TextBufferTests::TestBoundaryMeasuresEmptyString() +{ + auto csBufferWidth = GetBufferWidth(); + + // length 0, left 80, right 0 + const auto pwszLazyDog = L""; + DoBoundaryTest(pwszLazyDog, 0, csBufferWidth, 80, 0); +} + +void TextBufferTests::TestBoundaryMeasuresFullString() +{ + auto csBufferWidth = GetBufferWidth(); + + // length 0, left 80, right 0 + std::wstring str(csBufferWidth, L'X'); + DoBoundaryTest(str.data(), csBufferWidth, csBufferWidth, 0, 80); } void TextBufferTests::TestBoundaryMeasuresRegularString() @@ -402,18 +409,15 @@ void TextBufferTests::TestInsertCharacter() // create some sample test data const auto wch = L'Z'; const std::wstring_view wchTest(&wch, 1); - DbcsAttribute dbcsAttribute; - dbcsAttribute.SetTrailing(); + const auto dbcsAttribute = DbcsAttribute::Leading; const auto wAttrTest = BACKGROUND_INTENSITY | FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE; auto TestAttributes = TextAttribute(wAttrTest); - auto& charRow = Row.GetCharRow(); - charRow.DbcsAttrAt(coordCursorBefore.X).SetLeading(); // ensure that the buffer didn't start with these fields - VERIFY_ARE_NOT_EQUAL(charRow.GlyphAt(coordCursorBefore.X), wchTest); - VERIFY_ARE_NOT_EQUAL(charRow.DbcsAttrAt(coordCursorBefore.X), dbcsAttribute); + VERIFY_ARE_NOT_EQUAL(Row.GlyphAt(coordCursorBefore.X), wchTest); + VERIFY_ARE_NOT_EQUAL(Row.DbcsAttrAt(coordCursorBefore.X), dbcsAttribute); - auto attr = Row.GetAttrRow().GetAttrByColumn(coordCursorBefore.X); + auto attr = Row.GetAttrByColumn(coordCursorBefore.X); VERIFY_ARE_NOT_EQUAL(attr, TestAttributes); @@ -421,10 +425,10 @@ void TextBufferTests::TestInsertCharacter() textBuffer.InsertCharacter(wchTest, dbcsAttribute, TestAttributes); // ensure that the buffer position where the cursor WAS contains the test items - VERIFY_ARE_EQUAL(charRow.GlyphAt(coordCursorBefore.X), wchTest); - VERIFY_ARE_EQUAL(charRow.DbcsAttrAt(coordCursorBefore.X), dbcsAttribute); + VERIFY_ARE_EQUAL(Row.GlyphAt(coordCursorBefore.X), wchTest); + VERIFY_ARE_EQUAL(Row.DbcsAttrAt(coordCursorBefore.X), dbcsAttribute); - attr = Row.GetAttrRow().GetAttrByColumn(coordCursorBefore.X); + attr = Row.GetAttrByColumn(coordCursorBefore.X); VERIFY_ARE_EQUAL(attr, TestAttributes); // ensure that the cursor moved to a new position (X or Y or both have changed) @@ -531,7 +535,7 @@ void TextBufferTests::TestLastNonSpace(const til::CoordType cursorPosY) auto coordExpected = textBuffer.GetCursor().GetPosition(); // Try to get the X position from the current cursor position. - coordExpected.X = static_cast(textBuffer.GetRowByOffset(coordExpected.Y).GetCharRow().MeasureRight()) - 1; + coordExpected.X = textBuffer.GetRowByOffset(coordExpected.Y).MeasureRight() - 1; // If we went negative, this row was empty and we need to continue seeking upward... // - As long as X is negative (empty rows) @@ -539,7 +543,7 @@ void TextBufferTests::TestLastNonSpace(const til::CoordType cursorPosY) while (coordExpected.X < 0 && coordExpected.Y > 0) { coordExpected.Y--; - coordExpected.X = static_cast(textBuffer.GetRowByOffset(coordExpected.Y).GetCharRow().MeasureRight()) - 1; + coordExpected.X = textBuffer.GetRowByOffset(coordExpected.Y).MeasureRight() - 1; } VERIFY_ARE_EQUAL(coordLastNonSpace.X, coordExpected.X); @@ -618,12 +622,10 @@ void TextBufferTests::TestIncrementCircularBuffer() // fill first row with some stuff auto& FirstRow = textBuffer._GetFirstRow(); - auto& charRow = FirstRow.GetCharRow(); - const auto stuff = L'A'; - charRow.GlyphAt(0) = { &stuff, 1 }; + FirstRow.ReplaceCharacters(0, 1, { L"A" }); // ensure it does say that it contains text - VERIFY_IS_TRUE(FirstRow.GetCharRow().ContainsText()); + VERIFY_IS_TRUE(FirstRow.ContainsText()); // try increment textBuffer.IncrementCircularBuffer(); @@ -633,7 +635,7 @@ void TextBufferTests::TestIncrementCircularBuffer() VERIFY_ARE_NOT_EQUAL(textBuffer._GetFirstRow(), FirstRow); // the old first row is no longer the first // ensure old first row has been emptied - VERIFY_IS_FALSE(FirstRow.GetCharRow().ContainsText()); + VERIFY_IS_FALSE(FirstRow.ContainsText()); } } @@ -658,8 +660,7 @@ void TextBufferTests::TestMixedRgbAndLegacyForeground() const auto x = cursor.GetPosition().X; const auto y = cursor.GetPosition().Y; const auto& row = tbi.GetRowByOffset(y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[x - 2]; const auto attrB = attrs[x - 1]; Log::Comment(NoThrowString().Format( @@ -703,8 +704,7 @@ void TextBufferTests::TestMixedRgbAndLegacyBackground() const auto x = cursor.GetPosition().X; const auto y = cursor.GetPosition().Y; const auto& row = tbi.GetRowByOffset(y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[x - 2]; const auto attrB = attrs[x - 1]; Log::Comment(NoThrowString().Format( @@ -746,8 +746,7 @@ void TextBufferTests::TestMixedRgbAndLegacyUnderline() const auto x = cursor.GetPosition().X; const auto y = cursor.GetPosition().Y; const auto& row = tbi.GetRowByOffset(y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[x - 2]; const auto attrB = attrs[x - 1]; Log::Comment(NoThrowString().Format( @@ -796,8 +795,7 @@ void TextBufferTests::TestMixedRgbAndLegacyBrightness() const auto x = cursor.GetPosition().X; const auto y = cursor.GetPosition().Y; const auto& row = tbi.GetRowByOffset(y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[x - 2]; const auto attrB = attrs[x - 1]; Log::Comment(NoThrowString().Format( @@ -856,9 +854,8 @@ void TextBufferTests::TestRgbEraseLine() VERIFY_ARE_EQUAL(y, 0); const auto& row = tbi.GetRowByOffset(y); - const auto attrRow = &row.GetAttrRow(); const auto len = tbi.GetSize().Width(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attr0 = attrs[0]; @@ -908,8 +905,7 @@ void TextBufferTests::TestUnintense() VERIFY_ARE_EQUAL(y, 0); const auto& row = tbi.GetRowByOffset(y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[x - 2]; const auto attrB = attrs[x - 1]; @@ -960,8 +956,7 @@ void TextBufferTests::TestUnintenseRgb() VERIFY_ARE_EQUAL(y, 0); const auto& row = tbi.GetRowByOffset(y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[x - 2]; const auto attrB = attrs[x - 1]; @@ -1020,8 +1015,7 @@ void TextBufferTests::TestComplexUnintense() VERIFY_ARE_EQUAL(y, 0); const auto& row = tbi.GetRowByOffset(y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[x - 6]; const auto attrB = attrs[x - 5]; const auto attrC = attrs[x - 4]; @@ -1103,8 +1097,7 @@ void TextBufferTests::CopyAttrs() VERIFY_ARE_EQUAL(y, 0); const auto& row = tbi.GetRowByOffset(0); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[0]; const auto attrB = attrs[1]; @@ -1155,8 +1148,7 @@ void TextBufferTests::EmptySgrTest() VERIFY_IS_TRUE(x >= 3); const auto& row = tbi.GetRowByOffset(y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[x - 3]; const auto attrB = attrs[x - 2]; const auto attrC = attrs[x - 1]; @@ -1217,8 +1209,7 @@ void TextBufferTests::TestReverseReset() VERIFY_IS_TRUE(x >= 3); const auto& row = tbi.GetRowByOffset(y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[x - 3]; const auto attrB = attrs[x - 2]; const auto attrC = attrs[x - 1]; @@ -1321,9 +1312,9 @@ void TextBufferTests::CopyLastAttr() const auto& row2 = tbi.GetRowByOffset(y + 2); const auto& row3 = tbi.GetRowByOffset(y + 3); - const std::vector attrs1{ row1.GetAttrRow().begin(), row1.GetAttrRow().end() }; - const std::vector attrs2{ row2.GetAttrRow().begin(), row2.GetAttrRow().end() }; - const std::vector attrs3{ row3.GetAttrRow().begin(), row3.GetAttrRow().end() }; + const std::vector attrs1{ row1.AttrBegin(), row1.AttrEnd() }; + const std::vector attrs2{ row2.AttrBegin(), row2.AttrEnd() }; + const std::vector attrs3{ row3.AttrBegin(), row3.AttrEnd() }; const auto attr1A = attrs1[0]; @@ -1382,8 +1373,7 @@ void TextBufferTests::TestRgbThenIntense() const auto x = cursor.GetPosition().X; const auto y = cursor.GetPosition().Y; const auto& row = tbi.GetRowByOffset(y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[x - 2]; const auto attrB = attrs[x - 1]; Log::Comment(NoThrowString().Format( @@ -1435,8 +1425,7 @@ void TextBufferTests::TestResetClearsIntensity() const auto x = cursor.GetPosition().X; const auto y = cursor.GetPosition().Y; const auto& row = tbi.GetRowByOffset(y); - const auto attrRow = &row.GetAttrRow(); - const std::vector attrs{ attrRow->begin(), attrRow->end() }; + const std::vector attrs{ row.AttrBegin(), row.AttrEnd() }; const auto attrA = attrs[x0]; const auto attrB = attrs[x0 + 1]; const auto attrC = attrs[x0 + 2]; @@ -1853,13 +1842,12 @@ void TextBufferTests::ResizeTraditionalRotationPreservesHighUnicode() // Get a position inside the buffer const til::point pos{ 2, 1 }; - auto position = _buffer->_storage[pos.Y].GetCharRow().GlyphAt(pos.X); // Fill it up with a sequence that will have to hit the high unicode storage. // This is the negative squared latin capital letter B emoji: 🅱 // It's encoded in UTF-16, as needed by the buffer. const auto bButton = L"\xD83C\xDD71"; - position = bButton; + _buffer->_storage[pos.Y].ReplaceCharacters(pos.X, 2, bButton); // Read back the text at that position and ensure that it matches what we wrote. const auto readBack = _buffer->GetTextDataAt(pos); @@ -1895,13 +1883,12 @@ void TextBufferTests::ScrollBufferRotationPreservesHighUnicode() // Get a position inside the buffer const til::point pos{ 2, 1 }; - auto position = _buffer->_storage[pos.Y].GetCharRow().GlyphAt(pos.X); // Fill it up with a sequence that will have to hit the high unicode storage. // This is the fire emoji: 🔥 // It's encoded in UTF-16, as needed by the buffer. const auto fire = L"\xD83D\xDD25"; - position = fire; + _buffer->_storage[pos.Y].ReplaceCharacters(pos.X, 2, fire); // Read back the text at that position and ensure that it matches what we wrote. const auto readBack = _buffer->GetTextDataAt(pos); @@ -1934,28 +1921,23 @@ void TextBufferTests::ResizeTraditionalHighUnicodeRowRemoval() auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer); // Get a position inside the buffer in the bottom row - const til::point pos{ 0, bufferSize.Y - 1 }; - auto position = _buffer->_storage[pos.Y].GetCharRow().GlyphAt(pos.X); + const til::point pos{ 0, bufferSize.Y - 2 }; // Fill it up with a sequence that will have to hit the high unicode storage. // This is the eggplant emoji: 🍆 // It's encoded in UTF-16, as needed by the buffer. const auto emoji = L"\xD83C\xDF46"; - position = emoji; + _buffer->_storage[pos.Y].ReplaceCharacters(pos.X, 2, emoji); // Read back the text at that position and ensure that it matches what we wrote. const auto readBack = _buffer->GetTextDataAt(pos); const auto readBackText = *readBack; VERIFY_ARE_EQUAL(String(emoji), String(readBackText.data(), gsl::narrow(readBackText.size()))); - VERIFY_ARE_EQUAL(1u, _buffer->GetUnicodeStorage()._map.size(), L"There should be one item in the map."); - // Perform resize to trim off the row of the buffer that included the emoji til::size trimmedBufferSize{ bufferSize.X, bufferSize.Y - 1 }; VERIFY_NT_SUCCESS(_buffer->ResizeTraditional(trimmedBufferSize)); - - VERIFY_IS_TRUE(_buffer->GetUnicodeStorage()._map.empty(), L"The map should now be empty."); } // This tests that columns removed from the buffer while resizing traditionally will also drop the high unicode @@ -1969,28 +1951,23 @@ void TextBufferTests::ResizeTraditionalHighUnicodeColumnRemoval() auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer); // Get a position inside the buffer in the last column - const til::point pos{ bufferSize.X - 1, 0 }; - auto position = _buffer->_storage[pos.Y].GetCharRow().GlyphAt(pos.X); + const til::point pos{ bufferSize.X - 2, 0 }; // Fill it up with a sequence that will have to hit the high unicode storage. // This is the peach emoji: 🍑 // It's encoded in UTF-16, as needed by the buffer. const auto emoji = L"\xD83C\xDF51"; - position = emoji; + _buffer->_storage[pos.Y].ReplaceCharacters(pos.X, 2, emoji); // Read back the text at that position and ensure that it matches what we wrote. const auto readBack = _buffer->GetTextDataAt(pos); const auto readBackText = *readBack; VERIFY_ARE_EQUAL(String(emoji), String(readBackText.data(), gsl::narrow(readBackText.size()))); - VERIFY_ARE_EQUAL(1u, _buffer->GetUnicodeStorage()._map.size(), L"There should be one item in the map."); - // Perform resize to trim off the column of the buffer that included the emoji til::size trimmedBufferSize{ bufferSize.X - 1, bufferSize.Y }; VERIFY_NT_SUCCESS(_buffer->ResizeTraditional(trimmedBufferSize)); - - VERIFY_IS_TRUE(_buffer->GetUnicodeStorage()._map.empty(), L"The map should now be empty."); } void TextBufferTests::TestBurrito() @@ -2014,6 +1991,57 @@ void TextBufferTests::TestBurrito() VERIFY_IS_FALSE(afterBurritoIter); } +void TextBufferTests::TestOverwriteChars() +{ + til::size bufferSize{ 10, 3 }; + UINT cursorSize = 12; + TextAttribute attr{ 0x7f }; + TextBuffer buffer{ bufferSize, attr, cursorSize, false, _renderer }; + auto& row = buffer.GetRowByOffset(0); + + // scientist emoji U+1F9D1 U+200D U+1F52C + static constexpr std::wstring_view complex1{ L"\U0001F9D1\U0000200D\U0001F52C" }; + // technologist emoji U+1F9D1 U+200D U+1F4BB + static constexpr std::wstring_view complex2{ L"\U0001F9D1\U0000200D\U0001F4BB" }; + static constexpr std::wstring_view simple{ L"X" }; + + // Test overwriting narrow chars with wide chars at the begin/end of a row. + row.ReplaceCharacters(0, 2, complex1); + row.ReplaceCharacters(8, 2, complex1); + VERIFY_ARE_EQUAL(L"\U0001F9D1\U0000200D\U0001F52C \U0001F9D1\U0000200D\U0001F52C", row.GetText()); + + // Test overwriting wide chars with wide chars slightly shifted left/right. + row.ReplaceCharacters(1, 2, complex1); + row.ReplaceCharacters(7, 2, complex1); + VERIFY_ARE_EQUAL(L" \U0001F9D1\U0000200D\U0001F52C \U0001F9D1\U0000200D\U0001F52C ", row.GetText()); + + // Test overwriting wide chars with wide chars. + row.ReplaceCharacters(1, 2, complex2); + row.ReplaceCharacters(7, 2, complex2); + VERIFY_ARE_EQUAL(L" \U0001F9D1\U0000200D\U0001F4BB \U0001F9D1\U0000200D\U0001F4BB ", row.GetText()); + + // Test overwriting wide chars with narrow chars. + row.ReplaceCharacters(1, 1, simple); + row.ReplaceCharacters(8, 1, simple); + VERIFY_ARE_EQUAL(L" X X ", row.GetText()); + + // Test clearing narrow/wide chars. + row.ReplaceCharacters(0, 1, simple); + row.ReplaceCharacters(1, 2, complex2); + row.ReplaceCharacters(3, 1, simple); + row.ReplaceCharacters(6, 1, simple); + row.ReplaceCharacters(7, 2, complex2); + row.ReplaceCharacters(9, 1, simple); + VERIFY_ARE_EQUAL(L"X\U0001F9D1\U0000200D\U0001F4BBX X\U0001F9D1\U0000200D\U0001F4BBX", row.GetText()); + row.ClearCell(0); + row.ClearCell(1); + row.ClearCell(3); + row.ClearCell(6); + row.ClearCell(8); + row.ClearCell(9); + VERIFY_ARE_EQUAL(L" ", row.GetText()); +} + void TextBufferTests::TestAppendRTFText() { { @@ -2270,11 +2298,12 @@ void TextBufferTests::GetGlyphBoundaries() // clang-format off const std::vector expected = { - { L"Buffer Start", { 0, 0 }, { 2, 0 }, { 1, 0 } }, - { L"Line Start", { 0, 1 }, { 2, 1 }, { 1, 1 } }, - { L"General Case", { 1, 1 }, { 3, 1 }, { 2, 1 } }, - { L"Line End", { 9, 1 }, { 0, 2 }, { 0, 2 } }, - { L"Buffer End", { 9, 9 }, { 0, 10 }, { 0, 10 } }, + { L"Buffer Start", { 0, 0 }, { 2, 0 }, { 1, 0 } }, + { L"Line Start", { 0, 1 }, { 2, 1 }, { 1, 1 } }, + { L"General Case 1", { 1, 1 }, { 3, 1 }, { 2, 1 } }, + { L"Line End", { 8, 1 }, { 0, 2 }, { 9, 1 } }, + { L"General Case 2", { 7, 1 }, { 9, 1 }, { 8, 1 } }, + { L"Buffer End", { 9, 9 }, { 0, 10 }, { 0, 10 } }, }; // clang-format on @@ -2644,14 +2673,14 @@ void TextBufferTests::HyperlinkTrim() const auto id = _buffer->GetHyperlinkId(url, customId); TextAttribute newAttr{ 0x7f }; newAttr.SetHyperlinkId(id); - _buffer->GetRowByOffset(pos.Y).GetAttrRow().SetAttrToEnd(pos.X, newAttr); + _buffer->GetRowByOffset(pos.Y).SetAttrToEnd(pos.X, newAttr); _buffer->AddHyperlinkToMap(url, id); // Set a different hyperlink id somewhere else in the buffer const til::point otherPos{ 70, 5 }; const auto otherId = _buffer->GetHyperlinkId(otherUrl, otherCustomId); newAttr.SetHyperlinkId(otherId); - _buffer->GetRowByOffset(otherPos.Y).GetAttrRow().SetAttrToEnd(otherPos.X, newAttr); + _buffer->GetRowByOffset(otherPos.Y).SetAttrToEnd(otherPos.X, newAttr); _buffer->AddHyperlinkToMap(otherUrl, otherId); // Increment the circular buffer @@ -2688,12 +2717,12 @@ void TextBufferTests::NoHyperlinkTrim() const auto id = _buffer->GetHyperlinkId(url, customId); TextAttribute newAttr{ 0x7f }; newAttr.SetHyperlinkId(id); - _buffer->GetRowByOffset(pos.Y).GetAttrRow().SetAttrToEnd(pos.X, newAttr); + _buffer->GetRowByOffset(pos.Y).SetAttrToEnd(pos.X, newAttr); _buffer->AddHyperlinkToMap(url, id); // Set the same hyperlink id somewhere else in the buffer const til::point otherPos{ 70, 5 }; - _buffer->GetRowByOffset(otherPos.Y).GetAttrRow().SetAttrToEnd(otherPos.X, newAttr); + _buffer->GetRowByOffset(otherPos.Y).SetAttrToEnd(otherPos.X, newAttr); // Increment the circular buffer _buffer->IncrementCircularBuffer(); diff --git a/src/inc/test/CommonState.hpp b/src/inc/test/CommonState.hpp index 40e584b543d..dabb9d0587a 100644 --- a/src/inc/test/CommonState.hpp +++ b/src/inc/test/CommonState.hpp @@ -24,7 +24,6 @@ unit testing projects in the codebase without a bunch of overhead. #include "../host/globals.h" #include "../host/inputReadHandleData.h" -#include "../buffer/out/CharRow.hpp" #include "../interactivity/inc/ServiceLocator.hpp" class CommonState @@ -241,26 +240,7 @@ class CommonState for (til::CoordType iRow = 0; iRow < cRowsToFill; iRow++) { ROW& row = textBuffer.GetRowByOffset(iRow); - FillRow(&row); - } - - textBuffer.GetCursor().SetYPosition(cRowsToFill); - } - - void FillTextBufferBisect() - { - CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation(); - // fill with some text that fills the whole row and has bisecting double byte characters - const auto cRowsToFill = s_csBufferHeight; - - VERIFY_IS_TRUE(gci.HasActiveOutputBuffer()); - - TextBuffer& textBuffer = gci.GetActiveOutputBuffer().GetTextBuffer(); - - for (til::CoordType iRow = 0; iRow < cRowsToFill; iRow++) - { - ROW& row = textBuffer.GetRowByOffset(iRow); - FillBisect(&row); + FillRow(&row, iRow & 1); } textBuffer.GetCursor().SetYPosition(cRowsToFill); @@ -278,54 +258,72 @@ class CommonState std::unique_ptr m_backupTextBufferInfo; std::unique_ptr m_readHandle; - void FillRow(ROW* pRow) + struct TestString + { + std::wstring_view string; + bool wide = false; + }; + + static void applyTestString(ROW* pRow, const auto& testStrings) + { + uint16_t x = 0; + for (const auto& t : testStrings) + { + if (t.wide) + { + pRow->ReplaceCharacters(x, 2, t.string); + x += 2; + } + else + { + for (const auto& ch : t.string) + { + pRow->ReplaceCharacters(x, 1, { &ch, 1 }); + x += 1; + } + } + } + } + + void FillRow(ROW* pRow, bool wrapForced) { // fill a row // 9 characters, 6 spaces. 15 total // か = \x304b // き = \x304d - const PCWSTR pwszText = L"AB" - L"\x304b\x304b" - L"C" - L"\x304d\x304d" - L"DE "; - const size_t length = wcslen(pwszText); - - std::vector attrs(length, DbcsAttribute()); - // set double-byte/double-width attributes - attrs[2].SetLeading(); - attrs[3].SetTrailing(); - attrs[5].SetLeading(); - attrs[6].SetTrailing(); - - CharRow& charRow = pRow->GetCharRow(); - OverwriteColumns(pwszText, pwszText + length, attrs.cbegin(), charRow.begin()); - - // set some colors - TextAttribute Attr = TextAttribute(0); - pRow->GetAttrRow().Reset(Attr); + + static constexpr std::array testStrings{ + TestString{ L"AB" }, + TestString{ L"\x304b", true }, + TestString{ L"C" }, + TestString{ L"\x304d", true }, + TestString{ L"DE " }, + }; + + applyTestString(pRow, testStrings); + // A = bright red on dark gray // This string starts at index 0 - Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY); - pRow->GetAttrRow().SetAttrToEnd(0, Attr); + auto Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY); + pRow->SetAttrToEnd(0, Attr); // BかC = dark gold on bright blue // This string starts at index 1 Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY); - pRow->GetAttrRow().SetAttrToEnd(1, Attr); + pRow->SetAttrToEnd(1, Attr); // き = bright white on dark purple // This string starts at index 5 Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_BLUE); - pRow->GetAttrRow().SetAttrToEnd(5, Attr); + pRow->SetAttrToEnd(5, Attr); // DE = black on dark green // This string starts at index 7 Attr = TextAttribute(BACKGROUND_GREEN); - pRow->GetAttrRow().SetAttrToEnd(7, Attr); + pRow->SetAttrToEnd(7, Attr); // odd rows forced a wrap - if (pRow->GetId() % 2 != 0) + if (wrapForced) { pRow->SetWrapForced(true); } @@ -334,41 +332,4 @@ class CommonState pRow->SetWrapForced(false); } } - - void FillBisect(ROW* pRow) - { - const CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation(); - // length 80 string of text with bisecting characters at the beginning and end. - // positions of き(\x304d) are at 0, 27-28, 39-40, 67-68, 79 - auto pwszText = - L"\x304d" - L"ABCDEFGHIJKLMNOPQRSTUVWXYZ" - L"\x304d\x304d" - L"0123456789" - L"\x304d\x304d" - L"ABCDEFGHIJKLMNOPQRSTUVWXYZ" - L"\x304d\x304d" - L"0123456789" - L"\x304d"; - const size_t length = wcslen(pwszText); - - std::vector attrs(length, DbcsAttribute()); - // set double-byte/double-width attributes - attrs[0].SetTrailing(); - attrs[27].SetLeading(); - attrs[28].SetTrailing(); - attrs[39].SetLeading(); - attrs[40].SetTrailing(); - attrs[67].SetLeading(); - attrs[68].SetTrailing(); - attrs[79].SetLeading(); - - CharRow& charRow = pRow->GetCharRow(); - OverwriteColumns(pwszText, pwszText + length, attrs.cbegin(), charRow.begin()); - - // everything gets default attributes - pRow->GetAttrRow().Reset(gci.GetActiveOutputBuffer().GetAttributes()); - - pRow->SetWrapForced(true); - } }; diff --git a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index 5185811315f..7655ca7ec74 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -360,18 +360,13 @@ class UiaTextRangeTests // fill first half of text buffer with text for (auto i = 0; i < _pTextBuffer->TotalRowCount() / 2; ++i) { + const std::wstring_view glyph{ i % 2 == 0 ? L" " : L"X" }; auto& row = _pTextBuffer->GetRowByOffset(i); - auto& charRow = row.GetCharRow(); - for (auto& cell : charRow) + auto width = row.size(); + + for (decltype(width) x = 0; x < width; ++x) { - if (i % 2 == 0) - { - cell.Char() = L' '; - } - else - { - cell.Char() = L'X'; - } + row.ReplaceCharacters(x, 1, glyph); } } @@ -495,20 +490,12 @@ class UiaTextRangeTests for (auto i = 0; i < _pTextBuffer->TotalRowCount(); ++i) { auto& row = _pTextBuffer->GetRowByOffset(i); - auto& charRow = row.GetCharRow(); - for (auto j = 0; j < charRow.size(); ++j) + auto width = row.size(); + + for (decltype(width) x = 0; x < width; ++x) { - // every 5th cell is a space, otherwise a letter - // this is used to simulate words - auto cell = charRow.GlyphAt(j); - if (j % 5 == 0) - { - cell = L" "; - } - else - { - cell = L"x"; - } + const std::wstring_view glyph{ x % 5 == 0 ? L" " : L"x" }; + row.ReplaceCharacters(x, 1, glyph); } } diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index f686f4cc0a3..2c898ff6023 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -848,7 +848,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, // If we're on the first cluster to be added and it's marked as "trailing" // (a.k.a. the right half of a two column character), then we need some special handling. - if (_clusterBuffer.empty() && it->DbcsAttr().IsTrailing()) + if (_clusterBuffer.empty() && it->DbcsAttr() == DbcsAttribute::Trailing) { // Move left to the one so the whole character can be struck correctly. --screenPoint.X; diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp index b6b051199b6..abb3641ef89 100644 --- a/src/renderer/base/renderer.hpp +++ b/src/renderer/base/renderer.hpp @@ -22,7 +22,6 @@ Author(s): #include "thread.hpp" #include "../../buffer/out/textBuffer.hpp" -#include "../../buffer/out/CharRow.hpp" // fwdecl unittest classes #ifdef UNIT_TESTING From 3fd27886d8d099efd1e93da962ea488c5a70942e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 31 Jul 2022 16:59:45 +0200 Subject: [PATCH 02/19] Fix resize with wide characters --- src/buffer/out/Row.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 3be6de8d887..eee148ebe87 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -291,30 +291,36 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType c case DbcsAttribute::Leading: if (fillingLastColumn) { - // If we're trying to fill the last cell with a leading byte, pad it out instead by clearing it. - // Don't increment iterator. We'll exit because we couldn't write a lead at the end of a line. + // The wide char doesn't fit. Pad with whitespace. + // Don't increment the iterator. Instead we'll return from this function and the + // caller can call WriteCells() again on the next row with the same iterator position. ClearCell(currentIndex); SetDoubleBytePadded(true); } else { ReplaceCharacters(currentIndex, 2, chars); + ++it; } break; case DbcsAttribute::Trailing: + // Handling the trailing half of wide chars ensures that we correctly restore + // wide characters when a user backs up and restores the viewport via CHAR_INFOs. if (fillingFirstColumn) { + // The wide char doesn't fit. Pad with whitespace. + // Ignore the character. There's no correct alternative way to handle this situation. ClearCell(currentIndex); } else { ReplaceCharacters(currentIndex - 1, 2, chars); } - break; - case DbcsAttribute::Single: - ReplaceCharacters(currentIndex, 1, chars); + ++it; break; default: + ReplaceCharacters(currentIndex, 1, chars); + ++it; break; } @@ -331,7 +337,6 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType c } // Move to the next cell for the next time through the loop. - ++it; ++currentIndex; } From ffcac717a36babfad048de50afe5495dc390302a Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 31 Jul 2022 17:24:12 +0200 Subject: [PATCH 03/19] Add test for buffer backup/restore --- src/host/ft_host/CJK_DbcsTests.cpp | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/host/ft_host/CJK_DbcsTests.cpp b/src/host/ft_host/CJK_DbcsTests.cpp index e3091d17f28..bc192d368d5 100644 --- a/src/host/ft_host/CJK_DbcsTests.cpp +++ b/src/host/ft_host/CJK_DbcsTests.cpp @@ -131,6 +131,10 @@ class DbcsTests TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") END_TEST_METHOD() + BEGIN_TEST_METHOD(TestDbcsBisectWriteCells) + TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") + END_TEST_METHOD() + BEGIN_TEST_METHOD(TestDbcsBisectWriteCellsEndW) TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") END_TEST_METHOD() @@ -1577,6 +1581,47 @@ void DbcsTests::TestDbcsBisect() } } +// Read/WriteConsoleOutput allow a user to implement a restricted form of buffer "backup" and "restore". +// But what if the saved region clips ("bisects") a wide character? This test ensures that we restore proper +// wide characters when given an unpaired trailing/leading CHAR_INFO in the first/last column of the given region. +void DbcsTests::TestDbcsBisectWriteCells() +{ + const auto out = GetStdHandle(STD_OUTPUT_HANDLE); + + std::array expected = PrepPattern::DoubledW; + PrepPattern::replaceColorPlaceholders(expected, FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_GREEN); + + // Write DoubledW to the viewport. + { + SMALL_RECT region{ .Left = 0, .Right = 15 }; + VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(out, expected.data(), { 16, 1 }, {}, ®ion)); + } + + // Make a "backup" of the viewport. + // The twist is that our backup region only copies the trailing/leading half of the first/last glyph respectively. + std::array backup{}; + constexpr COORD backupSize{ 13, 1 }; + SMALL_RECT backupRegion{ .Left = 2, .Right = 14 }; + VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(out, backup.data(), backupSize, {}, &backupRegion)); + + // Overwrite the DoubledW string except the first glyph. + // This produces "Qxxxxxx...". We leave the "Q" so that after our restore all 16 original CHAR_INFOs are present. + { + DWORD ignored; + VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputCharacterW(out, L'x', 15, { 1, 0 }, &ignored)); + } + + // Restore our "backup". + VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(out, backup.data(), backupSize, {}, &backupRegion)); + + std::array infos{}; + { + SMALL_RECT region{ .Left = 0, .Right = 15 }; + VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(out, infos.data(), { 16, 1 }, {}, ®ion)); + } + DbcsWriteRead::Verify(expected, infos); +} + // The following W versions of the tests check that we can't insert a bisecting cell even // when we try to force one in by writing cell-by-cell. // NOTE: This is a change in behavior from the legacy behavior. From bd9f6adce37e0f11822f30b7c47c1b1d123916c2 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 31 Jul 2022 17:34:33 +0200 Subject: [PATCH 04/19] Fix spell check --- .github/actions/spelling/expect/expect.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 52596dbb1ea..c6e3e9b2bdc 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -42,7 +42,6 @@ antialias antialiasing ANull anycpu -AOn APARTMENTTHREADED APCs api @@ -80,7 +79,6 @@ ASingle asm asmv asmx -AStomps ASYNCWINDOWPOS atch ATest @@ -1877,6 +1875,7 @@ QUESTIONMARK quickedit QUZ QWER +Qxxxxxx qzmp RAII RALT From ceea6e3734b01894797e9ec2e0c0f39ac37d3d87 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 13 Sep 2022 17:43:33 +0200 Subject: [PATCH 05/19] Finish merge, Address feedback --- src/buffer/out/Row.hpp | 35 +++++++++++++++++++++++++++++------ src/buffer/out/textBuffer.cpp | 2 +- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index f289837e5a5..7445e39eda2 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -107,18 +107,41 @@ class ROW final // These fields are a bit "wasteful", but it makes all this a bit more robust against // programming errors during initial development (which is when this comment was written). - // * _charsHeap as unique_ptr - // It can be stored in _chars and delete[] manually called if `_chars != _charsBuffer` - // * _chars as std::span - // The size may never exceed an uint16_t anyways - // * _charOffsets as std::span - // The length is already stored in _columns + // * _chars and _charsHeap are redundant + // If _charsHeap is stored in _chars, we can still infer that + // _chars was allocated on the heap if _chars != _charsBuffer. + // * _chars doesn't need a size_t size() + // The size may never exceed an uint16_t anyways. + // * _charOffsets doesn't need a size() at all + // The length is already stored in _columns. + + // Most text uses only a single wchar_t per codepoint / grapheme cluster. + // That's why TextBuffer allocates a large blob of virtual memory which we can use as + // a simplified chars buffer, without having to allocate any additional heap memory. + // _charsBuffer fits _columnCount characters at most. wchar_t* _charsBuffer = nullptr; + // ...but if this ROW needs to store more than _columnCount characters + // then it will allocate a larger string on the heap and store it here. + // The capacity of this string on the heap is stored in _chars.size(). std::unique_ptr _charsHeap; + // _chars either refers to our _charsBuffer or _charsHeap, defaulting to the former. + // _chars.size() is NOT the length of the string, but rather its capacity. + // _charOffsets[_columnCount] stores the length. std::span _chars; + // _charOffsets accelerates indexing into the above _chars string given a terminal column, + // by storing the character index/offset at which a column's text in _chars starts. + // It stores 1 more item than this row is wide, allowing it to store the + // past-the-end offset, which is thus equal to the length of the string. + // For instance given a 4 column ROW containing "abcd" it would store 01234. + // Given "a\u732Bd" ("\u732B" is a wide glyph) it would store 01123. + // Given "a\uD83D\uDE00d" ("\uD83D\uDE00" is an Emoji) it would store 01134. std::span _charOffsets; + // _attr is a run-length-encoded vector of TextAttribute with a decompressed + // length equal to _columnCount (= 1 TextAttribute per column). til::small_rle _attr; + // The width of the row in visual columns. uint16_t _columnCount = 0; + // Stores double-width/height (DECSWL/DECDWL/DECDHL) attributes. LineRendition _lineRendition = LineRendition::SingleWidth; // Occurs when the user runs out of text in a given row and we're forced to wrap the cursor to the next line bool _wrapForced = false; diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index f7cd9845d50..43a5d345249 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -1901,7 +1901,7 @@ std::wstring TextBuffer::GetPlainText(const til::point& start, const til::point& for (; it && spanLength > 0; ++it, --spanLength) { const auto& cell = *it; - if (!cell.DbcsAttr().IsTrailing()) + if (cell.DbcsAttr() != DbcsAttribute::Trailing) { const auto chars = cell.Chars(); text.append(chars); From 9200b21eb51cf2f9bf674aef44585f4aecd93442 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 14 Sep 2022 00:01:36 +0200 Subject: [PATCH 06/19] Fix a regression --- src/buffer/out/Row.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index eee148ebe87..32ee835ce8f 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -335,6 +335,10 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType c SetWrapForced(*wrap); } } + else + { + ++it; + } // Move to the next cell for the next time through the loop. ++currentIndex; From ea16b1a8db5a87daa7f9530ed08147eef4e965e3 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 20 Oct 2022 18:15:06 +0200 Subject: [PATCH 07/19] Minor adjustments, Fix build --- src/buffer/out/Row.cpp | 28 +++++++++++++------------- src/buffer/out/Row.hpp | 8 ++++---- src/terminal/adapter/adaptDispatch.cpp | 6 ++---- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 32ee835ce8f..0990c9b6467 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -118,14 +118,14 @@ bool ROW::WasDoubleBytePadded() const noexcept return _doubleBytePadded; } -LineRendition ROW::GetLineRendition() const noexcept +void ROW::SetLineRendition(const LineRendition lineRendition) noexcept { - return _lineRendition; + _lineRendition = lineRendition; } -void ROW::SetLineRendition(const LineRendition lineRendition) noexcept +LineRendition ROW::GetLineRendition() const noexcept { - _lineRendition = lineRendition; + return _lineRendition; } // Routine Description: @@ -355,14 +355,14 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType c bool ROW::SetAttrToEnd(const til::CoordType columnBegin, const TextAttribute attr) { - _attr.replace(clampedColumnInclusive(columnBegin), _attr.size(), attr); + _attr.replace(_clampedColumnInclusive(columnBegin), _attr.size(), attr); return true; } void ROW::ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars) { - const auto colBeg = clampedUint16(columnBegin); - const auto colEnd = clampedUint16(columnBegin + width); + const auto colBeg = _clampedUint16(columnBegin); + const auto colEnd = _clampedUint16(columnBegin + width); if (colBeg >= colEnd || colEnd > _columnCount || chars.empty()) { @@ -453,7 +453,7 @@ const til::small_rle& ROW::Attributes() const noexce TextAttribute ROW::GetAttrByColumn(const til::CoordType column) const { - return _attr.at(clampedUint16(column)); + return _attr.at(_clampedUint16(column)); } std::vector ROW::GetHyperlinks() const @@ -536,7 +536,7 @@ bool ROW::ContainsText() const noexcept std::wstring_view ROW::GlyphAt(til::CoordType column) const noexcept { - auto col = clampedColumn(column); + auto col = _clampedColumn(column); // Safety: col is [0, _columnCount). const auto beg = _uncheckedCharOffset(col); @@ -553,7 +553,7 @@ std::wstring_view ROW::GlyphAt(til::CoordType column) const noexcept DbcsAttribute ROW::DbcsAttrAt(til::CoordType column) const noexcept { - const auto col = clampedColumn(column); + const auto col = _clampedColumn(column); auto attr = DbcsAttribute::Single; // Safety: col is [0, _columnCount). @@ -577,7 +577,7 @@ std::wstring_view ROW::GetText() const noexcept DelimiterClass ROW::DelimiterClassAt(til::CoordType column, const std::wstring_view& wordDelimiters) const noexcept { - const auto col = clampedColumn(column); + const auto col = _clampedColumn(column); // Safety: col is [0, _columnCount). const auto glyph = _uncheckedChar(_uncheckedCharOffset(col)); @@ -596,19 +596,19 @@ DelimiterClass ROW::DelimiterClassAt(til::CoordType column, const std::wstring_v } template -constexpr uint16_t ROW::clampedUint16(T v) noexcept +constexpr uint16_t ROW::_clampedUint16(T v) noexcept { return static_cast(std::max(T{ 0 }, std::min(T{ 65535 }, v))); } template -constexpr uint16_t ROW::clampedColumn(T v) const noexcept +constexpr uint16_t ROW::_clampedColumn(T v) const noexcept { return static_cast(std::max(T{ 0 }, std::min(_columnCount - 1u, v))); } template -constexpr uint16_t ROW::clampedColumnInclusive(T v) const noexcept +constexpr uint16_t ROW::_clampedColumnInclusive(T v) const noexcept { return static_cast(std::max(T{ 0 }, std::min(_columnCount, v))); } diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index 7445e39eda2..550b9c08655 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -54,8 +54,8 @@ class ROW final bool WasWrapForced() const noexcept; void SetDoubleBytePadded(const bool doubleBytePadded) noexcept; bool WasDoubleBytePadded() const noexcept; - LineRendition GetLineRendition() const noexcept; void SetLineRendition(const LineRendition lineRendition) noexcept; + LineRendition GetLineRendition() const noexcept; void Reset(const TextAttribute& attr); void Resize(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute); @@ -91,11 +91,11 @@ class ROW final static constexpr uint16_t CharOffsetsMask = 0x7fff; template - static constexpr uint16_t clampedUint16(T v) noexcept; + static constexpr uint16_t _clampedUint16(T v) noexcept; template - constexpr uint16_t clampedColumn(T v) const noexcept; + constexpr uint16_t _clampedColumn(T v) const noexcept; template - constexpr uint16_t clampedColumnInclusive(T v) const noexcept; + constexpr uint16_t _clampedColumnInclusive(T v) const noexcept; wchar_t _uncheckedChar(size_t off) const noexcept; uint16_t _charSize() const noexcept; diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 7e5fb800f8d..a473850394c 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -700,15 +700,13 @@ void AdaptDispatch::_SelectiveEraseRect(TextBuffer& textBuffer, const til::rect& for (auto row = eraseRect.top; row < eraseRect.bottom; row++) { auto& rowBuffer = textBuffer.GetRowByOffset(row); - const auto& attrs = rowBuffer.GetAttrRow(); - auto& chars = rowBuffer.GetCharRow(); for (auto col = eraseRect.left; col < eraseRect.right; col++) { // Only unprotected cells are affected. - if (!attrs.GetAttrByColumn(col).IsProtected()) + if (!rowBuffer.GetAttrByColumn(col).IsProtected()) { // The text is cleared but the attributes are left as is. - chars.ClearGlyph(col); + rowBuffer.ClearCell(col); textBuffer.TriggerRedraw(Viewport::FromCoord({ col, row })); } } From 31c7ca7d34f6d200f42c5e13cc387cbb3cfe7efe Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sat, 5 Nov 2022 17:33:32 +0100 Subject: [PATCH 08/19] Address feedback --- src/buffer/out/Row.cpp | 2 +- src/buffer/out/sources.inc | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 0990c9b6467..3289b4a732a 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -372,7 +372,7 @@ void ROW::ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, co // Algorithm explanation // // Task: - // Replace the characters in cells [colBeg, colEnd) with a single cell `width`-wide consisting of `chars`. + // Replace the characters in cells [colBeg, colEnd) with a single `width`-wide glyph consisting of `chars`. // // Problem: // Imagine that we have the following ROW contents: diff --git a/src/buffer/out/sources.inc b/src/buffer/out/sources.inc index b78f575b240..af3d278749c 100644 --- a/src/buffer/out/sources.inc +++ b/src/buffer/out/sources.inc @@ -29,7 +29,6 @@ PRECOMPILED_CXX = 1 PRECOMPILED_INCLUDE = ..\precomp.h SOURCES= \ - ..\AttrRow.cpp \ ..\cursor.cpp \ ..\OutputCell.cpp \ ..\OutputCellIterator.cpp \ @@ -41,10 +40,6 @@ SOURCES= \ ..\textBuffer.cpp \ ..\textBufferCellIterator.cpp \ ..\textBufferTextIterator.cpp \ - ..\CharRow.cpp \ - ..\CharRowCell.cpp \ - ..\CharRowCellReference.cpp \ - ..\UnicodeStorage.cpp \ ..\search.cpp \ INCLUDES= \ From a707283b83c3e029d39f27794a94d1f8bca25014 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 7 Nov 2022 15:36:34 +0100 Subject: [PATCH 09/19] Some minor cleanup --- src/buffer/out/Row.cpp | 12 ++-- src/host/ft_host/CJK_DbcsTests.cpp | 90 +++++++++++++++--------------- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 3289b4a732a..d265f0cd7fd 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -369,6 +369,10 @@ void ROW::ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, co return; } + // Safety: + // * colBeg is now [0, _columnCount) + // * colEnd is now (colBeg, _columnCount] + // Algorithm explanation // // Task: @@ -378,8 +382,8 @@ void ROW::ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, co // Imagine that we have the following ROW contents: // "xxyyzz" // xx, yy, zz are 2 cell wide glyphs. We want to insert a 2 cell wide glyph ww at colBeg 1: - // ^^ - // ww + // ^^ + // ww // An incorrect result would be: // "xwwyzz" // The half cut off x and y glyph wouldn't make much sense, so we need to fill them with whitespace: @@ -392,10 +396,6 @@ void ROW::ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, co // The amount of leading whitespace we need to insert is thus colBeg - colExtBeg // and the amount of trailing whitespace colExtEnd - colEnd. - // Safety: - // * colBeg is now [0, _columnCount] - // * colEnd is now [colBeg, _columnCount] - // Extend range downwards (leading whitespace) uint16_t colExtBeg = colBeg; // Safety: colExtBeg is [0, _columnCount], because colBeg is. diff --git a/src/host/ft_host/CJK_DbcsTests.cpp b/src/host/ft_host/CJK_DbcsTests.cpp index bc192d368d5..50a371f0395 100644 --- a/src/host/ft_host/CJK_DbcsTests.cpp +++ b/src/host/ft_host/CJK_DbcsTests.cpp @@ -131,10 +131,6 @@ class DbcsTests TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") END_TEST_METHOD() - BEGIN_TEST_METHOD(TestDbcsBisectWriteCells) - TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") - END_TEST_METHOD() - BEGIN_TEST_METHOD(TestDbcsBisectWriteCellsEndW) TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") END_TEST_METHOD() @@ -158,6 +154,10 @@ class DbcsTests BEGIN_TEST_METHOD(TestDbcsStdCoutScenario) TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") END_TEST_METHOD() + + BEGIN_TEST_METHOD(TestDbcsBackupRestore) + TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") + END_TEST_METHOD() }; bool DbcsTests::DbcsTestSetup() @@ -1581,47 +1581,6 @@ void DbcsTests::TestDbcsBisect() } } -// Read/WriteConsoleOutput allow a user to implement a restricted form of buffer "backup" and "restore". -// But what if the saved region clips ("bisects") a wide character? This test ensures that we restore proper -// wide characters when given an unpaired trailing/leading CHAR_INFO in the first/last column of the given region. -void DbcsTests::TestDbcsBisectWriteCells() -{ - const auto out = GetStdHandle(STD_OUTPUT_HANDLE); - - std::array expected = PrepPattern::DoubledW; - PrepPattern::replaceColorPlaceholders(expected, FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_GREEN); - - // Write DoubledW to the viewport. - { - SMALL_RECT region{ .Left = 0, .Right = 15 }; - VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(out, expected.data(), { 16, 1 }, {}, ®ion)); - } - - // Make a "backup" of the viewport. - // The twist is that our backup region only copies the trailing/leading half of the first/last glyph respectively. - std::array backup{}; - constexpr COORD backupSize{ 13, 1 }; - SMALL_RECT backupRegion{ .Left = 2, .Right = 14 }; - VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(out, backup.data(), backupSize, {}, &backupRegion)); - - // Overwrite the DoubledW string except the first glyph. - // This produces "Qxxxxxx...". We leave the "Q" so that after our restore all 16 original CHAR_INFOs are present. - { - DWORD ignored; - VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputCharacterW(out, L'x', 15, { 1, 0 }, &ignored)); - } - - // Restore our "backup". - VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(out, backup.data(), backupSize, {}, &backupRegion)); - - std::array infos{}; - { - SMALL_RECT region{ .Left = 0, .Right = 15 }; - VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(out, infos.data(), { 16, 1 }, {}, ®ion)); - } - DbcsWriteRead::Verify(expected, infos); -} - // The following W versions of the tests check that we can't insert a bisecting cell even // when we try to force one in by writing cell-by-cell. // NOTE: This is a change in behavior from the legacy behavior. @@ -2083,3 +2042,44 @@ void DbcsTests::TestDbcsStdCoutScenario() VERIFY_ARE_EQUAL(cchReadBack, dwRead, L"We should have read as many characters as we expected (length of original printed line.)"); VERIFY_ARE_EQUAL(String(test), String(psReadBack.get()), L"String should match what we wrote."); } + +// Read/WriteConsoleOutput allow a user to implement a restricted form of buffer "backup" and "restore". +// But what if the saved region clips ("bisects") a wide character? This test ensures that we restore proper +// wide characters when given an unpaired trailing/leading CHAR_INFO in the first/last column of the given region. +void DbcsTests::TestDbcsBackupRestore() +{ + const auto out = GetStdHandle(STD_OUTPUT_HANDLE); + + std::array expected = PrepPattern::DoubledW; + PrepPattern::replaceColorPlaceholders(expected, FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_GREEN); + + // Write DoubledW to the viewport. + { + SMALL_RECT region{ .Left = 0, .Right = 15 }; + VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(out, expected.data(), { 16, 1 }, {}, ®ion)); + } + + // Make a "backup" of the viewport. + // The twist is that our backup region only copies the trailing/leading half of the first/last glyph respectively. + std::array backup{}; + constexpr COORD backupSize{ 13, 1 }; + SMALL_RECT backupRegion{ .Left = 2, .Right = 14 }; + VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(out, backup.data(), backupSize, {}, &backupRegion)); + + // Overwrite the DoubledW string except the first glyph. + // This produces "Qxxxxxx...". We leave the "Q" so that after our restore all 16 original CHAR_INFOs are present. + { + DWORD ignored; + VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputCharacterW(out, L'x', 15, { 1, 0 }, &ignored)); + } + + // Restore our "backup". + VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(out, backup.data(), backupSize, {}, &backupRegion)); + + std::array infos{}; + { + SMALL_RECT region{ .Left = 0, .Right = 15 }; + VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(out, infos.data(), { 16, 1 }, {}, ®ion)); + } + DbcsWriteRead::Verify(expected, infos); +} From e81ec2b5ea0aaf1a6b9acefc447488f4477483f0 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 7 Nov 2022 15:38:33 +0100 Subject: [PATCH 10/19] Tests run faster without sleep --- src/host/ft_host/InitTests.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/host/ft_host/InitTests.cpp b/src/host/ft_host/InitTests.cpp index 4c51a048025..7b7b3348494 100644 --- a/src/host/ft_host/InitTests.cpp +++ b/src/host/ft_host/InitTests.cpp @@ -238,7 +238,6 @@ MODULE_SETUP(ModuleSetup) VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(FreeConsole()); // Wait a moment for the driver to be ready after freeing to attach. - Sleep(1000); VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(AttachConsole(dwFindPid)); auto tries = 0; From 4a4feee7fe7bc7615c21a12bda74828e1cf4dd41 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 9 Nov 2022 16:41:28 +0100 Subject: [PATCH 11/19] Address feedback --- src/buffer/out/ut_textbuffer/ReflowTests.cpp | 11 ++++++++--- src/host/ut_host/TextBufferTests.cpp | 15 ++++++++------- .../ut_interactivity_win32/UiaTextRangeTests.cpp | 4 ++-- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/buffer/out/ut_textbuffer/ReflowTests.cpp b/src/buffer/out/ut_textbuffer/ReflowTests.cpp index e2b394507b0..c04719ba3d7 100644 --- a/src/buffer/out/ut_textbuffer/ReflowTests.cpp +++ b/src/buffer/out/ut_textbuffer/ReflowTests.cpp @@ -781,26 +781,31 @@ class ReflowTests VERIFY_ARE_EQUAL(testRow.wrap, row.WasWrapForced(), indexString); til::CoordType x = 0; + til::CoordType j = 0; for (const auto& ch : testRow.text) { + indexString.Format(L"[Cell %d, %d; Text line index %d]", x, y, j); + if (IsGlyphFullWidth(ch)) { // Char is full width in test buffer, so // ensure that real buffer is LEAD, TRAIL (ch) - VERIFY_IS_TRUE(row.DbcsAttrAt(x) == DbcsAttribute::Leading, indexString); + VERIFY_ARE_EQUAL(row.DbcsAttrAt(x), DbcsAttribute::Leading, indexString); VERIFY_ARE_EQUAL(ch, row.GlyphAt(x).front(), indexString); ++x; - VERIFY_IS_TRUE(row.DbcsAttrAt(x) == DbcsAttribute::Trailing, indexString); + VERIFY_ARE_EQUAL(row.DbcsAttrAt(x), DbcsAttribute::Trailing, indexString); VERIFY_ARE_EQUAL(ch, row.GlyphAt(x).front(), indexString); ++x; } else { - VERIFY_IS_TRUE(row.DbcsAttrAt(x) == DbcsAttribute::Single, indexString); + VERIFY_ARE_EQUAL(row.DbcsAttrAt(x), DbcsAttribute::Single, indexString); VERIFY_ARE_EQUAL(ch, row.GlyphAt(x).front(), indexString); ++x; } + + j++; } y++; diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index 9647bff25c5..c6dc226886d 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -324,7 +324,7 @@ void TextBufferTests::DoBoundaryTest(PCWCHAR const pwszInputString, void TextBufferTests::TestBoundaryMeasuresEmptyString() { - auto csBufferWidth = GetBufferWidth(); + const auto csBufferWidth = GetBufferWidth(); // length 0, left 80, right 0 const auto pwszLazyDog = L""; @@ -333,16 +333,16 @@ void TextBufferTests::TestBoundaryMeasuresEmptyString() void TextBufferTests::TestBoundaryMeasuresFullString() { - auto csBufferWidth = GetBufferWidth(); + const auto csBufferWidth = GetBufferWidth(); // length 0, left 80, right 0 - std::wstring str(csBufferWidth, L'X'); + const std::wstring str(csBufferWidth, L'X'); DoBoundaryTest(str.data(), csBufferWidth, csBufferWidth, 0, 80); } void TextBufferTests::TestBoundaryMeasuresRegularString() { - auto csBufferWidth = GetBufferWidth(); + const auto csBufferWidth = GetBufferWidth(); // length 44, left 0, right 44 const auto pwszLazyDog = L"The quick brown fox jumps over the lazy dog."; @@ -351,7 +351,7 @@ void TextBufferTests::TestBoundaryMeasuresRegularString() void TextBufferTests::TestBoundaryMeasuresFloatingString() { - auto csBufferWidth = GetBufferWidth(); + const auto csBufferWidth = GetBufferWidth(); // length 5 spaces + 4 chars + 5 spaces = 14, left 5, right 9 const auto pwszOffsets = L" C:\\> "; @@ -1921,7 +1921,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeRowRemoval() auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer); // Get a position inside the buffer in the bottom row - const til::point pos{ 0, bufferSize.Y - 2 }; + const til::point pos{ 0, bufferSize.Y - 1 }; // Fill it up with a sequence that will have to hit the high unicode storage. // This is the eggplant emoji: 🍆 @@ -1950,7 +1950,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeColumnRemoval() const TextAttribute attr{ 0x7f }; auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer); - // Get a position inside the buffer in the last column + // Get a position inside the buffer in the last column (-2 as the inserted character is 2 columns wide). const til::point pos{ bufferSize.X - 2, 0 }; // Fill it up with a sequence that will have to hit the high unicode storage. @@ -2033,6 +2033,7 @@ void TextBufferTests::TestOverwriteChars() row.ReplaceCharacters(7, 2, complex2); row.ReplaceCharacters(9, 1, simple); VERIFY_ARE_EQUAL(L"X\U0001F9D1\U0000200D\U0001F4BBX X\U0001F9D1\U0000200D\U0001F4BBX", row.GetText()); + row.ClearCell(0); row.ClearCell(1); row.ClearCell(3); diff --git a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index b73c37b8875..b868cab4f50 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -362,7 +362,7 @@ class UiaTextRangeTests { const std::wstring_view glyph{ i % 2 == 0 ? L" " : L"X" }; auto& row = _pTextBuffer->GetRowByOffset(i); - auto width = row.size(); + const auto width = row.size(); for (decltype(width) x = 0; x < width; ++x) { @@ -490,7 +490,7 @@ class UiaTextRangeTests for (auto i = 0; i < _pTextBuffer->TotalRowCount(); ++i) { auto& row = _pTextBuffer->GetRowByOffset(i); - auto width = row.size(); + const auto width = row.size(); for (decltype(width) x = 0; x < width; ++x) { From c819d0da5a97b61833b865e0ef9b880e8ee3e5a5 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 9 Nov 2022 16:59:03 +0100 Subject: [PATCH 12/19] Fix bad merge --- src/cascadia/TerminalCore/terminalrenderdata.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index 3fbc94ee0a1..51b0696d56d 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -72,7 +72,8 @@ CursorType Terminal::GetCursorStyle() const noexcept bool Terminal::IsCursorDoubleWidth() const { - const auto position = _activeBuffer().GetCursor().GetPosition(); + const auto& buffer = _activeBuffer(); + const auto position = buffer.GetCursor().GetPosition(); return buffer.GetRowByOffset(position.y).DbcsAttrAt(position.x) != DbcsAttribute::Single; } From 1dee096e444c10d0c602eabf3b7672215aeeda53 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 9 Nov 2022 20:44:55 +0100 Subject: [PATCH 13/19] Address feedback --- src/host/ut_host/TextBufferTests.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index c6dc226886d..7c8b9a04ec3 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -1999,31 +1999,31 @@ void TextBufferTests::TestOverwriteChars() TextBuffer buffer{ bufferSize, attr, cursorSize, false, _renderer }; auto& row = buffer.GetRowByOffset(0); - // scientist emoji U+1F9D1 U+200D U+1F52C - static constexpr std::wstring_view complex1{ L"\U0001F9D1\U0000200D\U0001F52C" }; - // technologist emoji U+1F9D1 U+200D U+1F4BB - static constexpr std::wstring_view complex2{ L"\U0001F9D1\U0000200D\U0001F4BB" }; - static constexpr std::wstring_view simple{ L"X" }; +// scientist emoji U+1F9D1 U+200D U+1F52C +#define complex1 L"\U0001F9D1\U0000200D\U0001F52C" +// technologist emoji U+1F9D1 U+200D U+1F4BB +#define complex2 L"\U0001F9D1\U0000200D\U0001F4BB" +#define simple L"X" // Test overwriting narrow chars with wide chars at the begin/end of a row. row.ReplaceCharacters(0, 2, complex1); row.ReplaceCharacters(8, 2, complex1); - VERIFY_ARE_EQUAL(L"\U0001F9D1\U0000200D\U0001F52C \U0001F9D1\U0000200D\U0001F52C", row.GetText()); + VERIFY_ARE_EQUAL(complex1 L" " complex1, row.GetText()); // Test overwriting wide chars with wide chars slightly shifted left/right. row.ReplaceCharacters(1, 2, complex1); row.ReplaceCharacters(7, 2, complex1); - VERIFY_ARE_EQUAL(L" \U0001F9D1\U0000200D\U0001F52C \U0001F9D1\U0000200D\U0001F52C ", row.GetText()); + VERIFY_ARE_EQUAL(L" " complex1 L" " complex1 L" ", row.GetText()); // Test overwriting wide chars with wide chars. row.ReplaceCharacters(1, 2, complex2); row.ReplaceCharacters(7, 2, complex2); - VERIFY_ARE_EQUAL(L" \U0001F9D1\U0000200D\U0001F4BB \U0001F9D1\U0000200D\U0001F4BB ", row.GetText()); + VERIFY_ARE_EQUAL(L" " complex2 L" " complex2 L" ", row.GetText()); // Test overwriting wide chars with narrow chars. row.ReplaceCharacters(1, 1, simple); row.ReplaceCharacters(8, 1, simple); - VERIFY_ARE_EQUAL(L" X X ", row.GetText()); + VERIFY_ARE_EQUAL(L" " simple L" " simple L" ", row.GetText()); // Test clearing narrow/wide chars. row.ReplaceCharacters(0, 1, simple); @@ -2032,7 +2032,7 @@ void TextBufferTests::TestOverwriteChars() row.ReplaceCharacters(6, 1, simple); row.ReplaceCharacters(7, 2, complex2); row.ReplaceCharacters(9, 1, simple); - VERIFY_ARE_EQUAL(L"X\U0001F9D1\U0000200D\U0001F4BBX X\U0001F9D1\U0000200D\U0001F4BBX", row.GetText()); + VERIFY_ARE_EQUAL(simple L" " complex2 L" " simple L" " simple L" " complex2 L" " simple, row.GetText()); row.ClearCell(0); row.ClearCell(1); @@ -2041,6 +2041,10 @@ void TextBufferTests::TestOverwriteChars() row.ClearCell(8); row.ClearCell(9); VERIFY_ARE_EQUAL(L" ", row.GetText()); + +#undef simple +#undef complex2 +#undef complex1 } void TextBufferTests::TestAppendRTFText() From f95977b67a517358c54f76b786ca9956c73a9b07 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 9 Nov 2022 21:06:16 +0100 Subject: [PATCH 14/19] Pacify AuditMode --- src/cascadia/TerminalCore/Terminal.hpp | 2 +- src/cascadia/TerminalCore/terminalrenderdata.cpp | 2 +- src/host/renderData.cpp | 2 +- src/host/renderData.hpp | 2 +- src/host/screenInfo.cpp | 2 +- src/host/screenInfo.hpp | 2 +- src/host/ut_host/VtIoTests.cpp | 2 +- src/renderer/inc/IRenderData.hpp | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 55875361888..85fdfd41d5f 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -184,7 +184,7 @@ class Microsoft::Terminal::Core::Terminal final : ULONG GetCursorHeight() const noexcept override; ULONG GetCursorPixelWidth() const noexcept override; CursorType GetCursorStyle() const noexcept override; - bool IsCursorDoubleWidth() const override; + bool IsCursorDoubleWidth() const noexcept override; const std::vector GetOverlays() const noexcept override; const bool IsGridLineDrawingAllowed() noexcept override; const std::wstring GetHyperlinkUri(uint16_t id) const override; diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index 51b0696d56d..39825f32ec7 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -70,7 +70,7 @@ CursorType Terminal::GetCursorStyle() const noexcept return _activeBuffer().GetCursor().GetType(); } -bool Terminal::IsCursorDoubleWidth() const +bool Terminal::IsCursorDoubleWidth() const noexcept { const auto& buffer = _activeBuffer(); const auto position = buffer.GetCursor().GetPosition(); diff --git a/src/host/renderData.cpp b/src/host/renderData.cpp index 2861db30c31..3d381aea18d 100644 --- a/src/host/renderData.cpp +++ b/src/host/renderData.cpp @@ -250,7 +250,7 @@ const std::vector RenderData::GetOver // - // Return Value: // - true if the cursor should be drawn twice as wide as usual -bool RenderData::IsCursorDoubleWidth() const +bool RenderData::IsCursorDoubleWidth() const noexcept { const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); return gci.GetActiveOutputBuffer().CursorIsDoubleWidth(); diff --git a/src/host/renderData.hpp b/src/host/renderData.hpp index 4158d000465..b25b9493662 100644 --- a/src/host/renderData.hpp +++ b/src/host/renderData.hpp @@ -42,7 +42,7 @@ class RenderData final : ULONG GetCursorHeight() const noexcept override; CursorType GetCursorStyle() const noexcept override; ULONG GetCursorPixelWidth() const noexcept override; - bool IsCursorDoubleWidth() const override; + bool IsCursorDoubleWidth() const noexcept override; const std::vector GetOverlays() const noexcept override; diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index 6cd58e1e852..3dcfc5817cc 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -2684,7 +2684,7 @@ Viewport SCREEN_INFORMATION::GetVirtualViewport() const noexcept // - // Return Value: // - true if the character at the cursor's current position is wide -bool SCREEN_INFORMATION::CursorIsDoubleWidth() const +bool SCREEN_INFORMATION::CursorIsDoubleWidth() const noexcept { const auto& buffer = GetTextBuffer(); const auto position = buffer.GetCursor().GetPosition(); diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp index 02043c5884f..6845f369ed3 100644 --- a/src/host/screenInfo.hpp +++ b/src/host/screenInfo.hpp @@ -157,7 +157,7 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console InputBuffer* const GetActiveInputBuffer() const override; #pragma endregion - bool CursorIsDoubleWidth() const; + bool CursorIsDoubleWidth() const noexcept; DWORD OutputMode; WORD ResizingWindow; // > 0 if we should ignore WM_SIZE messages diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 703e973eb20..c522b64923e 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -325,7 +325,7 @@ class MockRenderData : public IRenderData, IUiaData return 12ul; } - bool IsCursorDoubleWidth() const override + bool IsCursorDoubleWidth() const noexcept override { return false; } diff --git a/src/renderer/inc/IRenderData.hpp b/src/renderer/inc/IRenderData.hpp index 1477c2bd784..a8d615e602b 100644 --- a/src/renderer/inc/IRenderData.hpp +++ b/src/renderer/inc/IRenderData.hpp @@ -52,7 +52,7 @@ namespace Microsoft::Console::Render virtual ULONG GetCursorHeight() const noexcept = 0; virtual CursorType GetCursorStyle() const noexcept = 0; virtual ULONG GetCursorPixelWidth() const noexcept = 0; - virtual bool IsCursorDoubleWidth() const = 0; + virtual bool IsCursorDoubleWidth() const noexcept = 0; virtual const std::vector GetOverlays() const noexcept = 0; From b37ae2132d4ab0e095ff02b701d290f055569935 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 9 Nov 2022 21:33:08 +0100 Subject: [PATCH 15/19] Fix build failure --- .../win32/ut_interactivity_win32/UiaTextRangeTests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index b868cab4f50..ae5c9e42eef 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -364,7 +364,7 @@ class UiaTextRangeTests auto& row = _pTextBuffer->GetRowByOffset(i); const auto width = row.size(); - for (decltype(width) x = 0; x < width; ++x) + for (uint16_t x = 0; x < width; ++x) { row.ReplaceCharacters(x, 1, glyph); } @@ -492,7 +492,7 @@ class UiaTextRangeTests auto& row = _pTextBuffer->GetRowByOffset(i); const auto width = row.size(); - for (decltype(width) x = 0; x < width; ++x) + for (uint16_t x = 0; x < width; ++x) { const std::wstring_view glyph{ x % 5 == 0 ? L" " : L"x" }; row.ReplaceCharacters(x, 1, glyph); From dd59ffce5e7b3139bb23a3f3aba0cf385c5aabe7 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 9 Nov 2022 22:15:48 +0100 Subject: [PATCH 16/19] Fix test failure --- src/host/ut_host/TextBufferTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index 7c8b9a04ec3..c68f029162e 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -2032,7 +2032,7 @@ void TextBufferTests::TestOverwriteChars() row.ReplaceCharacters(6, 1, simple); row.ReplaceCharacters(7, 2, complex2); row.ReplaceCharacters(9, 1, simple); - VERIFY_ARE_EQUAL(simple L" " complex2 L" " simple L" " simple L" " complex2 L" " simple, row.GetText()); + VERIFY_ARE_EQUAL(simple complex2 simple L" " simple complex2 simple, row.GetText()); row.ClearCell(0); row.ClearCell(1); From dc3c593cff322de445626d20b9363a8994d7e174 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 11 Nov 2022 01:54:24 +0100 Subject: [PATCH 17/19] Address feedback --- src/buffer/out/Row.cpp | 93 +++++++++++++------------- src/buffer/out/Row.hpp | 18 ++++- src/buffer/out/textBuffer.cpp | 119 +++++++++++++++++----------------- 3 files changed, 124 insertions(+), 106 deletions(-) diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index d265f0cd7fd..8690abced45 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -145,12 +145,19 @@ void ROW::Reset(const TextAttribute& attr) _init(); } +void ROW::_init() noexcept +{ + std::fill_n(_chars.begin(), _columnCount, UNICODE_SPACE); + std::iota(_charOffsets.begin(), _charOffsets.end(), uint16_t{ 0 }); +} + // Routine Description: // - resizes ROW to new width // Arguments: -// - width - the new width, in cells -// Return Value: -// - S_OK if successful, otherwise relevant error +// - charsBuffer - a new backing buffer to use for _charsBuffer +// - charOffsetsBuffer - a new backing buffer to use for _charOffsets +// - rowWidth - the new width, in cells +// - fillAttribute - the attribute to use for any newly added, trailing cells void ROW::Resize(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute) { // A default-constructed ROW has no cols/chars to copy. @@ -446,6 +453,43 @@ void ROW::ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, co } } +// This function represents the slow path of ReplaceCharacters(), +// as it reallocates the backing buffer and shifts the char offsets. +// The parameters are difficult to explain, but their names are identical to +// local variables in ReplaceCharacters() which I've attempted to document there. +void ROW::_resizeChars(uint16_t colExtEnd, uint16_t chExtBeg, uint16_t chExtEnd, size_t chExtEndNew) +{ + const auto diff = chExtEndNew - chExtEnd; + const auto currentLength = _charSize(); + const auto newLength = currentLength + diff; + + if (newLength <= _chars.size()) + { + std::copy_n(_chars.begin() + chExtEnd, currentLength - chExtEnd, _chars.begin() + chExtEndNew); + } + else + { + const auto minCapacity = std::min(UINT16_MAX, _chars.size() + (_chars.size() >> 1)); + const auto newCapacity = gsl::narrow(std::max(newLength, minCapacity)); + + auto charsHeap = std::make_unique_for_overwrite(newCapacity); + const std::span chars{ charsHeap.get(), newCapacity }; + + std::copy_n(_chars.begin(), chExtBeg, chars.begin()); + std::copy_n(_chars.begin() + chExtEnd, currentLength - chExtEnd, chars.begin() + chExtEndNew); + + _charsHeap = std::move(charsHeap); + _chars = chars; + } + + auto it = _charOffsets.begin() + colExtEnd; + const auto end = _charOffsets.end(); + for (; it != end; ++it) + { + *it = gsl::narrow_cast(*it + diff); + } +} + const til::small_rle& ROW::Attributes() const noexcept { return _attr; @@ -545,7 +589,7 @@ std::wstring_view ROW::GlyphAt(til::CoordType column) const noexcept while (_uncheckedIsTrailer(++col)) { } - // Safety: col is now [1, _columnCount]. + // Safety: col is now (0, _columnCount]. const auto end = _uncheckedCharOffset(col); return { _chars.begin() + beg, _chars.begin() + end }; @@ -613,7 +657,7 @@ constexpr uint16_t ROW::_clampedColumnInclusive(T v) const noexcept return static_cast(std::max(T{ 0 }, std::min(_columnCount, v))); } -// Safety: col must be [0, _charSize()]. +// Safety: off must be [0, _charSize()]. wchar_t ROW::_uncheckedChar(size_t off) const noexcept { return til::at(_chars, off); @@ -636,42 +680,3 @@ bool ROW::_uncheckedIsTrailer(size_t col) const noexcept { return WI_IsFlagSet(til::at(_charOffsets, col), CharOffsetsTrailer); } - -void ROW::_init() noexcept -{ - std::fill_n(_chars.begin(), _columnCount, UNICODE_SPACE); - iota_n(_charOffsets.begin(), _columnCount + 1u, uint16_t{ 0 }); -} - -void ROW::_resizeChars(uint16_t colExtEnd, uint16_t chExtBeg, uint16_t chExtEnd, size_t chExtEndNew) -{ - const auto diff = chExtEndNew - chExtEnd; - const auto currentLength = _charSize(); - const auto newLength = currentLength + diff; - - if (newLength <= _chars.size()) - { - std::copy_n(_chars.begin() + chExtEnd, currentLength - chExtEnd, _chars.begin() + chExtEndNew); - } - else - { - const auto minCapacity = std::min(UINT16_MAX, _chars.size() + (_chars.size() >> 1)); - const auto newCapacity = gsl::narrow(std::max(newLength, minCapacity)); - - auto charsHeap = std::make_unique_for_overwrite(newCapacity); - const std::span chars{ charsHeap.get(), newCapacity }; - - std::copy_n(_chars.begin(), chExtBeg, chars.begin()); - std::copy_n(_chars.begin() + chExtEnd, currentLength - chExtEnd, chars.begin() + chExtEndNew); - - _charsHeap = std::move(charsHeap); - _chars = chars; - } - - auto it = _charOffsets.begin() + colExtEnd; - const auto end = _charOffsets.end(); - for (; it != end; ++it) - { - *it = gsl::narrow_cast(*it + diff); - } -} diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index 550b9c08655..6c279cda67a 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -87,6 +87,9 @@ class ROW final #endif private: + // To simplify the detection of wide glyphs, we don't just store the simple character offset as described + // for _charOffsets. Instead we use the most significant bit to indicate whether any column is the + // trailing half of a wide glyph. This simplifies many implementation details via _uncheckedIsTrailer. static constexpr uint16_t CharOffsetsTrailer = 0x8000; static constexpr uint16_t CharOffsetsMask = 0x7fff; @@ -132,9 +135,18 @@ class ROW final // by storing the character index/offset at which a column's text in _chars starts. // It stores 1 more item than this row is wide, allowing it to store the // past-the-end offset, which is thus equal to the length of the string. - // For instance given a 4 column ROW containing "abcd" it would store 01234. - // Given "a\u732Bd" ("\u732B" is a wide glyph) it would store 01123. - // Given "a\uD83D\uDE00d" ("\uD83D\uDE00" is an Emoji) it would store 01134. + // + // For instance given a 4 column ROW containing "abcd" it would store 01234, + // because each of "abcd" are 1 column wide and 1 wchar_t per column. + // Given "a\u732Bd" it would store 01123, because "\u732B" is a wide glyph + // and "11" indicates that both column 1 and 2 start at &_chars[1] (= wide glyph). + // The fact that the next offset is 2 tells us that the glyph is 1 wchar_t long. + // Given "a\uD83D\uDE00d" ("\uD83D\uDE00" is an Emoji) it would store 01134, + // because while it's 2 cells wide as indicated by 2 offsets that are identical (11), + // the next offset is 3, which indicates that the glyph is 3-1 = 2 wchar_t long. + // + // In other words, _charOffsets tells us both the width in chars and width in columns. + // See CharOffsetsTrailer for more information. std::span _charOffsets; // _attr is a run-length-encoded vector of TextAttribute with a decompressed // length equal to _columnCount (= 1 TextAttribute per column). diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 43a5d345249..fb658b007b2 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -13,71 +13,74 @@ #include "../../types/inc/Utf16Parser.hpp" #include "../../types/inc/GlyphWidth.hpp" -struct BufferAllocator +namespace { - BufferAllocator(til::size sz) + struct BufferAllocator { - 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)); - - _buffer = wil::unique_virtualalloc_ptr{ static_cast(VirtualAlloc(nullptr, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) }; - THROW_IF_NULL_ALLOC(_buffer); - - _data = std::span{ _buffer.get(), allocSize }.begin(); - _rowStride = rowStride; - _indicesOffset = charsBytes; - _width = w; - _height = h; - } + BufferAllocator(til::size sz) + { + 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)); + + _buffer = wil::unique_virtualalloc_ptr{ static_cast(VirtualAlloc(nullptr, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) }; + THROW_IF_NULL_ALLOC(_buffer); + + _data = std::span{ _buffer.get(), allocSize }.begin(); + _rowStride = rowStride; + _indicesOffset = charsBytes; + _width = w; + _height = h; + } - BufferAllocator& operator++() noexcept - { - _data += _rowStride; - return *this; - } + BufferAllocator& operator++() noexcept + { + _data += _rowStride; + return *this; + } - wchar_t* chars() const noexcept - { - return til::bit_cast(&*_data); - } + wchar_t* chars() const noexcept + { + return til::bit_cast(&*_data); + } - uint16_t* indices() const noexcept - { - return til::bit_cast(&*(_data + _indicesOffset)); - } + uint16_t* indices() const noexcept + { + return til::bit_cast(&*(_data + _indicesOffset)); + } - uint16_t width() const noexcept - { - return _width; - } + uint16_t width() const noexcept + { + return _width; + } - uint16_t height() const noexcept - { - return _height; - } + uint16_t height() const noexcept + { + return _height; + } - wil::unique_virtualalloc_ptr&& take() noexcept - { - return std::move(_buffer); - } + wil::unique_virtualalloc_ptr&& take() noexcept + { + return std::move(_buffer); + } -private: - wil::unique_virtualalloc_ptr _buffer; - std::span::iterator _data; - size_t _rowStride; - size_t _indicesOffset; - uint16_t _width; - uint16_t _height; -}; + private: + wil::unique_virtualalloc_ptr _buffer; + std::span::iterator _data; + size_t _rowStride; + size_t _indicesOffset; + uint16_t _width; + uint16_t _height; + }; +} using namespace Microsoft::Console; using namespace Microsoft::Console::Types; @@ -984,9 +987,7 @@ void TextBuffer::Reset() // remove rows if we're shrinking _storage.resize(allocator.height()); - // Now that we've tampered with the row placement, refresh all the row IDs. - // Also take advantage of the row ID refresh loop to resize the rows in the X dimension - // and cleanup the UnicodeStorage characters that might fall outside the resized buffer. + // realloc in the X direction for (auto& it : _storage) { it.Resize(allocator.chars(), allocator.indices(), allocator.width(), attributes); From 8c1aeadd19fd42978cd6313864d796935e9fa291 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 11 Nov 2022 17:33:32 +0100 Subject: [PATCH 18/19] Address feedback --- src/host/ft_host/CJK_DbcsTests.cpp | 35 +++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/host/ft_host/CJK_DbcsTests.cpp b/src/host/ft_host/CJK_DbcsTests.cpp index 50a371f0395..8802812b66c 100644 --- a/src/host/ft_host/CJK_DbcsTests.cpp +++ b/src/host/ft_host/CJK_DbcsTests.cpp @@ -2046,6 +2046,7 @@ void DbcsTests::TestDbcsStdCoutScenario() // Read/WriteConsoleOutput allow a user to implement a restricted form of buffer "backup" and "restore". // But what if the saved region clips ("bisects") a wide character? This test ensures that we restore proper // wide characters when given an unpaired trailing/leading CHAR_INFO in the first/last column of the given region. +// In other words, writing a trailing CHAR_INFO will also automatically write a leading CHAR_INFO in the preceeding cell. void DbcsTests::TestDbcsBackupRestore() { const auto out = GetStdHandle(STD_OUTPUT_HANDLE); @@ -2053,27 +2054,49 @@ void DbcsTests::TestDbcsBackupRestore() std::array expected = PrepPattern::DoubledW; PrepPattern::replaceColorPlaceholders(expected, FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_GREEN); - // Write DoubledW to the viewport. + // DoubledW will show up like this in the top/left corner of the terminal: + // +---------------- + // |QいかなZYXWVUTに + // + // Since those 4 Japanese characters probably aren't going to be monospace for you in your editor + // (as they most likely aren't exactly 2 ASCII characters wide), I'll continue referring to them like this: + // +---------------- + // |QaabbccZYXWVUTdd { SMALL_RECT region{ .Left = 0, .Right = 15 }; VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(out, expected.data(), { 16, 1 }, {}, ®ion)); } - // Make a "backup" of the viewport. - // The twist is that our backup region only copies the trailing/leading half of the first/last glyph respectively. + // Make a "backup" of the viewport. The twist is that our backup region only + // copies the trailing/leading half of the first/last glyph respectively like so: + // +---------------- + // | abbccZYXWVUTd std::array backup{}; constexpr COORD backupSize{ 13, 1 }; SMALL_RECT backupRegion{ .Left = 2, .Right = 14 }; VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(out, backup.data(), backupSize, {}, &backupRegion)); - // Overwrite the DoubledW string except the first glyph. - // This produces "Qxxxxxx...". We leave the "Q" so that after our restore all 16 original CHAR_INFOs are present. + // Destroy the text with some narrow ASCII characters, resulting in: + // +---------------- + // |Qxxxxxxxxxxxxxxx { DWORD ignored; VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputCharacterW(out, L'x', 15, { 1, 0 }, &ignored)); } - // Restore our "backup". + // Restore our "backup". The trailing half of the first wide glyph (indicated as "a" above) + // as well as the leading half of the last wide glyph ("d"), will automatically get a + // matching leading/trailing half respectively. In other words, this: + // +---------------- + // | abbccZYXWVUTd + // + // turns into this: + // +---------------- + // | aabbccZYXWVUTdd + // + // and so we restore this, overwriting all the "x" characters in the process: + // +---------------- + // |QいかなZYXWVUTに VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(out, backup.data(), backupSize, {}, &backupRegion)); std::array infos{}; From 524ed47e2781f03014f0e4458147084d8657987f Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 11 Nov 2022 17:41:02 +0100 Subject: [PATCH 19/19] Fix build --- src/buffer/out/Row.cpp | 5 +++++ src/buffer/out/Row.hpp | 4 +++- src/terminal/adapter/adaptDispatch.cpp | 5 ++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 8690abced45..de6bc7e8e0b 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -366,6 +366,11 @@ bool ROW::SetAttrToEnd(const til::CoordType columnBegin, const TextAttribute att return true; } +void ROW::ReplaceAttributes(const til::CoordType beginIndex, const til::CoordType endIndex, const TextAttribute& newAttr) +{ + _attr.replace(_clampedColumnInclusive(beginIndex), _clampedColumnInclusive(endIndex), newAttr); +} + void ROW::ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars) { const auto colBeg = _clampedUint16(columnBegin); diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index 6c279cda67a..7393bf67097 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -22,7 +22,8 @@ Revision History: #include -#include "til/rle.h" +#include + #include "LineRendition.hpp" #include "OutputCell.hpp" #include "OutputCellIterator.hpp" @@ -64,6 +65,7 @@ class ROW final void ClearCell(til::CoordType column); OutputCellIterator WriteCells(OutputCellIterator it, til::CoordType columnBegin, std::optional wrap = std::nullopt, std::optional limitRight = std::nullopt); bool SetAttrToEnd(til::CoordType columnBegin, TextAttribute attr); + void ReplaceAttributes(til::CoordType beginIndex, til::CoordType endIndex, const TextAttribute& newAttr); void ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars); const til::small_rle& Attributes() const noexcept; diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 6cac1134c61..03eae9ae443 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -798,10 +798,9 @@ void AdaptDispatch::_ChangeRectAttributes(TextBuffer& textBuffer, const til::rec for (auto row = changeRect.top; row < changeRect.bottom; row++) { auto& rowBuffer = textBuffer.GetRowByOffset(row); - auto& attrs = rowBuffer.GetAttrRow(); for (auto col = changeRect.left; col < changeRect.right; col++) { - auto attr = attrs.GetAttrByColumn(col); + auto attr = rowBuffer.GetAttrByColumn(col); auto characterAttributes = attr.GetCharacterAttributes(); characterAttributes &= changeOps.andAttrMask; characterAttributes ^= changeOps.xorAttrMask; @@ -814,7 +813,7 @@ void AdaptDispatch::_ChangeRectAttributes(TextBuffer& textBuffer, const til::rec { attr.SetBackground(*changeOps.background); } - attrs.Replace(col, col + 1, attr); + rowBuffer.ReplaceAttributes(col, col + 1, attr); } } textBuffer.TriggerRedraw(Viewport::FromExclusive(changeRect));