Compare commits

...

3 commits

Author SHA1 Message Date
Leonard Hecker e7d00cab8e wip 2021-07-20 03:58:47 +02:00
Leonard Hecker dbf9343320 wip 2021-07-20 01:07:48 +02:00
Leonard Hecker a9c50f9bf7 wip 2021-07-18 01:16:33 +02:00
37 changed files with 576 additions and 909 deletions

View file

@ -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:

View file

@ -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<value_type>::iterator;
using const_iterator = typename boost::container::small_vector_base<value_type>::const_iterator;
using const_reverse_iterator = typename boost::container::small_vector_base<value_type>::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<value_type, 120> _data;
gsl::span<CharRowCell> _data;
// ROW that this CharRow belongs to
ROW* _pParent;
};
template<typename InputIt1, typename InputIt2>
void OverwriteColumns(InputIt1 startChars, InputIt1 endChars, InputIt2 startAttrs, CharRow::iterator outIt)
template<typename InputIt1, typename InputIt2, typename OutputIt>
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 };
});
}

View file

@ -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:

View file

@ -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:
// - <none>
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:

View file

@ -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

View file

@ -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<SHORT, SHORT>& rowMap, const std::optional<SHORT> width)
void UnicodeStorage::Remap(const std::unordered_map<SHORT, SHORT>& rowMap, SHORT width)
{
// Make a temporary map to hold all the new row positioning
std::unordered_map<key_type, mapped_type> newMap;
@ -58,18 +58,10 @@ void UnicodeStorage::Remap(const std::unordered_map<SHORT, SHORT>& 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

View file

@ -55,7 +55,7 @@ public:
void Erase(const key_type key) noexcept;
void Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width);
void Remap(const std::unordered_map<SHORT, SHORT>& rowMap, SHORT width);
private:
std::unordered_map<key_type, mapped_type> _map;

View file

@ -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<size_t>(screenBufferSize.X);
const auto dy = static_cast<size_t>(screenBufferSize.Y);
auto buffer = static_cast<CharRowCell*>(VirtualAlloc(nullptr, dx * dy * sizeof(CharRowCell), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
THROW_IF_NULL_ALLOC(buffer);
_charBuffer = buffer;
_storage.reserve(static_cast<size_t>(screenBufferSize.Y));
for (size_t i = 0; i < static_cast<size_t>(screenBufferSize.Y); ++i)
for (SHORT i = 0; i < screenBufferSize.Y; ++i)
{
_storage.emplace_back(static_cast<SHORT>(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)
{
@ -779,7 +781,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
@ -897,6 +899,11 @@ void TextBuffer::Reset()
{
RETURN_HR_IF(E_INVALIDARG, newSize.X < 0 || newSize.Y < 0);
const auto dx = static_cast<size_t>(newSize.X);
const auto dy = static_cast<size_t>(newSize.Y);
auto buffer = static_cast<CharRowCell*>(VirtualAlloc(nullptr, dx * dy * sizeof(CharRowCell), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
RETURN_IF_NULL_ALLOC(buffer);
try
{
const auto currentSize = GetSize().Dimensions();
@ -910,12 +917,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
@ -927,19 +929,26 @@ void TextBuffer::Reset()
// add rows if we're growing
while (_storage.size() < static_cast<size_t>(newSize.Y))
{
_storage.emplace_back(static_cast<short>(_storage.size()), newSize.X, attributes, this);
_storage.emplace_back(static_cast<short>(_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;
}
@ -962,7 +971,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<SHORT> newRowWidth)
void TextBuffer::_RefreshRowIDs(SHORT width)
{
std::unordered_map<SHORT, SHORT> rowMap;
SHORT i = 0;
@ -976,17 +985,19 @@ void TextBuffer::_RefreshRowIDs(std::optional<SHORT> 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
@ -1045,7 +1056,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);
}
@ -1060,7 +1071,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 "
@ -1105,7 +1116,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();
@ -1150,7 +1161,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();
@ -1182,7 +1193,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 "
@ -1219,7 +1230,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;
@ -1267,7 +1278,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();
@ -1350,7 +1361,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"
@ -1374,7 +1385,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<std::wstring_view>{}(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));

View file

@ -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<size_t> 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<SMALL_RECT> 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<Microsoft::Console::Types::Viewport> lastCharacterViewport,
std::optional<std::reference_wrapper<PositionInformation>> 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<til::point, size_t> GetPatterns(const size_t firstRow, const size_t lastRow) const;
private:
void _UpdateSize();
Microsoft::Console::Types::Viewport _size;
std::vector<ROW> _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<uint16_t, std::wstring> _hyperlinkMap;
std::unordered_map<std::wstring, uint16_t> _hyperlinkCustomIdMap;
uint16_t _currentHyperlinkId;
void _RefreshRowIDs(std::optional<SHORT> 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<ROW> _storage;
std::unordered_map<uint16_t, std::wstring> _hyperlinkMap;
std::unordered_map<std::wstring, uint16_t> _hyperlinkCustomIdMap;
std::unordered_map<uint16_t, std::wstring> _hyperlinkCustomIdMapReverse;
std::unordered_map<size_t, std::wstring> _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;

View file

@ -51,7 +51,6 @@ namespace SettingsModelLocalTests
void TerminalSettingsTests::TryCreateWinRTType()
{
TerminalSettings settings;
VERIFY_IS_NOT_NULL(settings);
auto oldFontSize = settings.FontSize();
settings.FontSize(oldFontSize + 5);
auto newFontSize = settings.FontSize();
@ -60,7 +59,10 @@ namespace SettingsModelLocalTests
void TerminalSettingsTests::TestTerminalArgsForBinding()
{
const std::string settingsJson{ R"(
const winrt::guid guid0{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}") };
const winrt::guid guid1{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}") };
CascadiaSettings settings{ LR"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -98,11 +100,6 @@ namespace SettingsModelLocalTests
]
})" };
const winrt::guid guid0{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}") };
const winrt::guid guid1{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}") };
CascadiaSettings settings{ til::u8u16(settingsJson) };
auto actionMap = settings.GlobalSettings().ActionMap();
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
@ -385,7 +382,7 @@ namespace SettingsModelLocalTests
void TerminalSettingsTests::MakeSettingsForProfileThatDoesntExist()
{
// Test that making settings throws when the GUID doesn't exist
const std::string settingsString{ R"(
CascadiaSettings settings{ LR"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -401,7 +398,6 @@ namespace SettingsModelLocalTests
}
]
})" };
CascadiaSettings settings{ til::u8u16(settingsString) };
const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
@ -449,7 +445,7 @@ namespace SettingsModelLocalTests
// defaultProfile that's not in the list, we validate the settings, and
// then call MakeSettings(nullopt). The validation should ensure that
// the default profile is something reasonable
const std::string settingsString{ R"(
CascadiaSettings settings{ LR"(
{
"defaultProfile": "{6239a42c-3333-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -465,7 +461,6 @@ namespace SettingsModelLocalTests
}
]
})" };
CascadiaSettings settings{ til::u8u16(settingsString) };
VERIFY_ARE_EQUAL(2u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(2u, settings.ActiveProfiles().Size());
@ -487,7 +482,7 @@ namespace SettingsModelLocalTests
Log::Comment(NoThrowString().Format(
L"Ensure that setting (or not) a property in the profile that should override a property of the color scheme works correctly."));
const std::string settings0String{ R"(
CascadiaSettings settings{ LR"(
{
"defaultProfile": "profile5",
"profiles": [
@ -528,8 +523,6 @@ namespace SettingsModelLocalTests
]
})" };
CascadiaSettings settings{ til::u8u16(settings0String) };
VERIFY_ARE_EQUAL(6u, settings.ActiveProfiles().Size());
VERIFY_ARE_EQUAL(2u, settings.GlobalSettings().ColorSchemes().Size());

View file

@ -79,7 +79,10 @@ namespace TerminalAppLocalTests
// containing a ${profile.name} to replace. When we expand it, it should
// have created one command for each profile.
const std::string settingsJson{ R"(
const auto guid0 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}");
const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
CascadiaSettings settings{ LR"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -111,11 +114,6 @@ namespace TerminalAppLocalTests
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
})" };
const auto guid0 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}");
const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
CascadiaSettings settings{ til::u8u16(settingsJson) };
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
@ -207,7 +205,10 @@ namespace TerminalAppLocalTests
// For this test, put an iterable command without a given `name` to
// replace. When we expand it, it should still work.
const std::string settingsJson{ R"(
const auto guid0 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}");
const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
CascadiaSettings settings{ LR"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -238,11 +239,6 @@ namespace TerminalAppLocalTests
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
})" };
const auto guid0 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}");
const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
CascadiaSettings settings{ til::u8u16(settingsJson) };
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
@ -335,7 +331,10 @@ namespace TerminalAppLocalTests
// cause bad json to be filled in. Something like a profile with a name
// of "Foo\"", so the trailing '"' might break the json parsing.
const std::string settingsJson{ R"(
const auto guid0 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}");
const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
CascadiaSettings settings{ LR"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -367,11 +366,6 @@ namespace TerminalAppLocalTests
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
})" };
const auto guid0 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}");
const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
CascadiaSettings settings{ til::u8u16(settingsJson) };
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
@ -468,7 +462,7 @@ namespace TerminalAppLocalTests
// ├─ first.com
// └─ second.com
const std::string settingsJson{ R"(
CascadiaSettings settings{ LR"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -508,8 +502,6 @@ namespace TerminalAppLocalTests
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
})" };
CascadiaSettings settings{ til::u8u16(settingsJson) };
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
@ -558,7 +550,7 @@ namespace TerminalAppLocalTests
// ├─ child1
// └─ child2
const std::string settingsJson{ R"(
CascadiaSettings settings{ LR"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -603,8 +595,6 @@ namespace TerminalAppLocalTests
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
})" };
CascadiaSettings settings{ til::u8u16(settingsJson) };
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
@ -691,7 +681,7 @@ namespace TerminalAppLocalTests
// ├─ Split pane, direction: vertical, profile: profile2
// └─ Split pane, direction: horizontal, profile: profile2
const std::string settingsJson{ R"(
CascadiaSettings settings{ LR"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -727,8 +717,6 @@ namespace TerminalAppLocalTests
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
})" };
CascadiaSettings settings{ til::u8u16(settingsJson) };
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
@ -828,7 +816,7 @@ namespace TerminalAppLocalTests
// ├─ Profile 2
// └─ Profile 3
const std::string settingsJson{ R"(
CascadiaSettings settings{ LR"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -864,8 +852,6 @@ namespace TerminalAppLocalTests
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
})" };
CascadiaSettings settings{ til::u8u16(settingsJson) };
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
@ -926,7 +912,7 @@ namespace TerminalAppLocalTests
// ├─ Split vertically
// └─ Split horizontally
const std::string settingsJson{ R"(
CascadiaSettings settings{ LR"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -967,8 +953,6 @@ namespace TerminalAppLocalTests
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
})" };
CascadiaSettings settings{ til::u8u16(settingsJson) };
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
@ -1071,7 +1055,7 @@ namespace TerminalAppLocalTests
// containing a ${profile.name} to replace. When we expand it, it should
// have created one command for each profile.
const std::string settingsJson{ R"(
CascadiaSettings settings{ LR"(
{
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -1107,8 +1091,6 @@ namespace TerminalAppLocalTests
]
})" };
CascadiaSettings settings{ til::u8u16(settingsJson) };
// Since at least one profile does not reference a color scheme,
// we add a warning saying "the color scheme is unknown"
VERIFY_ARE_EQUAL(1u, settings.Warnings().Size());

View file

@ -129,7 +129,6 @@ namespace TerminalAppLocalTests
// Verify we can create a WinRT type we authored
// Just creating it is enough to know that everything is working.
TerminalSettings settings;
VERIFY_IS_NOT_NULL(settings);
auto oldFontSize = settings.FontSize();
settings.FontSize(oldFontSize + 5);
auto newFontSize = settings.FontSize();
@ -307,7 +306,7 @@ namespace TerminalAppLocalTests
// TerminalPage and not only create them successfully, but also create a
// tab using those settings successfully.
const std::string settingsJson0{ R"(
CascadiaSettings settings0{ LR"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -324,9 +323,6 @@ namespace TerminalAppLocalTests
]
})" };
CascadiaSettings settings0{ til::u8u16(settingsJson0) };
VERIFY_IS_NOT_NULL(settings0);
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
@ -353,7 +349,7 @@ namespace TerminalAppLocalTests
//
// Created to test GH#2455
const std::string settingsJson0{ R"(
CascadiaSettings settings0{ LR"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -368,9 +364,9 @@ namespace TerminalAppLocalTests
"historySize": 2
}
]
})" };
})") };
const std::string settingsJson1{ R"(
CascadiaSettings settings1{ LR"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -382,12 +378,6 @@ namespace TerminalAppLocalTests
]
})" };
CascadiaSettings settings0{ til::u8u16(settingsJson0) };
VERIFY_IS_NOT_NULL(settings0);
CascadiaSettings settings1{ til::u8u16(settingsJson1) };
VERIFY_IS_NOT_NULL(settings1);
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
@ -440,7 +430,7 @@ namespace TerminalAppLocalTests
//
// Created to test GH#2455
const std::string settingsJson0{ R"(
CascadiaSettings settings0{ LR"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -457,7 +447,7 @@ namespace TerminalAppLocalTests
]
})" };
const std::string settingsJson1{ R"(
CascadiaSettings settings1{ LR"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
@ -469,12 +459,6 @@ namespace TerminalAppLocalTests
]
})" };
CascadiaSettings settings0{ til::u8u16(settingsJson0) };
VERIFY_IS_NOT_NULL(settings0);
CascadiaSettings settings1{ til::u8u16(settingsJson1) };
VERIFY_IS_NOT_NULL(settings1);
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
@ -554,7 +538,7 @@ namespace TerminalAppLocalTests
// - The initialized TerminalPage, ready to use.
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> TabTests::_commonSetup()
{
const std::string settingsJson0{ R"(
CascadiaSettings settings0{ LR"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"showTabsInTitlebar": false,
@ -655,9 +639,6 @@ namespace TerminalAppLocalTests
]
})" };
CascadiaSettings settings0{ til::u8u16(settingsJson0) };
VERIFY_IS_NOT_NULL(settings0);
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");

