From 6569666338f252f0cc6e8609444ea8ad0b43feb9 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sat, 24 Jul 2021 18:55:43 +0200 Subject: [PATCH] wip --- src/buffer/out/CharRow.cpp | 67 ++++------ src/buffer/out/CharRow.hpp | 53 ++++---- src/buffer/out/CharRowCellReference.cpp | 4 +- src/buffer/out/Row.cpp | 27 +--- src/buffer/out/Row.hpp | 10 +- src/buffer/out/UnicodeStorage.cpp | 16 +-- src/buffer/out/UnicodeStorage.hpp | 2 +- src/buffer/out/textBuffer.cpp | 156 +++++++++++++----------- src/buffer/out/textBuffer.hpp | 76 +++++------- src/inc/LibraryIncludes.h | 4 +- src/inc/til/bitmap.h | 10 +- 11 files changed, 191 insertions(+), 234 deletions(-) diff --git a/src/buffer/out/CharRow.cpp b/src/buffer/out/CharRow.cpp index 40a945c50..08b7810f5 100644 --- a/src/buffer/out/CharRow.cpp +++ b/src/buffer/out/CharRow.cpp @@ -17,8 +17,8 @@ // 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(size_t rowWidth, ROW* const pParent) noexcept : - _data(rowWidth, value_type()), +CharRow::CharRow(CharRowCell* buffer, size_t rowWidth, ROW* const pParent) noexcept : + _data(buffer, rowWidth), _pParent{ FAIL_FAST_IF_NULL(pParent) } { } @@ -53,38 +53,9 @@ void CharRow::Reset() noexcept // - 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 size_t newSize) noexcept +void CharRow::Resize(CharRowCell* buffer, const size_t 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(); + _data = {buffer, newSize}; } // Routine Description: @@ -95,12 +66,16 @@ typename CharRow::const_iterator CharRow::cend() const noexcept // - The calculated left boundary of the internal string. size_t CharRow::MeasureLeft() const noexcept { - const_iterator it = _data.cbegin(); - while (it != _data.cend() && it->IsSpace()) + const auto beg = _data.begin(); + const auto end = _data.end(); + + auto it = beg; + while (it != end && it->IsSpace()) { ++it; } - return it - _data.cbegin(); + + return it - beg; } // Routine Description: @@ -111,17 +86,21 @@ size_t CharRow::MeasureLeft() const noexcept // - The calculated right boundary of the internal string. size_t CharRow::MeasureRight() const { - const_reverse_iterator it = _data.crbegin(); - while (it != _data.crend() && it->IsSpace()) + const auto beg = _data.rbegin(); + const auto end = _data.rend(); + + auto it = beg; + while (it != end && it->IsSpace()) { ++it; } - return _data.crend() - it; + + return end - it; } void CharRow::ClearCell(const size_t column) { - _data.at(column).Reset(); + _data[column].Reset(); } // Routine Description: @@ -132,7 +111,7 @@ void CharRow::ClearCell(const size_t column) // - True if there is valid text in this row. False otherwise. bool CharRow::ContainsText() const noexcept { - for (const value_type& cell : _data) + for (const auto& cell : _data) { if (!cell.IsSpace()) { @@ -151,7 +130,7 @@ bool CharRow::ContainsText() const noexcept // Note: will throw exception if column is out of bounds const DbcsAttribute& CharRow::DbcsAttrAt(const size_t column) const { - return _data.at(column).DbcsAttr(); + return _data[column].DbcsAttr(); } // Routine Description: @@ -163,7 +142,7 @@ const DbcsAttribute& CharRow::DbcsAttrAt(const size_t column) const // Note: will throw exception if column is out of bounds DbcsAttribute& CharRow::DbcsAttrAt(const size_t column) { - return _data.at(column).DbcsAttr(); + return _data[column].DbcsAttr(); } // Routine Description: @@ -175,7 +154,7 @@ DbcsAttribute& CharRow::DbcsAttrAt(const size_t column) // Note: will throw exception if column is out of bounds void CharRow::ClearGlyph(const size_t column) { - _data.at(column).EraseChars(); + _data[column].EraseChars(); } // Routine Description: diff --git a/src/buffer/out/CharRow.hpp b/src/buffer/out/CharRow.hpp index 55d747e87..54bff48c1 100644 --- a/src/buffer/out/CharRow.hpp +++ b/src/buffer/out/CharRow.hpp @@ -49,15 +49,12 @@ 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(size_t rowWidth, ROW* const pParent) noexcept; + CharRow(CharRowCell* buffer, size_t rowWidth, ROW* const pParent) noexcept; size_t size() const noexcept; - [[nodiscard]] HRESULT Resize(const size_t newSize) noexcept; + void Resize(CharRowCell* buffer, const size_t newSize) noexcept; size_t MeasureLeft() const noexcept; size_t MeasureRight() const; bool ContainsText() const noexcept; @@ -71,14 +68,25 @@ public: const reference GlyphAt(const size_t column) const; reference GlyphAt(const size_t column); - // iterators - iterator begin() noexcept; - const_iterator cbegin() const noexcept; - const_iterator begin() const noexcept { return cbegin(); } + auto begin() noexcept + { + return _data.begin(); + } - iterator end() noexcept; - const_iterator cend() const noexcept; - const_iterator end() const noexcept { return cend(); } + auto begin() const noexcept + { + return _data.begin(); + } + + auto end() noexcept + { + return _data.end(); + } + + auto end() const noexcept + { + return _data.end(); + } UnicodeStorage& GetUnicodeStorage() noexcept; const UnicodeStorage& GetUnicodeStorage() const noexcept; @@ -96,20 +104,21 @@ private: protected: // storage for glyph data and dbcs attributes - boost::container::small_vector _data; + gsl::span _data; // ROW that this CharRow belongs to ROW* _pParent; }; -template -void OverwriteColumns(InputIt1 startChars, InputIt1 endChars, InputIt2 startAttrs, CharRow::iterator outIt) +template +void OverwriteColumns(InputIt1 startChars, InputIt1 endChars, InputIt2 startAttrs, OutputIt outIt) { - std::transform(startChars, - endChars, - startAttrs, - outIt, - [](const wchar_t wch, const DbcsAttribute attr) { - return CharRow::value_type{ wch, attr }; - }); + 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/CharRowCellReference.cpp b/src/buffer/out/CharRowCellReference.cpp index 0f8a2e1a5..f711d5472 100644 --- a/src/buffer/out/CharRowCellReference.cpp +++ b/src/buffer/out/CharRowCellReference.cpp @@ -41,7 +41,7 @@ CharRowCellReference::operator std::wstring_view() const // - ref to the CharRowCell CharRowCell& CharRowCellReference::_cellData() { - return _parent._data.at(_index); + return _parent._data[_index]; } // Routine Description: @@ -50,7 +50,7 @@ CharRowCell& CharRowCellReference::_cellData() // - ref to the CharRowCell const CharRowCell& CharRowCellReference::_cellData() const { - return _parent._data.at(_index); + return _parent._data[_index]; } // Routine Description: diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 0f4cdc842..e52c3ede7 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -16,10 +16,9 @@ // - pParent - the text buffer that this row belongs to // Return Value: // - constructed object -ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent) : +ROW::ROW(const SHORT rowId, CharRowCell* buffer, const unsigned short rowWidth, const TextAttribute& fillAttribute, TextBuffer* const pParent) : _id{ rowId }, - _rowWidth{ rowWidth }, - _charRow{ rowWidth, this }, + _charRow{ buffer, rowWidth, this }, _attrRow{ rowWidth, fillAttribute }, _lineRendition{ LineRendition::SingleWidth }, _wrapForced{ false }, @@ -34,7 +33,7 @@ ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute f // - Attr - The default attribute (color) to fill // Return Value: // - -bool ROW::Reset(const TextAttribute Attr) +bool ROW::Reset(const TextAttribute& Attr) { _lineRendition = LineRendition::SingleWidth; _wrapForced = false; @@ -52,26 +51,6 @@ bool ROW::Reset(const TextAttribute Attr) return true; } -// Routine Description: -// - resizes ROW to new width -// Arguments: -// - width - the new width, in cells -// Return Value: -// - S_OK if successful, otherwise relevant error -[[nodiscard]] HRESULT ROW::Resize(const unsigned short width) -{ - RETURN_IF_FAILED(_charRow.Resize(width)); - try - { - _attrRow.Resize(width); - } - CATCH_RETURN(); - - _rowWidth = width; - - return S_OK; -} - // Routine Description: // - clears char data in column in row // Arguments: diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index cee3b53bd..526be7beb 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -32,9 +32,9 @@ class TextBuffer; class ROW final { public: - ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent); + ROW(const SHORT rowId, CharRowCell* buffer, const unsigned short rowWidth, const TextAttribute& fillAttribute, TextBuffer* const pParent); - size_t size() const noexcept { return _rowWidth; } + size_t size() const noexcept { return _charRow.size(); } void SetWrapForced(const bool wrap) noexcept { _wrapForced = wrap; } bool WasWrapForced() const noexcept { return _wrapForced; } @@ -54,8 +54,7 @@ public: SHORT GetId() const noexcept { return _id; } void SetId(const SHORT id) noexcept { _id = id; } - bool Reset(const TextAttribute Attr); - [[nodiscard]] HRESULT Resize(const unsigned short width); + bool Reset(const TextAttribute& Attr); void ClearColumn(const size_t column); std::wstring GetText() const { return _charRow.GetText(); } @@ -74,13 +73,12 @@ private: CharRow _charRow; ATTR_ROW _attrRow; LineRendition _lineRendition; + TextBuffer* _pParent; // non ownership pointer SHORT _id; - unsigned short _rowWidth; // 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; // 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 }; #ifdef UNIT_TESTING diff --git a/src/buffer/out/UnicodeStorage.cpp b/src/buffer/out/UnicodeStorage.cpp index 7de3c1102..63b0f6ae0 100644 --- a/src/buffer/out/UnicodeStorage.cpp +++ b/src/buffer/out/UnicodeStorage.cpp @@ -47,7 +47,7 @@ void UnicodeStorage::Erase(const key_type key) noexcept // - 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) +void UnicodeStorage::Remap(const std::unordered_map& rowMap, SHORT width) { // Make a temporary map to hold all the new row positioning std::unordered_map newMap; @@ -58,18 +58,10 @@ void UnicodeStorage::Remap(const std::unordered_map& rowMap, const // 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()) + // If the column index is at/beyond the row width, don't bother copying it to the new map. + if (oldCoord.X >= width) { - // 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; - } + continue; } // Get the row ID from the position as that's what we need to remap diff --git a/src/buffer/out/UnicodeStorage.hpp b/src/buffer/out/UnicodeStorage.hpp index e1bd2ff36..7af651cd0 100644 --- a/src/buffer/out/UnicodeStorage.hpp +++ b/src/buffer/out/UnicodeStorage.hpp @@ -55,7 +55,7 @@ public: void Erase(const key_type key) noexcept; - void Remap(const std::unordered_map& rowMap, const std::optional width); + void Remap(const std::unordered_map& rowMap, SHORT width); private: std::unordered_map _map; diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 58de15c20..fb95fe9e7 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -31,26 +31,32 @@ TextBuffer::TextBuffer(const COORD screenBufferSize, const TextAttribute defaultAttributes, const UINT cursorSize, Microsoft::Console::Render::IRenderTarget& renderTarget) : - _firstRow{ 0 }, _currentAttributes{ defaultAttributes }, _cursor{ cursorSize, *this }, - _storage{}, - _unicodeStorage{}, - _renderTarget{ renderTarget }, - _size{}, - _currentHyperlinkId{ 1 }, - _currentPatternId{ 0 } + _renderTarget{ renderTarget } { - // initialize ROWs + const auto dx = static_cast(screenBufferSize.X); + const auto dy = static_cast(screenBufferSize.Y); + auto buffer = static_cast(VirtualAlloc(nullptr, dx * dy * sizeof(CharRowCell), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); + THROW_IF_NULL_ALLOC(buffer); + + _charBuffer = buffer; + _storage.reserve(static_cast(screenBufferSize.Y)); - for (size_t i = 0; i < static_cast(screenBufferSize.Y); ++i) + for (SHORT i = 0; i < screenBufferSize.Y; ++i) { - _storage.emplace_back(static_cast(i), screenBufferSize.X, _currentAttributes, this); + _storage.emplace_back(i, buffer, screenBufferSize.X, _currentAttributes, this); + buffer += dx; } _UpdateSize(); } +TextBuffer::~TextBuffer() +{ + VirtualFree(_charBuffer, 0, MEM_RELEASE); +} + // Routine Description: // - Copies properties from another text buffer into this one. // - This is primarily to copy properties that would otherwise not be specified during CreateInstance @@ -83,11 +89,9 @@ UINT TextBuffer::TotalRowCount() const noexcept // - const reference to the requested row. Asserts if out of bounds. const ROW& TextBuffer::GetRowByOffset(const size_t index) const { - const size_t totalRows = TotalRowCount(); - // Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows. - const size_t offsetIndex = (_firstRow + index) % totalRows; - return _storage.at(offsetIndex); + const size_t offsetIndex = (_firstRow + index) % _storage.size(); + return _storage[offsetIndex]; } // Routine Description: @@ -99,11 +103,9 @@ const ROW& TextBuffer::GetRowByOffset(const size_t index) const // - reference to the requested row. Asserts if out of bounds. ROW& TextBuffer::GetRowByOffset(const size_t index) { - const size_t totalRows = TotalRowCount(); - // Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows. - const size_t offsetIndex = (_firstRow + index) % totalRows; - return _storage.at(offsetIndex); + const size_t offsetIndex = (_firstRow + index) % _storage.size(); + return _storage[offsetIndex]; } // Routine Description: @@ -400,7 +402,7 @@ OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt, //Return Value: // - true if we successfully inserted the character // - false otherwise (out of memory) -bool TextBuffer::InsertCharacter(const std::wstring_view chars, +bool TextBuffer::InsertCharacter(const std::wstring_view& chars, const DbcsAttribute dbcsAttribute, const TextAttribute attr) { @@ -778,7 +780,7 @@ void TextBuffer::ScrollRows(const SHORT firstRow, const SHORT size, const SHORT // 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); + _RefreshRowIDs(_size.Width()); } Cursor& TextBuffer::GetCursor() noexcept @@ -896,6 +898,11 @@ void TextBuffer::Reset() { RETURN_HR_IF(E_INVALIDARG, newSize.X < 0 || newSize.Y < 0); + const auto dx = static_cast(newSize.X); + const auto dy = static_cast(newSize.Y); + auto buffer = static_cast(VirtualAlloc(nullptr, dx * dy * sizeof(CharRowCell), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); + RETURN_IF_NULL_ALLOC(buffer); + try { const auto currentSize = GetSize().Dimensions(); @@ -909,12 +916,7 @@ void TextBuffer::Reset() const SHORT TopRowIndex = (GetFirstRowIndex() + TopRow) % currentSize.Y; // rotate rows until the top row is at index 0 - for (int 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 @@ -926,19 +928,26 @@ void TextBuffer::Reset() // add rows if we're growing while (_storage.size() < static_cast(newSize.Y)) { - _storage.emplace_back(static_cast(_storage.size()), newSize.X, attributes, this); + _storage.emplace_back(static_cast(_storage.size()), nullptr, newSize.X, attributes, this); } // 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); + _RefreshRowWidth(buffer, newSize.X); // Update the cached size value _UpdateSize(); } - CATCH_RETURN(); + catch (...) + { + VirtualFree(buffer, 0, MEM_RELEASE); + RETURN_CAUGHT_EXCEPTION(); + }; + VirtualFree(_charBuffer, 0, MEM_RELEASE); + _charBuffer = buffer; return S_OK; } @@ -961,7 +970,7 @@ UnicodeStorage& TextBuffer::GetUnicodeStorage() noexcept // 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) +void TextBuffer::_RefreshRowIDs(SHORT width) { std::unordered_map rowMap; SHORT i = 0; @@ -975,17 +984,19 @@ void TextBuffer::_RefreshRowIDs(std::optional newRowWidth) // 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); + _unicodeStorage.Remap(rowMap, width); +} + +void TextBuffer::_RefreshRowWidth(CharRowCell *data, size_t width) noexcept +{ + for (auto& it : _storage) + { + it.GetCharRow().Resize(data, width); + data += width; + } } void TextBuffer::_NotifyPaint(const Viewport& viewport) const @@ -1044,7 +1055,7 @@ Microsoft::Console::Render::IRenderTarget& TextBuffer::GetRenderTarget() noexcep // - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar // Return Value: // - the delimiter class for the given char -const DelimiterClass TextBuffer::_GetDelimiterClassAt(const COORD pos, const std::wstring_view wordDelimiters) const +const DelimiterClass TextBuffer::_GetDelimiterClassAt(const COORD pos, const std::wstring_view& wordDelimiters) const { return GetRowByOffset(pos.Y).GetCharRow().DelimiterClassAt(pos.X, wordDelimiters); } @@ -1059,7 +1070,7 @@ const DelimiterClass TextBuffer::_GetDelimiterClassAt(const COORD pos, const std // (or a row boundary is encountered) // Return Value: // - The COORD for the first character on the "word" (inclusive) -const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode) const +const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view& wordDelimiters, bool accessibilityMode) const { // Consider a buffer with this text in it: // " word other " @@ -1104,7 +1115,7 @@ const COORD TextBuffer::GetWordStart(const COORD target, const std::wstring_view // - wordDelimiters - what characters are we considering for the separation of words // Return Value: // - The COORD for the first character on the current/previous READABLE "word" (inclusive) -const COORD TextBuffer::_GetWordStartForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const +const COORD TextBuffer::_GetWordStartForAccessibility(const COORD target, const std::wstring_view& wordDelimiters) const { COORD result = target; const auto bufferSize = GetSize(); @@ -1149,7 +1160,7 @@ const COORD TextBuffer::_GetWordStartForAccessibility(const COORD target, const // - wordDelimiters - what characters are we considering for the separation of words // Return Value: // - The COORD for the first character on the current word or delimiter run (stopped by the left margin) -const COORD TextBuffer::_GetWordStartForSelection(const COORD target, const std::wstring_view wordDelimiters) const +const COORD TextBuffer::_GetWordStartForSelection(const COORD target, const std::wstring_view& wordDelimiters) const { COORD result = target; const auto bufferSize = GetSize(); @@ -1181,7 +1192,7 @@ const COORD TextBuffer::_GetWordStartForSelection(const COORD target, const std: // (or a row boundary is encountered) // Return Value: // - The COORD for the last character on the "word" (inclusive) -const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode) const +const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view& wordDelimiters, bool accessibilityMode) const { // Consider a buffer with this text in it: // " word other " @@ -1218,7 +1229,7 @@ const COORD TextBuffer::GetWordEnd(const COORD target, const std::wstring_view w // - lastCharPos - the position of the last nonspace character in the text buffer (to improve performance) // Return Value: // - The COORD for the first character of the next readable "word". If no next word, return one past the end of the buffer -const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters, const COORD lastCharPos) const +const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const std::wstring_view& wordDelimiters, const COORD lastCharPos) const { const auto bufferSize = GetSize(); COORD result = target; @@ -1266,7 +1277,7 @@ const COORD TextBuffer::_GetWordEndForAccessibility(const COORD target, const st // - wordDelimiters - what characters are we considering for the separation of words // Return Value: // - The COORD for the last character of the current word or delimiter run (stopped by right margin) -const COORD TextBuffer::_GetWordEndForSelection(const COORD target, const std::wstring_view wordDelimiters) const +const COORD TextBuffer::_GetWordEndForSelection(const COORD target, const std::wstring_view& wordDelimiters) const { const auto bufferSize = GetSize(); @@ -1349,7 +1360,7 @@ void TextBuffer::_PruneHyperlinks() // Return Value: // - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary) // - pos - The COORD for the first character on the "word" (inclusive) -bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, COORD lastCharPos) const +bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view& wordDelimiters, COORD lastCharPos) const { // move to the beginning of the next word // NOTE: _GetWordEnd...() returns the exclusive position of the "end of the word" @@ -1373,7 +1384,7 @@ bool TextBuffer::MoveToNextWord(COORD& pos, const std::wstring_view wordDelimite // Return Value: // - true, if successfully updated pos. False, if we are unable to move (usually due to a buffer boundary) // - pos - The COORD for the first character on the "word" (inclusive) -bool TextBuffer::MoveToPreviousWord(COORD& pos, std::wstring_view wordDelimiters) const +bool TextBuffer::MoveToPreviousWord(COORD& pos, const std::wstring_view& wordDelimiters) const { // move to the beginning of the current word auto copy{ GetWordStart(pos, wordDelimiters, true) }; @@ -1732,7 +1743,7 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF, // - string containing the generated HTML std::string TextBuffer::GenHTML(const TextAndColor& rows, const int fontHeightPoints, - const std::wstring_view fontFaceName, + const std::wstring_view& fontFaceName, const COLORREF backgroundColor) { try @@ -1795,7 +1806,7 @@ std::string TextBuffer::GenHTML(const TextAndColor& rows, const auto writeAccumulatedChars = [&](bool includeCurrent) { if (col >= startOffset) { - const auto unescapedText = ConvertToA(CP_UTF8, std::wstring_view(rows.text.at(row)).substr(startOffset, col - startOffset + includeCurrent)); + const auto unescapedText = ConvertToA(CP_UTF8, rows.text.at(row).substr(startOffset, col - startOffset + includeCurrent)); for (const auto c : unescapedText) { switch (c) @@ -1921,7 +1932,7 @@ std::string TextBuffer::GenHTML(const TextAndColor& rows, // - htmlTitle - value used in title tag of html header. Used to name the application // Return Value: // - string containing the generated RTF -std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoints, const std::wstring_view fontFaceName, const COLORREF backgroundColor) +std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoints, const std::wstring_view& fontFaceName, const COLORREF backgroundColor) { try { @@ -1983,7 +1994,7 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi const auto writeAccumulatedChars = [&](bool includeCurrent) { if (col >= startOffset) { - const auto unescapedText = ConvertToA(CP_UTF8, std::wstring_view(rows.text.at(row)).substr(startOffset, col - startOffset + includeCurrent)); + const auto unescapedText = ConvertToA(CP_UTF8, rows.text.at(row).substr(startOffset, col - startOffset + includeCurrent)); for (const auto c : unescapedText) { switch (c) @@ -2361,9 +2372,9 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, // - Adds or updates a hyperlink in our hyperlink table // Arguments: // - The hyperlink URI, the hyperlink id (could be new or old) -void TextBuffer::AddHyperlinkToMap(std::wstring_view uri, uint16_t id) +void TextBuffer::AddHyperlinkToMap(const std::wstring_view& uri, uint16_t id) { - _hyperlinkMap[id] = uri; + _hyperlinkMap.emplace(id, uri); } // Method Description: @@ -2383,28 +2394,31 @@ std::wstring TextBuffer::GetHyperlinkUriFromId(uint16_t id) const // - The user-defined id // Return value: // - The internal hyperlink ID -uint16_t TextBuffer::GetHyperlinkId(std::wstring_view uri, std::wstring_view id) +uint16_t TextBuffer::GetHyperlinkId(const std::wstring_view& uri, const std::wstring_view& id) { uint16_t numericId = 0; if (id.empty()) { // no custom id specified, return our internal count - numericId = _currentHyperlinkId; - ++_currentHyperlinkId; + numericId = _currentHyperlinkId++; } else { - // assign _currentHyperlinkId if the custom id does not already exist - std::wstring newId{ id }; - // hash the URL and add it to the custom ID - GH#7698 - newId += L"%" + std::to_wstring(std::hash{}(uri)); - const auto result = _hyperlinkCustomIdMap.emplace(newId, _currentHyperlinkId); + // We need to use both uri and id for hashing. See GH#7698 + std::wstring key; + key.reserve(uri.size() + id.size()); + key.append(uri); + key.append(id); + + const auto result = _hyperlinkCustomIdMap.emplace(key, _currentHyperlinkId); + numericId = result.first->second; + if (result.second) { // the custom id did not already exist + _hyperlinkCustomIdMapReverse.insert_or_assign(_currentHyperlinkId, key); ++_currentHyperlinkId; } - numericId = (*(result.first)).second; } // _currentHyperlinkId could overflow, make sure its not 0 if (_currentHyperlinkId == 0) @@ -2422,13 +2436,11 @@ uint16_t TextBuffer::GetHyperlinkId(std::wstring_view uri, std::wstring_view id) void TextBuffer::RemoveHyperlinkFromMap(uint16_t id) noexcept { _hyperlinkMap.erase(id); - for (const auto& customIdPair : _hyperlinkCustomIdMap) + + if (auto it = _hyperlinkCustomIdMapReverse.find(id); it != _hyperlinkCustomIdMapReverse.end()) { - if (customIdPair.second == id) - { - _hyperlinkCustomIdMap.erase(customIdPair.first); - break; - } + _hyperlinkCustomIdMap.erase(it->second); + _hyperlinkCustomIdMapReverse.erase(it); } } @@ -2441,12 +2453,9 @@ void TextBuffer::RemoveHyperlinkFromMap(uint16_t id) noexcept // - The custom ID if there was one, empty string otherwise std::wstring TextBuffer::GetCustomIdFromId(uint16_t id) const { - for (auto customIdPair : _hyperlinkCustomIdMap) + if (auto it = _hyperlinkCustomIdMapReverse.find(id); it != _hyperlinkCustomIdMapReverse.end()) { - if (customIdPair.second == id) - { - return customIdPair.first; - } + return it->second; } return {}; } @@ -2460,6 +2469,7 @@ void TextBuffer::CopyHyperlinkMaps(const TextBuffer& other) { _hyperlinkMap = other._hyperlinkMap; _hyperlinkCustomIdMap = other._hyperlinkCustomIdMap; + _hyperlinkCustomIdMapReverse = other._hyperlinkCustomIdMapReverse; _currentHyperlinkId = other._currentHyperlinkId; } @@ -2470,7 +2480,7 @@ void TextBuffer::CopyHyperlinkMaps(const TextBuffer& other) // - The regex pattern // Return value: // - An ID that the caller should associate with the given pattern -const size_t TextBuffer::AddPatternRecognizer(const std::wstring_view regexString) +const size_t TextBuffer::AddPatternRecognizer(const std::wstring_view& regexString) { ++_currentPatternId; _idsAndPatterns.emplace(std::make_pair(_currentPatternId, regexString)); diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 468a9fc4f..227cf8e5e 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -69,6 +69,9 @@ public: const TextAttribute defaultAttributes, const UINT cursorSize, Microsoft::Console::Render::IRenderTarget& renderTarget); + + ~TextBuffer(); + TextBuffer(const TextBuffer& a) = delete; // Used for duplicating properties to another text buffer @@ -98,7 +101,7 @@ public: const std::optional limitRight = std::nullopt); bool InsertCharacter(const wchar_t wch, const DbcsAttribute dbcsAttribute, const TextAttribute attr); - bool InsertCharacter(const std::wstring_view chars, const DbcsAttribute dbcsAttribute, const TextAttribute attr); + bool InsertCharacter(const std::wstring_view& chars, const DbcsAttribute dbcsAttribute, const TextAttribute attr); bool IncrementCursor(); bool NewlineCursor(); @@ -141,10 +144,10 @@ public: Microsoft::Console::Render::IRenderTarget& GetRenderTarget() noexcept; - const COORD GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false) const; - const COORD GetWordEnd(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false) const; - bool MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, COORD lastCharPos) const; - bool MoveToPreviousWord(COORD& pos, const std::wstring_view wordDelimiters) const; + const COORD GetWordStart(const COORD target, const std::wstring_view& wordDelimiters, bool accessibilityMode = false) const; + const COORD GetWordEnd(const COORD target, const std::wstring_view& wordDelimiters, bool accessibilityMode = false) const; + bool MoveToNextWord(COORD& pos, const std::wstring_view& wordDelimiters, COORD lastCharPos) const; + bool MoveToPreviousWord(COORD& pos, const std::wstring_view& wordDelimiters) const; const til::point GetGlyphStart(const til::point pos) const; const til::point GetGlyphEnd(const til::point pos) const; @@ -153,9 +156,9 @@ public: const std::vector GetTextRects(COORD start, COORD end, bool blockSelection, bool bufferCoordinates) const; - void AddHyperlinkToMap(std::wstring_view uri, uint16_t id); + void AddHyperlinkToMap(const std::wstring_view& uri, uint16_t id); std::wstring GetHyperlinkUriFromId(uint16_t id) const; - uint16_t GetHyperlinkId(std::wstring_view uri, std::wstring_view id); + uint16_t GetHyperlinkId(const std::wstring_view& uri, const std::wstring_view& id); void RemoveHyperlinkFromMap(uint16_t id) noexcept; std::wstring GetCustomIdFromId(uint16_t id) const; void CopyHyperlinkMaps(const TextBuffer& OtherBuffer); @@ -176,12 +179,12 @@ public: static std::string GenHTML(const TextAndColor& rows, const int fontHeightPoints, - const std::wstring_view fontFaceName, + const std::wstring_view& fontFaceName, const COLORREF backgroundColor); static std::string GenRTF(const TextAndColor& rows, const int fontHeightPoints, - const std::wstring_view fontFaceName, + const std::wstring_view& fontFaceName, const COLORREF backgroundColor); struct PositionInformation @@ -195,60 +198,47 @@ public: const std::optional lastCharacterViewport, std::optional> positionInfo); - const size_t AddPatternRecognizer(const std::wstring_view regexString); + const size_t AddPatternRecognizer(const std::wstring_view& regexString); void ClearPatternRecognizers() noexcept; void CopyPatterns(const TextBuffer& OtherBuffer); interval_tree::IntervalTree GetPatterns(const size_t firstRow, const size_t lastRow) const; private: void _UpdateSize(); - Microsoft::Console::Types::Viewport _size; - std::vector _storage; - Cursor _cursor; - - SHORT _firstRow; // indexes top row (not necessarily 0) - - TextAttribute _currentAttributes; - - // storage location for glyphs that can't fit into the buffer normally - UnicodeStorage _unicodeStorage; - - std::unordered_map _hyperlinkMap; - std::unordered_map _hyperlinkCustomIdMap; - uint16_t _currentHyperlinkId; - - void _RefreshRowIDs(std::optional newRowWidth); - - Microsoft::Console::Render::IRenderTarget& _renderTarget; - + void _RefreshRowIDs(SHORT width); + void _RefreshRowWidth(CharRowCell* data, size_t width) noexcept; void _SetFirstRowIndex(const SHORT FirstRowIndex) noexcept; - COORD _GetPreviousFromCursor() const; - void _SetWrapOnCurrentRow(); void _AdjustWrapOnCurrentRow(const bool fSet); - void _NotifyPaint(const Microsoft::Console::Types::Viewport& viewport) const; - // Assist with maintaining proper buffer state for Double Byte character sequences bool _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute); bool _AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute); - ROW& _GetFirstRow(); ROW& _GetPrevRowNoWrap(const ROW& row); - void _ExpandTextRow(SMALL_RECT& selectionRow) const; - - const DelimiterClass _GetDelimiterClassAt(const COORD pos, const std::wstring_view wordDelimiters) const; - const COORD _GetWordStartForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const; - const COORD _GetWordStartForSelection(const COORD target, const std::wstring_view wordDelimiters) const; - const COORD _GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters, const COORD lastCharPos) const; - const COORD _GetWordEndForSelection(const COORD target, const std::wstring_view wordDelimiters) const; - + const DelimiterClass _GetDelimiterClassAt(const COORD pos, const std::wstring_view& wordDelimiters) const; + const COORD _GetWordStartForAccessibility(const COORD target, const std::wstring_view& wordDelimiters) const; + const COORD _GetWordStartForSelection(const COORD target, const std::wstring_view& wordDelimiters) const; + const COORD _GetWordEndForAccessibility(const COORD target, const std::wstring_view& wordDelimiters, const COORD lastCharPos) const; + const COORD _GetWordEndForSelection(const COORD target, const std::wstring_view& wordDelimiters) const; void _PruneHyperlinks(); + Microsoft::Console::Render::IRenderTarget& _renderTarget; + std::vector _storage; + std::unordered_map _hyperlinkMap; + std::unordered_map _hyperlinkCustomIdMap; + std::unordered_map _hyperlinkCustomIdMapReverse; std::unordered_map _idsAndPatterns; - size_t _currentPatternId; + TextAttribute _currentAttributes; + UnicodeStorage _unicodeStorage; + Cursor _cursor; + void* _charBuffer; + Microsoft::Console::Types::Viewport _size; + uint16_t _currentHyperlinkId{ 1 }; + size_t _currentPatternId{ 0 }; + SHORT _firstRow{ 0 }; // indexes top row (not necessarily 0) #ifdef UNIT_TESTING friend class TextBufferTests; diff --git a/src/inc/LibraryIncludes.h b/src/inc/LibraryIncludes.h index b19b42f16..1b76baa24 100644 --- a/src/inc/LibraryIncludes.h +++ b/src/inc/LibraryIncludes.h @@ -51,8 +51,8 @@ #include // WIL -#include -#include +#include +#include #include #include #include diff --git a/src/inc/til/bitmap.h b/src/inc/til/bitmap.h index f4bef68b6..01644f73f 100644 --- a/src/inc/til/bitmap.h +++ b/src/inc/til/bitmap.h @@ -15,11 +15,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" class _bitmap_const_iterator { public: - using iterator_category = typename std::input_iterator_tag; - using value_type = typename const til::rectangle; - using difference_type = typename ptrdiff_t; - using pointer = typename const til::rectangle*; - using reference = typename const til::rectangle&; + using iterator_category = std::input_iterator_tag; + using value_type = const til::rectangle; + using difference_type = ptrdiff_t; + using pointer = const til::rectangle*; + using reference = const til::rectangle&; _bitmap_const_iterator(const dynamic_bitset& values, til::rectangle rc, ptrdiff_t pos) : _values(values),