Compare commits
21 commits
main
...
dev/duhowe
Author | SHA1 | Date | |
---|---|---|---|
5bdb5e6caa | |||
d2f0f50651 | |||
271132edb4 | |||
7c5b39a041 | |||
34035509a9 | |||
a13f6237ff | |||
97702a1d9d | |||
d4cdbc9b91 | |||
596a8155ca | |||
58ad0a34fc | |||
8f4c4f4916 | |||
8133f2856d | |||
f64660ac2f | |||
b1a981daa9 | |||
7d8df11ede | |||
89fde46a94 | |||
00af538278 | |||
227ce8ff20 | |||
99d9bac51d | |||
97a2dc6878 | |||
abf66b2ff8 |
|
@ -31,6 +31,8 @@ public:
|
|||
using const_iterator = rle_vector::const_iterator;
|
||||
|
||||
ATTR_ROW(uint16_t width, TextAttribute attr);
|
||||
ATTR_ROW(rle_vector&& v) :
|
||||
_data(std::move(v)) {}
|
||||
|
||||
~ATTR_ROW() = default;
|
||||
|
||||
|
@ -57,11 +59,11 @@ public:
|
|||
friend bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept;
|
||||
friend class ROW;
|
||||
|
||||
rle_vector _data;
|
||||
|
||||
private:
|
||||
void Reset(const TextAttribute attr);
|
||||
|
||||
rle_vector _data;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class CommonState;
|
||||
#endif
|
||||
|
|
|
@ -1,281 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "CharRow.hpp"
|
||||
#include "unicode.hpp"
|
||||
#include "Row.hpp"
|
||||
|
||||
// Routine Description:
|
||||
// - constructor
|
||||
// Arguments:
|
||||
// - rowWidth - the size (in wchar_t) of the char and attribute rows
|
||||
// - pParent - the parent ROW
|
||||
// Return Value:
|
||||
// - instantiated object
|
||||
// Note: will through if unable to allocate char/attribute buffers
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26447) // small_vector's constructor says it can throw but it should not given how we use it. This suppresses this error for the AuditMode build.
|
||||
CharRow::CharRow(size_t rowWidth, ROW* const pParent) noexcept :
|
||||
_data(rowWidth, value_type()),
|
||||
_pParent{ FAIL_FAST_IF_NULL(pParent) }
|
||||
{
|
||||
}
|
||||
#pragma warning(pop)
|
||||
|
||||
// Routine Description:
|
||||
// - gets the size of the row, in glyph cells
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the size of the row
|
||||
size_t CharRow::size() const noexcept
|
||||
{
|
||||
return _data.size();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Sets all properties of the CharRowBase to default values
|
||||
// Arguments:
|
||||
// - sRowWidth - The width of the row.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CharRow::Reset() noexcept
|
||||
{
|
||||
for (auto& cell : _data)
|
||||
{
|
||||
cell.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - resizes the width of the CharRowBase
|
||||
// Arguments:
|
||||
// - newSize - the new width of the character and attributes rows
|
||||
// Return Value:
|
||||
// - S_OK on success, otherwise relevant error code
|
||||
[[nodiscard]] HRESULT CharRow::Resize(const 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();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Inspects the current internal string to find the left edge of it
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - 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())
|
||||
{
|
||||
++it;
|
||||
}
|
||||
return it - _data.cbegin();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Inspects the current internal string to find the right edge of it
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - 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())
|
||||
{
|
||||
++it;
|
||||
}
|
||||
return _data.crend() - it;
|
||||
}
|
||||
|
||||
void CharRow::ClearCell(const size_t column)
|
||||
{
|
||||
_data.at(column).Reset();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Tells you whether or not this row contains any valid text.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - True if there is valid text in this row. False otherwise.
|
||||
bool CharRow::ContainsText() const noexcept
|
||||
{
|
||||
for (const value_type& cell : _data)
|
||||
{
|
||||
if (!cell.IsSpace())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - gets the attribute at the specified column
|
||||
// Arguments:
|
||||
// - column - the column to get the attribute for
|
||||
// Return Value:
|
||||
// - the attribute
|
||||
// Note: will throw exception if column is out of bounds
|
||||
const DbcsAttribute& CharRow::DbcsAttrAt(const size_t column) const
|
||||
{
|
||||
return _data.at(column).DbcsAttr();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - gets the attribute at the specified column
|
||||
// Arguments:
|
||||
// - column - the column to get the attribute for
|
||||
// Return Value:
|
||||
// - the attribute
|
||||
// Note: will throw exception if column is out of bounds
|
||||
DbcsAttribute& CharRow::DbcsAttrAt(const size_t column)
|
||||
{
|
||||
return _data.at(column).DbcsAttr();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - resets text data at column
|
||||
// Arguments:
|
||||
// - column - column index to clear text data from
|
||||
// Return Value:
|
||||
// - <none>
|
||||
// Note: will throw exception if column is out of bounds
|
||||
void CharRow::ClearGlyph(const size_t column)
|
||||
{
|
||||
_data.at(column).EraseChars();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - returns text data at column as a const reference.
|
||||
// Arguments:
|
||||
// - column - column to get text data for
|
||||
// Return Value:
|
||||
// - text data at column
|
||||
// - Note: will throw exception if column is out of bounds
|
||||
const CharRow::reference CharRow::GlyphAt(const size_t column) const
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, column >= _data.size());
|
||||
return { const_cast<CharRow&>(*this), column };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - returns text data at column as a reference.
|
||||
// Arguments:
|
||||
// - column - column to get text data for
|
||||
// Return Value:
|
||||
// - text data at column
|
||||
// - Note: will throw exception if column is out of bounds
|
||||
CharRow::reference CharRow::GlyphAt(const size_t column)
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, column >= _data.size());
|
||||
return { *this, column };
|
||||
}
|
||||
|
||||
std::wstring CharRow::GetText() const
|
||||
{
|
||||
std::wstring wstr;
|
||||
wstr.reserve(_data.size());
|
||||
|
||||
for (size_t i = 0; i < _data.size(); ++i)
|
||||
{
|
||||
const auto glyph = GlyphAt(i);
|
||||
if (!DbcsAttrAt(i).IsTrailing())
|
||||
{
|
||||
for (const auto wch : glyph)
|
||||
{
|
||||
wstr.push_back(wch);
|
||||
}
|
||||
}
|
||||
}
|
||||
return wstr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - get delimiter class for a position in the char row
|
||||
// - used for double click selection and uia word navigation
|
||||
// Arguments:
|
||||
// - column: column to get text data for
|
||||
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
|
||||
// Return Value:
|
||||
// - the delimiter class for the given char
|
||||
const DelimiterClass CharRow::DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, column >= _data.size());
|
||||
|
||||
const auto glyph = *GlyphAt(column).begin();
|
||||
if (glyph <= UNICODE_SPACE)
|
||||
{
|
||||
return DelimiterClass::ControlChar;
|
||||
}
|
||||
else if (wordDelimiters.find(glyph) != std::wstring_view::npos)
|
||||
{
|
||||
return DelimiterClass::DelimiterChar;
|
||||
}
|
||||
else
|
||||
{
|
||||
return DelimiterClass::RegularChar;
|
||||
}
|
||||
}
|
||||
|
||||
UnicodeStorage& CharRow::GetUnicodeStorage() noexcept
|
||||
{
|
||||
return _pParent->GetUnicodeStorage();
|
||||
}
|
||||
|
||||
const UnicodeStorage& CharRow::GetUnicodeStorage() const noexcept
|
||||
{
|
||||
return _pParent->GetUnicodeStorage();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - calculates the key used by the given column of the char row to store glyph data in UnicodeStorage
|
||||
// Arguments:
|
||||
// - column - the column to generate the key for
|
||||
// Return Value:
|
||||
// - the COORD key for data access from UnicodeStorage for the column
|
||||
COORD CharRow::GetStorageKey(const size_t column) const noexcept
|
||||
{
|
||||
return { gsl::narrow<SHORT>(column), _pParent->GetId() };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Updates the pointer to the parent row (which might change if we shuffle the rows around)
|
||||
// Arguments:
|
||||
// - pParent - Pointer to the parent row
|
||||
void CharRow::UpdateParent(ROW* const pParent)
|
||||
{
|
||||
_pParent = FAIL_FAST_IF_NULL(pParent);
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- CharRow.hpp
|
||||
|
||||
Abstract:
|
||||
- contains data structure for UCS2 encoded character data of a row
|
||||
|
||||
Author(s):
|
||||
- Michael Niksa (miniksa) 10-Apr-2014
|
||||
- Paul Campbell (paulcam) 10-Apr-2014
|
||||
|
||||
Revision History:
|
||||
- From components of output.h/.c
|
||||
by Therese Stowell (ThereseS) 1990-1991
|
||||
- Pulled into its own file from textBuffer.hpp/cpp (AustDi, 2017)
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DbcsAttribute.hpp"
|
||||
#include "CharRowCellReference.hpp"
|
||||
#include "CharRowCell.hpp"
|
||||
#include "UnicodeStorage.hpp"
|
||||
|
||||
class ROW;
|
||||
|
||||
enum class DelimiterClass
|
||||
{
|
||||
ControlChar,
|
||||
DelimiterChar,
|
||||
RegularChar
|
||||
};
|
||||
|
||||
// the characters of one row of screen buffer
|
||||
// we keep the following values so that we don't write
|
||||
// more pixels to the screen than we have to:
|
||||
// left is initialized to screenbuffer width. right is
|
||||
// initialized to zero.
|
||||
//
|
||||
// [ foo.bar 12-12-61 ]
|
||||
// ^ ^ ^ ^
|
||||
// | | | |
|
||||
// Chars Left Right end of Chars buffer
|
||||
class CharRow final
|
||||
{
|
||||
public:
|
||||
using glyph_type = typename wchar_t;
|
||||
using value_type = typename CharRowCell;
|
||||
using iterator = typename boost::container::small_vector_base<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;
|
||||
|
||||
size_t size() const noexcept;
|
||||
[[nodiscard]] HRESULT Resize(const size_t newSize) noexcept;
|
||||
size_t MeasureLeft() const noexcept;
|
||||
size_t MeasureRight() const;
|
||||
bool ContainsText() const noexcept;
|
||||
const DbcsAttribute& DbcsAttrAt(const size_t column) const;
|
||||
DbcsAttribute& DbcsAttrAt(const size_t column);
|
||||
void ClearGlyph(const size_t column);
|
||||
|
||||
const DelimiterClass DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const;
|
||||
|
||||
// working with glyphs
|
||||
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(); }
|
||||
|
||||
iterator end() noexcept;
|
||||
const_iterator cend() const noexcept;
|
||||
const_iterator end() const noexcept { return cend(); }
|
||||
|
||||
UnicodeStorage& GetUnicodeStorage() noexcept;
|
||||
const UnicodeStorage& GetUnicodeStorage() const noexcept;
|
||||
COORD GetStorageKey(const size_t column) const noexcept;
|
||||
|
||||
void UpdateParent(ROW* const pParent);
|
||||
|
||||
friend CharRowCellReference;
|
||||
friend class ROW;
|
||||
|
||||
private:
|
||||
void Reset() noexcept;
|
||||
void ClearCell(const size_t column);
|
||||
std::wstring GetText() const;
|
||||
|
||||
protected:
|
||||
// storage for glyph data and dbcs attributes
|
||||
boost::container::small_vector<value_type, 120> _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)
|
||||
{
|
||||
std::transform(startChars,
|
||||
endChars,
|
||||
startAttrs,
|
||||
outIt,
|
||||
[](const wchar_t wch, const DbcsAttribute attr) {
|
||||
return CharRow::value_type{ wch, attr };
|
||||
});
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
#include "precomp.h"
|
||||
|
||||
#include "CharRowCell.hpp"
|
||||
#include "unicode.hpp"
|
||||
|
||||
// default glyph value, used for resetting the character data portion of a cell
|
||||
static constexpr wchar_t DefaultValue = UNICODE_SPACE;
|
||||
|
||||
// Routine Description:
|
||||
// - "erases" the glyph. really sets it back to the default "empty" value
|
||||
void CharRowCell::EraseChars() noexcept
|
||||
{
|
||||
if (_attr.IsGlyphStored())
|
||||
{
|
||||
_attr.SetGlyphStored(false);
|
||||
}
|
||||
_wch = DefaultValue;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - resets this object back to the defaults it would have from the default constructor
|
||||
void CharRowCell::Reset() noexcept
|
||||
{
|
||||
_attr.Reset();
|
||||
_wch = DefaultValue;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - checks if cell contains a space glyph
|
||||
// Return Value:
|
||||
// - true if cell contains a space glyph, false otherwise
|
||||
bool CharRowCell::IsSpace() const noexcept
|
||||
{
|
||||
return !_attr.IsGlyphStored() && _wch == UNICODE_SPACE;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Access the DbcsAttribute for the cell
|
||||
// Return Value:
|
||||
// - ref to the cells' DbcsAttribute
|
||||
DbcsAttribute& CharRowCell::DbcsAttr() noexcept
|
||||
{
|
||||
return _attr;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Access the DbcsAttribute for the cell
|
||||
// Return Value:
|
||||
// - ref to the cells' DbcsAttribute
|
||||
const DbcsAttribute& CharRowCell::DbcsAttr() const noexcept
|
||||
{
|
||||
return _attr;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Access the cell's wchar field. this does not access any char data through UnicodeStorage.
|
||||
// Return Value:
|
||||
// - the cell's wchar field
|
||||
wchar_t& CharRowCell::Char() noexcept
|
||||
{
|
||||
return _wch;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Access the cell's wchar field. this does not access any char data through UnicodeStorage.
|
||||
// Return Value:
|
||||
// - the cell's wchar field
|
||||
const wchar_t& CharRowCell::Char() const noexcept
|
||||
{
|
||||
return _wch;
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- CharRowCell.hpp
|
||||
|
||||
Abstract:
|
||||
- data structure for one cell of a char row. contains the char data for one
|
||||
coordinate position in the output buffer (leading/trailing information and
|
||||
the char itself.
|
||||
|
||||
Author(s):
|
||||
- Austin Diviness (AustDi) 02-May-2018
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DbcsAttribute.hpp"
|
||||
#include "unicode.hpp"
|
||||
|
||||
#if (defined(_M_IX86) || defined(_M_AMD64))
|
||||
// currently CharRowCell's fields use 3 bytes of memory, leaving the 4th byte in unused. this leads
|
||||
// to a rather large amount of useless memory allocated. so instead, pack CharRowCell by bytes instead of words.
|
||||
#pragma pack(push, 1)
|
||||
#endif
|
||||
|
||||
class CharRowCell final
|
||||
{
|
||||
public:
|
||||
CharRowCell() noexcept = default;
|
||||
CharRowCell(const wchar_t wch, const DbcsAttribute attr) noexcept
|
||||
:
|
||||
_wch(wch),
|
||||
_attr(attr)
|
||||
{
|
||||
}
|
||||
|
||||
void EraseChars() noexcept;
|
||||
void Reset() noexcept;
|
||||
|
||||
bool IsSpace() const noexcept;
|
||||
|
||||
DbcsAttribute& DbcsAttr() noexcept;
|
||||
const DbcsAttribute& DbcsAttr() const noexcept;
|
||||
|
||||
wchar_t& Char() noexcept;
|
||||
const wchar_t& Char() const noexcept;
|
||||
|
||||
friend constexpr bool operator==(const CharRowCell& a, const CharRowCell& b) noexcept;
|
||||
|
||||
private:
|
||||
wchar_t _wch{ UNICODE_SPACE };
|
||||
DbcsAttribute _attr{};
|
||||
};
|
||||
|
||||
#if (defined(_M_IX86) || defined(_M_AMD64))
|
||||
#pragma pack(pop)
|
||||
#endif
|
||||
|
||||
constexpr bool operator==(const CharRowCell& a, const CharRowCell& b) noexcept
|
||||
{
|
||||
return (a._wch == b._wch &&
|
||||
a._attr == b._attr);
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "UnicodeStorage.hpp"
|
||||
#include "CharRow.hpp"
|
||||
|
||||
// Routine Description:
|
||||
// - assignment operator. will store extended glyph data in a separate storage location
|
||||
// Arguments:
|
||||
// - chars - the glyph data to store
|
||||
void CharRowCellReference::operator=(const std::wstring_view chars)
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, chars.empty());
|
||||
if (chars.size() == 1)
|
||||
{
|
||||
_cellData().Char() = chars.front();
|
||||
_cellData().DbcsAttr().SetGlyphStored(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& storage = _parent.GetUnicodeStorage();
|
||||
const auto key = _parent.GetStorageKey(_index);
|
||||
storage.StoreGlyph(key, { chars.cbegin(), chars.cend() });
|
||||
_cellData().DbcsAttr().SetGlyphStored(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - implicit conversion to vector<wchar_t> operator.
|
||||
// Return Value:
|
||||
// - std::vector<wchar_t> of the glyph data in the referenced cell
|
||||
CharRowCellReference::operator std::wstring_view() const
|
||||
{
|
||||
return _glyphData();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - The CharRowCell this object "references"
|
||||
// Return Value:
|
||||
// - ref to the CharRowCell
|
||||
CharRowCell& CharRowCellReference::_cellData()
|
||||
{
|
||||
return _parent._data.at(_index);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - The CharRowCell this object "references"
|
||||
// Return Value:
|
||||
// - ref to the CharRowCell
|
||||
const CharRowCell& CharRowCellReference::_cellData() const
|
||||
{
|
||||
return _parent._data.at(_index);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - the glyph data of the referenced cell
|
||||
// Return Value:
|
||||
// - the glyph data
|
||||
std::wstring_view CharRowCellReference::_glyphData() const
|
||||
{
|
||||
if (_cellData().DbcsAttr().IsGlyphStored())
|
||||
{
|
||||
const auto& text = _parent.GetUnicodeStorage().GetText(_parent.GetStorageKey(_index));
|
||||
|
||||
return { text.data(), text.size() };
|
||||
}
|
||||
else
|
||||
{
|
||||
return { &_cellData().Char(), 1 };
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - gets read-only iterator to the beginning of the glyph data
|
||||
// Return Value:
|
||||
// - iterator of the glyph data
|
||||
CharRowCellReference::const_iterator CharRowCellReference::begin() const
|
||||
{
|
||||
if (_cellData().DbcsAttr().IsGlyphStored())
|
||||
{
|
||||
return _parent.GetUnicodeStorage().GetText(_parent.GetStorageKey(_index)).data();
|
||||
}
|
||||
else
|
||||
{
|
||||
return &_cellData().Char();
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - get read-only iterator to the end of the glyph data
|
||||
// Return Value:
|
||||
// - end iterator of the glyph data
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26481)
|
||||
// TODO GH 2672: eliminate using pointers raw as begin/end markers in this class
|
||||
CharRowCellReference::const_iterator CharRowCellReference::end() const
|
||||
{
|
||||
if (_cellData().DbcsAttr().IsGlyphStored())
|
||||
{
|
||||
const auto& chars = _parent.GetUnicodeStorage().GetText(_parent.GetStorageKey(_index));
|
||||
return chars.data() + chars.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
return &_cellData().Char() + 1;
|
||||
}
|
||||
}
|
||||
#pragma warning(pop)
|
||||
|
||||
bool operator==(const CharRowCellReference& ref, const std::vector<wchar_t>& glyph)
|
||||
{
|
||||
const DbcsAttribute& dbcsAttr = ref._cellData().DbcsAttr();
|
||||
if (glyph.size() == 1 && dbcsAttr.IsGlyphStored())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (glyph.size() > 1 && !dbcsAttr.IsGlyphStored())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (glyph.size() == 1 && !dbcsAttr.IsGlyphStored())
|
||||
{
|
||||
return ref._cellData().Char() == glyph.front();
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& chars = ref._parent.GetUnicodeStorage().GetText(ref._parent.GetStorageKey(ref._index));
|
||||
return chars == glyph;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const std::vector<wchar_t>& glyph, const CharRowCellReference& ref)
|
||||
{
|
||||
return ref == glyph;
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- CharRowCellReference.hpp
|
||||
|
||||
Abstract:
|
||||
- reference class for the glyph data of a char row cell
|
||||
|
||||
Author(s):
|
||||
- Austin Diviness (AustDi) 02-May-2018
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DbcsAttribute.hpp"
|
||||
#include "CharRowCell.hpp"
|
||||
#include <utility>
|
||||
|
||||
class CharRow;
|
||||
|
||||
class CharRowCellReference final
|
||||
{
|
||||
public:
|
||||
using const_iterator = const wchar_t*;
|
||||
|
||||
CharRowCellReference(CharRow& parent, const size_t index) noexcept :
|
||||
_parent{ parent },
|
||||
_index{ index }
|
||||
{
|
||||
}
|
||||
|
||||
~CharRowCellReference() = default;
|
||||
CharRowCellReference(const CharRowCellReference&) noexcept = default;
|
||||
CharRowCellReference(CharRowCellReference&&) noexcept = default;
|
||||
|
||||
void operator=(const CharRowCellReference&) = delete;
|
||||
void operator=(CharRowCellReference&&) = delete;
|
||||
|
||||
void operator=(const std::wstring_view chars);
|
||||
operator std::wstring_view() const;
|
||||
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
|
||||
friend bool operator==(const CharRowCellReference& ref, const std::vector<wchar_t>& glyph);
|
||||
friend bool operator==(const std::vector<wchar_t>& glyph, const CharRowCellReference& ref);
|
||||
|
||||
private:
|
||||
// what char row the object belongs to
|
||||
CharRow& _parent;
|
||||
// the index of the cell in the parent char row
|
||||
const size_t _index;
|
||||
|
||||
CharRowCell& _cellData();
|
||||
const CharRowCell& _cellData() const;
|
||||
|
||||
std::wstring_view _glyphData() const;
|
||||
};
|
||||
|
||||
bool operator==(const CharRowCellReference& ref, const std::vector<wchar_t>& glyph);
|
||||
bool operator==(const std::vector<wchar_t>& glyph, const CharRowCellReference& ref);
|
|
@ -27,14 +27,12 @@ public:
|
|||
};
|
||||
|
||||
DbcsAttribute() noexcept :
|
||||
_attribute{ Attribute::Single },
|
||||
_glyphStored{ false }
|
||||
_attribute{ Attribute::Single }
|
||||
{
|
||||
}
|
||||
|
||||
DbcsAttribute(const Attribute attribute) noexcept :
|
||||
_attribute{ attribute },
|
||||
_glyphStored{ false }
|
||||
_attribute{ attribute }
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -58,16 +56,6 @@ public:
|
|||
return IsLeading() || IsTrailing();
|
||||
}
|
||||
|
||||
constexpr bool IsGlyphStored() const noexcept
|
||||
{
|
||||
return _glyphStored;
|
||||
}
|
||||
|
||||
void SetGlyphStored(const bool stored) noexcept
|
||||
{
|
||||
_glyphStored = stored;
|
||||
}
|
||||
|
||||
void SetSingle() noexcept
|
||||
{
|
||||
_attribute = Attribute::Single;
|
||||
|
@ -86,7 +74,6 @@ public:
|
|||
void Reset() noexcept
|
||||
{
|
||||
SetSingle();
|
||||
SetGlyphStored(false);
|
||||
}
|
||||
|
||||
WORD GeneratePublicApiAttributeFormat() const noexcept
|
||||
|
@ -127,7 +114,6 @@ public:
|
|||
|
||||
private:
|
||||
Attribute _attribute : 2;
|
||||
bool _glyphStored : 1;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class TextBufferTests;
|
||||
|
|
|
@ -1,546 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "OutputCellIterator.hpp"
|
||||
|
||||
#include "../../types/inc/convert.hpp"
|
||||
#include "../../types/inc/Utf16Parser.hpp"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
|
||||
static constexpr TextAttribute InvalidTextAttribute{ INVALID_COLOR, INVALID_COLOR };
|
||||
|
||||
// Routine Description:
|
||||
// - This is a fill-mode iterator for one particular wchar. It will repeat forever if fillLimit is 0.
|
||||
// Arguments:
|
||||
// - wch - The character to use for filling
|
||||
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
|
||||
OutputCellIterator::OutputCellIterator(const wchar_t& wch, const size_t fillLimit) noexcept :
|
||||
_mode(Mode::Fill),
|
||||
_currentView(s_GenerateView(wch)),
|
||||
_run(),
|
||||
_attr(InvalidTextAttribute),
|
||||
_pos(0),
|
||||
_distance(0),
|
||||
_fillLimit(fillLimit)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is a fill-mode iterator for one particular color. It will repeat forever if fillLimit is 0.
|
||||
// Arguments:
|
||||
// - attr - The color attribute to use for filling
|
||||
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
|
||||
OutputCellIterator::OutputCellIterator(const TextAttribute& attr, const size_t fillLimit) noexcept :
|
||||
_mode(Mode::Fill),
|
||||
_currentView(s_GenerateView(attr)),
|
||||
_run(),
|
||||
_attr(InvalidTextAttribute),
|
||||
_pos(0),
|
||||
_distance(0),
|
||||
_fillLimit(fillLimit)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is a fill-mode iterator for one particular character and color. It will repeat forever if fillLimit is 0.
|
||||
// Arguments:
|
||||
// - wch - The character to use for filling
|
||||
// - attr - The color attribute to use for filling
|
||||
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
|
||||
OutputCellIterator::OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit) noexcept :
|
||||
_mode(Mode::Fill),
|
||||
_currentView(s_GenerateView(wch, attr)),
|
||||
_run(),
|
||||
_attr(InvalidTextAttribute),
|
||||
_pos(0),
|
||||
_distance(0),
|
||||
_fillLimit(fillLimit)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is a fill-mode iterator for one particular CHAR_INFO. It will repeat forever if fillLimit is 0.
|
||||
// Arguments:
|
||||
// - charInfo - The legacy character and color data to use for filling (uses Unicode portion of text data)
|
||||
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
|
||||
OutputCellIterator::OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit) noexcept :
|
||||
_mode(Mode::Fill),
|
||||
_currentView(s_GenerateView(charInfo)),
|
||||
_run(),
|
||||
_attr(InvalidTextAttribute),
|
||||
_pos(0),
|
||||
_distance(0),
|
||||
_fillLimit(fillLimit)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over a range of text only. No color data will be modified as the text is inserted.
|
||||
// Arguments:
|
||||
// - utf16Text - UTF-16 text range
|
||||
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) :
|
||||
_mode(Mode::LooseTextOnly),
|
||||
_currentView(s_GenerateView(utf16Text)),
|
||||
_run(utf16Text),
|
||||
_attr(InvalidTextAttribute),
|
||||
_pos(0),
|
||||
_distance(0),
|
||||
_fillLimit(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over a range text that will apply the same color to every position.
|
||||
// Arguments:
|
||||
// - utf16Text - UTF-16 text range
|
||||
// - attribute - Color to apply over the entire range
|
||||
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute attribute) :
|
||||
_mode(Mode::Loose),
|
||||
_currentView(s_GenerateView(utf16Text, attribute)),
|
||||
_run(utf16Text),
|
||||
_attr(attribute),
|
||||
_distance(0),
|
||||
_pos(0),
|
||||
_fillLimit(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over legacy colors only. The text is not modified.
|
||||
// Arguments:
|
||||
// - legacyAttrs - One legacy color item per cell
|
||||
OutputCellIterator::OutputCellIterator(const gsl::span<const WORD> legacyAttrs) noexcept :
|
||||
_mode(Mode::LegacyAttr),
|
||||
_currentView(s_GenerateViewLegacyAttr(til::at(legacyAttrs, 0))),
|
||||
_run(legacyAttrs),
|
||||
_attr(InvalidTextAttribute),
|
||||
_distance(0),
|
||||
_pos(0),
|
||||
_fillLimit(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over legacy cell data. We will use the unicode text and the legacy color attribute.
|
||||
// Arguments:
|
||||
// - charInfos - Multiple cell with unicode text and legacy color data.
|
||||
OutputCellIterator::OutputCellIterator(const gsl::span<const CHAR_INFO> charInfos) noexcept :
|
||||
_mode(Mode::CharInfo),
|
||||
_currentView(s_GenerateView(til::at(charInfos, 0))),
|
||||
_run(charInfos),
|
||||
_attr(InvalidTextAttribute),
|
||||
_distance(0),
|
||||
_pos(0),
|
||||
_fillLimit(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over existing OutputCells with full text and color data.
|
||||
// Arguments:
|
||||
// - cells - Multiple cells in a run
|
||||
OutputCellIterator::OutputCellIterator(const gsl::span<const OutputCell> cells) :
|
||||
_mode(Mode::Cell),
|
||||
_currentView(s_GenerateView(til::at(cells, 0))),
|
||||
_run(cells),
|
||||
_attr(InvalidTextAttribute),
|
||||
_distance(0),
|
||||
_pos(0),
|
||||
_fillLimit(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Specifies whether this iterator is valid for dereferencing (still valid underlying data)
|
||||
// Return Value:
|
||||
// - True if the views on dereference are valid. False if it shouldn't be dereferenced.
|
||||
OutputCellIterator::operator bool() const noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (_mode)
|
||||
{
|
||||
case Mode::Loose:
|
||||
case Mode::LooseTextOnly:
|
||||
{
|
||||
// In lieu of using start and end, this custom iterator type simply becomes bool false
|
||||
// when we run out of items to iterate over.
|
||||
return _pos < std::get<std::wstring_view>(_run).length();
|
||||
}
|
||||
case Mode::Fill:
|
||||
{
|
||||
if (_fillLimit > 0)
|
||||
{
|
||||
return _pos < _fillLimit;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case Mode::Cell:
|
||||
{
|
||||
return _pos < std::get<gsl::span<const OutputCell>>(_run).size();
|
||||
}
|
||||
case Mode::CharInfo:
|
||||
{
|
||||
return _pos < std::get<gsl::span<const CHAR_INFO>>(_run).size();
|
||||
}
|
||||
case Mode::LegacyAttr:
|
||||
{
|
||||
return _pos < std::get<gsl::span<const WORD>>(_run).size();
|
||||
}
|
||||
default:
|
||||
FAIL_FAST_HR(E_NOTIMPL);
|
||||
}
|
||||
}
|
||||
CATCH_FAIL_FAST();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Advances the iterator one position over the underlying data source.
|
||||
// Return Value:
|
||||
// - Reference to self after advancement.
|
||||
OutputCellIterator& OutputCellIterator::operator++()
|
||||
{
|
||||
// Keep track of total distance moved (cells filled)
|
||||
_distance++;
|
||||
|
||||
switch (_mode)
|
||||
{
|
||||
case Mode::Loose:
|
||||
{
|
||||
if (!_TryMoveTrailing())
|
||||
{
|
||||
// When walking through a text sequence, we need to move forward by the number of wchar_ts consumed in the previous view
|
||||
// in case we had a surrogate pair (or wider complex sequence) in the previous view.
|
||||
_pos += _currentView.Chars().size();
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateView(std::get<std::wstring_view>(_run).substr(_pos), _attr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::LooseTextOnly:
|
||||
{
|
||||
if (!_TryMoveTrailing())
|
||||
{
|
||||
// When walking through a text sequence, we need to move forward by the number of wchar_ts consumed in the previous view
|
||||
// in case we had a surrogate pair (or wider complex sequence) in the previous view.
|
||||
_pos += _currentView.Chars().size();
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateView(std::get<std::wstring_view>(_run).substr(_pos));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::Fill:
|
||||
{
|
||||
if (!_TryMoveTrailing())
|
||||
{
|
||||
if (_currentView.DbcsAttr().IsTrailing())
|
||||
{
|
||||
auto dbcsAttr = _currentView.DbcsAttr();
|
||||
dbcsAttr.SetLeading();
|
||||
|
||||
_currentView = OutputCellView(_currentView.Chars(),
|
||||
dbcsAttr,
|
||||
_currentView.TextAttr(),
|
||||
_currentView.TextAttrBehavior());
|
||||
}
|
||||
|
||||
if (_fillLimit > 0)
|
||||
{
|
||||
// We walk forward by one because we fill with the same cell over and over no matter what
|
||||
_pos++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::Cell:
|
||||
{
|
||||
// Walk forward by one because cells are assumed to be in the form they needed to be
|
||||
_pos++;
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateView(til::at(std::get<gsl::span<const OutputCell>>(_run), _pos));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::CharInfo:
|
||||
{
|
||||
// Walk forward by one because charinfos are just the legacy version of cells and prealigned to columns
|
||||
_pos++;
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateView(til::at(std::get<gsl::span<const CHAR_INFO>>(_run), _pos));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::LegacyAttr:
|
||||
{
|
||||
// Walk forward by one because color attributes apply cell by cell (no complex text information)
|
||||
_pos++;
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateViewLegacyAttr(til::at(std::get<gsl::span<const WORD>>(_run), _pos));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
FAIL_FAST_HR(E_NOTIMPL);
|
||||
}
|
||||
|
||||
return (*this);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Advances the iterator one position over the underlying data source.
|
||||
// Return Value:
|
||||
// - Reference to self after advancement.
|
||||
OutputCellIterator OutputCellIterator::operator++(int)
|
||||
{
|
||||
auto temp(*this);
|
||||
operator++();
|
||||
return temp;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Reference the view to fully-formed output cell data representing the underlying data source.
|
||||
// Return Value:
|
||||
// - Reference to the view
|
||||
const OutputCellView& OutputCellIterator::operator*() const noexcept
|
||||
{
|
||||
return _currentView;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Get pointer to the view to fully-formed output cell data representing the underlying data source.
|
||||
// Return Value:
|
||||
// - Pointer to the view
|
||||
const OutputCellView* OutputCellIterator::operator->() const noexcept
|
||||
{
|
||||
return &_currentView;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Checks the current view. If it is a leading half, it updates the current
|
||||
// view to the trailing half of the same glyph.
|
||||
// - This helps us to draw glyphs that are two columns wide by "doubling"
|
||||
// the view that is returned so it will consume two cells.
|
||||
// Return Value:
|
||||
// - True if we just turned a lead half into a trailing half (and caller doesn't
|
||||
// need to further update the view).
|
||||
// - False if this wasn't applicable and the caller should update the view.
|
||||
bool OutputCellIterator::_TryMoveTrailing() noexcept
|
||||
{
|
||||
if (_currentView.DbcsAttr().IsLeading())
|
||||
{
|
||||
auto dbcsAttr = _currentView.DbcsAttr();
|
||||
dbcsAttr.SetTrailing();
|
||||
|
||||
_currentView = OutputCellView(_currentView.Chars(),
|
||||
dbcsAttr,
|
||||
_currentView.TextAttr(),
|
||||
_currentView.TextAttrBehavior());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and specify that the attributes shouldn't be changed.
|
||||
// Arguments:
|
||||
// - view - View representing characters corresponding to a single glyph
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view)
|
||||
{
|
||||
return s_GenerateView(view, InvalidTextAttribute, TextAttributeBehavior::Current);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - view - View representing characters corresponding to a single glyph
|
||||
// - attr - Color attributes to apply to the text
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
|
||||
const TextAttribute attr)
|
||||
{
|
||||
return s_GenerateView(view, attr, TextAttributeBehavior::Stored);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - view - View representing characters corresponding to a single glyph
|
||||
// - attr - Color attributes to apply to the text
|
||||
// - behavior - Behavior of the given text attribute (used when writing)
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
|
||||
const TextAttribute attr,
|
||||
const TextAttributeBehavior behavior)
|
||||
{
|
||||
const auto glyph = Utf16Parser::ParseNext(view);
|
||||
DbcsAttribute dbcsAttr;
|
||||
if (IsGlyphFullWidth(glyph))
|
||||
{
|
||||
dbcsAttr.SetLeading();
|
||||
}
|
||||
|
||||
return OutputCellView(glyph, dbcsAttr, attr, behavior);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - wch - View representing a single UTF-16 character (that can be represented without surrogates)
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch) noexcept
|
||||
{
|
||||
const auto glyph = std::wstring_view(&wch, 1);
|
||||
|
||||
DbcsAttribute dbcsAttr;
|
||||
if (IsGlyphFullWidth(wch))
|
||||
{
|
||||
dbcsAttr.SetLeading();
|
||||
}
|
||||
|
||||
return OutputCellView(glyph, dbcsAttr, InvalidTextAttribute, TextAttributeBehavior::Current);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - attr - View representing a single color
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const TextAttribute& attr) noexcept
|
||||
{
|
||||
return OutputCellView({}, {}, attr, TextAttributeBehavior::StoredOnly);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - wch - View representing a single UTF-16 character (that can be represented without surrogates)
|
||||
// - attr - View representing a single color
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch, const TextAttribute& attr) noexcept
|
||||
{
|
||||
const auto glyph = std::wstring_view(&wch, 1);
|
||||
|
||||
DbcsAttribute dbcsAttr;
|
||||
if (IsGlyphFullWidth(wch))
|
||||
{
|
||||
dbcsAttr.SetLeading();
|
||||
}
|
||||
|
||||
return OutputCellView(glyph, dbcsAttr, attr, TextAttributeBehavior::Stored);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - legacyAttr - View representing a single legacy color
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateViewLegacyAttr(const WORD& legacyAttr) noexcept
|
||||
{
|
||||
WORD cleanAttr = legacyAttr;
|
||||
WI_ClearAllFlags(cleanAttr, COMMON_LVB_SBCSDBCS); // don't use legacy lead/trailing byte flags for colors
|
||||
|
||||
const TextAttribute attr(cleanAttr);
|
||||
return s_GenerateView(attr);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - charInfo - character and attribute pair representing a single cell
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const CHAR_INFO& charInfo) noexcept
|
||||
{
|
||||
const auto glyph = std::wstring_view(&charInfo.Char.UnicodeChar, 1);
|
||||
|
||||
DbcsAttribute dbcsAttr;
|
||||
if (WI_IsFlagSet(charInfo.Attributes, COMMON_LVB_LEADING_BYTE))
|
||||
{
|
||||
dbcsAttr.SetLeading();
|
||||
}
|
||||
else if (WI_IsFlagSet(charInfo.Attributes, COMMON_LVB_TRAILING_BYTE))
|
||||
{
|
||||
dbcsAttr.SetTrailing();
|
||||
}
|
||||
|
||||
const TextAttribute textAttr(charInfo.Attributes);
|
||||
|
||||
const auto behavior = TextAttributeBehavior::Stored;
|
||||
return OutputCellView(glyph, dbcsAttr, textAttr, behavior);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// Arguments:
|
||||
// - cell - A reference to the cell for which we will make the read-only view
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const OutputCell& cell)
|
||||
{
|
||||
return OutputCellView(cell.Chars(), cell.DbcsAttr(), cell.TextAttr(), cell.TextAttrBehavior());
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Gets the distance between two iterators relative to the input data given in.
|
||||
// Return Value:
|
||||
// - The number of items of the input run consumed between these two iterators.
|
||||
ptrdiff_t OutputCellIterator::GetInputDistance(OutputCellIterator other) const noexcept
|
||||
{
|
||||
return _pos - other._pos;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Gets the distance between two iterators relative to the number of cells inserted.
|
||||
// Return Value:
|
||||
// - The number of cells in the backing buffer filled between these two iterators.
|
||||
ptrdiff_t OutputCellIterator::GetCellDistance(OutputCellIterator other) const noexcept
|
||||
{
|
||||
return _distance - other._distance;
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- OutputCellIterator.hpp
|
||||
|
||||
Abstract:
|
||||
- Read-only view into an entire batch of data to be written into the output buffer.
|
||||
- This is done for performance reasons (avoid heap allocs and copies).
|
||||
|
||||
Author:
|
||||
- Michael Niksa (MiNiksa) 06-Oct-2018
|
||||
|
||||
Revision History:
|
||||
- Based on work from OutputCell.hpp/cpp by Austin Diviness (AustDi)
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "TextAttribute.hpp"
|
||||
|
||||
#include "OutputCell.hpp"
|
||||
#include "OutputCellView.hpp"
|
||||
|
||||
class OutputCellIterator final
|
||||
{
|
||||
public:
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using value_type = OutputCellView;
|
||||
using difference_type = ptrdiff_t;
|
||||
using pointer = OutputCellView*;
|
||||
using reference = OutputCellView&;
|
||||
|
||||
OutputCellIterator(const wchar_t& wch, const size_t fillLimit = 0) noexcept;
|
||||
OutputCellIterator(const TextAttribute& attr, const size_t fillLimit = 0) noexcept;
|
||||
OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit = 0) noexcept;
|
||||
OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit = 0) noexcept;
|
||||
OutputCellIterator(const std::wstring_view utf16Text);
|
||||
OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute attribute);
|
||||
OutputCellIterator(const gsl::span<const WORD> legacyAttributes) noexcept;
|
||||
OutputCellIterator(const gsl::span<const CHAR_INFO> charInfos) noexcept;
|
||||
OutputCellIterator(const gsl::span<const OutputCell> cells);
|
||||
~OutputCellIterator() = default;
|
||||
|
||||
OutputCellIterator& operator=(const OutputCellIterator& it) = default;
|
||||
|
||||
operator bool() const noexcept;
|
||||
|
||||
ptrdiff_t GetCellDistance(OutputCellIterator other) const noexcept;
|
||||
ptrdiff_t GetInputDistance(OutputCellIterator other) const noexcept;
|
||||
friend ptrdiff_t operator-(OutputCellIterator one, OutputCellIterator two) = delete;
|
||||
|
||||
OutputCellIterator& operator++();
|
||||
OutputCellIterator operator++(int);
|
||||
|
||||
const OutputCellView& operator*() const noexcept;
|
||||
const OutputCellView* operator->() const noexcept;
|
||||
|
||||
private:
|
||||
enum class Mode
|
||||
{
|
||||
// Loose mode is where we're given text and attributes in a raw sort of form
|
||||
// like while data is being inserted from an API call.
|
||||
Loose,
|
||||
|
||||
// Loose mode with only text is where we're given just text and we want
|
||||
// to use the attribute already in the buffer when writing
|
||||
LooseTextOnly,
|
||||
|
||||
// Fill mode is where we were given one thing and we just need to keep giving
|
||||
// that back over and over for eternity.
|
||||
Fill,
|
||||
|
||||
// Given a run of legacy attributes, convert each of them and insert only attribute data.
|
||||
LegacyAttr,
|
||||
|
||||
// CharInfo mode is where we've been given a pair of text and attribute for each
|
||||
// cell in the legacy format from an API call.
|
||||
CharInfo,
|
||||
|
||||
// Cell mode is where we have an already fully structured cell data usually
|
||||
// from accessing/copying data already put into the OutputBuffer.
|
||||
Cell,
|
||||
};
|
||||
Mode _mode;
|
||||
|
||||
gsl::span<const WORD> _legacyAttrs;
|
||||
|
||||
std::variant<
|
||||
std::wstring_view,
|
||||
gsl::span<const WORD>,
|
||||
gsl::span<const CHAR_INFO>,
|
||||
gsl::span<const OutputCell>,
|
||||
std::monostate>
|
||||
_run;
|
||||
|
||||
TextAttribute _attr;
|
||||
|
||||
bool _TryMoveTrailing() noexcept;
|
||||
|
||||
static OutputCellView s_GenerateView(const std::wstring_view view);
|
||||
|
||||
static OutputCellView s_GenerateView(const std::wstring_view view,
|
||||
const TextAttribute attr);
|
||||
|
||||
static OutputCellView s_GenerateView(const std::wstring_view view,
|
||||
const TextAttribute attr,
|
||||
const TextAttributeBehavior behavior);
|
||||
|
||||
static OutputCellView s_GenerateView(const wchar_t& wch) noexcept;
|
||||
static OutputCellView s_GenerateViewLegacyAttr(const WORD& legacyAttr) noexcept;
|
||||
static OutputCellView s_GenerateView(const TextAttribute& attr) noexcept;
|
||||
static OutputCellView s_GenerateView(const wchar_t& wch, const TextAttribute& attr) noexcept;
|
||||
static OutputCellView s_GenerateView(const CHAR_INFO& charInfo) noexcept;
|
||||
|
||||
static OutputCellView s_GenerateView(const OutputCell& cell);
|
||||
|
||||
OutputCellView _currentView;
|
||||
|
||||
size_t _pos;
|
||||
size_t _distance;
|
||||
size_t _fillLimit;
|
||||
};
|
|
@ -42,19 +42,6 @@ gsl::span<OutputCell> OutputCellRect::GetRow(const size_t row)
|
|||
return gsl::span<OutputCell>(_FindRowOffset(row), _cols);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Gets a read-only iterator view over a single row of the rectangle.
|
||||
// Arguments:
|
||||
// - row - The Y position or row index in the buffer.
|
||||
// Return Value:
|
||||
// - Read-only iterator of OutputCells
|
||||
OutputCellIterator OutputCellRect::GetRowIter(const size_t row) const
|
||||
{
|
||||
const gsl::span<const OutputCell> view(_FindRowOffset(row), _cols);
|
||||
|
||||
return OutputCellIterator(view);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Internal helper to find the pointer to the specific row offset in the giant
|
||||
// contiguous block of memory allocated for this rectangle.
|
||||
|
|
|
@ -21,10 +21,8 @@ Revision History:
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "DbcsAttribute.hpp"
|
||||
#include "TextAttribute.hpp"
|
||||
#include "OutputCell.hpp"
|
||||
#include "OutputCellIterator.hpp"
|
||||
|
||||
class OutputCellRect final
|
||||
{
|
||||
|
@ -33,7 +31,6 @@ public:
|
|||
OutputCellRect(const size_t rows, const size_t cols);
|
||||
|
||||
gsl::span<OutputCell> GetRow(const size_t row);
|
||||
OutputCellIterator GetRowIter(const size_t row) const;
|
||||
|
||||
size_t Height() const noexcept;
|
||||
size_t Width() const noexcept;
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include "precomp.h"
|
||||
#include "Row.hpp"
|
||||
#include "CharRow.hpp"
|
||||
#include "textBuffer.hpp"
|
||||
#include "../types/inc/convert.hpp"
|
||||
|
||||
|
@ -16,15 +15,16 @@
|
|||
// - 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) :
|
||||
_id{ rowId },
|
||||
_rowWidth{ rowWidth },
|
||||
_charRow{ rowWidth, this },
|
||||
_attrRow{ rowWidth, fillAttribute },
|
||||
ROW::ROW(const SHORT /*rowId*/, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const /*pParent*/) :
|
||||
_data{
|
||||
std::wstring(rowWidth, UNICODE_SPACE),
|
||||
{ { gsl::narrow_cast<uint8_t>(1), gsl::narrow_cast<uint16_t>(rowWidth) } },
|
||||
{ rowWidth, fillAttribute },
|
||||
rowWidth
|
||||
},
|
||||
_lineRendition{ LineRendition::SingleWidth },
|
||||
_wrapForced{ false },
|
||||
_doubleBytePadded{ false },
|
||||
_pParent{ pParent }
|
||||
_doubleBytePadded{ false }
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -39,10 +39,11 @@ bool ROW::Reset(const TextAttribute Attr)
|
|||
_lineRendition = LineRendition::SingleWidth;
|
||||
_wrapForced = false;
|
||||
_doubleBytePadded = false;
|
||||
_charRow.Reset();
|
||||
_data._cwid.replace(0, _data._width, { 1, _data._width }); // replace entire RLE with one run
|
||||
_data._data.replace(0, _data._width, _data._width, UNICODE_SPACE);
|
||||
try
|
||||
{
|
||||
_attrRow.Reset(Attr);
|
||||
_data._attrRow.Reset(Attr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
@ -60,14 +61,20 @@ bool ROW::Reset(const TextAttribute Attr)
|
|||
// - S_OK if successful, otherwise relevant error
|
||||
[[nodiscard]] HRESULT ROW::Resize(const unsigned short width)
|
||||
{
|
||||
RETURN_IF_FAILED(_charRow.Resize(width));
|
||||
_data._data.resize(width, L' ');
|
||||
auto oldEnd{ _data._cwid.size() };
|
||||
_data._cwid.resize_trailing_extent(width);
|
||||
if (width > oldEnd)
|
||||
{
|
||||
_data._cwid.replace(oldEnd, width, { 1, gsl::narrow_cast<uint16_t>(width - oldEnd) });
|
||||
}
|
||||
try
|
||||
{
|
||||
_attrRow.Resize(width);
|
||||
_data._attrRow.Resize(width);
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
_rowWidth = width;
|
||||
_data._width = width;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
@ -80,20 +87,12 @@ bool ROW::Reset(const TextAttribute Attr)
|
|||
// - <none>
|
||||
void ROW::ClearColumn(const size_t column)
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, column >= _charRow.size());
|
||||
_charRow.ClearCell(column);
|
||||
}
|
||||
|
||||
UnicodeStorage& ROW::GetUnicodeStorage() noexcept
|
||||
{
|
||||
return _pParent->GetUnicodeStorage();
|
||||
}
|
||||
|
||||
const UnicodeStorage& ROW::GetUnicodeStorage() const noexcept
|
||||
{
|
||||
return _pParent->GetUnicodeStorage();
|
||||
THROW_HR_IF(E_INVALIDARG, column >= _data._width);
|
||||
WriteGlyphAtMeasured(column, 1, L" ");
|
||||
}
|
||||
|
||||
#if 0
|
||||
TODO (DH)
|
||||
// Routine Description:
|
||||
// - writes cell data to the row
|
||||
// Arguments:
|
||||
|
@ -105,11 +104,11 @@ const UnicodeStorage& ROW::GetUnicodeStorage() const noexcept
|
|||
// - iterator to first cell that was not written to this row.
|
||||
OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, const std::optional<bool> wrap, std::optional<size_t> limitRight)
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, index >= _charRow.size());
|
||||
THROW_HR_IF(E_INVALIDARG, limitRight.value_or(0) >= _charRow.size());
|
||||
THROW_HR_IF(E_INVALIDARG, index >= _data._width);
|
||||
THROW_HR_IF(E_INVALIDARG, limitRight.value_or(0) >= _data._width);
|
||||
|
||||
// If we're given a right-side column limit, use it. Otherwise, the write limit is the final column index available in the char row.
|
||||
const auto finalColumnInRow = limitRight.value_or(_charRow.size() - 1);
|
||||
const auto finalColumnInRow = limitRight.value_or(_data._width - 1);
|
||||
|
||||
auto currentColor = it->TextAttr();
|
||||
uint16_t colorUses = 0;
|
||||
|
@ -131,7 +130,7 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
|
|||
{
|
||||
// Otherwise, commit this color into the run and save off the new one.
|
||||
// Now commit the new color runs into the attr row.
|
||||
_attrRow.Replace(colorStarts, currentIndex, currentColor);
|
||||
_data._attrRow.Replace(colorStarts, currentIndex, currentColor);
|
||||
currentColor = it->TextAttr();
|
||||
colorUses = 1;
|
||||
colorStarts = currentIndex;
|
||||
|
@ -151,21 +150,31 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
|
|||
// Don't increment iterator. We'll advance the index and try again with this value on the next round through the loop.
|
||||
if (currentIndex == 0 && it->DbcsAttr().IsTrailing())
|
||||
{
|
||||
_charRow.ClearCell(currentIndex);
|
||||
ClearColumn(currentIndex);
|
||||
it.AddCellDistanceFault(1); // we couldn't fit a cell here but we skipped a column :|
|
||||
}
|
||||
// If we're trying to fill the last cell with a leading byte, pad it out instead by clearing it.
|
||||
// Don't increment iterator. We'll exit because we couldn't write a lead at the end of a line.
|
||||
else if (fillingLastColumn && it->DbcsAttr().IsLeading())
|
||||
{
|
||||
_charRow.ClearCell(currentIndex);
|
||||
ClearColumn(currentIndex);
|
||||
it.AddCellDistanceFault(1); // we couldn't fit a cell here but we skipped a column :|
|
||||
SetDoubleBytePadded(true);
|
||||
}
|
||||
// Otherwise, copy the data given and increment the iterator.
|
||||
else
|
||||
{
|
||||
_charRow.DbcsAttrAt(currentIndex) = it->DbcsAttr();
|
||||
_charRow.GlyphAt(currentIndex) = it->Chars();
|
||||
++it;
|
||||
if (!it->DbcsAttr().IsTrailing())
|
||||
{
|
||||
uint16_t d = it->DbcsAttr().IsSingle() ? 1 : 2;
|
||||
WriteGlyphAtMeasured(currentIndex, d, it->Chars());
|
||||
currentIndex += d - 1;
|
||||
while (d > 0)
|
||||
{ // TODO(DH) FFS
|
||||
++it;
|
||||
--d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're asked to (un)set the wrap status and we just filled the last column with some text...
|
||||
|
@ -191,8 +200,9 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
|
|||
// Now commit the final color into the attr row
|
||||
if (colorUses)
|
||||
{
|
||||
_attrRow.Replace(colorStarts, currentIndex, currentColor);
|
||||
_data._attrRow.Replace(colorStarts, currentIndex, currentColor);
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -23,18 +23,157 @@ Revision History:
|
|||
#include "AttrRow.hpp"
|
||||
#include "LineRendition.hpp"
|
||||
#include "OutputCell.hpp"
|
||||
#include "OutputCellIterator.hpp"
|
||||
#include "CharRow.hpp"
|
||||
#include "UnicodeStorage.hpp"
|
||||
#include "unicode.hpp"
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4267)
|
||||
class TextBuffer;
|
||||
|
||||
using RowMeasurementBuffer = til::small_rle<uint8_t, uint16_t, 3>;
|
||||
|
||||
struct DamageRanges
|
||||
{
|
||||
size_t dataOffset;
|
||||
size_t dataLength;
|
||||
uint16_t firstColumn;
|
||||
uint16_t lastColumnExclusive;
|
||||
};
|
||||
|
||||
template<typename TRuns>
|
||||
static DamageRanges DamageRangesForColumnInMeasurementBuffer(const TRuns& cwid, size_t col)
|
||||
{
|
||||
size_t currentCol{ 0 };
|
||||
size_t currentWchar{ 0 };
|
||||
auto it{ cwid.runs().cbegin() };
|
||||
while (it != cwid.runs().cend())
|
||||
{
|
||||
// Each compressed pair tells us how many columns x N wchar_t
|
||||
const auto colsCoveredByRun{ it->value * it->length };
|
||||
if (currentCol + colsCoveredByRun > col)
|
||||
{
|
||||
// We want to break out of the loop to manually handle this run, because
|
||||
// we've determined that it is the run that covers the column of interest.
|
||||
break;
|
||||
}
|
||||
currentCol += colsCoveredByRun;
|
||||
currentWchar += it->length;
|
||||
it++;
|
||||
}
|
||||
|
||||
if (it == cwid.runs().cend())
|
||||
{
|
||||
// this is an interesting case- somebody requested a column we cannot answer for.
|
||||
// The string might actually have data, and the caller might be interested in where that data is.
|
||||
// Ideally, we would return the index of the first char out-of-bounds, and the length of the remaining data as a single unit.
|
||||
// We can't answer for how much space it takes up, though.
|
||||
__debugbreak();
|
||||
return { 0, 0, 0u, 0u };
|
||||
//return { currentWchar, _data.size() - currentWchar, 0u, 0u };
|
||||
}
|
||||
// currentWchar is how many wchar_t we are into the string before processing this run
|
||||
// currentCol is how many columns we've covered before processing this run
|
||||
|
||||
// We are *guaranteed* that the hit is in this run -- no need to check it->length
|
||||
// col-currentCol is how many columns are left unaccounted for (how far into this run we need to go)
|
||||
const auto colsLeftToCountInCurrentRun{ col - currentCol };
|
||||
currentWchar += colsLeftToCountInCurrentRun / it->value; // one wch per column unit -- rounds down (correct behavior)
|
||||
|
||||
size_t lenInWchars{ 1 }; // the first hit takes up one wchar
|
||||
|
||||
// We use this to determine if we have exhausted every column this run can cough up.
|
||||
// colsLeftToCountInCurrentRun is 0-indexed, but colsConsumedByRun is 1-indexed (index 0 consumes N columns, etc.)
|
||||
// therefore, we reindex colsLeftToCountInCurrentRun and compare it to colsConsumedByRun
|
||||
const auto colsConsumedFromRun{ colsLeftToCountInCurrentRun + it->value };
|
||||
const auto colsCoveredByRun{ it->value * it->length };
|
||||
// If we *have* consumed every column this run can provide, we must check the run after it:
|
||||
// if it contributes "0" columns, it is actually a set of trailing code units.
|
||||
if (colsConsumedFromRun >= colsCoveredByRun && it != cwid.runs().cend())
|
||||
{
|
||||
const auto nextRunIt{ it + 1 };
|
||||
if (nextRunIt != cwid.runs().cend() && nextRunIt->value == 0)
|
||||
{
|
||||
// we were at the boundary of a column run, so if the next one is 0 it tells us that each
|
||||
// wchar after it is a trailer
|
||||
lenInWchars += nextRunIt->length;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
currentWchar, // wchar start
|
||||
lenInWchars, // wchar size
|
||||
gsl::narrow_cast<uint16_t>(col - (colsLeftToCountInCurrentRun % it->value)), // Column damage to the left (where we overlapped the right of a wide glyph)
|
||||
gsl::narrow_cast<uint16_t>(col - (colsLeftToCountInCurrentRun % it->value) + it->value), // Column damage to the right (where we overlapped the left of a wide glyph)
|
||||
};
|
||||
}
|
||||
|
||||
class ROW;
|
||||
struct RowImage
|
||||
{
|
||||
std::wstring _data;
|
||||
RowMeasurementBuffer _cwid;
|
||||
ATTR_ROW _attrRow;
|
||||
uint16_t _width;
|
||||
friend class ROW;
|
||||
friend class TextBuffer;
|
||||
RowImage() :
|
||||
_data{}, _cwid{}, _attrRow{ {} }, _width{ 0 } {}
|
||||
RowImage(const std::wstring& data, const RowMeasurementBuffer& cwid, ATTR_ROW attrRow, uint16_t width):
|
||||
_data{data}, _cwid{cwid}, _attrRow{std::move(attrRow)}, _width{width} {}
|
||||
|
||||
// exclusive
|
||||
std::tuple<RowImage, RowImage> split(uint16_t col) const
|
||||
{
|
||||
if (col >= _width)
|
||||
{
|
||||
return { *this, RowImage{} };
|
||||
}
|
||||
else if (col == 0)
|
||||
{
|
||||
return { RowImage{}, *this };
|
||||
}
|
||||
auto yes_more_fucking_damage_ranges = DamageRangesForColumnInMeasurementBuffer(_cwid, col);
|
||||
// here's a dumb decision: when you split along a wide char, you get spaces over its damage on the left side.
|
||||
// X X XX X X X
|
||||
// ^ split
|
||||
// X X S <- left
|
||||
// XX X X X <- right
|
||||
auto chopped_off = col == yes_more_fucking_damage_ranges.lastColumnExclusive ? 0 : /*didn't get whole thing*/ yes_more_fucking_damage_ranges.lastColumnExclusive - col;
|
||||
auto chopped_on = col == yes_more_fucking_damage_ranges.lastColumnExclusive ? yes_more_fucking_damage_ranges.dataLength : 0;
|
||||
auto lwid = _cwid.slice(0, yes_more_fucking_damage_ranges.dataOffset + chopped_on);
|
||||
for (auto z{ chopped_off }; z > 0; --z)
|
||||
{
|
||||
lwid.append(1);
|
||||
}
|
||||
RowImage left{
|
||||
_data.substr(0, yes_more_fucking_damage_ranges.dataOffset + chopped_on) + std::wstring(chopped_off, UNICODE_SPACE),
|
||||
lwid,
|
||||
_attrRow._data.slice(0, yes_more_fucking_damage_ranges.lastColumnExclusive),
|
||||
col
|
||||
};
|
||||
RowImage right{
|
||||
_data.substr(yes_more_fucking_damage_ranges.dataOffset + chopped_on),
|
||||
_cwid.slice(yes_more_fucking_damage_ranges.dataOffset + chopped_on, _data.length()),
|
||||
// we use first col here to catch the overlap
|
||||
_attrRow._data.slice(yes_more_fucking_damage_ranges.firstColumn, _width),
|
||||
::base::ClampSub(_width, col),
|
||||
};
|
||||
return { left, right };
|
||||
}
|
||||
};
|
||||
|
||||
enum class DelimiterClass
|
||||
{
|
||||
ControlChar,
|
||||
DelimiterChar,
|
||||
RegularChar
|
||||
};
|
||||
|
||||
class ROW final
|
||||
{
|
||||
public:
|
||||
ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent);
|
||||
|
||||
size_t size() const noexcept { return _rowWidth; }
|
||||
size_t size() const noexcept { return _data._width; }
|
||||
|
||||
void SetWrapForced(const bool wrap) noexcept { _wrapForced = wrap; }
|
||||
bool WasWrapForced() const noexcept { return _wrapForced; }
|
||||
|
@ -42,52 +181,357 @@ public:
|
|||
void SetDoubleBytePadded(const bool doubleBytePadded) noexcept { _doubleBytePadded = doubleBytePadded; }
|
||||
bool WasDoubleBytePadded() const noexcept { return _doubleBytePadded; }
|
||||
|
||||
const CharRow& GetCharRow() const noexcept { return _charRow; }
|
||||
CharRow& GetCharRow() noexcept { return _charRow; }
|
||||
|
||||
const ATTR_ROW& GetAttrRow() const noexcept { return _attrRow; }
|
||||
ATTR_ROW& GetAttrRow() noexcept { return _attrRow; }
|
||||
const ATTR_ROW& GetAttrRow() const noexcept { return _data._attrRow; }
|
||||
ATTR_ROW& GetAttrRow() noexcept { return _data._attrRow; }
|
||||
|
||||
LineRendition GetLineRendition() const noexcept { return _lineRendition; }
|
||||
void SetLineRendition(const LineRendition lineRendition) noexcept { _lineRendition = lineRendition; }
|
||||
|
||||
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);
|
||||
|
||||
void ClearColumn(const size_t column);
|
||||
std::wstring GetText() const { return _charRow.GetText(); }
|
||||
|
||||
UnicodeStorage& GetUnicodeStorage() noexcept;
|
||||
const UnicodeStorage& GetUnicodeStorage() const noexcept;
|
||||
|
||||
OutputCellIterator WriteCells(OutputCellIterator it, const size_t index, const std::optional<bool> wrap = std::nullopt, std::optional<size_t> limitRight = std::nullopt);
|
||||
std::wstring GetText() const { return _data._data; }
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend constexpr bool operator==(const ROW& a, const ROW& b) noexcept;
|
||||
friend class RowTests;
|
||||
#endif
|
||||
|
||||
struct RowData
|
||||
{
|
||||
std::wstring _data;
|
||||
RowMeasurementBuffer _cwid;
|
||||
ATTR_ROW _attrRow;
|
||||
unsigned short _width;
|
||||
|
||||
DamageRanges _damageForColumn(size_t col) const
|
||||
{
|
||||
return DamageRangesForColumnInMeasurementBuffer(_cwid, col);
|
||||
}
|
||||
|
||||
DamageRanges _damageForColumns(size_t col, size_t ncols) const
|
||||
{
|
||||
// When we want to replace a column, or set of columns, with a glyph, we need to:
|
||||
// * Figure out the physical extent of the character in that cell (UTF-16 code units).
|
||||
// * Figure out the columnar extent of the character in that cell (how many columns it covers).
|
||||
// * In the simple case (1->1, 2->2), there will be no damage.
|
||||
// * In the complex case (2->1, 1->2, 2->2 with middle overlap), there *WILL* be damage.
|
||||
// * Replace the physical character data in that cell with the new character data.
|
||||
// * Insert padding characters to the left and right to account for damage.
|
||||
//
|
||||
// ## DAMAGE
|
||||
// Damage is measured in the number of columns to the left
|
||||
// and right of the new glyph that are now NO LONGER VALID because
|
||||
// they were double-width characters that are being cut in half,
|
||||
// or single-width characters that are collateral damage from stomping
|
||||
// them with a double-width character.
|
||||
auto damage{ _damageForColumn(col) };
|
||||
const auto lastDamage{ _damageForColumn(col + ncols - 1 /*inclusive*/) };
|
||||
|
||||
// *INVARIANT* the beginning of the next column range must have a different beginning byte
|
||||
// This column began at a different data index, so we have to delete its data too.
|
||||
// Since it's contiguous, just increment len.
|
||||
damage.dataLength = lastDamage.dataOffset + lastDamage.dataLength - damage.dataOffset;
|
||||
damage.lastColumnExclusive = lastDamage.lastColumnExclusive;
|
||||
|
||||
return damage;
|
||||
}
|
||||
|
||||
void _strikeDamageAndAdjust(size_t col, size_t ncols, size_t incomingCodeUnitCount, DamageRanges& range)
|
||||
{
|
||||
(void)incomingCodeUnitCount;
|
||||
const bool damaged{ range.firstColumn < col || col + ncols < range.lastColumnExclusive };
|
||||
if (damaged)
|
||||
{
|
||||
const auto damagedColumns{ range.lastColumnExclusive - range.firstColumn };
|
||||
_data.replace(range.dataOffset, range.dataLength, damagedColumns, UNICODE_SPACE);
|
||||
_cwid.replace(range.dataOffset, range.dataOffset + range.dataLength, { uint8_t{ 1 }, gsl::narrow_cast<uint16_t>(damagedColumns) });
|
||||
// We may have replaced surrogate pairs/etc with fewer/more code units.
|
||||
range.dataLength = damagedColumns;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
CharRow _charRow;
|
||||
ATTR_ROW _attrRow;
|
||||
RowData _data;
|
||||
LineRendition _lineRendition;
|
||||
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
|
||||
|
||||
public:
|
||||
std::wstring_view GlyphAt(size_t col) const
|
||||
{
|
||||
const auto lookup{ _data._damageForColumn(col) };
|
||||
return { _data._data.data() + lookup.dataOffset, lookup.dataLength };
|
||||
}
|
||||
|
||||
std::pair<size_t, size_t> WriteGlyphAtMeasured(size_t col, size_t ncols, std::wstring_view glyph)
|
||||
{
|
||||
const auto [begin, len, minDamageColumn, maxDamageColumnExclusive]{ _data._damageForColumns(col, ncols) };
|
||||
|
||||
if (minDamageColumn == col && maxDamageColumnExclusive == col + ncols)
|
||||
{
|
||||
// We are only damaging as many columns as we are introducing -- no spillover (!)
|
||||
// We can replace the code units in the data directly, and we can replace the
|
||||
// column counts with [col, 0, 0...] (with as many zeroes as we need to account
|
||||
// for any code units past the first.)
|
||||
_data._data.replace(begin, len, glyph);
|
||||
typename decltype(_data._cwid)::rle_type newRuns[]{
|
||||
{ gsl::narrow_cast<uint8_t>(ncols), 1 },
|
||||
{ 0, gsl::narrow_cast<uint16_t>(glyph.size() - 1) },
|
||||
};
|
||||
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), gsl::make_span(&newRuns[0], glyph.size() == 1 ? 1 : 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are damaging multiple columns -- oops. We need to insert replacement characters
|
||||
// to get us from the leftmost side of the damaged glyph up to the leftmost side of
|
||||
// our newly-inserted region. We also need to insert replacement characters from the
|
||||
// rightmost side of our glyph to the rightmost side of the glyph that was once in
|
||||
// that column.
|
||||
// Left side count : col - minDamageColumn
|
||||
// Right side count: maxDamageColumn - (col + ncols)
|
||||
const auto replacementCodeUnits{ (col - minDamageColumn) + glyph.size() + (maxDamageColumnExclusive - (col + ncols)) };
|
||||
std::wstring replacement(replacementCodeUnits, UNICODE_SPACE);
|
||||
replacement.replace(col - minDamageColumn, glyph.size(), glyph);
|
||||
|
||||
// New advances:
|
||||
// Our glyph and all its trailers
|
||||
// v-----v
|
||||
// [1, ..., 1, X, 0, 0, 1, ..., 1]
|
||||
// ^-------^ ^-------^
|
||||
// Each replacement space char
|
||||
// is one column wide. We have
|
||||
// to insert [1]s for each
|
||||
// damaged column.
|
||||
boost::container::small_vector<typename decltype(_data._cwid)::rle_type, 4> newRuns;
|
||||
if (col - minDamageColumn)
|
||||
{
|
||||
newRuns.emplace_back((uint8_t)1, gsl::narrow_cast<uint16_t>(col - minDamageColumn));
|
||||
}
|
||||
newRuns.emplace_back(gsl::narrow_cast<uint8_t>(ncols), (uint16_t)1);
|
||||
if (glyph.size() > 1)
|
||||
{
|
||||
newRuns.emplace_back((uint8_t)0, gsl::narrow_cast<uint16_t>(glyph.size() - 1)); // trailers
|
||||
}
|
||||
if (maxDamageColumnExclusive - (col + ncols))
|
||||
{
|
||||
newRuns.emplace_back((uint8_t)1, gsl::narrow_cast<uint16_t>(maxDamageColumnExclusive - (col + ncols)));
|
||||
}
|
||||
_data._data.replace(begin, len, replacement);
|
||||
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), gsl::make_span(newRuns));
|
||||
}
|
||||
|
||||
// Distance from requested column to final
|
||||
_maxc = std::max(_maxc, maxDamageColumnExclusive);
|
||||
return { begin + glyph.size(), col + ncols };
|
||||
}
|
||||
|
||||
DbcsAttribute DbcsAttrAt(size_t col) const
|
||||
{
|
||||
const auto [begin, len, first, lastE] = _data._damageForColumn(col);
|
||||
if (lastE - first == 1)
|
||||
{
|
||||
// The glyph under this column is only onw column wide.
|
||||
return DbcsAttribute{ DbcsAttribute::Attribute::Single };
|
||||
}
|
||||
else if (first != col)
|
||||
{
|
||||
// The glyph under this column is >1 col wide, and we're bisecting it
|
||||
return DbcsAttribute{ DbcsAttribute::Attribute::Trailing };
|
||||
}
|
||||
else
|
||||
{
|
||||
// The glyph under this column is >1 col wide, and we're at the head
|
||||
return DbcsAttribute{ DbcsAttribute::Attribute::Leading };
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<size_t, uint16_t, uint16_t> WriteStringAtMeasured(uint16_t col, uint16_t colCount, const std::wstring_view& string, const RowMeasurementBuffer& measurements)
|
||||
{
|
||||
size_t incomingLastColumn{ std::min<size_t>(_data._width - col, colCount) };
|
||||
auto incomingLastColumnOffsets{ DamageRangesForColumnInMeasurementBuffer(measurements, incomingLastColumn - 1 /*inclusive*/) };
|
||||
|
||||
auto codeUnitsToConsume{ incomingLastColumnOffsets.dataOffset };
|
||||
auto columnsToConsume{ incomingLastColumn };
|
||||
|
||||
const auto [begin, len, minDamageColumn, maxDamageColumnExclusive]{ _data._damageForColumns(col, incomingLastColumn) };
|
||||
|
||||
// If these don't match, we are cutting a multi-cell glyph.
|
||||
if (incomingLastColumnOffsets.lastColumnExclusive == incomingLastColumn)
|
||||
{
|
||||
// Since they *do* match, we should consume this part of the string too.
|
||||
codeUnitsToConsume += incomingLastColumnOffsets.dataLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only consume up to the final cell (the one we cut in half)
|
||||
columnsToConsume = incomingLastColumnOffsets.firstColumn;
|
||||
// **INVARIANT** we only get here if we had to cut off the incoming text, and that only
|
||||
// happens because we had to clamp the read buffer against our width. This means that the
|
||||
// incoming text definitely had a wide glyph that would not fit against the end of our
|
||||
// buffer.
|
||||
// THEREFORE: col+incomingLastColumn was our final column (exclusive)
|
||||
// which means that maxDamageColumnExclusive can be upgraded to be our width.
|
||||
// OPTIMIZATION: If we mark our last column as damaged, it will automatically get stomped
|
||||
// with spaces.
|
||||
// NO NO NO NO NO NO NO NO NO NO TODO(DH)
|
||||
// We can't do this without changing the damaged buffer region to delete the character
|
||||
// from _data at the same time. Use the non-optimial path.
|
||||
ClearColumn(_data._width - 1);
|
||||
SetDoubleBytePadded(true);
|
||||
// NO - we already marked this column when we calculated damage above
|
||||
//++columnsWrittenAfterInsertionPoint;
|
||||
}
|
||||
|
||||
auto mss = measurements.slice(0, gsl::narrow_cast<uint16_t>(codeUnitsToConsume));
|
||||
if (minDamageColumn == col && maxDamageColumnExclusive == col + incomingLastColumn)
|
||||
{
|
||||
// We are only damaging as many columns as we are introducing -- no spillover (!)
|
||||
// We can replace the code units in the data directly, and we can replace the
|
||||
// column counts with [col, 0, 0...] (with as many zeroes as we need to account
|
||||
// for any code units past the first.)
|
||||
_data._data.replace(begin, len, &*string.cbegin(), codeUnitsToConsume);
|
||||
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), mss.runs());
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are damaging multiple columns -- oops. We need to insert replacement characters
|
||||
// to get us from the leftmost side of the damaged glyph up to the leftmost side of
|
||||
// our newly-inserted region. We also need to insert replacement characters from the
|
||||
// rightmost side of our glyph to the rightmost side of the glyph that was once in
|
||||
// that column.
|
||||
// Left side count : col - minDamageColumn
|
||||
// Right side count: maxDamageColumn - (col + ncols)
|
||||
const auto replacementCodeUnits{ (col - minDamageColumn) + codeUnitsToConsume + (maxDamageColumnExclusive - (col + incomingLastColumn)) };
|
||||
std::wstring replacement(replacementCodeUnits, UNICODE_SPACE);
|
||||
replacement.replace(col - minDamageColumn, codeUnitsToConsume, &*string.cbegin(), codeUnitsToConsume);
|
||||
|
||||
// New advances:
|
||||
// Our glyph and all its trailers
|
||||
// v-----v
|
||||
// [1, ..., 1, X, 0, 0, 1, ..., 1]
|
||||
// ^-------^ ^-------^
|
||||
// Each replacement space char
|
||||
// is one column wide. We have
|
||||
// to insert [1]s for each
|
||||
// damaged column.
|
||||
mss.replace(0, 0, { uint8_t{ 1 }, gsl::narrow_cast<uint16_t>(col - minDamageColumn) });
|
||||
mss.replace(mss.size(), mss.size(), { uint8_t{ 1 }, gsl::narrow_cast<uint16_t>(maxDamageColumnExclusive - (col + incomingLastColumn)) });
|
||||
_data._data.replace(begin, len, replacement);
|
||||
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), mss.runs()); //gsl::make_span(newRuns));
|
||||
}
|
||||
|
||||
return {
|
||||
codeUnitsToConsume,
|
||||
gsl::narrow_cast<uint16_t>(maxDamageColumnExclusive - col),
|
||||
gsl::narrow_cast<uint16_t>(columnsToConsume)
|
||||
};
|
||||
}
|
||||
|
||||
size_t Fill(size_t col, size_t count, wchar_t ch, uint8_t w)
|
||||
{
|
||||
const auto charsFitOrRemain = std::min((_data._width - col) / w, count);
|
||||
|
||||
const auto columnsRequired{ charsFitOrRemain * w };
|
||||
auto damage = _data._damageForColumns(col, columnsRequired);
|
||||
// If we are filling over the left/right halves of a character
|
||||
// This is a bit wasteful since it can grow/shrink the buffers and we're about
|
||||
// to do it again, but I was trying to be expedient.
|
||||
_data._strikeDamageAndAdjust(col, columnsRequired, charsFitOrRemain, damage);
|
||||
|
||||
const auto [begin, len, min, max] = damage;
|
||||
_data._data.replace(begin, len, charsFitOrRemain, ch);
|
||||
_data._cwid.replace(begin, begin + len, { w, gsl::narrow_cast<uint16_t>(charsFitOrRemain) });
|
||||
const auto doubleBytePadded{
|
||||
w > 1 // We had a wide glyph...
|
||||
&& max != _data._width // ...and didn't reach the edge
|
||||
&& count > charsFitOrRemain // ...but we had spare characters, so we wanted to
|
||||
};
|
||||
if (doubleBytePadded)
|
||||
{
|
||||
const uint16_t remaining{ gsl::narrow_cast<uint16_t>(_data._width - max) };
|
||||
// overflow: add spaces
|
||||
_data._data.replace(begin + charsFitOrRemain, _data._data.size() - begin + charsFitOrRemain, remaining, UNICODE_SPACE);
|
||||
_data._cwid.replace(begin + charsFitOrRemain, _data._cwid.size(), { uint8_t{ 1u }, gsl::narrow_cast<uint16_t>(remaining) });
|
||||
}
|
||||
if (max == _data._width || doubleBytePadded)
|
||||
{
|
||||
// TODO(DH): Evaluate the above condition
|
||||
// We only want to do this if we touched or near-touched the lat col
|
||||
SetDoubleBytePadded(doubleBytePadded);
|
||||
SetWrapForced(false);
|
||||
}
|
||||
return charsFitOrRemain;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - get delimiter class for a position in the char row
|
||||
// - used for double click selection and uia word navigation
|
||||
// Arguments:
|
||||
// - column: column to get text data for
|
||||
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
|
||||
// Return Value:
|
||||
// - the delimiter class for the given char
|
||||
const DelimiterClass DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const
|
||||
{
|
||||
THROW_HR_IF(E_INVALIDARG, column >= _data._width);
|
||||
|
||||
const auto glyph = *GlyphAt(column).begin();
|
||||
if (glyph <= UNICODE_SPACE)
|
||||
{
|
||||
return DelimiterClass::ControlChar;
|
||||
}
|
||||
else if (wordDelimiters.find(glyph) != std::wstring_view::npos)
|
||||
{
|
||||
return DelimiterClass::DelimiterChar;
|
||||
}
|
||||
else
|
||||
{
|
||||
return DelimiterClass::RegularChar;
|
||||
}
|
||||
}
|
||||
|
||||
RowImage Dump(uint16_t left, uint16_t size)
|
||||
{
|
||||
auto [begin, len, min, max] = _data._damageForColumns(left, size);
|
||||
return RowImage{
|
||||
_data._data.substr(begin, len),
|
||||
_data._cwid.slice(begin, begin + len),
|
||||
ATTR_ROW{ _data._attrRow._data.slice(min, max) },
|
||||
::base::MakeClampedNum(max) - min
|
||||
};
|
||||
}
|
||||
|
||||
void Reinsert(uint16_t left, const RowImage& ri)
|
||||
{
|
||||
auto damage{ _data._damageForColumns(left, ri._width) };
|
||||
_data._strikeDamageAndAdjust(left, ri._width, ri._data.size(), damage);
|
||||
_data._data.replace(damage.dataOffset, damage.dataLength, ri._data);
|
||||
_data._cwid.replace(damage.dataOffset, damage.dataOffset + damage.dataLength, ri._cwid.runs());
|
||||
_data._attrRow._data.replace(damage.firstColumn, damage.lastColumnExclusive, ri._attrRow._data.runs());
|
||||
}
|
||||
|
||||
uint16_t _maxc{};
|
||||
size_t MeasureRight() const
|
||||
{
|
||||
return _maxc;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
constexpr bool operator==(const ROW& a, const ROW& b) noexcept
|
||||
{
|
||||
// comparison is only used in the tests; this should suffice.
|
||||
return (a._pParent == b._pParent &&
|
||||
a._id == b._id);
|
||||
return (a._data == b._data &&
|
||||
a._cwid == b._cwid &&
|
||||
a._attrRow == b._attrRow &&
|
||||
a._rowWidth == b._rowWidth &&
|
||||
a._wrapForced == b._wrapForced &&
|
||||
a._doubleBytePadded == b._doubleBytePadded);
|
||||
}
|
||||
#endif
|
||||
#pragma warning(pop)
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "UnicodeStorage.hpp"
|
||||
|
||||
UnicodeStorage::UnicodeStorage() noexcept :
|
||||
_map{}
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - fetches the text associated with key
|
||||
// Arguments:
|
||||
// - key - the key into the storage
|
||||
// Return Value:
|
||||
// - the glyph data associated with key
|
||||
// Note: will throw exception if key is not stored yet
|
||||
const UnicodeStorage::mapped_type& UnicodeStorage::GetText(const key_type key) const
|
||||
{
|
||||
return _map.at(key);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - stores glyph data associated with key.
|
||||
// Arguments:
|
||||
// - key - the key into the storage
|
||||
// - glyph - the glyph data to store
|
||||
void UnicodeStorage::StoreGlyph(const key_type key, const mapped_type& glyph)
|
||||
{
|
||||
_map.insert_or_assign(key, glyph);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - erases key and its associated data from the storage
|
||||
// Arguments:
|
||||
// - key - the key to remove
|
||||
void UnicodeStorage::Erase(const key_type key) noexcept
|
||||
{
|
||||
_map.erase(key);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Remaps all of the stored items to new coordinate positions
|
||||
// based on a bulk rearrangement of row IDs and potential row width resize.
|
||||
// Arguments:
|
||||
// - rowMap - A map of the old row IDs to the new row IDs.
|
||||
// - width - The width of the new row. Remove any items that are beyond the row width.
|
||||
// - Use nullopt if we're not resizing the width of the row, just renumbering the rows.
|
||||
void UnicodeStorage::Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width)
|
||||
{
|
||||
// Make a temporary map to hold all the new row positioning
|
||||
std::unordered_map<key_type, mapped_type> newMap;
|
||||
|
||||
// Walk through every stored item.
|
||||
for (const auto& pair : _map)
|
||||
{
|
||||
// Extract the old coordinate position
|
||||
const auto oldCoord = pair.first;
|
||||
|
||||
// Only try to short-circuit based on width if we were told it changed
|
||||
// by being given a new width value.
|
||||
if (width.has_value())
|
||||
{
|
||||
// Get the column ID
|
||||
const auto oldColId = oldCoord.X;
|
||||
|
||||
// If the column index is at/beyond the row width, don't bother copying it to the new map.
|
||||
if (oldColId >= width.value())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the row ID from the position as that's what we need to remap
|
||||
const auto oldRowId = oldCoord.Y;
|
||||
|
||||
// Use the mapping given to convert the old row ID to the new row ID
|
||||
const auto mapIter = rowMap.find(oldRowId);
|
||||
|
||||
// If there's no mapping to a new row, don't bother copying it to the new map. The row is gone.
|
||||
if (mapIter == rowMap.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto newRowId = mapIter->second;
|
||||
|
||||
// Generate a new coordinate with the same X as the old one, but a new Y value.
|
||||
const auto newCoord = COORD{ oldCoord.X, newRowId };
|
||||
|
||||
// Put the adjusted coordinate into the map with the original value.
|
||||
newMap.emplace(newCoord, pair.second);
|
||||
}
|
||||
|
||||
// Swap into the stored map, free the temporary when we exit.
|
||||
_map.swap(newMap);
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- UnicodeStorage.hpp
|
||||
|
||||
Abstract:
|
||||
- dynamic storage location for glyphs that can't normally fit in the output buffer
|
||||
|
||||
Author(s):
|
||||
- Austin Diviness (AustDi) 02-May-2018
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <climits>
|
||||
|
||||
// std::unordered_map needs help to know how to hash a COORD
|
||||
namespace std
|
||||
{
|
||||
template<>
|
||||
struct hash<COORD>
|
||||
{
|
||||
// Routine Description:
|
||||
// - hashes a coord. coord will be hashed by storing the x and y values consecutively in the lower
|
||||
// bits of a size_t.
|
||||
// Arguments:
|
||||
// - coord - the coord to hash
|
||||
// Return Value:
|
||||
// - the hashed coord
|
||||
constexpr size_t operator()(const COORD& coord) const noexcept
|
||||
{
|
||||
size_t retVal = coord.Y;
|
||||
const size_t xCoord = coord.X;
|
||||
retVal |= xCoord << (sizeof(coord.Y) * CHAR_BIT);
|
||||
return retVal;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class UnicodeStorage final
|
||||
{
|
||||
public:
|
||||
using key_type = typename COORD;
|
||||
using mapped_type = typename std::vector<wchar_t>;
|
||||
|
||||
UnicodeStorage() noexcept;
|
||||
|
||||
const mapped_type& GetText(const key_type key) const;
|
||||
|
||||
void StoreGlyph(const key_type key, const mapped_type& glyph);
|
||||
|
||||
void Erase(const key_type key) noexcept;
|
||||
|
||||
void Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width);
|
||||
|
||||
private:
|
||||
std::unordered_map<key_type, mapped_type> _map;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class UnicodeStorageTests;
|
||||
friend class TextBufferTests;
|
||||
#endif
|
||||
};
|
|
@ -13,7 +13,6 @@
|
|||
<ClCompile Include="..\AttrRow.cpp" />
|
||||
<ClCompile Include="..\cursor.cpp" />
|
||||
<ClCompile Include="..\OutputCell.cpp" />
|
||||
<ClCompile Include="..\OutputCellIterator.cpp" />
|
||||
<ClCompile Include="..\OutputCellRect.cpp" />
|
||||
<ClCompile Include="..\OutputCellView.cpp" />
|
||||
<ClCompile Include="..\Row.cpp" />
|
||||
|
@ -23,22 +22,16 @@
|
|||
<ClCompile Include="..\textBuffer.cpp" />
|
||||
<ClCompile Include="..\textBufferCellIterator.cpp" />
|
||||
<ClCompile Include="..\textBufferTextIterator.cpp" />
|
||||
<ClCompile Include="..\CharRow.cpp" />
|
||||
<ClCompile Include="..\CharRowCell.cpp" />
|
||||
<ClCompile Include="..\CharRowCellReference.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\UnicodeStorage.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\AttrRow.hpp" />
|
||||
<ClInclude Include="..\cursor.h" />
|
||||
<ClInclude Include="..\DbcsAttribute.hpp" />
|
||||
<ClInclude Include="..\ICharRow.hpp" />
|
||||
<ClInclude Include="..\LineRendition.hpp" />
|
||||
<ClInclude Include="..\OutputCell.hpp" />
|
||||
<ClInclude Include="..\OutputCellIterator.hpp" />
|
||||
<ClInclude Include="..\OutputCellRect.hpp" />
|
||||
<ClInclude Include="..\OutputCellView.hpp" />
|
||||
<ClInclude Include="..\Row.hpp" />
|
||||
|
@ -48,11 +41,7 @@
|
|||
<ClInclude Include="..\textBuffer.hpp" />
|
||||
<ClInclude Include="..\textBufferCellIterator.hpp" />
|
||||
<ClInclude Include="..\textBufferTextIterator.hpp" />
|
||||
<ClInclude Include="..\CharRow.hpp" />
|
||||
<ClInclude Include="..\CharRowCell.hpp" />
|
||||
<ClInclude Include="..\CharRowCellReference.hpp" />
|
||||
<ClInclude Include="..\precomp.h" />
|
||||
<ClInclude Include="..\UnicodeStorage.hpp" />
|
||||
</ItemGroup>
|
||||
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
|
||||
<Import Project="$(SolutionDir)src\common.build.post.props" />
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
#include "search.h"
|
||||
|
||||
#include "CharRow.hpp"
|
||||
#include "textBuffer.hpp"
|
||||
#include "../types/inc/Utf16Parser.hpp"
|
||||
#include "../types/inc/GlyphWidth.hpp"
|
||||
|
|
|
@ -41,10 +41,6 @@ SOURCES= \
|
|||
..\textBuffer.cpp \
|
||||
..\textBufferCellIterator.cpp \
|
||||
..\textBufferTextIterator.cpp \
|
||||
..\CharRow.cpp \
|
||||
..\CharRowCell.cpp \
|
||||
..\CharRowCellReference.cpp \
|
||||
..\UnicodeStorage.cpp \
|
||||
..\search.cpp \
|
||||
|
||||
INCLUDES= \
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
#include "precomp.h"
|
||||
|
||||
#include "textBuffer.hpp"
|
||||
#include "CharRow.hpp"
|
||||
|
||||
#include "../types/inc/utils.hpp"
|
||||
#include "../types/inc/convert.hpp"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
#include "../types/inc/Utf16Parser.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
|
@ -35,7 +35,6 @@ TextBuffer::TextBuffer(const COORD screenBufferSize,
|
|||
_currentAttributes{ defaultAttributes },
|
||||
_cursor{ cursorSize, *this },
|
||||
_storage{},
|
||||
_unicodeStorage{},
|
||||
_renderTarget{ renderTarget },
|
||||
_size{},
|
||||
_currentHyperlinkId{ 1 },
|
||||
|
@ -184,277 +183,6 @@ TextBufferCellIterator TextBuffer::GetCellDataAt(const COORD at, const Viewport
|
|||
return TextBufferCellIterator(*this, at, limit);
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// - Corrects and enforces consistent double byte character state (KAttrs line) within a row of the text buffer.
|
||||
// - This will take the given double byte information and check that it will be consistent when inserted into the buffer
|
||||
// at the current cursor position.
|
||||
// - It will correct the buffer (by erasing the character prior to the cursor) if necessary to make a consistent state.
|
||||
//Arguments:
|
||||
// - dbcsAttribute - Double byte information associated with the character about to be inserted into the buffer
|
||||
//Return Value:
|
||||
// - True if it is valid to insert a character with the given double byte attributes. False otherwise.
|
||||
bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute)
|
||||
{
|
||||
// To figure out if the sequence is valid, we have to look at the character that comes before the current one
|
||||
const COORD coordPrevPosition = _GetPreviousFromCursor();
|
||||
ROW& prevRow = GetRowByOffset(coordPrevPosition.Y);
|
||||
DbcsAttribute prevDbcsAttr;
|
||||
try
|
||||
{
|
||||
prevDbcsAttr = prevRow.GetCharRow().DbcsAttrAt(coordPrevPosition.X);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool fValidSequence = true; // Valid until proven otherwise
|
||||
bool fCorrectableByErase = false; // Can't be corrected until proven otherwise
|
||||
|
||||
// Here's the matrix of valid items:
|
||||
// N = None (single byte)
|
||||
// L = Lead (leading byte of double byte sequence
|
||||
// T = Trail (trailing byte of double byte sequence
|
||||
// Prev Curr Result
|
||||
// N N OK.
|
||||
// N L OK.
|
||||
// N T Fail, uncorrectable. Trailing byte must have had leading before it.
|
||||
// L N Fail, OK with erase. Lead needs trailing pair. Can erase lead to correct.
|
||||
// L L Fail, OK with erase. Lead needs trailing pair. Can erase prev lead to correct.
|
||||
// L T OK.
|
||||
// T N OK.
|
||||
// T L OK.
|
||||
// T T Fail, uncorrectable. New trailing byte must have had leading before it.
|
||||
|
||||
// Check for only failing portions of the matrix:
|
||||
if (prevDbcsAttr.IsSingle() && dbcsAttribute.IsTrailing())
|
||||
{
|
||||
// N, T failing case (uncorrectable)
|
||||
fValidSequence = false;
|
||||
}
|
||||
else if (prevDbcsAttr.IsLeading())
|
||||
{
|
||||
if (dbcsAttribute.IsSingle() || dbcsAttribute.IsLeading())
|
||||
{
|
||||
// L, N and L, L failing cases (correctable)
|
||||
fValidSequence = false;
|
||||
fCorrectableByErase = true;
|
||||
}
|
||||
}
|
||||
else if (prevDbcsAttr.IsTrailing() && dbcsAttribute.IsTrailing())
|
||||
{
|
||||
// T, T failing case (uncorrectable)
|
||||
fValidSequence = false;
|
||||
}
|
||||
|
||||
// If it's correctable by erase, erase the previous character
|
||||
if (fCorrectableByErase)
|
||||
{
|
||||
// Erase previous character into an N type.
|
||||
try
|
||||
{
|
||||
prevRow.ClearColumn(coordPrevPosition.X);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sequence is now N N or N L, which are both okay. Set sequence back to valid.
|
||||
fValidSequence = true;
|
||||
}
|
||||
|
||||
return fValidSequence;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// - Call before inserting a character into the buffer.
|
||||
// - This will ensure a consistent double byte state (KAttrs line) within the text buffer
|
||||
// - It will attempt to correct the buffer if we're inserting an unexpected double byte character type
|
||||
// and it will pad out the buffer if we're going to split a double byte sequence across two rows.
|
||||
//Arguments:
|
||||
// - dbcsAttribute - Double byte information associated with the character about to be inserted into the buffer
|
||||
//Return Value:
|
||||
// - true if we successfully prepared the buffer and moved the cursor
|
||||
// - false otherwise (out of memory)
|
||||
bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute)
|
||||
{
|
||||
// This function corrects most errors. If this is false, we had an uncorrectable one which
|
||||
// older versions of conhost simply let pass by unflinching.
|
||||
LOG_HR_IF(E_NOT_VALID_STATE, !(_AssertValidDoubleByteSequence(dbcsAttribute))); // Shouldn't be uncorrectable sequences unless something is very wrong.
|
||||
|
||||
bool fSuccess = true;
|
||||
// Now compensate if we don't have enough space for the upcoming double byte sequence
|
||||
// We only need to compensate for leading bytes
|
||||
if (dbcsAttribute.IsLeading())
|
||||
{
|
||||
const auto cursorPosition = GetCursor().GetPosition();
|
||||
const auto lineWidth = GetLineWidth(cursorPosition.Y);
|
||||
|
||||
// If we're about to lead on the last column in the row, we need to add a padding space
|
||||
if (cursorPosition.X == lineWidth - 1)
|
||||
{
|
||||
// set that we're wrapping for double byte reasons
|
||||
auto& row = GetRowByOffset(cursorPosition.Y);
|
||||
row.SetDoubleBytePadded(true);
|
||||
|
||||
// then move the cursor forward and onto the next row
|
||||
fSuccess = IncrementCursor();
|
||||
}
|
||||
}
|
||||
return fSuccess;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Writes cells to the output buffer. Writes at the cursor.
|
||||
// Arguments:
|
||||
// - givenIt - Iterator representing output cell data to write
|
||||
// Return Value:
|
||||
// - The final position of the iterator
|
||||
OutputCellIterator TextBuffer::Write(const OutputCellIterator givenIt)
|
||||
{
|
||||
const auto& cursor = GetCursor();
|
||||
const auto target = cursor.GetPosition();
|
||||
|
||||
const auto finalIt = Write(givenIt, target);
|
||||
|
||||
return finalIt;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Writes cells to the output buffer.
|
||||
// Arguments:
|
||||
// - givenIt - Iterator representing output cell data to write
|
||||
// - target - the row/column to start writing the text to
|
||||
// - wrap - change the wrap flag if we hit the end of the row while writing and there's still more data
|
||||
// Return Value:
|
||||
// - The final position of the iterator
|
||||
OutputCellIterator TextBuffer::Write(const OutputCellIterator givenIt,
|
||||
const COORD target,
|
||||
const std::optional<bool> wrap)
|
||||
{
|
||||
// Make mutable copy so we can walk.
|
||||
auto it = givenIt;
|
||||
|
||||
// Make mutable target so we can walk down lines.
|
||||
auto lineTarget = target;
|
||||
|
||||
// Get size of the text buffer so we can stay in bounds.
|
||||
const auto size = GetSize();
|
||||
|
||||
// While there's still data in the iterator and we're still targeting in bounds...
|
||||
while (it && size.IsInBounds(lineTarget))
|
||||
{
|
||||
// Attempt to write as much data as possible onto this line.
|
||||
// NOTE: if wrap = true/false, we want to set the line's wrap to true/false (respectively) if we reach the end of the line
|
||||
it = WriteLine(it, lineTarget, wrap);
|
||||
|
||||
// Move to the next line down.
|
||||
lineTarget.X = 0;
|
||||
++lineTarget.Y;
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Writes one line of text to the output buffer.
|
||||
// Arguments:
|
||||
// - givenIt - The iterator that will dereference into cell data to insert
|
||||
// - target - Coordinate targeted within output buffer
|
||||
// - wrap - change the wrap flag if we hit the end of the row while writing and there's still more data in the iterator.
|
||||
// - limitRight - Optionally restrict the right boundary for writing (e.g. stop writing earlier than the end of line)
|
||||
// Return Value:
|
||||
// - The iterator, but advanced to where we stopped writing. Use to find input consumed length or cells written length.
|
||||
OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt,
|
||||
const COORD target,
|
||||
const std::optional<bool> wrap,
|
||||
std::optional<size_t> limitRight)
|
||||
{
|
||||
// If we're not in bounds, exit early.
|
||||
if (!GetSize().IsInBounds(target))
|
||||
{
|
||||
return givenIt;
|
||||
}
|
||||
|
||||
// Get the row and write the cells
|
||||
ROW& row = GetRowByOffset(target.Y);
|
||||
const auto newIt = row.WriteCells(givenIt, target.X, wrap, limitRight);
|
||||
|
||||
// Take the cell distance written and notify that it needs to be repainted.
|
||||
const auto written = newIt.GetCellDistance(givenIt);
|
||||
const Viewport paint = Viewport::FromDimensions(target, { gsl::narrow<SHORT>(written), 1 });
|
||||
_NotifyPaint(paint);
|
||||
|
||||
return newIt;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// - Inserts one codepoint into the buffer at the current cursor position and advances the cursor as appropriate.
|
||||
//Arguments:
|
||||
// - chars - The codepoint to insert
|
||||
// - dbcsAttribute - Double byte information associated with the codepoint
|
||||
// - bAttr - Color data associated with the character
|
||||
//Return Value:
|
||||
// - true if we successfully inserted the character
|
||||
// - false otherwise (out of memory)
|
||||
bool TextBuffer::InsertCharacter(const std::wstring_view chars,
|
||||
const DbcsAttribute dbcsAttribute,
|
||||
const TextAttribute attr)
|
||||
{
|
||||
// Ensure consistent buffer state for double byte characters based on the character type we're about to insert
|
||||
bool fSuccess = _PrepareForDoubleByteSequence(dbcsAttribute);
|
||||
|
||||
if (fSuccess)
|
||||
{
|
||||
// Get the current cursor position
|
||||
short const iRow = GetCursor().GetPosition().Y; // row stored as logical position, not array position
|
||||
short const iCol = GetCursor().GetPosition().X; // column logical and array positions are equal.
|
||||
|
||||
// Get the row associated with the given logical position
|
||||
ROW& Row = GetRowByOffset(iRow);
|
||||
|
||||
// Store character and double byte data
|
||||
CharRow& charRow = Row.GetCharRow();
|
||||
|
||||
try
|
||||
{
|
||||
charRow.GlyphAt(iCol) = chars;
|
||||
charRow.DbcsAttrAt(iCol) = dbcsAttribute;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store color data
|
||||
fSuccess = Row.GetAttrRow().SetAttrToEnd(iCol, attr);
|
||||
if (fSuccess)
|
||||
{
|
||||
// Advance the cursor
|
||||
fSuccess = IncrementCursor();
|
||||
}
|
||||
}
|
||||
return fSuccess;
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// - Inserts one ucs2 codepoint into the buffer at the current cursor position and advances the cursor as appropriate.
|
||||
//Arguments:
|
||||
// - wch - The codepoint to insert
|
||||
// - dbcsAttribute - Double byte information associated with the codepoint
|
||||
// - bAttr - Color data associated with the character
|
||||
//Return Value:
|
||||
// - true if we successfully inserted the character
|
||||
// - false otherwise (out of memory)
|
||||
bool TextBuffer::InsertCharacter(const wchar_t wch, const DbcsAttribute dbcsAttribute, const TextAttribute attr)
|
||||
{
|
||||
return InsertCharacter({ &wch, 1 }, dbcsAttribute, attr);
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// - Finds the current row in the buffer (as indicated by the cursor position)
|
||||
// and specifies that we have forced a line wrap on that row
|
||||
|
@ -606,7 +334,7 @@ COORD TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Consol
|
|||
|
||||
const auto& currRow = GetRowByOffset(coordEndOfText.Y);
|
||||
// The X position of the end of the valid text is the Right draw boundary (which is one beyond the final valid character)
|
||||
coordEndOfText.X = gsl::narrow<short>(currRow.GetCharRow().MeasureRight()) - 1;
|
||||
coordEndOfText.X = gsl::narrow<short>(currRow.MeasureRight()) - 1;
|
||||
|
||||
// If the X coordinate turns out to be -1, the row was empty, we need to search backwards for the real end of text.
|
||||
const auto viewportTop = viewport.Top();
|
||||
|
@ -617,7 +345,7 @@ COORD TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Consol
|
|||
const auto& backupRow = GetRowByOffset(coordEndOfText.Y);
|
||||
// We need to back up to the previous row if this line is empty, AND there are more rows
|
||||
|
||||
coordEndOfText.X = gsl::narrow<short>(backupRow.GetCharRow().MeasureRight()) - 1;
|
||||
coordEndOfText.X = gsl::narrow<short>(backupRow.MeasureRight()) - 1;
|
||||
fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop);
|
||||
}
|
||||
|
||||
|
@ -777,7 +505,6 @@ 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);
|
||||
}
|
||||
|
||||
|
@ -814,13 +541,11 @@ void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition)
|
|||
// And if it's no longer single width, the right half of the row should be erased.
|
||||
if (lineRendition != LineRendition::SingleWidth)
|
||||
{
|
||||
const auto fillChar = L' ';
|
||||
auto fillAttrs = GetCurrentAttributes();
|
||||
fillAttrs.SetStandardErase();
|
||||
const size_t fillOffset = GetLineWidth(rowIndex);
|
||||
const size_t fillLength = GetSize().Width() - fillOffset;
|
||||
const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillLength };
|
||||
row.WriteCells(fillData, fillOffset, false);
|
||||
FillWithCharacterAndAttributeLinear(til::point{ fillOffset, ::base::saturated_cast<size_t>(cursorPosition.Y) }, fillLength, UNICODE_SPACE, fillAttrs);
|
||||
// We also need to make sure the cursor is clamped within the new width.
|
||||
GetCursor().SetPosition(ClampPositionWithinLine(cursorPosition));
|
||||
}
|
||||
|
@ -930,8 +655,7 @@ void TextBuffer::Reset()
|
|||
}
|
||||
|
||||
// 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.
|
||||
// Also take advantage of the row ID refresh loop to resize the rows in the X dimension.
|
||||
_RefreshRowIDs(newSize.X);
|
||||
|
||||
// Update the cached size value
|
||||
|
@ -942,40 +666,16 @@ void TextBuffer::Reset()
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
const UnicodeStorage& TextBuffer::GetUnicodeStorage() const noexcept
|
||||
{
|
||||
return _unicodeStorage;
|
||||
}
|
||||
|
||||
UnicodeStorage& TextBuffer::GetUnicodeStorage() noexcept
|
||||
{
|
||||
return _unicodeStorage;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Method to help refresh all the Row IDs after manipulating the row
|
||||
// by shuffling pointers around.
|
||||
// - This will also update parent pointers that are stored in depth within the buffer
|
||||
// (e.g. it will update CharRow parents pointing at Rows that might have been moved around)
|
||||
// - Optionally takes a new row width if we're resizing to perform a resize operation and cleanup
|
||||
// any high unicode (UnicodeStorage) runs while we're already looping through the rows.
|
||||
// - Optionally takes a new row width if we're resizing to perform a resize operation
|
||||
// Arguments:
|
||||
// - newRowWidth - Optional new value for the row width.
|
||||
void TextBuffer::_RefreshRowIDs(std::optional<SHORT> newRowWidth)
|
||||
{
|
||||
std::unordered_map<SHORT, SHORT> rowMap;
|
||||
SHORT i = 0;
|
||||
for (auto& it : _storage)
|
||||
{
|
||||
// Build a map so we can update Unicode Storage
|
||||
rowMap.emplace(it.GetId(), i);
|
||||
|
||||
// Update the IDs
|
||||
it.SetId(i++);
|
||||
|
||||
// Also update the char row parent pointers as they can get shuffled up in the rotates.
|
||||
it.GetCharRow().UpdateParent(&it);
|
||||
|
||||
// Resize the rows in the X dimension if we have a new width
|
||||
if (newRowWidth.has_value())
|
||||
{
|
||||
|
@ -983,9 +683,6 @@ void TextBuffer::_RefreshRowIDs(std::optional<SHORT> newRowWidth)
|
|||
THROW_IF_FAILED(it.Resize(newRowWidth.value()));
|
||||
}
|
||||
}
|
||||
|
||||
// Give the new mapping to Unicode Storage
|
||||
_unicodeStorage.Remap(rowMap, newRowWidth);
|
||||
}
|
||||
|
||||
void TextBuffer::_NotifyPaint(const Viewport& viewport) const
|
||||
|
@ -1004,27 +701,6 @@ ROW& TextBuffer::_GetFirstRow()
|
|||
return GetRowByOffset(0);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves the row that comes before the given row.
|
||||
// - Does not wrap around the screen buffer.
|
||||
// Arguments:
|
||||
// - The current row.
|
||||
// Return Value:
|
||||
// - reference to the previous row
|
||||
// Note:
|
||||
// - will throw exception if called with the first row of the text buffer
|
||||
ROW& TextBuffer::_GetPrevRowNoWrap(const ROW& Row)
|
||||
{
|
||||
int prevRowIndex = Row.GetId() - 1;
|
||||
if (prevRowIndex < 0)
|
||||
{
|
||||
prevRowIndex = TotalRowCount() - 1;
|
||||
}
|
||||
|
||||
THROW_HR_IF(E_FAIL, Row.GetId() == _firstRow);
|
||||
return _storage.at(prevRowIndex);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Retrieves this buffer's current render target.
|
||||
// Arguments:
|
||||
|
@ -1046,7 +722,7 @@ Microsoft::Console::Render::IRenderTarget& TextBuffer::GetRenderTarget() noexcep
|
|||
// - the delimiter class for the given char
|
||||
const DelimiterClass TextBuffer::_GetDelimiterClassAt(const COORD pos, const std::wstring_view wordDelimiters) const
|
||||
{
|
||||
return GetRowByOffset(pos.Y).GetCharRow().DelimiterClassAt(pos.X, wordDelimiters);
|
||||
return GetRowByOffset(pos.Y).DelimiterClassAt(pos.X, wordDelimiters);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -2138,8 +1814,13 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
|||
// Fetch the row and its "right" which is the last printable character.
|
||||
const ROW& row = oldBuffer.GetRowByOffset(iOldRow);
|
||||
const short cOldColsTotal = oldBuffer.GetLineWidth(iOldRow);
|
||||
const CharRow& charRow = row.GetCharRow();
|
||||
short iRight = gsl::narrow_cast<short>(charRow.MeasureRight());
|
||||
short iRight = gsl::narrow_cast<short>(row.MeasureRight());
|
||||
if (iRight == 0 && !row.WasWrapForced())
|
||||
{
|
||||
// don't beef it if iRight=0 on iterator
|
||||
newBuffer.NewlineCursor();
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're starting a new row, try and preserve the line rendition
|
||||
// from the row in the original buffer.
|
||||
|
@ -2174,33 +1855,26 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
|||
}
|
||||
}
|
||||
|
||||
// Loop through every character in the current row (up to
|
||||
// the "right" boundary, which is one past the final valid
|
||||
// character)
|
||||
for (short iOldCol = 0; iOldCol < iRight; iOldCol++)
|
||||
#if 0
|
||||
OutputCellIterator it{ oldBuffer.GetCellDataAt({ 0, iOldRow }, Viewport::FromDimensions({ 0, iOldRow }, iRight, 1)) };
|
||||
auto preCurPos{ newBuffer.GetCursor().GetPosition() };
|
||||
const auto last = newBuffer.Write(it, preCurPos, true); // true - wrap if we don't fit!
|
||||
const auto distance = last.GetCellDistance(it);
|
||||
#endif
|
||||
auto preCurPos{ newBuffer.GetCursor().GetPosition() };
|
||||
const auto distance = 5;
|
||||
|
||||
if (iOldRow == cOldCursorPos.Y)
|
||||
{
|
||||
if (iOldCol == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
|
||||
{
|
||||
cNewCursorPos = newCursor.GetPosition();
|
||||
fFoundCursorPos = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: MSFT: 19446208 - this should just use an iterator and the inserter...
|
||||
const auto glyph = row.GetCharRow().GlyphAt(iOldCol);
|
||||
const auto dbcsAttr = row.GetCharRow().DbcsAttrAt(iOldCol);
|
||||
const auto textAttr = row.GetAttrRow().GetAttrByColumn(iOldCol);
|
||||
|
||||
if (!newBuffer.InsertCharacter(glyph, dbcsAttr, textAttr))
|
||||
{
|
||||
hr = E_OUTOFMEMORY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CATCH_RETURN();
|
||||
cNewCursorPos = preCurPos;
|
||||
// we started at (0, NewY) -- walk forward by the old position's X to figure out where we land in the new line
|
||||
newBuffer.GetSize().MoveInBounds(cOldCursorPos.X, cNewCursorPos);
|
||||
fFoundCursorPos = true;
|
||||
}
|
||||
|
||||
newBuffer.GetSize().MoveInBounds(distance, preCurPos);
|
||||
newBuffer.GetCursor().SetPosition(preCurPos);
|
||||
|
||||
// If we found the old row that the caller was interested in, set the
|
||||
// out value of that parameter to the cursor's current Y position (the
|
||||
// new location of the _end_ of that row in the buffer).
|
||||
|
@ -2232,7 +1906,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
|||
// Only do so if we were not forced to wrap. If we did
|
||||
// force a word wrap, then the existing line break was
|
||||
// only because we ran out of space.
|
||||
if (iRight < cOldColsTotal && !row.WasWrapForced())
|
||||
if (distance < cOldColsTotal && !row.WasWrapForced())
|
||||
{
|
||||
if (iRight == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
|
||||
{
|
||||
|
@ -2563,3 +2237,224 @@ PointTree TextBuffer::GetPatterns(const size_t firstRow, const size_t lastRow) c
|
|||
PointTree result(std::move(intervals));
|
||||
return result;
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::FillWithAttributeRectangular(const til::rectangle& region, const TextAttribute& attribute)
|
||||
{
|
||||
return _WriteRectangular(
|
||||
region,
|
||||
[&](WriteResult& result, auto&& at, auto&& size) {
|
||||
auto& row = GetRowByOffset(at.y());
|
||||
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), size), attribute);
|
||||
result.columnsWritten += size;
|
||||
});
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::FillWithAttributeLinear(til::point at, size_t count, const TextAttribute& attribute)
|
||||
{
|
||||
auto out = _WriteLinear(
|
||||
at,
|
||||
[&](WriteResult& result, auto&& at, auto&& size) -> bool {
|
||||
const auto w{ ::base::ClampMin(::base::saturated_cast<uint16_t>(size), count) };
|
||||
|
||||
auto& row = GetRowByOffset(at.y());
|
||||
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), size), attribute);
|
||||
|
||||
result.columnsWritten += w.RawValue();
|
||||
|
||||
const Viewport paint = Viewport::FromDimensions(at, { w.Cast<SHORT>(), 1 });
|
||||
_NotifyPaint(paint);
|
||||
|
||||
return (count -= w.RawValue()) > 0; // keep going if data remains
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
// TODO(DH): make this a member so that IsGlyphFullWidth can be a member ref
|
||||
static std::tuple<RowMeasurementBuffer, uint16_t> _MeasureStringAndCountColumns(std::wstring_view string) //const noexcept
|
||||
{
|
||||
RowMeasurementBuffer measurements;
|
||||
uint16_t colCount{};
|
||||
while (!string.empty())
|
||||
{
|
||||
auto codepoint = Utf16Parser::ParseNext(string);
|
||||
string = string.substr(codepoint.size());
|
||||
|
||||
uint8_t measurement{ IsGlyphFullWidth(codepoint) ? 2u : 1u };
|
||||
measurements.replace(measurements.size(), measurements.size(), { measurement, gsl::narrow_cast<uint16_t>(1) });
|
||||
if (codepoint.size() > 1)
|
||||
{
|
||||
measurements.replace(measurements.size(), measurements.size(), typename decltype(measurements)::rle_type{ uint8_t{ 0 }, gsl::narrow_cast<uint16_t>(codepoint.size() - 1) });
|
||||
}
|
||||
colCount += measurement;
|
||||
}
|
||||
return { std::move(measurements), colCount };
|
||||
}
|
||||
|
||||
struct no_attributes
|
||||
{
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
TextBuffer::WriteResult TextBuffer::_WriteMeasuredStringLinearWithAttributes(const til::point& at, std::wstring_view string, RowMeasurementBuffer measurements, [[maybe_unused]] const T& attributes)
|
||||
{
|
||||
uint16_t colCount = std::accumulate(
|
||||
measurements.runs().cbegin(),
|
||||
measurements.runs().cend(),
|
||||
uint16_t{},
|
||||
[](auto&& v, auto&& r) -> uint16_t { return v + (r.value * r.length); });
|
||||
const auto out = _WriteLinear(
|
||||
at,
|
||||
[&](WriteResult& result, auto&& at, auto&& /* size */) {
|
||||
auto& row = GetRowByOffset(at.y());
|
||||
const auto [consumedWchar, consumedDestCols, consumedSourceCols] = row.WriteStringAtMeasured(at.x<uint16_t>(), colCount, string, measurements);
|
||||
|
||||
if constexpr (std::is_same<T, TextAttribute>::value)
|
||||
{
|
||||
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), consumedDestCols), attributes);
|
||||
}
|
||||
#if 0
|
||||
else if constexpr (std::is_same<T, ATTR_ROW::rle_vector>::value)
|
||||
{
|
||||
// user has passed us a packed representation of attributes
|
||||
// It is a programming error to pass one whose total length does
|
||||
// not match the measurement buffer
|
||||
const auto attrRunSlice{ attributes.slice(0, consumedSourceCols) };
|
||||
attrRunSlice.resize_trailing_extent(consumedDestCols); // this may be very wrong. TODO(DH)
|
||||
// intent: I believe that this will take the attribute from the cell before the one we had to cut off
|
||||
// and then extend it to cover our empty column?
|
||||
row.GetAttrRow()._data.replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), consumedSourceCols), attrRunSlice.runs());
|
||||
}
|
||||
#endif
|
||||
|
||||
const Viewport paint = Viewport::FromDimensions(at, { ::base::saturated_cast<SHORT>(consumedDestCols), 1 });
|
||||
_NotifyPaint(paint);
|
||||
colCount -= consumedSourceCols;
|
||||
|
||||
result.charactersRead += consumedWchar;
|
||||
result.charactersWritten += consumedWchar;
|
||||
result.columnsWritten += consumedDestCols;
|
||||
result.columnsRead += consumedSourceCols;
|
||||
|
||||
// unchecked substr
|
||||
//string = std::wstring_view{ &*string.begin() + consumedWchar, string.size() - consumedWchar };
|
||||
//string = til::unchecked_substr(string, consumedWchar);
|
||||
string = string.substr(consumedWchar);
|
||||
measurements = measurements.slice(gsl::narrow_cast<uint16_t>(consumedWchar), measurements.size());
|
||||
|
||||
return !string.empty();
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::WriteMeasuredStringLinear(const til::point& at, const std::wstring_view& string, RowMeasurementBuffer measurements)
|
||||
{
|
||||
return _WriteMeasuredStringLinearWithAttributes(at, string, std::move(measurements), _currentAttributes);
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::WriteStringLinearWithAttributes(const til::point& at, const std::wstring_view& string, const TextAttribute& attr)
|
||||
{
|
||||
auto [measurements, colCount] = _MeasureStringAndCountColumns(string);
|
||||
return _WriteMeasuredStringLinearWithAttributes(at, string, std::move(measurements), attr);
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::WriteStringLinearKeepAttributes(const til::point& at, const std::wstring_view& string)
|
||||
{
|
||||
auto [measurements, colCount] = _MeasureStringAndCountColumns(string);
|
||||
return _WriteMeasuredStringLinearWithAttributes(at, string, std::move(measurements), no_attributes{});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
TextBuffer::WriteResult TextBuffer::_FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, [[maybe_unused]] const T& attr)
|
||||
{
|
||||
const uint8_t width{ IsGlyphFullWidth(character) ? uint8_t{ 2u } : uint8_t{ 1u } };
|
||||
auto out = _WriteRectangular(
|
||||
region,
|
||||
[&](WriteResult& /* result */, auto&& at, auto&& size) {
|
||||
auto& row = GetRowByOffset(at.y());
|
||||
row.Fill(region.left(), size / width, character, width);
|
||||
if constexpr (std::is_same<T, TextAttribute>::value)
|
||||
{
|
||||
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), size), attr);
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
TextBuffer::WriteResult TextBuffer::_FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, [[maybe_unused]] const T& attr)
|
||||
{
|
||||
const uint8_t width{ IsGlyphFullWidth(character) ? uint8_t{ 2u } : uint8_t{ 1u } };
|
||||
return _WriteLinear(
|
||||
at,
|
||||
[&](WriteResult& result, auto&& at, auto&& size) -> bool {
|
||||
// size is how many *columns* we have left to fill, k?
|
||||
const auto w{ ::base::ClampMin(::base::saturated_cast<uint16_t>(size), count * width) };
|
||||
|
||||
auto& row = GetRowByOffset(at.y());
|
||||
const auto consumedWchars = row.Fill(at.x(), count, character, width);
|
||||
if constexpr (std::is_same<T, TextAttribute>::value)
|
||||
{
|
||||
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), w), attr);
|
||||
}
|
||||
|
||||
result.charactersWritten += consumedWchars;
|
||||
result.columnsWritten += w.RawValue();
|
||||
|
||||
const Viewport paint = Viewport::FromDimensions(at, { w.Cast<SHORT>(), 1 });
|
||||
_NotifyPaint(paint);
|
||||
|
||||
return (count -= consumedWchars) > 0; // keep going if data remains
|
||||
});
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::FillWithCharacterRectangular(const til::rectangle& region, const wchar_t character)
|
||||
{
|
||||
return _FillWithCharacterAndAttributeRectangular(region, character, no_attributes{});
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const TextAttribute& attr)
|
||||
{
|
||||
return _FillWithCharacterAndAttributeRectangular(region, character, attr);
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::FillWithCharacterLinear(const til::point& at, size_t count, const wchar_t character)
|
||||
{
|
||||
return _FillWithCharacterAndAttributeLinear(at, count, character, no_attributes{});
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const TextAttribute& attr)
|
||||
{
|
||||
return _FillWithCharacterAndAttributeLinear(at, count, character, attr);
|
||||
}
|
||||
|
||||
TextBuffer::WriteResult TextBuffer::WriteRowImage(const til::point at, const RowImage& ri)
|
||||
{
|
||||
auto& row{ GetRowByOffset(at.y()) };
|
||||
row.Reinsert(at.x<uint16_t>(), ri);
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename TLambda>
|
||||
TextBuffer::WriteResult TextBuffer::_WriteRectangular(const til::rectangle& rect, TLambda&& thing)
|
||||
{
|
||||
WriteResult out{};
|
||||
for (auto y{ rect.top() }; y < rect.bottom(); ++y)
|
||||
{
|
||||
thing(out, til::point{ rect.left(), y }, rect.width());
|
||||
}
|
||||
_NotifyPaint(Viewport::FromDimensions(rect.origin(), rect.size()));
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename TLambda>
|
||||
TextBuffer::WriteResult TextBuffer::_WriteLinear(const til::point& start, TLambda&& thing)
|
||||
{
|
||||
auto point{ start };
|
||||
WriteResult out{};
|
||||
while (_size.IsInBounds(point) && thing(out, point, _size.Width() - point.x()))
|
||||
{
|
||||
point = { 0, point.y() };
|
||||
};
|
||||
return out;
|
||||
}
|
||||
|
|
|
@ -54,7 +54,6 @@ filling in the last row, and updating the screen.
|
|||
#include "cursor.h"
|
||||
#include "Row.hpp"
|
||||
#include "TextAttribute.hpp"
|
||||
#include "UnicodeStorage.hpp"
|
||||
#include "../types/inc/Viewport.hpp"
|
||||
|
||||
#include "../buffer/out/textBufferCellIterator.hpp"
|
||||
|
@ -86,19 +85,6 @@ public:
|
|||
TextBufferTextIterator GetTextDataAt(const COORD at, const Microsoft::Console::Types::Viewport limit) const;
|
||||
|
||||
// Text insertion functions
|
||||
OutputCellIterator Write(const OutputCellIterator givenIt);
|
||||
|
||||
OutputCellIterator Write(const OutputCellIterator givenIt,
|
||||
const COORD target,
|
||||
const std::optional<bool> wrap = true);
|
||||
|
||||
OutputCellIterator WriteLine(const OutputCellIterator givenIt,
|
||||
const COORD target,
|
||||
const std::optional<bool> setWrap = std::nullopt,
|
||||
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 IncrementCursor();
|
||||
bool NewlineCursor();
|
||||
|
||||
|
@ -136,9 +122,6 @@ public:
|
|||
|
||||
[[nodiscard]] HRESULT ResizeTraditional(const COORD newSize) noexcept;
|
||||
|
||||
const UnicodeStorage& GetUnicodeStorage() const noexcept;
|
||||
UnicodeStorage& GetUnicodeStorage() noexcept;
|
||||
|
||||
Microsoft::Console::Render::IRenderTarget& GetRenderTarget() noexcept;
|
||||
|
||||
const COORD GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false) const;
|
||||
|
@ -200,6 +183,30 @@ public:
|
|||
void CopyPatterns(const TextBuffer& OtherBuffer);
|
||||
interval_tree::IntervalTree<til::point, size_t> GetPatterns(const size_t firstRow, const size_t lastRow) const;
|
||||
|
||||
struct WriteResult
|
||||
{
|
||||
size_t charactersWritten;
|
||||
size_t charactersRead;
|
||||
size_t columnsWritten;
|
||||
size_t columnsRead;
|
||||
};
|
||||
|
||||
WriteResult WriteMeasuredStringLinear(const til::point& at, const std::wstring_view& string, RowMeasurementBuffer measurements);
|
||||
|
||||
WriteResult WriteStringLinearWithAttributes(const til::point& at, const std::wstring_view& string, const TextAttribute& attr);
|
||||
WriteResult WriteStringLinearKeepAttributes(const til::point& at, const std::wstring_view& string);
|
||||
|
||||
WriteResult FillWithCharacterRectangular(const til::rectangle& region, const wchar_t character);
|
||||
WriteResult FillWithCharacterLinear(const til::point& at, size_t count, const wchar_t character);
|
||||
|
||||
WriteResult FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const TextAttribute& attr);
|
||||
WriteResult FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const TextAttribute& attr);
|
||||
|
||||
WriteResult FillWithAttributeRectangular(const til::rectangle& region, const TextAttribute& attribute);
|
||||
WriteResult FillWithAttributeLinear(const til::point at, size_t count, const TextAttribute& attribute);
|
||||
|
||||
WriteResult WriteRowImage(const til::point at, const RowImage& ri);
|
||||
|
||||
private:
|
||||
void _UpdateSize();
|
||||
Microsoft::Console::Types::Viewport _size;
|
||||
|
@ -210,9 +217,6 @@ private:
|
|||
|
||||
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;
|
||||
|
@ -230,12 +234,7 @@ private:
|
|||
|
||||
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;
|
||||
|
||||
|
@ -247,6 +246,21 @@ private:
|
|||
|
||||
void _PruneHyperlinks();
|
||||
|
||||
template<typename TLambda>
|
||||
WriteResult _WriteRectangular(const til::rectangle& rect, TLambda&& thing);
|
||||
|
||||
template<typename TLambda>
|
||||
WriteResult _WriteLinear(const til::point& start, TLambda&& thing);
|
||||
|
||||
template<typename T>
|
||||
WriteResult _FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const T& attr);
|
||||
|
||||
template<typename T>
|
||||
WriteResult _FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const T& attr);
|
||||
|
||||
template<typename T>
|
||||
WriteResult _WriteMeasuredStringLinearWithAttributes(const til::point& at, std::wstring_view string, RowMeasurementBuffer measurements, const T& attributes);
|
||||
|
||||
std::unordered_map<size_t, std::wstring> _idsAndPatterns;
|
||||
size_t _currentPatternId;
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
#include "textBufferCellIterator.hpp"
|
||||
|
||||
#include "CharRow.hpp"
|
||||
#include "textBuffer.hpp"
|
||||
#include "../types/inc/convert.hpp"
|
||||
#include "../types/inc/viewport.hpp"
|
||||
|
@ -165,9 +164,8 @@ TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& move
|
|||
_attrIter += diff;
|
||||
_view.UpdateTextAttribute(*_attrIter);
|
||||
|
||||
const CharRow& charRow = _pRow->GetCharRow();
|
||||
_view.UpdateText(charRow.GlyphAt(newX));
|
||||
_view.UpdateDbcsAttribute(charRow.DbcsAttrAt(newX));
|
||||
_view.UpdateText(_pRow->GlyphAt(newX));
|
||||
_view.UpdateDbcsAttribute(_pRow->DbcsAttrAt(newX));
|
||||
_pos.X = newX;
|
||||
}
|
||||
else
|
||||
|
@ -326,8 +324,8 @@ const ROW* TextBufferCellIterator::s_GetRow(const TextBuffer& buffer, const COOR
|
|||
// - Updates the internal view. Call after updating row, attribute, or positions.
|
||||
void TextBufferCellIterator::_GenerateView()
|
||||
{
|
||||
_view = OutputCellView(_pRow->GetCharRow().GlyphAt(_pos.X),
|
||||
_pRow->GetCharRow().DbcsAttrAt(_pos.X),
|
||||
_view = OutputCellView(_pRow->GlyphAt(_pos.X),
|
||||
_pRow->DbcsAttrAt(_pos.X),
|
||||
*_attrIter,
|
||||
TextAttributeBehavior::Stored);
|
||||
}
|
||||
|
|
|
@ -15,12 +15,12 @@ Author(s):
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "CharRow.hpp"
|
||||
#include "AttrRow.hpp"
|
||||
#include "OutputCellView.hpp"
|
||||
#include "../../types/inc/viewport.hpp"
|
||||
|
||||
class TextBuffer;
|
||||
class ROW;
|
||||
|
||||
class TextBufferCellIterator
|
||||
{
|
||||
|
@ -58,8 +58,8 @@ protected:
|
|||
|
||||
const ROW* _pRow;
|
||||
ATTR_ROW::const_iterator _attrIter;
|
||||
const TextBuffer& _buffer;
|
||||
const Microsoft::Console::Types::Viewport _bounds;
|
||||
std::reference_wrapper<const TextBuffer> _buffer;
|
||||
Microsoft::Console::Types::Viewport _bounds;
|
||||
bool _exceeded;
|
||||
COORD _pos;
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
#include "textBufferTextIterator.hpp"
|
||||
|
||||
#include "CharRow.hpp"
|
||||
#include "Row.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
|
|
|
@ -336,9 +336,7 @@ void CommandListPopup::_drawList()
|
|||
size_t lStringLength = Width();
|
||||
for (SHORT i = 0; i < Height(); ++i)
|
||||
{
|
||||
const OutputCellIterator spaces(UNICODE_SPACE, _attributes, lStringLength);
|
||||
const auto result = _screenInfo.Write(spaces, WriteCoord);
|
||||
lStringLength = result.GetCellDistance(spaces);
|
||||
_screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(WriteCoord, Width(), UNICODE_SPACE, _attributes);
|
||||
WriteCoord.Y += 1i16;
|
||||
}
|
||||
|
||||
|
@ -431,10 +429,7 @@ void CommandListPopup::_drawList()
|
|||
TextAttribute inverted = _attributes;
|
||||
inverted.Invert();
|
||||
|
||||
const OutputCellIterator it(inverted, lStringLength);
|
||||
const auto done = _screenInfo.Write(it, WriteCoord);
|
||||
|
||||
lStringLength = done.GetCellDistance(it);
|
||||
_screenInfo.GetTextBuffer().FillWithAttributeRectangular(til::rectangle{ WriteCoord, til::size{ Width(), 1 } }, inverted);
|
||||
}
|
||||
|
||||
WriteCoord.Y += 1;
|
||||
|
@ -529,21 +524,13 @@ void CommandListPopup::_updateHighlight(const SHORT OldCurrentCommand, const SHO
|
|||
}
|
||||
COORD WriteCoord;
|
||||
WriteCoord.X = _region.Left + 1i16;
|
||||
size_t lStringLength = Width();
|
||||
|
||||
WriteCoord.Y = _region.Top + 1i16 + OldCurrentCommand - TopIndex;
|
||||
|
||||
const OutputCellIterator it(_attributes, lStringLength);
|
||||
const auto done = _screenInfo.Write(it, WriteCoord);
|
||||
lStringLength = done.GetCellDistance(it);
|
||||
_screenInfo.GetTextBuffer().FillWithAttributeRectangular(til::rectangle{ WriteCoord, til::size{ Width(), 1 } }, _attributes);
|
||||
|
||||
// highlight new command
|
||||
WriteCoord.Y = _region.Top + 1i16 + NewCurrentCommand - TopIndex;
|
||||
|
||||
// inverted attributes
|
||||
TextAttribute inverted = _attributes;
|
||||
inverted.Invert();
|
||||
const OutputCellIterator itAttr(inverted, lStringLength);
|
||||
const auto doneAttr = _screenInfo.Write(itAttr, WriteCoord);
|
||||
lStringLength = done.GetCellDistance(itAttr);
|
||||
_screenInfo.GetTextBuffer().FillWithAttributeRectangular(til::rectangle{ WriteCoord, til::size{ Width(), 1 } }, inverted);
|
||||
}
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
#include "handle.h"
|
||||
#include "misc.h"
|
||||
|
||||
#include "../buffer/out/CharRow.hpp"
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
#include "../types/inc/Viewport.hpp"
|
||||
#include "../types/inc/convert.hpp"
|
||||
|
@ -91,10 +89,48 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region)
|
|||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
const OutputCellIterator it(attrs);
|
||||
const auto done = screenInfo.Write(it, target);
|
||||
//const OutputCellIterator it(attrs);
|
||||
//const auto done = screenInfo.Write(it, target);
|
||||
__debugbreak();
|
||||
// TODO(DH) restore writing runs of attributes
|
||||
////////////////
|
||||
#if 0
|
||||
static rle_container rle_encode(const basic_container_view& from)
|
||||
{
|
||||
if (from.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
used = done.GetCellDistance(it);
|
||||
rle_container to;
|
||||
value_type value = from.front();
|
||||
size_type length = 0;
|
||||
|
||||
for (auto v : from)
|
||||
{
|
||||
if (v != value)
|
||||
{
|
||||
to.emplace_back(value, length);
|
||||
value = v;
|
||||
length = 0;
|
||||
}
|
||||
|
||||
length++;
|
||||
}
|
||||
|
||||
if (length)
|
||||
{
|
||||
to.emplace_back(value, length);
|
||||
}
|
||||
|
||||
return to;
|
||||
}
|
||||
#endif
|
||||
|
||||
////////////////
|
||||
|
||||
used = 0;
|
||||
//used = done.GetCellDistance(it);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
@ -133,9 +169,8 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region)
|
|||
|
||||
try
|
||||
{
|
||||
OutputCellIterator it(chars);
|
||||
const auto finished = screenInfo.Write(it, target);
|
||||
used = finished.GetInputDistance(it);
|
||||
const auto result = screenInfo.GetTextBuffer().WriteStringLinearKeepAttributes(target, chars);
|
||||
used = result.charactersWritten;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
|
@ -218,11 +253,8 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region)
|
|||
|
||||
try
|
||||
{
|
||||
TextAttribute useThisAttr(attribute);
|
||||
const OutputCellIterator it(useThisAttr, lengthToWrite);
|
||||
const auto done = screenBuffer.Write(it, startingCoordinate);
|
||||
|
||||
cellsModified = done.GetCellDistance(it);
|
||||
const auto result = screenBuffer.GetTextBuffer().FillWithAttributeLinear(startingCoordinate, lengthToWrite, TextAttribute{ attribute });
|
||||
cellsModified = result.columnsWritten;
|
||||
|
||||
if (screenBuffer.HasAccessibilityEventing())
|
||||
{
|
||||
|
@ -279,12 +311,11 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region)
|
|||
HRESULT hr = S_OK;
|
||||
try
|
||||
{
|
||||
const OutputCellIterator it(character, lengthToWrite);
|
||||
|
||||
// when writing to the buffer, specifically unset wrap if we get to the last column.
|
||||
// a fill operation should UNSET wrap in that scenario. See GH #1126 for more details.
|
||||
const auto done = screenInfo.Write(it, startingCoordinate, false);
|
||||
cellsModified = done.GetInputDistance(it);
|
||||
// TODO(DH) maintain above fix to clear wrap ^^^
|
||||
const auto result = screenInfo.GetTextBuffer().FillWithCharacterLinear(startingCoordinate, lengthToWrite, character);
|
||||
cellsModified = result.columnsWritten;
|
||||
|
||||
// Notify accessibility
|
||||
if (screenInfo.HasAccessibilityEventing())
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "../types/inc/Viewport.hpp"
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
#include "../types/inc/Utf16Parser.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
using namespace Microsoft::Console::Types;
|
||||
|
@ -339,7 +340,9 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
COORD CursorPosition = cursor.GetPosition();
|
||||
NTSTATUS Status = STATUS_SUCCESS;
|
||||
SHORT XPosition;
|
||||
WCHAR LocalBuffer[LOCAL_BUFFER_SIZE];
|
||||
std::wstring local;
|
||||
local.reserve(LOCAL_BUFFER_SIZE);
|
||||
RowMeasurementBuffer measurements;
|
||||
size_t TempNumSpaces = 0;
|
||||
const bool fUnprocessed = WI_IsFlagClear(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT);
|
||||
const bool fWrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
|
||||
|
@ -351,6 +354,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
const size_t BufferSize = *pcb;
|
||||
*pcb = 0;
|
||||
|
||||
const wchar_t* const base = pwchRealUnicode;
|
||||
const wchar_t* lpString = pwchRealUnicode;
|
||||
|
||||
COORD coordScreenBufferSize = screenInfo.GetBufferSize().Dimensions();
|
||||
|
@ -387,8 +391,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
// As an optimization, collect characters in buffer and print out all at once.
|
||||
XPosition = cursor.GetPosition().X;
|
||||
size_t i = 0;
|
||||
wchar_t* LocalBufPtr = LocalBuffer;
|
||||
while (*pcb < BufferSize && i < LOCAL_BUFFER_SIZE && XPosition < coordScreenBufferSize.X)
|
||||
while (*pcb < BufferSize && XPosition < coordScreenBufferSize.X)
|
||||
{
|
||||
#pragma prefast(suppress : 26019, "Buffer is taken in multiples of 2. Validation is ok.")
|
||||
const wchar_t Char = *lpString;
|
||||
|
@ -396,19 +399,21 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
// WCL-NOTE: to be identical to lpString. They are incremented in lockstep, never separately, and lpString
|
||||
// WCL-NOTE: is initialized from pwchRealUnicode.
|
||||
const wchar_t RealUnicodeChar = *pwchRealUnicode;
|
||||
const auto cbuf{ Utf16Parser::ParseNext(std::wstring_view{ pwchRealUnicode, (BufferSize / sizeof(WCHAR)) - static_cast<size_t>(pwchRealUnicode - base) }) };
|
||||
if (IS_GLYPH_CHAR(RealUnicodeChar) || fUnprocessed)
|
||||
{
|
||||
// WCL-NOTE: This operates on a single code unit instead of a whole codepoint. It will mis-measure surrogate pairs.
|
||||
if (IsGlyphFullWidth(Char))
|
||||
if (IsGlyphFullWidth(cbuf))
|
||||
{
|
||||
if (i < (LOCAL_BUFFER_SIZE - 1) && XPosition < (coordScreenBufferSize.X - 1))
|
||||
if (XPosition < (coordScreenBufferSize.X - 1))
|
||||
{
|
||||
*LocalBufPtr++ = Char;
|
||||
local.append(cbuf);
|
||||
measurements.append(uint8_t{ 2u });
|
||||
|
||||
// cursor adjusted by 2 because the char is double width
|
||||
XPosition += 2;
|
||||
i += 1;
|
||||
pwchBuffer++;
|
||||
i += cbuf.size();
|
||||
pwchBuffer += cbuf.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -417,11 +422,15 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
}
|
||||
else
|
||||
{
|
||||
*LocalBufPtr = Char;
|
||||
LocalBufPtr++;
|
||||
local.append(cbuf);
|
||||
measurements.append(uint8_t{ 1u });
|
||||
XPosition++;
|
||||
i++;
|
||||
pwchBuffer++;
|
||||
i += cbuf.size();
|
||||
pwchBuffer += cbuf.size();
|
||||
}
|
||||
for (auto z = cbuf.size(); z > 1; --z)
|
||||
{
|
||||
measurements.append(uint8_t{ 0 });
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -459,11 +468,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
goto EndWhile;
|
||||
}
|
||||
|
||||
for (ULONG j = 0; j < TabSize && i < LOCAL_BUFFER_SIZE; j++, i++)
|
||||
{
|
||||
*LocalBufPtr = UNICODE_SPACE;
|
||||
LocalBufPtr++;
|
||||
}
|
||||
local.append(TabSize, UNICODE_SPACE);
|
||||
|
||||
pwchBuffer++;
|
||||
break;
|
||||
|
@ -477,32 +482,30 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
if ((dwFlags & WC_PRINTABLE_CONTROL_CHARS) && (IS_CONTROL_CHAR(RealUnicodeChar)))
|
||||
{
|
||||
CtrlChar:
|
||||
if (i < (LOCAL_BUFFER_SIZE - 1))
|
||||
//if (i < (LOCAL_BUFFER_SIZE - 1))
|
||||
{
|
||||
// WCL-NOTE: We do not properly measure that there is space for two characters
|
||||
// WCL-NOTE: left on the screen.
|
||||
*LocalBufPtr = (WCHAR)'^';
|
||||
LocalBufPtr++;
|
||||
local.append(1, L'^');
|
||||
XPosition++;
|
||||
i++;
|
||||
|
||||
*LocalBufPtr = (WCHAR)(RealUnicodeChar + (WCHAR)'@');
|
||||
LocalBufPtr++;
|
||||
local.append(1, (WCHAR)(RealUnicodeChar + (WCHAR)'@'));
|
||||
XPosition++;
|
||||
i++;
|
||||
|
||||
pwchBuffer++;
|
||||
}
|
||||
else
|
||||
{
|
||||
goto EndWhile;
|
||||
}
|
||||
//else
|
||||
//{
|
||||
//goto EndWhile;
|
||||
//}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Char == UNICODE_NULL)
|
||||
{
|
||||
*LocalBufPtr = UNICODE_SPACE;
|
||||
local.append(1, UNICODE_SPACE);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -513,11 +516,13 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
GetStringTypeW(CT_CTYPE1, &RealUnicodeChar, 1, &CharType);
|
||||
if (WI_IsFlagSet(CharType, C1_CNTRL))
|
||||
{
|
||||
WCHAR ch{};
|
||||
ConvertOutputToUnicode(gci.OutputCP,
|
||||
(LPSTR)&RealUnicodeChar,
|
||||
1,
|
||||
LocalBufPtr,
|
||||
&ch,
|
||||
1);
|
||||
local.append(1, ch);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -525,20 +530,19 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
// WCL-NOTE: 1. Normal characters are handled via the early check for IS_GLYPH_CHAR
|
||||
// WCL-NOTE: 2. Control characters are handled via the CtrlChar label (if WC_PRINTABLE_CONTROL_CHARS is on)
|
||||
// WCL-NOTE: And if they are control characters they will trigger the C1_CNTRL check above.
|
||||
*LocalBufPtr = Char;
|
||||
local.append(1, Char);
|
||||
}
|
||||
}
|
||||
|
||||
LocalBufPtr++;
|
||||
XPosition++;
|
||||
i++;
|
||||
pwchBuffer++;
|
||||
}
|
||||
}
|
||||
}
|
||||
lpString++;
|
||||
pwchRealUnicode++;
|
||||
*pcb += sizeof(WCHAR);
|
||||
lpString += cbuf.size();
|
||||
pwchRealUnicode += cbuf.size();
|
||||
*pcb += cbuf.size() * sizeof(WCHAR);
|
||||
}
|
||||
EndWhile:
|
||||
if (i != 0)
|
||||
|
@ -553,8 +557,12 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
}
|
||||
|
||||
// line was wrapped if we're writing up to the end of the current row
|
||||
OutputCellIterator it(std::wstring_view(LocalBuffer, i), Attributes);
|
||||
const auto itEnd = screenInfo.Write(it);
|
||||
//OutputCellIterator it(std::wstring_view{ local }, Attributes);
|
||||
//const auto itEnd = screenInfo.Write(it);
|
||||
measurements.resize_trailing_extent(gsl::narrow_cast<uint16_t>(local.size()));
|
||||
auto result = screenInfo.GetTextBuffer().WriteMeasuredStringLinear(CursorPosition, local, measurements);
|
||||
local.clear();
|
||||
measurements.resize_trailing_extent(0);
|
||||
|
||||
// Notify accessibility
|
||||
if (screenInfo.HasAccessibilityEventing())
|
||||
|
@ -564,7 +572,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
|
||||
// The number of "spaces" or "cells" we have consumed needs to be reported and stored for later
|
||||
// when/if we need to erase the command line.
|
||||
TempNumSpaces += itEnd.GetCellDistance(it);
|
||||
TempNumSpaces += result.columnsWritten; //itEnd.GetCellDistance(it);
|
||||
// WCL-NOTE: We are using the "estimated" X position delta instead of the actual delta from
|
||||
// WCL-NOTE: the iterator. It is not clear why. If they differ, the cursor ends up in the
|
||||
// WCL-NOTE: wrong place (typically inside another character).
|
||||
|
@ -702,7 +710,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
{
|
||||
try
|
||||
{
|
||||
screenInfo.Write(OutputCellIterator(UNICODE_SPACE, Attributes, 1), CursorPosition);
|
||||
screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(CursorPosition, 1, UNICODE_SPACE, Attributes);
|
||||
Status = STATUS_SUCCESS;
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
@ -720,7 +728,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
{
|
||||
try
|
||||
{
|
||||
screenInfo.Write(OutputCellIterator(UNICODE_SPACE, Attributes, 1), CursorPosition);
|
||||
screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(CursorPosition, 1, UNICODE_SPACE, Attributes);
|
||||
Status = STATUS_SUCCESS;
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
@ -742,7 +750,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
{
|
||||
try
|
||||
{
|
||||
screenInfo.Write(OutputCellIterator(UNICODE_SPACE, Attributes, 1), cursor.GetPosition());
|
||||
screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(cursor.GetPosition(), 1, UNICODE_SPACE, Attributes);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
@ -797,9 +805,8 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
|
||||
try
|
||||
{
|
||||
const OutputCellIterator it(UNICODE_SPACE, Attributes, NumChars);
|
||||
const auto done = screenInfo.Write(it, cursor.GetPosition());
|
||||
NumChars = done.GetCellDistance(it);
|
||||
const auto result = screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(cursor.GetPosition(), NumChars, UNICODE_SPACE, Attributes);
|
||||
NumChars = result.columnsWritten;
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
|
@ -850,22 +857,18 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
{
|
||||
const COORD TargetPoint = cursor.GetPosition();
|
||||
ROW& Row = textBuffer.GetRowByOffset(TargetPoint.Y);
|
||||
const CharRow& charRow = Row.GetCharRow();
|
||||
|
||||
try
|
||||
{
|
||||
// If we're on top of a trailing cell, clear it and the previous cell.
|
||||
if (charRow.DbcsAttrAt(TargetPoint.X).IsTrailing())
|
||||
if (Row.DbcsAttrAt(TargetPoint.X).IsTrailing())
|
||||
{
|
||||
// Space to clear for 2 cells.
|
||||
OutputCellIterator it(UNICODE_SPACE, 2);
|
||||
|
||||
// Back target point up one.
|
||||
auto writeTarget = TargetPoint;
|
||||
writeTarget.X--;
|
||||
|
||||
// Write 2 clear cells.
|
||||
screenInfo.Write(it, writeTarget);
|
||||
screenInfo.GetTextBuffer().FillWithCharacterLinear(writeTarget, 2, UNICODE_SPACE);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
|
|
|
@ -228,7 +228,7 @@ void DeleteCommandLine(COOKED_READ_DATA& cookedReadData, const bool fUpdateField
|
|||
|
||||
try
|
||||
{
|
||||
cookedReadData.ScreenInfo().Write(OutputCellIterator(UNICODE_SPACE, CharsToWrite), coordOriginalCursor);
|
||||
cookedReadData.ScreenInfo().GetTextBuffer().FillWithCharacterLinear(coordOriginalCursor, CharsToWrite, UNICODE_SPACE);
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
|
|
|
@ -109,11 +109,10 @@ void ConversionAreaInfo::SetAttributes(const TextAttribute& attr)
|
|||
// Arguments:
|
||||
// - text - Text to insert into the conversion area buffer
|
||||
// - column - Column to start at (X position)
|
||||
void ConversionAreaInfo::WriteText(const std::vector<OutputCell>& text,
|
||||
void ConversionAreaInfo::WriteText(const RowImage& text,
|
||||
const SHORT column)
|
||||
{
|
||||
gsl::span<const OutputCell> view(text.data(), text.size());
|
||||
_screenBuffer->Write(view, { column, 0 });
|
||||
_screenBuffer->GetTextBuffer().WriteRowImage(til::point{ column, 0 }, text);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
|
|
@ -61,7 +61,7 @@ public:
|
|||
void SetWindowInfo(const SMALL_RECT view) noexcept;
|
||||
void Paint() const noexcept;
|
||||
|
||||
void WriteText(const std::vector<OutputCell>& text, const SHORT column);
|
||||
void WriteText(const RowImage& text, const SHORT column);
|
||||
void SetAttributes(const TextAttribute& attr);
|
||||
|
||||
const TextBuffer& GetTextBuffer() const noexcept;
|
||||
|
|
|
@ -217,76 +217,60 @@ TextAttribute ConsoleImeInfo::s_RetrieveAttributeAt(const size_t pos,
|
|||
// - colorArray - Array of color values provided by the IME.
|
||||
// Return Value:
|
||||
// - Vector of OutputCells where each one represents one cell of the output buffer.
|
||||
std::vector<OutputCell> ConsoleImeInfo::s_ConvertToCells(const std::wstring_view text,
|
||||
const gsl::span<const BYTE> attributes,
|
||||
const gsl::span<const WORD> colorArray)
|
||||
RowImage ConsoleImeInfo::s_ConvertToCells(std::wstring_view text,
|
||||
const gsl::span<const BYTE> attributes,
|
||||
const gsl::span<const WORD> colorArray)
|
||||
{
|
||||
RowImage e;
|
||||
std::vector<OutputCell> cells;
|
||||
|
||||
// - Convert incoming wchar_t stream into UTF-16 units.
|
||||
const auto glyphs = Utf16Parser::Parse(text);
|
||||
|
||||
// - Walk through all of the grouped up text, match up the correct attribute to it, and make a new cell.
|
||||
size_t attributesUsed = 0;
|
||||
for (const auto& parsedGlyph : glyphs)
|
||||
size_t attributesUsed{};
|
||||
while (!text.empty())
|
||||
{
|
||||
const std::wstring_view glyph{ parsedGlyph.data(), parsedGlyph.size() };
|
||||
// Collect up attributes that apply to this glyph range.
|
||||
auto drawingAttr = s_RetrieveAttributeAt(attributesUsed, attributes, colorArray);
|
||||
attributesUsed++;
|
||||
const auto glyph{ Utf16Parser::ParseNext(text) };
|
||||
text = text.substr(glyph.size());
|
||||
const uint8_t width{ gsl::narrow_cast<uint8_t>(IsGlyphFullWidth(glyph) ? 2u : 1u) };
|
||||
|
||||
// The IME gave us an attribute for every glyph position in a surrogate pair.
|
||||
// But the only important information will be the cursor position.
|
||||
// Check all additional attributes to see if the cursor resides on top of them.
|
||||
for (size_t i = 1; i < glyph.size(); i++)
|
||||
{
|
||||
TextAttribute additionalAttr = s_RetrieveAttributeAt(attributesUsed, attributes, colorArray);
|
||||
attributesUsed++;
|
||||
if (additionalAttr.IsLeftVerticalDisplayed())
|
||||
{
|
||||
drawingAttr.SetLeftVerticalDisplayed(true);
|
||||
}
|
||||
if (additionalAttr.IsRightVerticalDisplayed())
|
||||
{
|
||||
drawingAttr.SetRightVerticalDisplayed(true);
|
||||
}
|
||||
// add glyph
|
||||
e._data.append(glyph);
|
||||
|
||||
// register its width
|
||||
e._cwid.append(width);
|
||||
for (auto z{ glyph.size() }; z > 1; --z)
|
||||
{ // add a trailing 0-width for every surrogate pair trail
|
||||
e._cwid.append(0);
|
||||
}
|
||||
|
||||
// We have to determine if the glyph range is 1 column or two.
|
||||
// If it's full width, it's two, and we need to make sure we don't draw the cursor
|
||||
// right down the middle of the character.
|
||||
// Otherwise it's one column and we'll push it in with the default empty DbcsAttribute.
|
||||
DbcsAttribute dbcsAttr;
|
||||
if (IsGlyphFullWidth(glyph))
|
||||
auto drawingAttr = s_RetrieveAttributeAt(attributesUsed++, attributes, colorArray);
|
||||
if (width == 1 || !(drawingAttr.IsLeftVerticalDisplayed() || drawingAttr.IsRightVerticalDisplayed()))
|
||||
{
|
||||
auto leftHalfAttr = drawingAttr;
|
||||
auto rightHalfAttr = drawingAttr;
|
||||
|
||||
// Don't draw lines in the middle of full width glyphs.
|
||||
// If we need a right vertical, don't apply it to the left side of the character
|
||||
if (leftHalfAttr.IsRightVerticalDisplayed())
|
||||
for (auto z{ width }; z > 0; --z) // add an attribute for each cell covered
|
||||
{
|
||||
leftHalfAttr.SetRightVerticalDisplayed(false);
|
||||
e._attrRow._data.append(drawingAttr);
|
||||
}
|
||||
|
||||
dbcsAttr.SetLeading();
|
||||
cells.emplace_back(glyph, dbcsAttr, leftHalfAttr);
|
||||
dbcsAttr.SetTrailing();
|
||||
|
||||
// If we need a left vertical, don't apply it to the right side of the character
|
||||
if (rightHalfAttr.IsLeftVerticalDisplayed())
|
||||
{
|
||||
rightHalfAttr.SetLeftVerticalDisplayed(false);
|
||||
}
|
||||
cells.emplace_back(glyph, dbcsAttr, rightHalfAttr);
|
||||
}
|
||||
else
|
||||
{
|
||||
cells.emplace_back(glyph, dbcsAttr, drawingAttr);
|
||||
}
|
||||
}
|
||||
// the width was >1 and the attribute contained the left or the right cursor indicator; split them into [ ___ ] (left, middle, right)
|
||||
auto first = drawingAttr;
|
||||
first.SetRightVerticalDisplayed(false);
|
||||
auto middle = drawingAttr;
|
||||
middle.SetLeftVerticalDisplayed(false);
|
||||
middle.SetRightVerticalDisplayed(false);
|
||||
auto last = drawingAttr;
|
||||
last.SetLeftVerticalDisplayed(false);
|
||||
|
||||
return cells;
|
||||
e._attrRow._data.append(first);
|
||||
for (auto z{ width }; z > 2; --z)
|
||||
{
|
||||
e._attrRow._data.append(middle);
|
||||
}
|
||||
e._attrRow._data.append(last);
|
||||
}
|
||||
|
||||
e._width += width;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
@ -308,81 +292,55 @@ std::vector<OutputCell> ConsoleImeInfo::s_ConvertToCells(const std::wstring_view
|
|||
// However, if text couldn't fit in our line (full-width character starting at the very last cell)
|
||||
// then we will give back the same begin and update the position for the next call to try again.
|
||||
// If the viewport is deemed too small, we'll skip past it and advance begin past the entire full-width character.
|
||||
std::vector<OutputCell>::const_iterator ConsoleImeInfo::_WriteConversionArea(const std::vector<OutputCell>::const_iterator begin,
|
||||
const std::vector<OutputCell>::const_iterator end,
|
||||
COORD& pos,
|
||||
const Microsoft::Console::Types::Viewport view,
|
||||
SCREEN_INFORMATION& screenInfo)
|
||||
void ConsoleImeInfo::_WriteConversionArea(const RowImage& cells,
|
||||
COORD pos,
|
||||
const Microsoft::Console::Types::Viewport view,
|
||||
SCREEN_INFORMATION& screenInfo)
|
||||
{
|
||||
// The position in the viewport where we will start inserting cells for this conversion area
|
||||
// NOTE: We might exit early if there's not enough space to fit here, so we take a copy of
|
||||
// the original and increment it up front.
|
||||
const auto insertionPos = pos;
|
||||
|
||||
// Advance the cursor position to set up the next call for success (insert the next conversion area
|
||||
// at the beginning of the following line)
|
||||
pos.X = view.Left();
|
||||
pos.Y++;
|
||||
|
||||
// The index of the last column in the viewport. (view is inclusive)
|
||||
const auto finalViewColumn = view.RightInclusive();
|
||||
|
||||
// The maximum number of cells we can insert into a line.
|
||||
const auto lineWidth = finalViewColumn - insertionPos.X + 1; // +1 because view was inclusive
|
||||
|
||||
// The iterator to the beginning position to form our line
|
||||
const auto lineBegin = begin;
|
||||
|
||||
// The total number of cells we could insert.
|
||||
const auto size = end - begin;
|
||||
FAIL_FAST_IF(size <= 0); // It's a programming error to have <= 0 cells to insert.
|
||||
|
||||
// The end is the smaller of the remaining number of cells or the amount of line cells we can write before
|
||||
// hitting the right edge of the viewport
|
||||
auto lineEnd = lineBegin + std::min(size, (ptrdiff_t)lineWidth);
|
||||
|
||||
// We must attempt to compensate for ending on a leading byte. We can't split a full-width character across lines.
|
||||
// As such, if the last item is a leading byte, back the end up by one.
|
||||
FAIL_FAST_IF(lineEnd <= lineBegin); // We should have at least 1 space we can back up.
|
||||
|
||||
// Get the last cell in the run and if it's a leading byte, move the end position back one so we don't
|
||||
// try to insert it.
|
||||
const auto lastCell = lineEnd - 1;
|
||||
if (lastCell->DbcsAttr().IsLeading())
|
||||
auto remain{ cells };
|
||||
do
|
||||
{
|
||||
lineEnd--;
|
||||
}
|
||||
// The maximum number of cells we can insert into a line.
|
||||
const auto lineWidth = finalViewColumn - pos.X + 1; // +1 because view was inclusive
|
||||
|
||||
// Copy out the substring into a vector.
|
||||
const std::vector<OutputCell> lineVec(lineBegin, lineEnd);
|
||||
auto [draw, left] = cells.split(::base::saturated_cast<uint16_t>(lineWidth));
|
||||
|
||||
// Add a conversion area to the internal state to hold this line.
|
||||
THROW_IF_FAILED(_AddConversionArea());
|
||||
// Add a conversion area to the internal state to hold this line.
|
||||
THROW_IF_FAILED(_AddConversionArea());
|
||||
|
||||
// Get the added conversion area.
|
||||
auto& area = ConvAreaCompStr.back();
|
||||
// Get the added conversion area.
|
||||
auto& area = ConvAreaCompStr.back();
|
||||
|
||||
// Write our text into the conversion area.
|
||||
area.WriteText(lineVec, insertionPos.X);
|
||||
area.WriteText(draw, pos.X);
|
||||
|
||||
// Set the viewport and positioning parameters for the conversion area to describe to the renderer
|
||||
// the appropriate location to overlay this conversion area on top of the main screen buffer inside the viewport.
|
||||
const SMALL_RECT region{ insertionPos.X, 0, gsl::narrow<SHORT>(insertionPos.X + lineVec.size() - 1), 0 };
|
||||
area.SetWindowInfo(region);
|
||||
area.SetViewPos({ 0 - view.Left(), insertionPos.Y - view.Top() });
|
||||
// Set the viewport and positioning parameters for the conversion area to describe to the renderer
|
||||
// the appropriate location to overlay this conversion area on top of the main screen buffer inside the viewport.
|
||||
const SMALL_RECT region{ pos.X, 0, gsl::narrow<SHORT>(pos.X + draw._width - 1), 0 };
|
||||
area.SetWindowInfo(region);
|
||||
area.SetViewPos({ 0 - view.Left(), pos.Y - view.Top() });
|
||||
|
||||
// Make it visible and paint it.
|
||||
area.SetHidden(false);
|
||||
area.Paint();
|
||||
// Make it visible and paint it.
|
||||
area.SetHidden(false);
|
||||
area.Paint();
|
||||
|
||||
// Notify accessibility that we have updated the text in this display region within the viewport.
|
||||
if (screenInfo.HasAccessibilityEventing())
|
||||
{
|
||||
screenInfo.NotifyAccessibilityEventing(insertionPos.X, insertionPos.Y, gsl::narrow<SHORT>(insertionPos.X + lineVec.size() - 1), insertionPos.Y);
|
||||
}
|
||||
// Notify accessibility that we have updated the text in this display region within the viewport.
|
||||
if (screenInfo.HasAccessibilityEventing())
|
||||
{
|
||||
screenInfo.NotifyAccessibilityEventing(pos.X, pos.Y, gsl::narrow<SHORT>(pos.X + draw._width - 1), pos.Y);
|
||||
}
|
||||
|
||||
// Hand back the iterator representing the end of what we used to be fed into the beginning of the next call.
|
||||
return lineEnd;
|
||||
if (remain._data.empty())
|
||||
break;
|
||||
|
||||
// Advance the cursor position to set up the next call for success (insert the next conversion area
|
||||
// at the beginning of the following line)
|
||||
pos.X = view.Left();
|
||||
pos.Y++;
|
||||
remain = std::move(left);
|
||||
} while (true);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
@ -432,17 +390,7 @@ void ConsoleImeInfo::_WriteUndeterminedChars(const std::wstring_view text,
|
|||
const auto view = screenInfo.GetViewport();
|
||||
// Set cursor position relative to viewport
|
||||
|
||||
// Set up our iterators. We will walk through the entire set of cells from beginning to end.
|
||||
// The first time, we will give the iterators as the whole span and the begin
|
||||
// will be moved forward by the conversion area write to set up the next call.
|
||||
auto begin = cells.cbegin();
|
||||
const auto end = cells.cend();
|
||||
|
||||
// Write over and over updating the beginning iterator until we reach the end.
|
||||
do
|
||||
{
|
||||
begin = _WriteConversionArea(begin, end, pos, view, screenInfo);
|
||||
} while (begin < end);
|
||||
_WriteConversionArea(cells, pos, view, screenInfo);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
|
|
@ -22,6 +22,7 @@ Revision History:
|
|||
#include "../buffer/out/OutputCell.hpp"
|
||||
#include "../buffer/out/TextAttribute.hpp"
|
||||
#include "../renderer/inc/FontInfo.hpp"
|
||||
#include "../buffer/out/Row.hpp"
|
||||
#include "../types/inc/viewport.hpp"
|
||||
|
||||
#include "conareainfo.h"
|
||||
|
@ -73,15 +74,14 @@ private:
|
|||
const gsl::span<const BYTE> attributes,
|
||||
const gsl::span<const WORD> colorArray);
|
||||
|
||||
static std::vector<OutputCell> s_ConvertToCells(const std::wstring_view text,
|
||||
const gsl::span<const BYTE> attributes,
|
||||
const gsl::span<const WORD> colorArray);
|
||||
static RowImage s_ConvertToCells(std::wstring_view text,
|
||||
const gsl::span<const BYTE> attributes,
|
||||
const gsl::span<const WORD> colorArray);
|
||||
|
||||
std::vector<OutputCell>::const_iterator _WriteConversionArea(const std::vector<OutputCell>::const_iterator begin,
|
||||
const std::vector<OutputCell>::const_iterator end,
|
||||
COORD& pos,
|
||||
const Microsoft::Console::Types::Viewport view,
|
||||
SCREEN_INFORMATION& screenInfo);
|
||||
void _WriteConversionArea(const RowImage& cells,
|
||||
COORD pos,
|
||||
const Microsoft::Console::Types::Viewport view,
|
||||
SCREEN_INFORMATION& screenInfo);
|
||||
|
||||
bool _isSavedCursorVisible;
|
||||
|
||||
|
|
|
@ -1010,8 +1010,9 @@ void EventsToUnicode(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents,
|
|||
const auto charInfos = gsl::span<const CHAR_INFO>(subspan.data(), subspan.size());
|
||||
|
||||
// Make the iterator and write to the target position.
|
||||
OutputCellIterator it(charInfos);
|
||||
storageBuffer.Write(it, target);
|
||||
__debugbreak();
|
||||
//OutputCellIterator it(charInfos);
|
||||
//storageBuffer.Write(it, target);
|
||||
}
|
||||
|
||||
// Since we've managed to write part of the request, return the clamped part that we actually used.
|
||||
|
|
|
@ -2206,8 +2206,9 @@ void DoSrvPrivateMoveToBottom(SCREEN_INFORMATION& screenInfo)
|
|||
fillAttrs.SetStandardErase();
|
||||
}
|
||||
|
||||
const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillLength };
|
||||
screenInfo.Write(fillData, startPosition, false);
|
||||
//const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillLength };
|
||||
//screenInfo.Write(fillData, startPosition, false);
|
||||
screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(startPosition, fillLength, fillChar, fillAttrs);
|
||||
|
||||
// Notify accessibility
|
||||
if (screenInfo.HasAccessibilityEventing())
|
||||
|
|
|
@ -104,8 +104,9 @@ static void _CopyRectangle(SCREEN_INFORMATION& screenInfo,
|
|||
|
||||
do
|
||||
{
|
||||
const auto data = OutputCell(*screenInfo.GetCellDataAt(sourcePos));
|
||||
screenInfo.Write(OutputCellIterator({ &data, 1 }), targetPos);
|
||||
__debugbreak();
|
||||
//const auto data = OutputCell(*screenInfo.GetCellDataAt(sourcePos));
|
||||
//screenInfo.Write(OutputCellIterator({ &data, 1 }), targetPos);
|
||||
|
||||
source.WalkInBounds(sourcePos, walkDirection);
|
||||
} while (target.WalkInBounds(targetPos, walkDirection));
|
||||
|
@ -339,8 +340,8 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
|
|||
const SMALL_RECT scrollRectGiven,
|
||||
const std::optional<SMALL_RECT> clipRectGiven,
|
||||
const COORD destinationOriginGiven,
|
||||
const wchar_t fillCharGiven,
|
||||
const TextAttribute fillAttrsGiven)
|
||||
wchar_t fillChar,
|
||||
TextAttribute fillAttrs)
|
||||
{
|
||||
// ------ 1. PREP SOURCE ------
|
||||
// Set up the source viewport.
|
||||
|
@ -380,15 +381,12 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
|
|||
return;
|
||||
}
|
||||
|
||||
// Determine the cell we will use to fill in any revealed/uncovered space.
|
||||
// We generally use exactly what was given to us.
|
||||
OutputCellIterator fillData(fillCharGiven, fillAttrsGiven);
|
||||
|
||||
// However, if the character is null and we were given a null attribute (represented as legacy 0),
|
||||
// then we'll just fill with spaces and whatever the buffer's default colors are.
|
||||
if (fillCharGiven == UNICODE_NULL && fillAttrsGiven == TextAttribute{ 0 })
|
||||
if (fillChar == UNICODE_NULL && fillAttrs == TextAttribute{ 0 })
|
||||
{
|
||||
fillData = OutputCellIterator(UNICODE_SPACE, screenInfo.GetAttributes());
|
||||
fillChar = UNICODE_SPACE;
|
||||
fillAttrs = screenInfo.GetAttributes();
|
||||
}
|
||||
|
||||
// ------ 4. PREP TARGET ------
|
||||
|
@ -447,10 +445,9 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
|
|||
const auto remaining = Viewport::Subtract(fill, target);
|
||||
|
||||
// Apply the fill data to each of the viewports we're given here.
|
||||
for (size_t i = 0; i < remaining.size(); i++)
|
||||
for (const auto& view : remaining)
|
||||
{
|
||||
const auto& view = remaining.at(i);
|
||||
screenInfo.WriteRect(fillData, view);
|
||||
screenInfo.GetTextBuffer().FillWithCharacterAndAttributeRectangular(til::rectangle{ view.Origin(), til::size{ view.Dimensions() } }, fillChar, fillAttrs);
|
||||
|
||||
// If we're scrolling an area that encompasses the full buffer width,
|
||||
// then the filled rows should also have their line rendition reset.
|
||||
|
|
|
@ -90,50 +90,46 @@ void Popup::_DrawBorder()
|
|||
COORD WriteCoord;
|
||||
WriteCoord.X = _region.Left;
|
||||
WriteCoord.Y = _region.Top;
|
||||
_screenInfo.Write(OutputCellIterator(_attributes, Width() + 2), WriteCoord);
|
||||
_screenInfo.GetTextBuffer().FillWithAttributeRectangular(_region, _attributes);
|
||||
|
||||
// draw upper left corner
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_DOWN_AND_RIGHT, 1), WriteCoord);
|
||||
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, 1, UNICODE_BOX_DRAW_LIGHT_DOWN_AND_RIGHT);
|
||||
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, 1, UNICODE_BOX_DRAW_LIGHT_DOWN_AND_RIGHT);
|
||||
|
||||
// draw upper bar
|
||||
WriteCoord.X += 1;
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_HORIZONTAL, Width()), WriteCoord);
|
||||
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, Width(), UNICODE_BOX_DRAW_LIGHT_HORIZONTAL);
|
||||
|
||||
// draw upper right corner
|
||||
WriteCoord.X = _region.Right;
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_DOWN_AND_LEFT, 1), WriteCoord);
|
||||
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, 1, UNICODE_BOX_DRAW_LIGHT_DOWN_AND_LEFT);
|
||||
|
||||
for (SHORT i = 0; i < Height(); i++)
|
||||
{
|
||||
WriteCoord.Y += 1;
|
||||
WriteCoord.X = _region.Left;
|
||||
|
||||
// fill attributes
|
||||
_screenInfo.Write(OutputCellIterator(_attributes, Width() + 2), WriteCoord);
|
||||
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_VERTICAL, 1), WriteCoord);
|
||||
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, 1, UNICODE_BOX_DRAW_LIGHT_VERTICAL);
|
||||
|
||||
WriteCoord.X = _region.Right;
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_VERTICAL, 1), WriteCoord);
|
||||
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, 1, UNICODE_BOX_DRAW_LIGHT_VERTICAL);
|
||||
}
|
||||
|
||||
// Draw bottom line.
|
||||
// Fill attributes of top line.
|
||||
WriteCoord.X = _region.Left;
|
||||
WriteCoord.Y = _region.Bottom;
|
||||
_screenInfo.Write(OutputCellIterator(_attributes, Width() + 2), WriteCoord);
|
||||
|
||||
// Draw bottom left corner.
|
||||
WriteCoord.X = _region.Left;
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_UP_AND_RIGHT, 1), WriteCoord);
|
||||
WriteCoord.Y = _region.Bottom;
|
||||
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, 1, UNICODE_BOX_DRAW_LIGHT_UP_AND_RIGHT);
|
||||
|
||||
// Draw lower bar.
|
||||
WriteCoord.X += 1;
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_HORIZONTAL, Width()), WriteCoord);
|
||||
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, Width(), UNICODE_BOX_DRAW_LIGHT_HORIZONTAL);
|
||||
|
||||
// draw lower right corner
|
||||
WriteCoord.X = _region.Right;
|
||||
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_UP_AND_LEFT, 1), WriteCoord);
|
||||
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, 1, UNICODE_BOX_DRAW_LIGHT_UP_AND_LEFT);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
@ -151,9 +147,7 @@ void Popup::_DrawPrompt(const UINT id)
|
|||
size_t lStringLength = Width();
|
||||
for (SHORT i = 0; i < Height(); i++)
|
||||
{
|
||||
const OutputCellIterator it(UNICODE_SPACE, _attributes, lStringLength);
|
||||
const auto done = _screenInfo.Write(it, WriteCoord);
|
||||
lStringLength = done.GetCellDistance(it);
|
||||
_screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(WriteCoord, lStringLength, UNICODE_SPACE, _attributes);
|
||||
|
||||
WriteCoord.Y += 1;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include "_output.h"
|
||||
#include "misc.h"
|
||||
#include "handle.h"
|
||||
#include "../buffer/out/CharRow.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
|
@ -2251,10 +2250,10 @@ void SCREEN_INFORMATION::SetViewport(const Viewport& newViewport,
|
|||
// i.e. the current background color, but with no meta attributes set.
|
||||
auto fillAttributes = GetAttributes();
|
||||
fillAttributes.SetStandardErase();
|
||||
auto fillPosition = COORD{ 0, _viewport.Top() };
|
||||
auto fillLength = gsl::narrow_cast<size_t>(_viewport.Height() * GetBufferSize().Width());
|
||||
auto fillData = OutputCellIterator{ fillAttributes, fillLength };
|
||||
Write(fillData, fillPosition, false);
|
||||
// TODO(DH): The original code moved Left to 0 and Right to Width
|
||||
_textBuffer->FillWithAttributeRectangular(til::rectangle{ _viewport.Origin(), til::size{ _viewport.Dimensions() } }, fillAttributes);
|
||||
// TODO(DH): THIS ALSO CLEARED THE WRAP ON ALL ROWS (!)
|
||||
//Write(fillData, fillPosition, false);
|
||||
|
||||
// Also reset the line rendition for the erased rows.
|
||||
_textBuffer->ResetLineRenditionRange(_viewport.Top(), _viewport.BottomExclusive());
|
||||
|
@ -2335,37 +2334,8 @@ OutputCellRect SCREEN_INFORMATION::ReadRect(const Viewport viewport) const
|
|||
return result;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Writes cells to the output buffer at the cursor position.
|
||||
// Arguments:
|
||||
// - it - Iterator representing output cell data to write.
|
||||
// Return Value:
|
||||
// - the iterator at its final position
|
||||
// Note:
|
||||
// - will throw exception on error.
|
||||
OutputCellIterator SCREEN_INFORMATION::Write(const OutputCellIterator it)
|
||||
{
|
||||
return _textBuffer->Write(it);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Writes cells to the output buffer.
|
||||
// Arguments:
|
||||
// - it - Iterator representing output cell data to write.
|
||||
// - target - The position to start writing at
|
||||
// - wrap - change the wrap flag if we hit the end of the row while writing and there's still more data
|
||||
// Return Value:
|
||||
// - the iterator at its final position
|
||||
// Note:
|
||||
// - will throw exception on error.
|
||||
OutputCellIterator SCREEN_INFORMATION::Write(const OutputCellIterator it,
|
||||
const COORD target,
|
||||
const std::optional<bool> wrap)
|
||||
{
|
||||
// NOTE: if wrap = true/false, we want to set the line's wrap to true/false (respectively) if we reach the end of the line
|
||||
return _textBuffer->Write(it, target, wrap);
|
||||
}
|
||||
|
||||
#if 0
|
||||
TODO(DH)
|
||||
// Routine Description:
|
||||
// - This routine writes a rectangular region into the screen buffer.
|
||||
// Arguments:
|
||||
|
@ -2389,6 +2359,7 @@ OutputCellIterator SCREEN_INFORMATION::WriteRect(const OutputCellIterator it,
|
|||
|
||||
return iter;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Routine Description:
|
||||
// - This routine writes a rectangular region into the screen buffer.
|
||||
|
@ -2402,6 +2373,10 @@ OutputCellIterator SCREEN_INFORMATION::WriteRect(const OutputCellIterator it,
|
|||
void SCREEN_INFORMATION::WriteRect(const OutputCellRect& data,
|
||||
const COORD location)
|
||||
{
|
||||
(void)data;
|
||||
(void)location;
|
||||
__debugbreak();
|
||||
#if 0
|
||||
for (size_t i = 0; i < data.Height(); i++)
|
||||
{
|
||||
const auto iter = data.GetRowIter(i);
|
||||
|
@ -2410,8 +2385,10 @@ void SCREEN_INFORMATION::WriteRect(const OutputCellRect& data,
|
|||
point.X = location.X;
|
||||
point.Y = location.Y + static_cast<short>(i);
|
||||
|
||||
_textBuffer->WriteLine(iter, point);
|
||||
__debugbreak();
|
||||
//_textBuffer->WriteLine(iter, point);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
|
|
@ -135,14 +135,7 @@ public:
|
|||
TextBufferTextIterator GetTextLineDataAt(const COORD at) const;
|
||||
TextBufferTextIterator GetTextDataAt(const COORD at, const Microsoft::Console::Types::Viewport limit) const;
|
||||
|
||||
OutputCellIterator Write(const OutputCellIterator it);
|
||||
|
||||
OutputCellIterator Write(const OutputCellIterator it,
|
||||
const COORD target,
|
||||
const std::optional<bool> wrap = true);
|
||||
|
||||
OutputCellIterator WriteRect(const OutputCellIterator it,
|
||||
const Microsoft::Console::Types::Viewport viewport);
|
||||
size_t WriteMeasuredStringLinear(std::wstring_view string, const RowMeasurementBuffer& measurements);
|
||||
|
||||
void WriteRect(const OutputCellRect& data,
|
||||
const COORD location);
|
||||
|
|
|
@ -388,25 +388,7 @@ void Selection::ColorSelection(const SMALL_RECT& srRect, const TextAttribute att
|
|||
// Read selection rectangle, assumed already clipped to buffer.
|
||||
SCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer();
|
||||
|
||||
COORD coordTargetSize;
|
||||
coordTargetSize.X = CalcWindowSizeX(srRect);
|
||||
coordTargetSize.Y = CalcWindowSizeY(srRect);
|
||||
|
||||
COORD coordTarget;
|
||||
coordTarget.X = srRect.Left;
|
||||
coordTarget.Y = srRect.Top;
|
||||
|
||||
// Now color the selection a line at a time.
|
||||
for (; (coordTarget.Y < srRect.Top + coordTargetSize.Y); ++coordTarget.Y)
|
||||
{
|
||||
const size_t cchWrite = gsl::narrow<size_t>(coordTargetSize.X);
|
||||
|
||||
try
|
||||
{
|
||||
screenInfo.Write(OutputCellIterator(attr, cchWrite), coordTarget);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
screenInfo.GetTextBuffer().FillWithAttributeRectangular(srRect, attr);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
|
|
@ -1,514 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "WexTestClass.h"
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
|
||||
#include "../buffer/out/outputCellIterator.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
|
||||
static constexpr TextAttribute InvalidTextAttribute{ INVALID_COLOR, INVALID_COLOR };
|
||||
|
||||
class OutputCellIteratorTests
|
||||
{
|
||||
CommonState* m_state;
|
||||
|
||||
TEST_CLASS(OutputCellIteratorTests);
|
||||
|
||||
TEST_METHOD(CharacterFillDoubleWidth)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
const wchar_t wch = L'\x30a2'; // katakana A
|
||||
const size_t limit = 5;
|
||||
|
||||
OutputCellIterator it(wch, limit);
|
||||
|
||||
OutputCellView expectedLead({ &wch, 1 },
|
||||
DbcsAttribute(DbcsAttribute::Attribute::Leading),
|
||||
InvalidTextAttribute,
|
||||
TextAttributeBehavior::Current);
|
||||
|
||||
OutputCellView expectedTrail({ &wch, 1 },
|
||||
DbcsAttribute(DbcsAttribute::Attribute::Trailing),
|
||||
InvalidTextAttribute,
|
||||
TextAttributeBehavior::Current);
|
||||
|
||||
for (size_t i = 0; i < limit; i++)
|
||||
{
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expectedLead, *it);
|
||||
it++;
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expectedTrail, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_FALSE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(CharacterFillLimited)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
const wchar_t wch = L'Q';
|
||||
const size_t limit = 5;
|
||||
|
||||
OutputCellIterator it(wch, limit);
|
||||
|
||||
OutputCellView expected({ &wch, 1 },
|
||||
DbcsAttribute{},
|
||||
InvalidTextAttribute,
|
||||
TextAttributeBehavior::Current);
|
||||
|
||||
for (size_t i = 0; i < limit; i++)
|
||||
{
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_FALSE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(CharacterFillUnlimited)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
const wchar_t wch = L'Q';
|
||||
|
||||
OutputCellIterator it(wch);
|
||||
|
||||
OutputCellView expected({ &wch, 1 },
|
||||
DbcsAttribute{},
|
||||
InvalidTextAttribute,
|
||||
TextAttributeBehavior::Current);
|
||||
|
||||
for (size_t i = 0; i < SHORT_MAX; i++)
|
||||
{
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_TRUE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(AttributeFillLimited)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
const TextAttribute attr(FOREGROUND_RED | BACKGROUND_BLUE);
|
||||
|
||||
const size_t limit = 5;
|
||||
|
||||
OutputCellIterator it(attr, limit);
|
||||
|
||||
OutputCellView expected({},
|
||||
{},
|
||||
attr,
|
||||
TextAttributeBehavior::StoredOnly);
|
||||
|
||||
for (size_t i = 0; i < limit; i++)
|
||||
{
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_FALSE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(AttributeFillUnlimited)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
const TextAttribute attr(FOREGROUND_RED | BACKGROUND_BLUE);
|
||||
|
||||
OutputCellIterator it(attr);
|
||||
|
||||
OutputCellView expected({},
|
||||
{},
|
||||
attr,
|
||||
TextAttributeBehavior::StoredOnly);
|
||||
|
||||
for (size_t i = 0; i < SHORT_MAX; i++)
|
||||
{
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_TRUE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(TextAndAttributeFillLimited)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
const wchar_t wch = L'Q';
|
||||
|
||||
const TextAttribute attr(FOREGROUND_RED | BACKGROUND_BLUE);
|
||||
|
||||
const size_t limit = 5;
|
||||
|
||||
OutputCellIterator it(wch, attr, limit);
|
||||
|
||||
OutputCellView expected({ &wch, 1 },
|
||||
{},
|
||||
attr,
|
||||
TextAttributeBehavior::Stored);
|
||||
|
||||
for (size_t i = 0; i < limit; i++)
|
||||
{
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_FALSE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(TextAndAttributeFillUnlimited)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
const wchar_t wch = L'Q';
|
||||
|
||||
const TextAttribute attr(FOREGROUND_RED | BACKGROUND_BLUE);
|
||||
|
||||
OutputCellIterator it(wch, attr);
|
||||
|
||||
OutputCellView expected({ &wch, 1 },
|
||||
{},
|
||||
attr,
|
||||
TextAttributeBehavior::Stored);
|
||||
|
||||
for (size_t i = 0; i < SHORT_MAX; i++)
|
||||
{
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_TRUE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(CharInfoFillLimited)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
CHAR_INFO ci;
|
||||
ci.Char.UnicodeChar = L'Q';
|
||||
ci.Attributes = FOREGROUND_RED | BACKGROUND_BLUE;
|
||||
|
||||
const size_t limit = 5;
|
||||
|
||||
OutputCellIterator it(ci, limit);
|
||||
|
||||
OutputCellView expected({ &ci.Char.UnicodeChar, 1 },
|
||||
{},
|
||||
TextAttribute(ci.Attributes),
|
||||
TextAttributeBehavior::Stored);
|
||||
|
||||
for (size_t i = 0; i < limit; i++)
|
||||
{
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_FALSE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(CharInfoFillUnlimited)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
CHAR_INFO ci;
|
||||
ci.Char.UnicodeChar = L'Q';
|
||||
ci.Attributes = FOREGROUND_RED | BACKGROUND_BLUE;
|
||||
|
||||
OutputCellIterator it(ci);
|
||||
|
||||
OutputCellView expected({ &ci.Char.UnicodeChar, 1 },
|
||||
{},
|
||||
TextAttribute(ci.Attributes),
|
||||
TextAttributeBehavior::Stored);
|
||||
|
||||
for (size_t i = 0; i < SHORT_MAX; i++)
|
||||
{
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_TRUE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(StringData)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
const std::wstring testText(L"The quick brown fox jumps over the lazy dog.");
|
||||
|
||||
OutputCellIterator it(testText);
|
||||
|
||||
for (const auto& wch : testText)
|
||||
{
|
||||
OutputCellView expected({ &wch, 1 },
|
||||
{},
|
||||
InvalidTextAttribute,
|
||||
TextAttributeBehavior::Current);
|
||||
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_FALSE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(FullWidthStringData)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
const std::wstring testText(L"\x30a2\x30a3\x30a4\x30a5\x30a6");
|
||||
|
||||
OutputCellIterator it(testText);
|
||||
|
||||
for (const auto& wch : testText)
|
||||
{
|
||||
auto expected = OutputCellView({ &wch, 1 },
|
||||
DbcsAttribute(DbcsAttribute::Attribute::Leading),
|
||||
InvalidTextAttribute,
|
||||
TextAttributeBehavior::Current);
|
||||
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
|
||||
expected = OutputCellView({ &wch, 1 },
|
||||
DbcsAttribute(DbcsAttribute::Attribute::Trailing),
|
||||
InvalidTextAttribute,
|
||||
TextAttributeBehavior::Current);
|
||||
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_FALSE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(StringDataWithColor)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
const std::wstring testText(L"The quick brown fox jumps over the lazy dog.");
|
||||
const TextAttribute color(FOREGROUND_GREEN | FOREGROUND_INTENSITY);
|
||||
|
||||
OutputCellIterator it(testText, color);
|
||||
|
||||
for (const auto& wch : testText)
|
||||
{
|
||||
OutputCellView expected({ &wch, 1 },
|
||||
{},
|
||||
color,
|
||||
TextAttributeBehavior::Stored);
|
||||
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_FALSE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(FullWidthStringDataWithColor)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
const std::wstring testText(L"\x30a2\x30a3\x30a4\x30a5\x30a6");
|
||||
const TextAttribute color(FOREGROUND_GREEN | FOREGROUND_INTENSITY);
|
||||
|
||||
OutputCellIterator it(testText, color);
|
||||
|
||||
for (const auto& wch : testText)
|
||||
{
|
||||
auto expected = OutputCellView({ &wch, 1 },
|
||||
DbcsAttribute(DbcsAttribute::Attribute::Leading),
|
||||
color,
|
||||
TextAttributeBehavior::Stored);
|
||||
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
|
||||
expected = OutputCellView({ &wch, 1 },
|
||||
DbcsAttribute(DbcsAttribute::Attribute::Trailing),
|
||||
color,
|
||||
TextAttributeBehavior::Stored);
|
||||
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_FALSE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(LegacyColorDataRun)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
const std::vector<WORD> colors{ FOREGROUND_GREEN, FOREGROUND_RED | BACKGROUND_BLUE, FOREGROUND_BLUE | FOREGROUND_INTENSITY, BACKGROUND_GREEN };
|
||||
const gsl::span<const WORD> view{ colors.data(), colors.size() };
|
||||
|
||||
OutputCellIterator it(view);
|
||||
|
||||
for (const auto& color : colors)
|
||||
{
|
||||
auto expected = OutputCellView({},
|
||||
{},
|
||||
TextAttribute{ color },
|
||||
TextAttributeBehavior::StoredOnly);
|
||||
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_FALSE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(LegacyCharInfoRun)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
std::vector<CHAR_INFO> charInfos;
|
||||
|
||||
for (auto i = 0; i < 5; i++)
|
||||
{
|
||||
CHAR_INFO ci;
|
||||
ci.Char.UnicodeChar = static_cast<wchar_t>(L'A' + i);
|
||||
ci.Attributes = gsl::narrow<WORD>(i);
|
||||
|
||||
charInfos.push_back(ci);
|
||||
}
|
||||
|
||||
const gsl::span<const CHAR_INFO> view{ charInfos.data(), charInfos.size() };
|
||||
|
||||
OutputCellIterator it(view);
|
||||
|
||||
for (const auto& ci : charInfos)
|
||||
{
|
||||
auto expected = OutputCellView({ &ci.Char.UnicodeChar, 1 },
|
||||
{},
|
||||
TextAttribute{ ci.Attributes },
|
||||
TextAttributeBehavior::Stored);
|
||||
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_FALSE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(OutputCellRun)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
std::vector<OutputCell> cells;
|
||||
|
||||
for (auto i = 0; i < 5; i++)
|
||||
{
|
||||
const std::wstring pair(L"\xd834\xdd1e");
|
||||
OutputCell cell(pair, {}, TextAttribute{ gsl::narrow<WORD>(i) });
|
||||
cells.push_back(cell);
|
||||
}
|
||||
|
||||
const gsl::span<const OutputCell> view{ cells.data(), cells.size() };
|
||||
|
||||
OutputCellIterator it(view);
|
||||
|
||||
for (const auto& cell : cells)
|
||||
{
|
||||
auto expected = OutputCellView(cell.Chars(),
|
||||
cell.DbcsAttr(),
|
||||
cell.TextAttr(),
|
||||
cell.TextAttrBehavior());
|
||||
|
||||
VERIFY_IS_TRUE(it);
|
||||
VERIFY_ARE_EQUAL(expected, *it);
|
||||
it++;
|
||||
}
|
||||
|
||||
VERIFY_IS_FALSE(it);
|
||||
}
|
||||
|
||||
TEST_METHOD(DistanceStandard)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
const std::wstring testText(L"The quick brown fox jumps over the lazy dog.");
|
||||
|
||||
OutputCellIterator it(testText);
|
||||
|
||||
const auto original = it;
|
||||
|
||||
ptrdiff_t expected = 0;
|
||||
for (const auto& wch : testText)
|
||||
{
|
||||
wch; // unused
|
||||
VERIFY_IS_TRUE(it);
|
||||
it++;
|
||||
|
||||
expected++;
|
||||
}
|
||||
|
||||
VERIFY_IS_FALSE(it);
|
||||
VERIFY_ARE_EQUAL(expected, it.GetCellDistance(original));
|
||||
VERIFY_ARE_EQUAL(expected, it.GetInputDistance(original));
|
||||
}
|
||||
|
||||
TEST_METHOD(DistanceFullWidth)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
|
||||
const std::wstring testText(L"QWER\x30a2\x30a3\x30a4\x30a5\x30a6TYUI");
|
||||
|
||||
OutputCellIterator it(testText);
|
||||
|
||||
const auto original = it;
|
||||
|
||||
ptrdiff_t cellsExpected = 0;
|
||||
ptrdiff_t inputExpected = 0;
|
||||
for (const auto& wch : testText)
|
||||
{
|
||||
wch; // unused
|
||||
VERIFY_IS_TRUE(it);
|
||||
const auto value = *it;
|
||||
it++;
|
||||
|
||||
if (value.DbcsAttr().IsLeading() || value.DbcsAttr().IsTrailing())
|
||||
{
|
||||
VERIFY_IS_TRUE(it);
|
||||
it++;
|
||||
cellsExpected++;
|
||||
}
|
||||
|
||||
cellsExpected++;
|
||||
inputExpected++;
|
||||
}
|
||||
|
||||
VERIFY_IS_FALSE(it);
|
||||
VERIFY_ARE_EQUAL(cellsExpected, it.GetCellDistance(original));
|
||||
VERIFY_ARE_EQUAL(inputExpected, it.GetInputDistance(original));
|
||||
}
|
||||
};
|
|
@ -93,8 +93,6 @@ class TextBufferTests
|
|||
|
||||
TEST_METHOD(TestCopyProperties);
|
||||
|
||||
TEST_METHOD(TestInsertCharacter);
|
||||
|
||||
TEST_METHOD(TestIncrementCursor);
|
||||
|
||||
TEST_METHOD(TestNewlineCursor);
|
||||
|
@ -386,50 +384,6 @@ void TextBufferTests::TestCopyProperties()
|
|||
VERIFY_IS_TRUE(testTextBuffer->GetCursor().GetDelay());
|
||||
}
|
||||
|
||||
void TextBufferTests::TestInsertCharacter()
|
||||
{
|
||||
TextBuffer& textBuffer = GetTbi();
|
||||
|
||||
// get starting cursor position
|
||||
COORD const coordCursorBefore = textBuffer.GetCursor().GetPosition();
|
||||
|
||||
// Get current row from the buffer
|
||||
ROW& Row = textBuffer.GetRowByOffset(coordCursorBefore.Y);
|
||||
|
||||
// create some sample test data
|
||||
const auto wch = L'Z';
|
||||
const std::wstring_view wchTest(&wch, 1);
|
||||
DbcsAttribute dbcsAttribute;
|
||||
dbcsAttribute.SetTrailing();
|
||||
WORD const wAttrTest = BACKGROUND_INTENSITY | FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE;
|
||||
TextAttribute TestAttributes = TextAttribute(wAttrTest);
|
||||
|
||||
CharRow& charRow = Row.GetCharRow();
|
||||
charRow.DbcsAttrAt(coordCursorBefore.X).SetLeading();
|
||||
// ensure that the buffer didn't start with these fields
|
||||
VERIFY_ARE_NOT_EQUAL(charRow.GlyphAt(coordCursorBefore.X), wchTest);
|
||||
VERIFY_ARE_NOT_EQUAL(charRow.DbcsAttrAt(coordCursorBefore.X), dbcsAttribute);
|
||||
|
||||
auto attr = Row.GetAttrRow().GetAttrByColumn(coordCursorBefore.X);
|
||||
|
||||
VERIFY_ARE_NOT_EQUAL(attr, TestAttributes);
|
||||
|
||||
// now apply the new data to the buffer
|
||||
textBuffer.InsertCharacter(wchTest, dbcsAttribute, TestAttributes);
|
||||
|
||||
// ensure that the buffer position where the cursor WAS contains the test items
|
||||
VERIFY_ARE_EQUAL(charRow.GlyphAt(coordCursorBefore.X), wchTest);
|
||||
VERIFY_ARE_EQUAL(charRow.DbcsAttrAt(coordCursorBefore.X), dbcsAttribute);
|
||||
|
||||
attr = Row.GetAttrRow().GetAttrByColumn(coordCursorBefore.X);
|
||||
VERIFY_ARE_EQUAL(attr, TestAttributes);
|
||||
|
||||
// ensure that the cursor moved to a new position (X or Y or both have changed)
|
||||
VERIFY_IS_TRUE((coordCursorBefore.X != textBuffer.GetCursor().GetPosition().X) ||
|
||||
(coordCursorBefore.Y != textBuffer.GetCursor().GetPosition().Y));
|
||||
// the proper advancement of the cursor (e.g. which position it goes to) is validated in other tests
|
||||
}
|
||||
|
||||
void TextBufferTests::TestIncrementCursor()
|
||||
{
|
||||
TextBuffer& textBuffer = GetTbi();
|
||||
|
|
|
@ -520,6 +520,22 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
|||
_total_length = new_size;
|
||||
}
|
||||
|
||||
void append(const value_type& value)
|
||||
{
|
||||
++_total_length;
|
||||
if (!_runs.empty())
|
||||
{
|
||||
auto& back{ _runs.back() };
|
||||
if (back.value == value)
|
||||
{
|
||||
++back.length;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Either runs was empty, or we have to append a new run.
|
||||
_runs.emplace_back(value, S{ 1 });
|
||||
}
|
||||
|
||||
constexpr bool operator==(const basic_rle& other) const noexcept
|
||||
{
|
||||
return _total_length == other._total_length && _runs == other._runs;
|
||||
|
|
|
@ -23,7 +23,6 @@ Author(s):
|
|||
#include "thread.hpp"
|
||||
|
||||
#include "../../buffer/out/textBuffer.hpp"
|
||||
#include "../../buffer/out/CharRow.hpp"
|
||||
|
||||
namespace Microsoft::Console::Render
|
||||
{
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace Microsoft::Console::Types
|
|||
~Viewport() {}
|
||||
constexpr Viewport() noexcept :
|
||||
_sr({ 0, 0, -1, -1 }){};
|
||||
Viewport(const Viewport& other) noexcept;
|
||||
Viewport(const Viewport& other) = default;
|
||||
Viewport(Viewport&&) = default;
|
||||
Viewport& operator=(const Viewport&) & = default;
|
||||
Viewport& operator=(Viewport&&) & = default;
|
||||
|
|
|
@ -11,11 +11,6 @@ Viewport::Viewport(const SMALL_RECT sr) noexcept :
|
|||
{
|
||||
}
|
||||
|
||||
Viewport::Viewport(const Viewport& other) noexcept :
|
||||
_sr(other._sr)
|
||||
{
|
||||
}
|
||||
|
||||
Viewport Viewport::Empty() noexcept
|
||||
{
|
||||
return Viewport();
|
||||
|
|
Loading…
Reference in a new issue