View file

@ -1335,8 +1335,7 @@ namespace winrt::TerminalApp::implementation
{
// The string they provided wasn't an int, it wasn't "new"
// or "last", so whatever it is, that's the name they get.
winrt::hstring winrtName{ til::u8u16(parsedTarget) };
return winrt::make<FindTargetWindowResult>(WindowingBehaviorUseName, winrtName);
return winrt::make<FindTargetWindowResult>(WindowingBehaviorUseName, til::u8u16(parsedTarget));
}
}
}

View file

@ -456,8 +456,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
// else we call convertUTF8ChunkToUTF16 with an empty string_view to convert possible remaining partials to U+FFFD
}
const HRESULT result{ til::u8u16(std::string_view{ _buffer.data(), read }, _u16Str, _u8State) };
if (FAILED(result))
try
{
til::u8u16(std::string_view{ _buffer.data(), read }, _u16Str, _u8State);
}
catch (...)
{
if (_isStateAtOrBeyond(ConnectionState::Closing))
{

View file

@ -5,6 +5,7 @@
#include "CascadiaSettings.h"
#include "CascadiaSettings.g.cpp"
#include <til/u8u16convert.h>
#include <LibraryResources.h>
#include "AzureCloudShellGenerator.h"

View file

@ -5,6 +5,7 @@
#include "CascadiaSettings.h"
#include <fmt/chrono.h>
#include <til/u8u16convert.h>
#include <shlobj.h>
// defaults.h is a file containing the default json settings in a std::string_view

View file

@ -5,9 +5,11 @@
#include "Command.h"
#include "Command.g.cpp"
#include <til/u8u16convert.h>
#include <LibraryResources.h>
#include "ActionAndArgs.h"
#include "KeyChordSerialization.h"
#include <LibraryResources.h>
#include "TerminalSettingsSerializationHelpers.h"
using namespace winrt::Microsoft::Terminal::Settings::Model;

View file

@ -6,6 +6,7 @@
#include "KeyChordSerialization.g.cpp"
#include <til/static_map.h>
#include <til/u8u16convert.h>
using namespace winrt::Microsoft::Terminal::Control;
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;

View file

@ -375,4 +375,7 @@ public:
const CONSOLE_FONT_INFOEX& consoleFontInfoEx) noexcept override;
#pragma endregion
private:
til::u8state _u8State;
};

View file

@ -70,20 +70,22 @@ VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe,
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
std::wstring wstr;
try
{
til::u8u16(u8Str, wstr, _u8State);
}
catch (...)
{
return S_FALSE;
}
try
{
std::wstring wstr{};
auto hr = til::u8u16(u8Str, wstr, _u8State);
// If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it.
if (FAILED(hr))
{
return S_FALSE;
}
_pInputStateMachine->ProcessString(wstr);
return S_OK;
}
CATCH_RETURN();
return S_OK;
}
// Function Description:

View file

@ -15,6 +15,7 @@ Author(s):
#pragma once
#include "../terminal/parser/StateMachine.hpp"
#include <til/u8u16convert.h>
namespace Microsoft::Console
{

View file

@ -1137,19 +1137,22 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
auto leadByteCaptured{ false };
auto leadByteConsumed{ false };
std::wstring wstr{};
static til::u8state u8State{};
// Convert our input parameters to Unicode
if (codepage == CP_UTF8)
{
RETURN_IF_FAILED(til::u8u16(buffer, wstr, u8State));
try
{
til::u8u16(buffer, wstr, _u8State);
}
CATCH_RETURN()
read = buffer.size();
}
else
{
// In case the codepage changes from UTF-8 to another,
// we discard partials that might still be cached.
u8State.reset();
_u8State.reset();
int mbPtrLength{};
RETURN_IF_FAILED(SizeTToInt(buffer.size(), &mbPtrLength));

View file

@ -43,9 +43,6 @@
<ProjectReference Include="..\..\renderer\base\lib\base.vcxproj">
<Project>{af0a096a-8b3a-4949-81ef-7df8f0fee91f}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\dx\lib\dx.vcxproj">
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\gdi\lib\gdi.vcxproj">
<Project>{1c959542-bac2-4e55-9a6d-13251914cbb9}</Project>
</ProjectReference>

View file

@ -15,8 +15,6 @@
#include "til/rectangle.h"
#include "til/rle.h"
#include "til/bitmap.h"
#include "til/u8u16convert.h"
#include "til/spsc.h"
#include "til/coalesce.h"
#include "til/replace.h"
#include "til/string.h"

View file

@ -14,288 +14,43 @@ could overcome disadvantages of syscalls. Test results can be read up
in PR #4093 and the test algorithms are available in src\tools\U8U16Test.
Based on the results the decision was made to keep using the platform
functions MultiByteToWideChar and WideCharToMultiByte.
Author(s):
- Steffen Illhardt (german-one) 2020
--*/
#pragma once
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
template<class charT>
class u8u16state final
struct u8state
{
public:
u8u16state() noexcept :
_buffer{},
_utfPartials{}
{
uint32_t buffer{};
uint32_t remaining{};
constexpr void reset() noexcept {
*this = {};
}
// Method Description:
// - Takes a UTF-8 string and populates it with *complete* UTF-8 codepoints.
// If it receives an incomplete codepoint, it will cache it until it can be completed.
// Arguments:
// - in - UTF-8 string_view potentially containing partial code points
// - out - on return, populated with complete codepoints at the string end
// Return Value:
// - S_OK - the resulting string doesn't end with a partial
// - S_FALSE - the resulting string contains the previously cached partials only
// - E_OUTOFMEMORY - the method failed to allocate memory for the resulting string
// - E_ABORT - the resulting string length would exceed the max_size and thus, the processing was aborted
// - E_UNEXPECTED - an unexpected error occurred
template<class T = charT>
[[nodiscard]] typename std::enable_if<std::is_same<T, char>::value, HRESULT>::type
operator()(const std::basic_string_view<T> in, std::basic_string_view<T>& out) noexcept
{
try
{
size_t capacity{};
RETURN_HR_IF(E_ABORT, !base::CheckAdd(in.length(), _partialsLen).AssignIfValid(&capacity));
_buffer.clear();
_buffer.reserve(capacity);
// copy UTF-8 code units that were remaining from the previous call (if any)
if (_partialsLen != 0u)
{
_buffer.assign(_utfPartials.cbegin(), _utfPartials.cbegin() + _partialsLen);
_partialsLen = 0u;
}
if (in.empty())
{
out = _buffer;
if (_buffer.empty())
{
return S_OK;
}
return S_FALSE; // the partial is populated
}
_buffer.append(in);
size_t remainingLength{ _buffer.length() };
auto backIter = _buffer.end();
// If the last byte in the string was a byte belonging to a UTF-8 multi-byte character
if ((*(backIter - 1) & _Utf8BitMasks::MaskAsciiByte) > _Utf8BitMasks::IsAsciiByte)
{
// Check only up to 3 last bytes, if no Lead Byte was found then the byte before must be the Lead Byte and no partials are in the string
const size_t stopLen{ std::min(_buffer.length(), gsl::narrow_cast<size_t>(3u)) };
for (size_t sequenceLen{ 1u }; sequenceLen <= stopLen; ++sequenceLen)
{
--backIter;
// If Lead Byte found
if ((*backIter & _Utf8BitMasks::MaskContinuationByte) > _Utf8BitMasks::IsContinuationByte)
{
// If the Lead Byte indicates that the last bytes in the string is a partial UTF-8 code point then cache them:
// Use the bitmask at index `sequenceLen`. Compare the result with the operand having the same index. If they
// are not equal then the sequence has to be cached because it is a partial code point. Otherwise the
// sequence is a complete UTF-8 code point and the whole string is ready for the conversion into a UTF-16 string.
if ((*backIter & _cmpMasks.at(sequenceLen)) != _cmpOperands.at(sequenceLen))
{
std::move(backIter, _buffer.end(), _utfPartials.begin());
remainingLength -= sequenceLen;
_partialsLen = sequenceLen;
}
break;
}
}
}
// populate the part of the string that contains complete code points only
out = { _buffer.data(), remainingLength };
return S_OK;
}
catch (std::length_error&)
{
return E_ABORT;
}
catch (std::bad_alloc&)
{
return E_OUTOFMEMORY;
}
catch (...)
{
return E_UNEXPECTED;
}
}
// Method Description:
// - Takes a UTF-16 string and populates it with *complete* UTF-16 codepoints.
// If it receives an incomplete codepoint, it will cache it until it can be completed.
// Arguments:
// - in - UTF-16 string_view potentially containing partial code points
// - out - on return, populated with complete codepoints at the string end
// Return Value:
// - S_OK - the resulting string doesn't end with a partial
// - S_FALSE - the resulting string contains the previously cached partials only
// - E_OUTOFMEMORY - the method failed to allocate memory for the resulting string
// - E_ABORT - the resulting string length would exceed the max_size and thus, the processing was aborted
// - E_UNEXPECTED - an unexpected error occurred
template<class T = charT>
[[nodiscard]] typename std::enable_if<std::is_same<T, wchar_t>::value, HRESULT>::type
operator()(const std::basic_string_view<T> in, std::basic_string_view<T>& out) noexcept
{
try
{
size_t remainingLength{ in.length() };
size_t capacity{};
RETURN_HR_IF(E_ABORT, !base::CheckAdd(remainingLength, _partialsLen).AssignIfValid(&capacity));
_buffer.clear();
_buffer.reserve(capacity);
// copy UTF-8 code units that were remaining from the previous call (if any)
if (_partialsLen != 0u)
{
_buffer.push_back(_utfPartials.front());
_partialsLen = 0u;
}
if (in.empty())
{
out = _buffer;
if (_buffer.empty())
{
return S_OK;
}
return S_FALSE; // the high surrogate is populated
}
// cache the last value in the string if it is in the range of high surrogates
if (in.back() >= 0xD800u && in.back() <= 0xDBFFu)
{
_utfPartials.front() = in.back();
--remainingLength;
_partialsLen = 1u;
}
else
{
_partialsLen = 0u;
}
// populate the part of the string that contains complete code points only
_buffer.append(in, 0u, remainingLength);
out = _buffer;
return S_OK;
}
catch (std::length_error&)
{
return E_ABORT;
}
catch (std::bad_alloc&)
{
return E_OUTOFMEMORY;
}
catch (...)
{
return E_UNEXPECTED;
}
}
// Method Description:
// - Discard cached partials.
// Arguments:
// - none
// Return Value:
// - void
void reset() noexcept
{
_partialsLen = 0u;
}
private:
enum _Utf8BitMasks : BYTE
{
IsAsciiByte = 0b0'0000000, // Any byte representing an ASCII character has the MSB set to 0
MaskAsciiByte = 0b1'0000000, // Bit mask to be used in a bitwise AND operation to find out whether or not a byte match the IsAsciiByte pattern
IsContinuationByte = 0b10'000000, // Continuation bytes of any UTF-8 non-ASCII character have the MSB set to 1 and the adjacent bit set to 0
MaskContinuationByte = 0b11'000000, // Bit mask to be used in a bitwise AND operation to find out whether or not a byte match the IsContinuationByte pattern
IsLeadByteTwoByteSequence = 0b110'00000, // A lead byte that indicates a UTF-8 non-ASCII character consisting of two bytes has the two highest bits set to 1 and the adjacent bit set to 0
MaskLeadByteTwoByteSequence = 0b111'00000, // Bit mask to be used in a bitwise AND operation to find out whether or not a lead byte match the IsLeadByteTwoByteSequence pattern
IsLeadByteThreeByteSequence = 0b1110'0000, // A lead byte that indicates a UTF-8 non-ASCII character consisting of three bytes has the three highest bits set to 1 and the adjacent bit set to 0
MaskLeadByteThreeByteSequence = 0b1111'0000, // Bit mask to be used in a bitwise AND operation to find out whether or not a lead byte match the IsLeadByteThreeByteSequence pattern
IsLeadByteFourByteSequence = 0b11110'000, // A lead byte that indicates a UTF-8 non-ASCII character consisting of four bytes has the four highest bits set to 1 and the adjacent bit set to 0
MaskLeadByteFourByteSequence = 0b11111'000 // Bit mask to be used in a bitwise AND operation to find out whether or not a lead byte match the IsLeadByteFourByteSequence pattern
};
// array of bitmasks
constexpr static std::array<BYTE, 4> _cmpMasks{
0, // unused
_Utf8BitMasks::MaskContinuationByte,
_Utf8BitMasks::MaskLeadByteTwoByteSequence,
_Utf8BitMasks::MaskLeadByteThreeByteSequence,
};
// array of values for the comparisons
constexpr static std::array<BYTE, 4> _cmpOperands{
0, // unused
_Utf8BitMasks::IsAsciiByte, // intentionally conflicts with MaskContinuationByte
_Utf8BitMasks::IsLeadByteTwoByteSequence,
_Utf8BitMasks::IsLeadByteThreeByteSequence,
};
std::basic_string<charT> _buffer; // buffer to which the populated string_view refers
std::array<charT, 4> _utfPartials; // buffer for code units of a partial code point that have to be cached
size_t _partialsLen{}; // number of cached code units
};
// make clear what incoming string type the state is for
typedef u8u16state<char> u8state;
typedef u8u16state<wchar_t> u16state;
// Routine Description:
// - Takes a UTF-8 string and performs the conversion to UTF-16. NOTE: The function relies on getting complete UTF-8 characters at the string boundaries.
// Arguments:
// - in - UTF-8 string to be converted
// - out - reference to the resulting UTF-16 string
// Return Value:
// - S_OK - the conversion succeeded
// - E_OUTOFMEMORY - the function failed to allocate memory for the resulting string
// - E_ABORT - the resulting string length would exceed the upper boundary of an int and thus, the conversion was aborted before the conversion has been completed
// - E_UNEXPECTED - an unexpected error occurred
template<class inT, class outT>
[[nodiscard]] typename std::enable_if<std::is_same<typename inT::value_type, char>::value && std::is_same<typename outT::value_type, wchar_t>::value, HRESULT>::type
u8u16(const inT in, outT& out) noexcept
template<typename Output>
void u8u16(const std::string_view& in, Output& out)
{
try
{
out.clear();
out.clear();
if (in.empty())
{
return S_OK;
}
if (in.empty())
{
return;
}
int lengthRequired{};
// The worst ratio of UTF-8 code units to UTF-16 code units is 1 to 1 if UTF-8 consists of ASCII only.
RETURN_HR_IF(E_ABORT, !base::MakeCheckedNum(in.length()).AssignIfValid(&lengthRequired));
out.resize(in.length()); // avoid to call MultiByteToWideChar twice only to get the required size
const int lengthOut = MultiByteToWideChar(gsl::narrow_cast<UINT>(CP_UTF8), 0ul, in.data(), lengthRequired, out.data(), lengthRequired);
out.resize(gsl::narrow_cast<size_t>(lengthOut));
return lengthOut == 0 ? E_UNEXPECTED : S_OK;
}
catch (std::length_error&)
{
return E_ABORT;
}
catch (std::bad_alloc&)
{
return E_OUTOFMEMORY;
}
catch (...)
{
return E_UNEXPECTED;
}
// The worst ratio of UTF-8 code units to UTF-16 code units is 1 to 1 if UTF-8 consists of ASCII only.
const auto lengthRequired = gsl::narrow<int>(in.length());
out.resize(in.length());
const int lengthOut = MultiByteToWideChar(CP_UTF8, 0, in.data(), lengthRequired, out.data(), lengthRequired);
out.resize(gsl::narrow_cast<size_t>(lengthOut));
THROW_LAST_ERROR_IF(lengthOut == 0);
}
// Routine Description:
@ -304,87 +59,93 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
// - in - UTF-8 string to be converted
// - out - reference to the resulting UTF-16 string
// - state - reference to a til::u8state class holding the status of the current partials handling
// Return Value:
// - S_OK - the conversion succeeded
// - E_OUTOFMEMORY - the function failed to allocate memory for the resulting string
// - E_ABORT - the resulting string length would exceed the upper boundary of an int and thus, the conversion was aborted before the conversion has been completed
// - E_UNEXPECTED - an unexpected error occurred
template<class inT, class outT>
[[nodiscard]] typename std::enable_if<std::is_same<typename inT::value_type, char>::value && std::is_same<typename outT::value_type, wchar_t>::value, HRESULT>::type
u8u16(const inT in, outT& out, u8state& state) noexcept
template<typename Output>
void u8u16(const std::string_view& in, Output& out, u8state& state) noexcept
{
std::string_view sv{};
RETURN_IF_FAILED(state(std::string_view{ in }, sv));
return til::u8u16(sv, out);
}
auto data = in.data();
auto size = in.size();
// Routine Description:
// - Takes a UTF-16 string and performs the conversion to UTF-8. NOTE: The function relies on getting complete UTF-16 characters at the string boundaries.
// Arguments:
// - in - UTF-16 string to be converted
// - out - reference to the resulting UTF-8 string
// Return Value:
// - S_OK - the conversion succeeded
// - E_OUTOFMEMORY - the function failed to allocate memory for the resulting string
// - E_ABORT - the resulting string length would exceed the upper boundary of an int and thus, the conversion was aborted before the conversion has been completed
// - E_UNEXPECTED - an unexpected error occurred
template<class inT, class outT>
[[nodiscard]] typename std::enable_if<std::is_same<typename inT::value_type, wchar_t>::value && std::is_same<typename outT::value_type, char>::value, HRESULT>::type
u16u8(const inT in, outT& out) noexcept
{
try
if (!size)
{
out.clear();
return;
}
if (in.empty())
if (auto remaining = state.remaining)
{
if (remaining > size)
{
return S_OK;
remaining = gsl::narrow_cast<uint32_t>(size);
}
int lengthIn{};
int lengthRequired{};
// Code Point U+0000..U+FFFF: 1 UTF-16 code unit --> 1..3 UTF-8 code units.
// Code Points >U+FFFF: 2 UTF-16 code units --> 4 UTF-8 code units.
// Thus, the worst ratio of UTF-16 code units to UTF-8 code units is 1 to 3.
RETURN_HR_IF(E_ABORT, !base::MakeCheckedNum(in.length()).AssignIfValid(&lengthIn) || !base::CheckMul(lengthIn, 3).AssignIfValid(&lengthRequired));
out.resize(gsl::narrow_cast<size_t>(lengthRequired)); // avoid to call WideCharToMultiByte twice only to get the required size
const int lengthOut = WideCharToMultiByte(gsl::narrow_cast<UINT>(CP_UTF8), 0ul, in.data(), lengthIn, out.data(), lengthRequired, nullptr, nullptr);
out.resize(gsl::narrow_cast<size_t>(lengthOut));
state.remaining -= remaining;
return lengthOut == 0 ? E_UNEXPECTED : S_OK;
}
catch (std::length_error&)
{
return E_ABORT;
}
catch (std::bad_alloc&)
{
return E_OUTOFMEMORY;
}
catch (...)
{
return E_UNEXPECTED;
}
}
do
{
state.buffer <<= 6;
state.buffer |= *data++ & 0x3f;
} while (--remaining);
// Routine Description:
// - Takes a UTF-16 string, complements and/or caches partials, and performs the conversion to UTF-8.
// Arguments:
// - in - UTF-16 string to be converted
// - out - reference to the resulting UTF-8 string
// - state - reference to a til::u16state class holding the status of the current partials handling
// Return Value:
// - S_OK - the conversion succeeded without any change of the represented code points
// - E_OUTOFMEMORY - the function failed to allocate memory for the resulting string
// - E_ABORT - the resulting string length would exceed the upper boundary of an int and thus, the conversion was aborted before the conversion has been completed
// - E_UNEXPECTED - an unexpected error occurred
template<class inT, class outT>
[[nodiscard]] typename std::enable_if<std::is_same<typename inT::value_type, wchar_t>::value && std::is_same<typename outT::value_type, char>::value, HRESULT>::type
u16u8(const inT in, outT& out, u16state& state) noexcept
{
std::wstring_view sv{};
RETURN_IF_FAILED(state(std::wstring_view{ in }, sv));
return u16u8(sv, out);
if (!state.remaining)
{
if (state.buffer < 0x10000)
{
const auto buffer = static_cast<wchar_t>(state.buffer);
out.append(&buffer, 1);
}
else
{
wchar_t buffer[2];
buffer[0] = ((state.buffer >> 10) & 0x3FF) + 0xD800;
buffer[1] = (state.buffer & 0x3FF) + 0xDC00;
out.append(&buffer[0], 2);
}
}
}
{
auto end = data + size;
size_t have = 1;
// Skip UTF-8 continuation bytes in the form of 0b10xxxxxx.
while ((*--end & 0b11000000) == 0b10000000 && end != data)
{
++have;
}
// A leading UTF-8 byte is either of:
// * 0b110xxxxx
// * 0b1110xxxx
// * 0b11110xxx
if (have != 1)
{
DWORD index = 0;
if (_BitScanReverse(&index, ~*end & 0xff))
{
const auto want = 7 - index;
if (want <= 4 && want > have)
{
auto ptr = end;
uint32_t buffer = *ptr++ & ((1 << index) - 1);
for (size_t i = 1; i < have; ++i)
{
buffer <<= 6;
buffer |= *ptr++ & 0x3f;
}
state.buffer = buffer;
state.remaining = gsl::narrow_cast<uint32_t>(want - have);
}
}
else
{
++end;
}
size = end - data;
}
}
u8u16({ data, size }, out);
}
// Routine Description:
@ -394,30 +155,104 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
// Return Value:
// - the resulting UTF-16 string
// - NOTE: Throws HRESULT errors that the non-throwing sibling returns
template<class inT>
typename std::enable_if<std::is_same<typename inT::value_type, char>::value, std::wstring>::type
u8u16(const inT in)
_TIL_INLINEPREFIX std::wstring u8u16(const std::string_view& in)
{
std::wstring out{};
THROW_IF_FAILED(u8u16(std::string_view{ in }, out));
std::wstring out;
u8u16(in, out);
return out;
}
// Routine Description:
// Takes a UTF-8 string, complements and/or caches partials, and performs the conversion to UTF-16.
// - Takes a UTF-16 string and performs the conversion to UTF-8. NOTE: The function relies on getting complete UTF-16 characters at the string boundaries.
// Arguments:
// - in - UTF-8 string to be converted
// - state - reference to a til::u8state class holding the status of the current partials handling
// Return Value:
// - the resulting UTF-16 string
// - NOTE: Throws HRESULT errors that the non-throwing sibling returns
template<class inT>
typename std::enable_if<std::is_same<typename inT::value_type, char>::value, std::wstring>::type
u8u16(const inT in, u8state& state)
// - in - UTF-16 string to be converted
// - out - reference to the resulting UTF-8 string
template<typename Output>
void u16u8(const std::wstring_view& in, Output& out) noexcept
{
std::wstring out{};
THROW_IF_FAILED(u8u16(std::string_view{ in }, out, state));
return out;
out.clear();
if (in.empty())
{
return;
}
// Code Point U+0000..U+FFFF: 1 UTF-16 code unit --> 1..3 UTF-8 code units.
// Code Points >U+FFFF: 2 UTF-16 code units --> 4 UTF-8 code units.
// Thus, the worst ratio of UTF-16 code units to UTF-8 code units is 1 to 3.
const size_t lengthIn = in.length();
const size_t lengthRequired = base::CheckMul(lengthIn, 3).ValueOrDie();
out.resize(lengthRequired);
const int lengthOut = WideCharToMultiByte(gsl::narrow_cast<UINT>(CP_UTF8), 0ul, in.data(), gsl::narrow<int>(lengthIn), out.data(), gsl::narrow<int>(lengthRequired), nullptr, nullptr);
out.resize(lengthOut);
THROW_LAST_ERROR_IF(lengthOut == 0);
}
struct u16state
{
wchar_t buffer{};
constexpr void reset() noexcept
{
*this = {};
}
};
// Routine Description:
// - Takes a UTF-16 string, complements and/or caches partials, and performs the conversion to UTF-8.
// Arguments:
// - in - UTF-16 string to be converted
// - out - reference to the resulting UTF-8 string
// - state - reference to a til::u16state class holding the status of the current partials handling
template<typename Output>
void u16u8(const std::wstring_view& in, Output& out, u16state& state) noexcept
{
auto data = in.data();
auto size = in.size();
if (!size)
{
return;
}
if (state.buffer)
{
if (*data >= 0xDC00 && *data <= 0xDFFF)
{
const uint32_t high = state.buffer - 0xD800;
const uint32_t low = *data - 0xDC00;
const auto codePoint = ((high << 10) | low) + 0x10000;
char buffer[4];
buffer[0] = 0b11110000 | ((codePoint >> 18) & 0x3f);
buffer[1] = 0b10000000 | ((codePoint >> 12) & 0x3f);
buffer[2] = 0b10000000 | ((codePoint >> 6) & 0x3f);
buffer[3] = 0b10000000 | ((codePoint >> 0) & 0x3f);
out.append(&buffer[0], 4);
++data;
--size;
if (!size)
{
return;
}
}
state = {};
}
if (auto end = data + size - 1; *end >= 0xD800 && *end <= 0xDBFF)
{
state.buffer = *end;
--size;
if (!size)
{
return;
}
}
u16u8({ data, size }, out);
}
// Routine Description:
@ -427,29 +262,10 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
// Return Value:
// - the resulting UTF-8 string
// - NOTE: Throws HRESULT errors that the non-throwing sibling returns
template<class inT>
typename std::enable_if<std::is_same<typename inT::value_type, wchar_t>::value, std::string>::type
u16u8(const inT in)
_TIL_INLINEPREFIX std::string u16u8(const std::wstring_view& in)
{
std::string out{};
THROW_IF_FAILED(u16u8(std::wstring_view{ in }, out));
return out;
}
// Routine Description:
// Takes a UTF-16 string, complements and/or caches partials, and performs the conversion to UTF-8.
// Arguments:
// - in - UTF-16 string to be converted
// - state - reference to a til::u16state class holding the status of the current partials handling
// Return Value:
// - the resulting UTF-8 string
// - NOTE: Throws HRESULT errors that the non-throwing sibling returns
template<class inT>
typename std::enable_if<std::is_same<typename inT::value_type, wchar_t>::value, std::string>::type
u16u8(const inT in, u16state& state)
{
std::string out{};
THROW_IF_FAILED(u16u8(std::wstring_view{ in }, out, state));
u16u8(in, out);
return out;
}
}

View file

@ -5,6 +5,8 @@
#include "vtrenderer.hpp"
#include "../../inc/conattrs.hpp"
#include <til/u8u16convert.h>
#pragma hdrstop
using namespace Microsoft::Console::Render;

View file

@ -5,6 +5,7 @@
#include "vtrenderer.hpp"
#include "../../inc/conattrs.hpp"
#include "../../types/inc/convert.hpp"
#include <til/u8u16convert.h>
// For _vcprintf
#include <conio.h>
@ -31,8 +32,7 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
_hFile(std::move(pipe)),
_lastTextAttributes(INVALID_COLOR, INVALID_COLOR),
_lastViewport(initialViewport),
_pool(til::pmr::get_default_resource()),
_invalidMap(initialViewport.Dimensions(), false, &_pool),
_invalidMap(initialViewport.Dimensions(), false),
_lastText({ 0 }),
_scrollDelta({ 0, 0 }),
_quickReturn(false),
@ -148,7 +148,11 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
// - S_OK or suitable HRESULT error from either conversion or writing pipe.
[[nodiscard]] HRESULT VtEngine::_WriteTerminalUtf8(const std::wstring_view wstr) noexcept
{
RETURN_IF_FAILED(til::u16u8(wstr, _conversionBuffer));
try
{
til::u16u8(wstr, _conversionBuffer);
}
CATCH_RETURN();
return _Write(_conversionBuffer);
}

View file

@ -150,7 +150,7 @@ void RenderTracing::TraceInvalidateScroll(const til::point scroll) const
}
void RenderTracing::TraceStartPaint(const bool quickReturn,
const til::pmr::bitmap& invalidMap,
const til::bitmap& invalidMap,
const til::rectangle lastViewport,
const til::point scrollDelt,
const bool cursorMoved,

View file

@ -39,7 +39,7 @@ namespace Microsoft::Console::VirtualTerminal
void TraceTriggerCircling(const bool newFrame) const;
void TraceInvalidateScroll(const til::point scroll) const;
void TraceStartPaint(const bool quickReturn,
const til::pmr::bitmap& invalidMap,
const til::bitmap& invalidMap,
const til::rectangle lastViewport,
const til::point scrollDelta,
const bool cursorMoved,

View file

@ -119,8 +119,7 @@ namespace Microsoft::Console::Render
Microsoft::Console::Types::Viewport _lastViewport;
std::pmr::unsynchronized_pool_resource _pool;
til::pmr::bitmap _invalidMap;
til::bitmap _invalidMap;
COORD _lastText;
til::point _scrollDelta;

View file

@ -104,8 +104,9 @@ try
{
RETURN_HR_IF(E_FAIL, State.ReadOffset > Descriptor.InputSize);
ULONG const cbReadSize = Descriptor.InputSize - State.ReadOffset;
// We need to limit the read buffer to something reasonable (here: 16MiB) unless we want to
// consume the user's entire system memory when someone calls WriteFile() with a huge buffer.
const ULONG cbReadSize = std::min(16777216ul, Descriptor.InputSize - State.ReadOffset);
_inputBuffer.resize(cbReadSize);
RETURN_IF_FAILED(ReadMessageInput(0, _inputBuffer.data(), cbReadSize));

View file

@ -1081,22 +1081,27 @@ bool OutputStateMachineEngine::_GetOscSetClipboard(const std::wstring_view strin
std::wstring& content,
bool& queryClipboard) const noexcept
{
const size_t pos = string.find(';');
if (pos != std::wstring_view::npos)
const auto pos = string.find(L';');
if (pos == std::wstring_view::npos)
{
const std::wstring_view substr = string.substr(pos + 1);
if (substr == L"?")
{
queryClipboard = true;
return true;
}
else
{
return Base64::s_Decode(substr, content);
}
return false;
}
return false;
const auto substr = string.substr(pos + 1);
if (substr == L"?")
{
queryClipboard = true;
return true;
}
try {
Base64::s_Decode(substr, content);
return true;
}
catch (...)
{
return false;
}
}
// Method Description:

View file

@ -4,66 +4,22 @@
#include "precomp.h"
#include "base64.hpp"
#include <til/u8u16convert.h>
using namespace Microsoft::Console::VirtualTerminal;
static const char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char padChar = '=';
#pragma warning(disable : 26446 26447 26482 26485 26493 26494)
// Routine Description:
// - Encode a string using base64. When there are not enough characters
// for one quantum, paddings are added.
// Arguments:
// - src - String to base64 encode.
// Return Value:
// - the encoded string.
std::wstring Base64::s_Encode(const std::wstring_view src) noexcept
{
std::wstring dst;
wchar_t input[3];
const auto len = (src.size() + 2) / 3 * 4;
if (len == 0)
{
return dst;
}
dst.reserve(len);
auto iter = src.cbegin();
// Encode each three chars into one quantum (four chars).
while (iter < src.cend() - 2)
{
input[0] = *iter++;
input[1] = *iter++;
input[2] = *iter++;
dst.push_back(base64Chars[input[0] >> 2]);
dst.push_back(base64Chars[(input[0] & 0x03) << 4 | input[1] >> 4]);
dst.push_back(base64Chars[(input[1] & 0x0f) << 2 | input[2] >> 6]);
dst.push_back(base64Chars[(input[2] & 0x3f)]);
}
// Here only zero, or one, or two chars are left. We may need to add paddings.
if (iter < src.cend())
{
input[0] = *iter++;
dst.push_back(base64Chars[input[0] >> 2]);
if (iter < src.cend()) // Two chars left.
{
input[1] = *iter++;
dst.push_back(base64Chars[(input[0] & 0x03) << 4 | input[1] >> 4]);
dst.push_back(base64Chars[(input[1] & 0x0f) << 2]);
}
else // Only one char left.
{
dst.push_back(base64Chars[(input[0] & 0x03) << 4]);
dst.push_back(padChar);
}
dst.push_back(padChar);
}
return dst;
}
// clang-format off
static constexpr uint8_t decodeTable[128] = {
255 /* NUL */, 255 /* SOH */, 255 /* STX */, 255 /* ETX */, 255 /* EOT */, 255 /* ENQ */, 255 /* ACK */, 255 /* BEL */, 255 /* BS */, 255 /* HT */, 64 /* LF */, 255 /* VT */, 255 /* FF */, 64 /* CR */, 255 /* SO */, 255 /* SI */,
255 /* DLE */, 255 /* DC1 */, 255 /* DC2 */, 255 /* DC3 */, 255 /* DC4 */, 255 /* NAK */, 255 /* SYN */, 255 /* ETB */, 255 /* CAN */, 255 /* EM */, 255 /* SUB */, 255 /* ESC */, 255 /* FS */, 255 /* GS */, 255 /* RS */, 255 /* US */,
255 /* SP */, 255 /* ! */, 255 /* " */, 255 /* # */, 255 /* $ */, 255 /* % */, 255 /* & */, 255 /* ' */, 255 /* ( */, 255 /* ) */, 255 /* * */, 62 /* + */, 255 /* , */, 62 /* - */, 255 /* . */, 63 /* / */,
52 /* 0 */, 53 /* 1 */, 54 /* 2 */, 55 /* 3 */, 56 /* 4 */, 57 /* 5 */, 58 /* 6 */, 59 /* 7 */, 60 /* 8 */, 61 /* 9 */, 255 /* : */, 255 /* ; */, 255 /* < */, 255 /* = */, 255 /* > */, 255 /* ? */,
255 /* @ */, 0 /* A */, 1 /* B */, 2 /* C */, 3 /* D */, 4 /* E */, 5 /* F */, 6 /* G */, 7 /* H */, 8 /* I */, 9 /* J */, 10 /* K */, 11 /* L */, 12 /* M */, 13 /* N */, 14 /* O */,
15 /* P */, 16 /* Q */, 17 /* R */, 18 /* S */, 19 /* T */, 20 /* U */, 21 /* V */, 22 /* W */, 23 /* X */, 24 /* Y */, 25 /* Z */, 255 /* [ */, 255 /* \ */, 255 /* ] */, 255 /* ^ */, 63 /* _ */,
255 /* ` */, 26 /* a */, 27 /* b */, 28 /* c */, 29 /* d */, 30 /* e */, 31 /* f */, 32 /* g */, 33 /* h */, 34 /* i */, 35 /* j */, 36 /* k */, 37 /* l */, 38 /* m */, 39 /* n */, 40 /* o */,
41 /* p */, 42 /* q */, 43 /* r */, 44 /* s */, 45 /* t */, 46 /* u */, 47 /* v */, 48 /* w */, 49 /* x */, 50 /* y */, 51 /* z */, 255 /* { */, 255 /* | */, 255 /* } */, 255 /* ~ */, 255 /* DEL */,
};
// clang-format on
// Routine Description:
// - Decode a base64 string. This requires the base64 string is properly padded.
@ -73,121 +29,98 @@ std::wstring Base64::s_Encode(const std::wstring_view src) noexcept
// - dst - Destination to decode into.
// Return Value:
// - true if decoding successfully, otherwise false.
bool Base64::s_Decode(const std::wstring_view src, std::wstring& dst) noexcept
void Base64::s_Decode(const std::wstring_view src, std::wstring& dst)
{
std::string mbStr;
int state = 0;
char tmp;
std::string result;
result.resize((src.size() / 4) * 3);
const auto len = src.size() / 4 * 3;
if (len == 0)
auto in = src.data();
const auto inEnd = in + src.size();
const auto inEndBatched = inEnd - 3;
const auto outBeg = reinterpret_cast<uint8_t*>(result.data());
auto out = outBeg;
uint_fast32_t r = 0;
uint_fast8_t ri = 0;
uint_fast16_t error = 0;
#define accumulate(ch) \
do \
{ \
const auto n = decodeTable[ch & 0x7f]; \
\
error |= (ch | n) & 0xff80; \
\
if ((n & 0b01000000) == 0) \
{ \
r = r << 6 | n; \
ri++; \
} \
} while (0)
while (in < inEndBatched)
{
return false;
}
mbStr.reserve(len);
const auto a = in[0];
const auto b = in[1];
const auto c = in[2];
const auto d = in[3];
auto iter = src.cbegin();
while (iter < src.cend())
{
if (s_IsSpace(*iter)) // Skip whitespace anywhere.
{
iter++;
continue;
}
accumulate(a);
accumulate(b);
accumulate(c);
accumulate(d);
if (*iter == padChar)
switch (ri)
{
break;
}
auto pos = strchr(base64Chars, *iter);
if (!pos) // A non-base64 character found.
{
return false;
}
switch (state)
{
case 0:
tmp = (char)(pos - base64Chars) << 2;
state = 1;
break;
case 1:
tmp |= (char)(pos - base64Chars) >> 4;
mbStr += tmp;
tmp = (char)((pos - base64Chars) & 0x0f) << 4;
state = 2;
break;
case 2:
tmp |= (char)(pos - base64Chars) >> 2;
mbStr += tmp;
tmp = (char)((pos - base64Chars) & 0x03) << 6;
state = 3;
out[0] = uint8_t(r >> 4);
out += 1;
ri = 1;
break;
case 3:
tmp |= pos - base64Chars;
mbStr += tmp;
state = 0;
out[0] = uint8_t(r >> 10);
out[1] = uint8_t(r >> 2);
out += 2;
ri = 1;
break;
default:
case 4:
out[0] = uint8_t(r >> 16);
out[1] = uint8_t(r >> 8);
out[2] = uint8_t(r >> 0);
out += 3;
ri = 0;
break;
}
iter++;
in += 4;
}
if (iter < src.cend()) // Padding char is met.
for (size_t i = 0, remaining = inEnd - in; i < remaining; i++)
{
iter++;
switch (state)
{
// Invalid when state is 0 or 1.
case 0:
case 1:
return false;
case 2:
// Skip any number of spaces.
while (iter < src.cend() && s_IsSpace(*iter))
{
iter++;
}
// Make sure there is another trailing padding character.
if (iter == src.cend() || *iter != padChar)
{
return false;
}
iter++; // Skip the padding character and fallthrough to "single trailing padding character" case.
[[fallthrough]];
case 3:
while (iter < src.cend())
{
if (!s_IsSpace(*iter))
{
return false;
}
iter++;
}
break;
default:
break;
}
const auto ch = in[i];
accumulate(ch);
}
else if (state != 0) // When no padding, we must be in state 0.
switch (ri)
{
return false;
case 2:
out[0] = uint8_t(r >> 4);
break;
case 3:
out[0] = uint8_t(r >> 10);
out[1] = uint8_t(r >> 2);
break;
case 4:
out[0] = uint8_t(r >> 16);
out[1] = uint8_t(r >> 8);
out[2] = uint8_t(r >> 0);
break;
}
return SUCCEEDED(til::u8u16(mbStr, dst));
}
if (error)
{
throw std::runtime_error("invalid base64");
}
// Routine Description:
// - Check if parameter is a base64 whitespace. Only carriage return or line feed
// is valid whitespace.
// Arguments:
// - ch - Character to check.
// Return Value:
// - true iff ch is a carriage return or line feed.
constexpr bool Base64::s_IsSpace(const wchar_t ch) noexcept
{
return ch == L'\r' || ch == L'\n';
result.resize(out - outBeg);
til::u8u16(result, dst);
}

View file

@ -16,10 +16,6 @@ namespace Microsoft::Console::VirtualTerminal
class Base64
{
public:
static std::wstring s_Encode(const std::wstring_view src) noexcept;
static bool s_Decode(const std::wstring_view src, std::wstring& dst) noexcept;
private:
static constexpr bool s_IsSpace(const wchar_t ch) noexcept;
static void s_Decode(const std::wstring_view src, std::wstring& dst);
};
}

View file

@ -28,15 +28,6 @@ class Microsoft::Console::VirtualTerminal::Base64Test
{
TEST_CLASS(Base64Test);
TEST_METHOD(TestBase64Encode)
{
VERIFY_ARE_EQUAL(L"Zm9v", Base64::s_Encode(L"foo"));
VERIFY_ARE_EQUAL(L"Zm9vYg==", Base64::s_Encode(L"foob"));
VERIFY_ARE_EQUAL(L"Zm9vYmE=", Base64::s_Encode(L"fooba"));
VERIFY_ARE_EQUAL(L"Zm9vYmFy", Base64::s_Encode(L"foobar"));
VERIFY_ARE_EQUAL(L"Zm9vYmFyDQo=", Base64::s_Encode(L"foobar\r\n"));
}
TEST_METHOD(TestBase64Decode)
{
std::wstring result;

View file

@ -4,6 +4,8 @@
#include "precomp.h"
#include "WexTestClass.h"
#include <til/spsc.h>
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;

View file

@ -4,6 +4,8 @@
#include "precomp.h"
#include "WexTestClass.h"
#include <til/u8u16convert.h>
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
@ -43,8 +45,7 @@ void Utf8Utf16ConvertTests::TestU8ToU16()
};
std::wstring u16Out{};
const HRESULT hRes{ til::u8u16(u8String, u16Out) };
VERIFY_ARE_EQUAL(S_OK, hRes);
til::u8u16(u8String, u16Out);
VERIFY_ARE_EQUAL(u16StringComp, u16Out);
}
@ -72,8 +73,7 @@ void Utf8Utf16ConvertTests::TestU16ToU8()
};
std::string u8Out{};
const HRESULT hRes{ til::u16u8(u16String, u8Out) };
VERIFY_ARE_EQUAL(S_OK, hRes);
til::u16u8(u16String, u8Out);
VERIFY_ARE_EQUAL(u8StringComp, u8Out);
}
@ -115,23 +115,19 @@ void Utf8Utf16ConvertTests::TestU8ToU16Partials()
til::u8state state{};
std::wstring u16Out1{};
const HRESULT hRes1{ til::u8u16(u8String1, u16Out1, state) };
VERIFY_ARE_EQUAL(S_OK, hRes1);
til::u8u16(u8String1, u16Out1, state);
VERIFY_ARE_EQUAL(u16StringComp1, u16Out1);
std::wstring u16Out2{};
const HRESULT hRes2{ til::u8u16(u8String2, u16Out2, state) };
VERIFY_ARE_EQUAL(S_OK, hRes2);
til::u8u16(u8String2, u16Out2, state);
VERIFY_ARE_EQUAL(u16StringComp1, u16Out2);
std::wstring u16Out3{};
const HRESULT hRes3{ til::u8u16(u8String3, u16Out3, state) };
VERIFY_ARE_EQUAL(S_OK, hRes3);
til::u8u16(u8String3, u16Out3, state);
VERIFY_ARE_EQUAL(std::wstring{}, u16Out3);
std::wstring u16Out4{};
const HRESULT hRes4{ til::u8u16(u8String4, u16Out4, state) };
VERIFY_ARE_EQUAL(S_OK, hRes4);
til::u8u16(u8String4, u16Out4, state);
VERIFY_ARE_EQUAL(u16StringComp2, u16Out4);
}
@ -157,13 +153,11 @@ void Utf8Utf16ConvertTests::TestU16ToU8Partials()
til::u16state state{};
std::string u8Out1{};
const HRESULT hRes1{ til::u16u8(u16String1, u8Out1, state) };
VERIFY_ARE_EQUAL(S_OK, hRes1);
til::u16u8(u16String1, u8Out1, state);
VERIFY_ARE_EQUAL(u8StringComp, u8Out1);
std::string u8Out2{};
const HRESULT hRes2{ til::u16u8(u16String2, u8Out2, state) };
VERIFY_ARE_EQUAL(S_OK, hRes2);
til::u16u8(u16String2, u8Out2, state);
VERIFY_ARE_EQUAL(u8StringComp, u8Out2);
}
@ -182,12 +176,12 @@ void Utf8Utf16ConvertTests::TestU8ToU16OneByOne()
til::u8state state{};
std::wstring u16Out1{};
VERIFY_SUCCEEDED(til::u8u16(u8String1_1, u16Out1, state));
til::u8u16(u8String1_1, u16Out1, state);
VERIFY_ARE_EQUAL(L"", u16Out1); // There should be no output for the first three bytes
VERIFY_SUCCEEDED(til::u8u16(u8String1_2, u16Out1, state));
til::u8u16(u8String1_2, u16Out1, state);
VERIFY_ARE_EQUAL(L"", u16Out1);
VERIFY_SUCCEEDED(til::u8u16(u8String1_3, u16Out1, state));
til::u8u16(u8String1_3, u16Out1, state);
VERIFY_ARE_EQUAL(L"", u16Out1);
VERIFY_SUCCEEDED(til::u8u16(u8String1_4, u16Out1, state));
til::u8u16(u8String1_4, u16Out1, state);
VERIFY_ARE_EQUAL(u16StringComp1, u16Out1);
}