Rewrite ROW to be Unicode capable (#13626)

This commit is a from-scratch rewrite of `ROW` with the primary goal to get
rid of the rather bodgy `UnicodeStorage` class and improve Unicode support.

Previously a 120x9001 terminal buffer would store a vector of 9001 `ROW`s
where each `ROW` stored exactly 120 `wchar_t`. Glyphs exceeding their
allocated space would be stored in the `UnicodeStorage` which was basically
a `hashmap<Coordinate, String>`. Iterating over the text in a `ROW` would
require us to check each glyph and fetch it from the map conditionally.
On newlines we'd have to invalidate all map entries that are now gone,
so for every invalidated `ROW` we'd iterate through all glyphs again and if
a single one was stored in `UnicodeStorage`, we'd then iterate through the
entire hashmap to remove all coordinates that were residing on that `ROW`.
All in all, this wasn't the most robust nor performant code.

The new implementation is simple (from a design perspective):
Store all text in a `ROW` in a regular string. Grow the string if needed.
The association between columns and text works by storing character offsets
in a column-wide array. This algorithm is <100 LOC and removes ~1000.

As an aside this PR does a few more things that go hand in hand:
* Remove most of `ROW` helper classes, which aren't needed anymore.
* Allocate backing memory in a single `VirtualAlloc` call.
* Rewrite `IsCursorDoubleWidth` to use `DbcsAttrAt` directly.
  Improves overall performance by 10-20% and makes this implementation
  faster than the previous NxM storage, despite the added complexity.

Part of #8000

## Validation Steps Performed
* Existing and new unit and feature tests complete 
* Printing Unicode completes without crashing 
* Resizing works without crashing 
This commit is contained in:
Leonard Hecker 2022-11-11 20:34:58 +01:00 committed by GitHub
parent c12dc2aa4d
commit a01500f051
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 1255 additions and 2107 deletions

View File

@ -32,10 +32,10 @@ DERR
dlldata
DNE
DONTADDTORECENT
DWMWA
DWORDLONG
DWMSBT
DWMWA
DWMWA
DWORDLONG
endfor
ENDSESSION
enumset
@ -110,8 +110,8 @@ memchr
memicmp
MENUCOMMAND
MENUDATA
MENUITEMINFOW
MENUINFO
MENUITEMINFOW
mmeapi
MOUSELEAVE
mov
@ -158,8 +158,8 @@ rcx
REGCLS
RETURNCMD
rfind
roundf
ROOTOWNER
roundf
RSHIFT
SACL
schandle
@ -211,6 +211,7 @@ UPDATEINIFILE
userenv
USEROBJECTFLAGS
Viewbox
virtualalloc
wcsstr
wcstoui
winmain

View File

@ -1881,6 +1881,7 @@ QUESTIONMARK
quickedit
QUZ
QWER
Qxxxxxx
qzmp
RAII
RALT
@ -2794,6 +2795,8 @@ xutr
xvalue
XVIRTUALSCREEN
XWalk
xwwyzz
xxyyzz
yact
YAML
YCast

View File

@ -1,134 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "AttrRow.hpp"
// Routine Description:
// - constructor
// Arguments:
// - cchRowWidth - the length of the default text attribute
// - attr - the default text attribute
// Return Value:
// - constructed object
ATTR_ROW::ATTR_ROW(const til::CoordType width, const TextAttribute attr) :
_data(gsl::narrow_cast<uint16_t>(width), attr) {}
// Routine Description:
// - Sets all properties of the ATTR_ROW to default values
// Arguments:
// - attr - The default text attributes to use on text in this row.
void ATTR_ROW::Reset(const TextAttribute attr)
{
_data.replace(0, _data.size(), attr);
}
// Routine Description:
// - Takes an existing row of attributes, and changes the length so that it fills the NewWidth.
// If the new size is bigger, then the last attr is extended to fill the NewWidth.
// If the new size is smaller, the runs are cut off to fit.
// Arguments:
// - oldWidth - The original width of the row.
// - newWidth - The new width of the row.
// Return Value:
// - <none>, throws exceptions on failures.
void ATTR_ROW::Resize(const til::CoordType newWidth)
{
_data.resize_trailing_extent(gsl::narrow<uint16_t>(newWidth));
}
// Routine Description:
// - returns a copy of the TextAttribute at the specified column
// Arguments:
// - column - the column to get the attribute for
// Return Value:
// - the text attribute at column
// Note:
// - will throw on error
TextAttribute ATTR_ROW::GetAttrByColumn(const til::CoordType column) const
{
return _data.at(gsl::narrow<uint16_t>(column));
}
// Routine Description:
// - Finds the hyperlink IDs present in this row and returns them
// Return value:
// - The hyperlink IDs present in this row
std::vector<uint16_t> ATTR_ROW::GetHyperlinks() const
{
std::vector<uint16_t> ids;
for (const auto& run : _data.runs())
{
if (run.value.IsHyperlink())
{
ids.emplace_back(run.value.GetHyperlinkId());
}
}
return ids;
}
// Routine Description:
// - Sets the attributes (colors) of all character positions from the given position through the end of the row.
// Arguments:
// - iStart - Starting index position within the row
// - attr - Attribute (color) to fill remaining characters with
// Return Value:
// - <none>
bool ATTR_ROW::SetAttrToEnd(const til::CoordType beginIndex, const TextAttribute attr)
{
_data.replace(gsl::narrow<uint16_t>(beginIndex), _data.size(), attr);
return true;
}
// Method Description:
// - Replaces all runs in the row with the given toBeReplacedAttr with the new
// attribute replaceWith.
// Arguments:
// - toBeReplacedAttr - the attribute to replace in this row.
// - replaceWith - the new value for the matching runs' attributes.
// Return Value:
// - <none>
void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith)
{
_data.replace_values(toBeReplacedAttr, replaceWith);
}
// Routine Description:
// - Takes an attribute, and merges it into this row from beginIndex (inclusive) to endIndex (exclusive).
// - For example, if the current row was [{4, BLUE}], the merge arguments were
// { beginIndex = 1, endIndex = 3, newAttr = RED }, then the row would modified to be
// [{ 1, BLUE}, {2, RED}, {1, BLUE}].
// Arguments:
// - beginIndex, endIndex: The [beginIndex, endIndex) range that's to be replaced with newAttr.
// - newAttr: The attribute to merge into this row.
// Return Value:
// - <none>
void ATTR_ROW::Replace(const til::CoordType beginIndex, const til::CoordType endIndex, const TextAttribute& newAttr)
{
_data.replace(gsl::narrow<uint16_t>(beginIndex), gsl::narrow<uint16_t>(endIndex), newAttr);
}
ATTR_ROW::const_iterator ATTR_ROW::begin() const noexcept
{
return _data.begin();
}
ATTR_ROW::const_iterator ATTR_ROW::end() const noexcept
{
return _data.end();
}
ATTR_ROW::const_iterator ATTR_ROW::cbegin() const noexcept
{
return _data.cbegin();
}
ATTR_ROW::const_iterator ATTR_ROW::cend() const noexcept
{
return _data.cend();
}
bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept
{
return a._data == b._data;
}

View File

@ -1,68 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- AttrRow.hpp
Abstract:
- contains data structure for the attributes of one row of screen buffer
Author(s):
- Michael Niksa (miniksa) 10-Apr-2014
- Paul Campbell (paulcam) 10-Apr-2014
Revision History:
- From components of output.h/.c
by Therese Stowell (ThereseS) 1990-1991
- Pulled into its own file from textBuffer.hpp/cpp (AustDi, 2017)
--*/
#pragma once
#include "til/rle.h"
#include "TextAttribute.hpp"
class ATTR_ROW final
{
using rle_vector = til::small_rle<TextAttribute, uint16_t, 1>;
public:
using const_iterator = rle_vector::const_iterator;
ATTR_ROW(til::CoordType width, TextAttribute attr);
~ATTR_ROW() = default;
ATTR_ROW(const ATTR_ROW&) = default;
ATTR_ROW& operator=(const ATTR_ROW&) = default;
ATTR_ROW(ATTR_ROW&&)
noexcept = default;
ATTR_ROW& operator=(ATTR_ROW&&) noexcept = default;
TextAttribute GetAttrByColumn(til::CoordType column) const;
std::vector<uint16_t> GetHyperlinks() const;
bool SetAttrToEnd(til::CoordType beginIndex, TextAttribute attr);
void ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith);
void Resize(til::CoordType newWidth);
void Replace(til::CoordType beginIndex, til::CoordType endIndex, const TextAttribute& newAttr);
const_iterator begin() const noexcept;
const_iterator end() const noexcept;
const_iterator cbegin() const noexcept;
const_iterator cend() const noexcept;
friend bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept;
friend class ROW;
private:
void Reset(const TextAttribute attr);
rle_vector _data;
#ifdef UNIT_TESTING
friend class CommonState;
#endif
};

View File

@ -1,281 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "CharRow.hpp"
#include "unicode.hpp"
#include "Row.hpp"
// Routine Description:
// - constructor
// Arguments:
// - rowWidth - the size (in wchar_t) of the char and attribute rows
// - pParent - the parent ROW
// Return Value:
// - instantiated object
// Note: will through if unable to allocate char/attribute buffers
#pragma warning(push)
#pragma warning(disable : 26447) // small_vector's constructor says it can throw but it should not given how we use it. This suppresses this error for the AuditMode build.
CharRow::CharRow(til::CoordType rowWidth, ROW* const pParent) noexcept :
_data(rowWidth, value_type()),
_pParent{ FAIL_FAST_IF_NULL(pParent) }
{
}
#pragma warning(pop)
// Routine Description:
// - gets the size of the row, in glyph cells
// Arguments:
// - <none>
// Return Value:
// - the size of the row
til::CoordType CharRow::size() const noexcept
{
return gsl::narrow_cast<til::CoordType>(_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 til::CoordType newSize) noexcept
{
try
{
const value_type insertVals;
_data.resize(newSize, insertVals);
}
CATCH_RETURN();
return S_OK;
}
typename CharRow::iterator CharRow::begin() noexcept
{
return _data.begin();
}
typename CharRow::const_iterator CharRow::cbegin() const noexcept
{
return _data.cbegin();
}
typename CharRow::iterator CharRow::end() noexcept
{
return _data.end();
}
typename CharRow::const_iterator CharRow::cend() const noexcept
{
return _data.cend();
}
// Routine Description:
// - Inspects the current internal string to find the left edge of it
// Arguments:
// - <none>
// Return Value:
// - The calculated left boundary of the internal string.
til::CoordType CharRow::MeasureLeft() const noexcept
{
auto it = _data.cbegin();
while (it != _data.cend() && it->IsSpace())
{
++it;
}
return gsl::narrow_cast<til::CoordType>(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.
til::CoordType CharRow::MeasureRight() const
{
auto it = _data.crbegin();
while (it != _data.crend() && it->IsSpace())
{
++it;
}
return gsl::narrow_cast<til::CoordType>(_data.crend() - it);
}
void CharRow::ClearCell(const til::CoordType column)
{
_data.at(column).Reset();
}
// Routine Description:
// - Tells you whether or not this row contains any valid text.
// Arguments:
// - <none>
// Return Value:
// - True if there is valid text in this row. False otherwise.
bool CharRow::ContainsText() const noexcept
{
for (const auto& cell : _data)
{
if (!cell.IsSpace())
{
return true;
}
}
return false;
}
// Routine Description:
// - gets the attribute at the specified column
// Arguments:
// - column - the column to get the attribute for
// Return Value:
// - the attribute
// Note: will throw exception if column is out of bounds
const DbcsAttribute& CharRow::DbcsAttrAt(const til::CoordType column) const
{
return _data.at(column).DbcsAttr();
}
// Routine Description:
// - gets the attribute at the specified column
// Arguments:
// - column - the column to get the attribute for
// Return Value:
// - the attribute
// Note: will throw exception if column is out of bounds
DbcsAttribute& CharRow::DbcsAttrAt(const til::CoordType column)
{
return _data.at(column).DbcsAttr();
}
// Routine Description:
// - resets text data at column
// Arguments:
// - column - column index to clear text data from
// Return Value:
// - <none>
// Note: will throw exception if column is out of bounds
void CharRow::ClearGlyph(const til::CoordType column)
{
_data.at(column).EraseChars();
}
// Routine Description:
// - returns text data at column as a const reference.
// Arguments:
// - column - column to get text data for
// Return Value:
// - text data at column
// - Note: will throw exception if column is out of bounds
const CharRow::reference CharRow::GlyphAt(const til::CoordType column) const
{
THROW_HR_IF(E_INVALIDARG, column < 0 || column >= gsl::narrow_cast<til::CoordType>(_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 til::CoordType column)
{
THROW_HR_IF(E_INVALIDARG, column < 0 || column >= gsl::narrow_cast<til::CoordType>(_data.size()));
return { *this, column };
}
std::wstring CharRow::GetText() const
{
std::wstring wstr;
wstr.reserve(_data.size());
for (til::CoordType i = 0; i < gsl::narrow_cast<til::CoordType>(_data.size()); ++i)
{
const auto glyph = GlyphAt(i);
if (!DbcsAttrAt(i).IsTrailing())
{
for (const auto wch : glyph)
{
wstr.push_back(wch);
}
}
}
return wstr;
}
// Method Description:
// - get delimiter class for a position in the char row
// - used for double click selection and uia word navigation
// Arguments:
// - column: column to get text data for
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
// Return Value:
// - the delimiter class for the given char
const DelimiterClass CharRow::DelimiterClassAt(const til::CoordType column, const std::wstring_view wordDelimiters) const
{
THROW_HR_IF(E_INVALIDARG, column < 0 || column >= gsl::narrow_cast<til::CoordType>(_data.size()));
const auto glyph = *GlyphAt(column).begin();
if (glyph <= UNICODE_SPACE)
{
return DelimiterClass::ControlChar;
}
else if (wordDelimiters.find(glyph) != std::wstring_view::npos)
{
return DelimiterClass::DelimiterChar;
}
else
{
return DelimiterClass::RegularChar;
}
}
UnicodeStorage& CharRow::GetUnicodeStorage() noexcept
{
return _pParent->GetUnicodeStorage();
}
const UnicodeStorage& CharRow::GetUnicodeStorage() const noexcept
{
return _pParent->GetUnicodeStorage();
}
// Routine Description:
// - calculates the key used by the given column of the char row to store glyph data in UnicodeStorage
// Arguments:
// - column - the column to generate the key for
// Return Value:
// - the til::point key for data access from UnicodeStorage for the column
til::point CharRow::GetStorageKey(const til::CoordType column) const noexcept
{
return { column, _pParent->GetId() };
}
// Routine Description:
// - Updates the pointer to the parent row (which might change if we shuffle the rows around)
// Arguments:
// - pParent - Pointer to the parent row
void CharRow::UpdateParent(ROW* const pParent)
{
_pParent = FAIL_FAST_IF_NULL(pParent);
}

View File

@ -1,117 +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 <til/small_vector.h>
#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 = wchar_t;
using value_type = CharRowCell;
using iterator = til::small_vector<value_type, 120>::iterator;
using const_iterator = til::small_vector<value_type, 120>::const_iterator;
using const_reverse_iterator = til::small_vector<value_type, 120>::const_reverse_iterator;
using reference = CharRowCellReference;
CharRow(til::CoordType rowWidth, ROW* const pParent) noexcept;
til::CoordType size() const noexcept;
[[nodiscard]] HRESULT Resize(const til::CoordType newSize) noexcept;
til::CoordType MeasureLeft() const noexcept;
til::CoordType MeasureRight() const;
bool ContainsText() const noexcept;
const DbcsAttribute& DbcsAttrAt(const til::CoordType column) const;
DbcsAttribute& DbcsAttrAt(const til::CoordType column);
void ClearGlyph(const til::CoordType column);
const DelimiterClass DelimiterClassAt(const til::CoordType column, const std::wstring_view wordDelimiters) const;
// working with glyphs
const reference GlyphAt(const til::CoordType column) const;
reference GlyphAt(const til::CoordType column);
// iterators
iterator begin() noexcept;
const_iterator cbegin() const noexcept;
const_iterator begin() const noexcept { return cbegin(); }
iterator end() noexcept;
const_iterator cend() const noexcept;
const_iterator end() const noexcept { return cend(); }
UnicodeStorage& GetUnicodeStorage() noexcept;
const UnicodeStorage& GetUnicodeStorage() const noexcept;
til::point GetStorageKey(const til::CoordType column) const noexcept;
void UpdateParent(ROW* const pParent);
friend CharRowCellReference;
friend class ROW;
private:
void Reset() noexcept;
void ClearCell(const til::CoordType column);
std::wstring GetText() const;
protected:
// storage for glyph data and dbcs attributes
til::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 };
});
}

View File

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

View File

@ -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);
}

View File

@ -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 auto& dbcsAttr = ref._cellData().DbcsAttr();
if (glyph.size() == 1 && dbcsAttr.IsGlyphStored())
{
return false;
}
else if (glyph.size() > 1 && !dbcsAttr.IsGlyphStored())
{
return false;
}
else if (glyph.size() == 1 && !dbcsAttr.IsGlyphStored())
{
return ref._cellData().Char() == glyph.front();
}
else
{
const auto& chars = ref._parent.GetUnicodeStorage().GetText(ref._parent.GetStorageKey(ref._index));
return chars == glyph;
}
}
bool operator==(const std::vector<wchar_t>& glyph, const CharRowCellReference& ref)
{
return ref == glyph;
}

View File

@ -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 til::CoordType index) noexcept :
_parent{ parent },
_index{ index }
{
}
~CharRowCellReference() = default;
CharRowCellReference(const CharRowCellReference&) noexcept = default;
CharRowCellReference(CharRowCellReference&&) noexcept = default;
void operator=(const CharRowCellReference&) = delete;
void operator=(CharRowCellReference&&) = delete;
void operator=(const std::wstring_view chars);
operator std::wstring_view() const;
const_iterator begin() const;
const_iterator end() const;
friend bool operator==(const CharRowCellReference& ref, const std::vector<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
til::CoordType _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);

View File

@ -16,129 +16,22 @@ Revision History:
#pragma once
class DbcsAttribute final
enum class DbcsAttribute : uint8_t
{
public:
enum class Attribute : BYTE
{
Single = 0x00,
Leading = 0x01,
Trailing = 0x02
};
DbcsAttribute() noexcept :
_attribute{ Attribute::Single },
_glyphStored{ false }
{
}
DbcsAttribute(const Attribute attribute) noexcept :
_attribute{ attribute },
_glyphStored{ false }
{
}
constexpr bool IsSingle() const noexcept
{
return _attribute == Attribute::Single;
}
constexpr bool IsLeading() const noexcept
{
return _attribute == Attribute::Leading;
}
constexpr bool IsTrailing() const noexcept
{
return _attribute == Attribute::Trailing;
}
constexpr bool IsDbcs() const noexcept
{
return IsLeading() || IsTrailing();
}
constexpr bool IsGlyphStored() const noexcept
{
return _glyphStored;
}
void SetGlyphStored(const bool stored) noexcept
{
_glyphStored = stored;
}
void SetSingle() noexcept
{
_attribute = Attribute::Single;
}
void SetLeading() noexcept
{
_attribute = Attribute::Leading;
}
void SetTrailing() noexcept
{
_attribute = Attribute::Trailing;
}
void Reset() noexcept
{
SetSingle();
SetGlyphStored(false);
}
WORD GeneratePublicApiAttributeFormat() const noexcept
{
WORD publicAttribute = 0;
if (IsLeading())
{
WI_SetFlag(publicAttribute, COMMON_LVB_LEADING_BYTE);
}
if (IsTrailing())
{
WI_SetFlag(publicAttribute, COMMON_LVB_TRAILING_BYTE);
}
return publicAttribute;
}
static DbcsAttribute FromPublicApiAttributeFormat(WORD publicAttribute)
{
// it's not valid to be both a leading and trailing byte
if (WI_AreAllFlagsSet(publicAttribute, COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE))
{
THROW_HR(E_INVALIDARG);
}
DbcsAttribute attr;
if (WI_IsFlagSet(publicAttribute, COMMON_LVB_LEADING_BYTE))
{
attr.SetLeading();
}
else if (WI_IsFlagSet(publicAttribute, COMMON_LVB_TRAILING_BYTE))
{
attr.SetTrailing();
}
return attr;
}
friend constexpr bool operator==(const DbcsAttribute& a, const DbcsAttribute& b) noexcept;
private:
Attribute _attribute : 2;
bool _glyphStored : 1;
#ifdef UNIT_TESTING
friend class TextBufferTests;
#endif
Single,
Leading,
Trailing,
};
constexpr bool operator==(const DbcsAttribute& a, const DbcsAttribute& b) noexcept
constexpr WORD GeneratePublicApiAttributeFormat(DbcsAttribute attribute) noexcept
{
return a._attribute == b._attribute;
switch (attribute)
{
case DbcsAttribute::Leading:
return COMMON_LVB_LEADING_BYTE;
case DbcsAttribute::Trailing:
return COMMON_LVB_TRAILING_BYTE;
default:
return 0;
}
}
static_assert(sizeof(DbcsAttribute) == sizeof(BYTE), "DbcsAttribute should be one byte big. if this changes then it needs "
"either an implicit conversion to a BYTE or an update to all places "
"that assume it's a byte big");

View File

@ -13,7 +13,7 @@ Abstract:
#pragma once
enum class LineRendition
enum class LineRendition : uint8_t
{
SingleWidth,
DoubleWidth,

View File

@ -80,7 +80,7 @@ private:
// basic_string contains a small storage internally so we don't need
// to worry about heap allocation for short strings.
std::wstring _text;
DbcsAttribute _dbcsAttribute;
DbcsAttribute _dbcsAttribute = DbcsAttribute::Single;
TextAttribute _textAttribute;
TextAttributeBehavior _behavior;

View File

@ -240,13 +240,10 @@ OutputCellIterator& OutputCellIterator::operator++()
{
if (!_TryMoveTrailing())
{
if (_currentView.DbcsAttr().IsTrailing())
if (_currentView.DbcsAttr() == DbcsAttribute::Trailing)
{
auto dbcsAttr = _currentView.DbcsAttr();
dbcsAttr.SetLeading();
_currentView = OutputCellView(_currentView.Chars(),
dbcsAttr,
DbcsAttribute::Leading,
_currentView.TextAttr(),
_currentView.TextAttrBehavior());
}
@ -336,13 +333,10 @@ const OutputCellView* OutputCellIterator::operator->() const noexcept
// - False if this wasn't applicable and the caller should update the view.
bool OutputCellIterator::_TryMoveTrailing() noexcept
{
if (_currentView.DbcsAttr().IsLeading())
if (_currentView.DbcsAttr() == DbcsAttribute::Leading)
{
auto dbcsAttr = _currentView.DbcsAttr();
dbcsAttr.SetTrailing();
_currentView = OutputCellView(_currentView.Chars(),
dbcsAttr,
DbcsAttribute::Trailing,
_currentView.TextAttr(),
_currentView.TextAttrBehavior());
return true;
@ -399,12 +393,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
const TextAttributeBehavior behavior)
{
const auto glyph = Utf16Parser::ParseNext(view);
DbcsAttribute dbcsAttr;
if (IsGlyphFullWidth(glyph))
{
dbcsAttr.SetLeading();
}
const auto dbcsAttr = IsGlyphFullWidth(glyph) ? DbcsAttribute::Leading : DbcsAttribute::Single;
return OutputCellView(glyph, dbcsAttr, attr, behavior);
}
@ -420,13 +409,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch) noexcept
{
const auto glyph = std::wstring_view(&wch, 1);
DbcsAttribute dbcsAttr;
if (IsGlyphFullWidth(wch))
{
dbcsAttr.SetLeading();
}
const auto dbcsAttr = IsGlyphFullWidth(wch) ? DbcsAttribute::Leading : DbcsAttribute::Single;
return OutputCellView(glyph, dbcsAttr, InvalidTextAttribute, TextAttributeBehavior::Current);
}
@ -457,13 +440,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const TextAttribute& attr) noe
OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch, const TextAttribute& attr) noexcept
{
const auto glyph = std::wstring_view(&wch, 1);
DbcsAttribute dbcsAttr;
if (IsGlyphFullWidth(wch))
{
dbcsAttr.SetLeading();
}
const auto dbcsAttr = IsGlyphFullWidth(wch) ? DbcsAttribute::Leading : DbcsAttribute::Single;
return OutputCellView(glyph, dbcsAttr, attr, TextAttributeBehavior::Stored);
}
@ -498,14 +475,14 @@ OutputCellView OutputCellIterator::s_GenerateView(const CHAR_INFO& charInfo) noe
{
const auto glyph = std::wstring_view(&charInfo.Char.UnicodeChar, 1);
DbcsAttribute dbcsAttr;
DbcsAttribute dbcsAttr = DbcsAttribute::Single;
if (WI_IsFlagSet(charInfo.Attributes, COMMON_LVB_LEADING_BYTE))
{
dbcsAttr.SetLeading();
dbcsAttr = DbcsAttribute::Leading;
}
else if (WI_IsFlagSet(charInfo.Attributes, COMMON_LVB_TRAILING_BYTE))
{
dbcsAttr.SetTrailing();
dbcsAttr = DbcsAttribute::Trailing;
}
const TextAttribute textAttr(charInfo.Attributes);

View File

@ -40,20 +40,7 @@ OutputCellView::OutputCellView(const std::wstring_view view,
// - Count of column cells on the screen
til::CoordType OutputCellView::Columns() const noexcept
{
if (DbcsAttr().IsSingle())
{
return 1;
}
else if (DbcsAttr().IsLeading())
{
return 2;
}
else if (DbcsAttr().IsTrailing())
{
return 1;
}
return 1;
return DbcsAttr() == DbcsAttribute::Leading ? 2 : 1;
}
// Routine Description:

View File

@ -56,7 +56,7 @@ public:
private:
std::wstring_view _view;
DbcsAttribute _dbcsAttr;
DbcsAttribute _dbcsAttr = DbcsAttribute::Single;
TextAttribute _textAttr;
TextAttributeBehavior _behavior;
};

View File

@ -3,29 +3,129 @@
#include "precomp.h"
#include "Row.hpp"
#include "CharRow.hpp"
#include "textBuffer.hpp"
#include "../types/inc/convert.hpp"
// The STL is missing a std::iota_n analogue for std::iota, so I made my own.
template<typename OutIt, typename Diff, typename T>
constexpr OutIt iota_n(OutIt dest, Diff count, T val)
{
for (; count; --count, ++dest, ++val)
{
*dest = val;
}
return dest;
}
// ROW::ReplaceCharacters needs to calculate `val + count` after
// calling iota_n() and this function achieves both things at once.
template<typename OutIt, typename Diff, typename T>
constexpr OutIt iota_n_mut(OutIt dest, Diff count, T& val)
{
for (; count; --count, ++dest, ++val)
{
*dest = val;
}
return dest;
}
// Same as std::fill, but purpose-built for very small `last - first`
// where a trivial loop outperforms vectorization.
template<typename FwdIt, typename T>
constexpr FwdIt fill_small(FwdIt first, FwdIt last, const T val)
{
for (; first != last; ++first)
{
*first = val;
}
return first;
}
// Same as std::fill_n, but purpose-built for very small `count`
// where a trivial loop outperforms vectorization.
template<typename OutIt, typename Diff, typename T>
constexpr OutIt fill_n_small(OutIt dest, Diff count, const T val)
{
for (; count; --count, ++dest)
{
*dest = val;
}
return dest;
}
// Same as std::copy_n, but purpose-built for very short `count`
// where a trivial loop outperforms vectorization.
template<typename InIt, typename Diff, typename OutIt>
constexpr OutIt copy_n_small(InIt first, Diff count, OutIt dest)
{
for (; count; --count, ++dest, ++first)
{
*dest = *first;
}
return dest;
}
// Routine Description:
// - constructor
// Arguments:
// - rowId - the row index in the text buffer
// - rowWidth - the width of the row, cell elements
// - fillAttribute - the default text attribute
// - pParent - the text buffer that this row belongs to
// Return Value:
// - constructed object
ROW::ROW(const til::CoordType rowId, const til::CoordType rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent) :
_id{ rowId },
_rowWidth{ rowWidth },
_charRow{ rowWidth, this },
_attrRow{ rowWidth, fillAttribute },
_lineRendition{ LineRendition::SingleWidth },
_wrapForced{ false },
_doubleBytePadded{ false },
_pParent{ pParent }
ROW::ROW(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute) :
_charsBuffer{ charsBuffer },
_chars{ charsBuffer, rowWidth },
_charOffsets{ charOffsetsBuffer, ::base::strict_cast<size_t>(rowWidth) + 1u },
_attr{ rowWidth, fillAttribute },
_columnCount{ rowWidth }
{
if (_chars.data())
{
_init();
}
}
void swap(ROW& lhs, ROW& rhs) noexcept
{
std::swap(lhs._charsBuffer, rhs._charsBuffer);
std::swap(lhs._charsHeap, rhs._charsHeap);
std::swap(lhs._chars, rhs._chars);
std::swap(lhs._charOffsets, rhs._charOffsets);
std::swap(lhs._attr, rhs._attr);
std::swap(lhs._columnCount, rhs._columnCount);
std::swap(lhs._lineRendition, rhs._lineRendition);
std::swap(lhs._wrapForced, rhs._wrapForced);
std::swap(lhs._doubleBytePadded, rhs._doubleBytePadded);
}
void ROW::SetWrapForced(const bool wrap) noexcept
{
_wrapForced = wrap;
}
bool ROW::WasWrapForced() const noexcept
{
return _wrapForced;
}
void ROW::SetDoubleBytePadded(const bool doubleBytePadded) noexcept
{
_doubleBytePadded = doubleBytePadded;
}
bool ROW::WasDoubleBytePadded() const noexcept
{
return _doubleBytePadded;
}
void ROW::SetLineRendition(const LineRendition lineRendition) noexcept
{
_lineRendition = lineRendition;
}
LineRendition ROW::GetLineRendition() const noexcept
{
return _lineRendition;
}
// Routine Description:
@ -34,42 +134,99 @@ ROW::ROW(const til::CoordType rowId, const til::CoordType rowWidth, const TextAt
// - Attr - The default attribute (color) to fill
// Return Value:
// - <none>
bool ROW::Reset(const TextAttribute Attr)
void ROW::Reset(const TextAttribute& attr)
{
_charsHeap.reset();
_chars = { _charsBuffer, _columnCount };
_attr = { _columnCount, attr };
_lineRendition = LineRendition::SingleWidth;
_wrapForced = false;
_doubleBytePadded = false;
_charRow.Reset();
try
{
_attrRow.Reset(Attr);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return false;
}
return true;
_init();
}
void ROW::_init() noexcept
{
std::fill_n(_chars.begin(), _columnCount, UNICODE_SPACE);
std::iota(_charOffsets.begin(), _charOffsets.end(), uint16_t{ 0 });
}
// Routine Description:
// - resizes ROW to new width
// Arguments:
// - width - the new width, in cells
// Return Value:
// - S_OK if successful, otherwise relevant error
[[nodiscard]] HRESULT ROW::Resize(const til::CoordType width)
// - charsBuffer - a new backing buffer to use for _charsBuffer
// - charOffsetsBuffer - a new backing buffer to use for _charOffsets
// - rowWidth - the new width, in cells
// - fillAttribute - the attribute to use for any newly added, trailing cells
void ROW::Resize(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute)
{
RETURN_IF_FAILED(_charRow.Resize(width));
try
// A default-constructed ROW has no cols/chars to copy.
// It can be detected by the lack of a _charsBuffer (among others).
//
// Otherwise, this block figures out how much we can copy into the new `rowWidth`.
uint16_t colsToCopy = 0;
uint16_t charsToCopy = 0;
if (_charsBuffer)
{
_attrRow.Resize(width);
colsToCopy = std::min(rowWidth, _columnCount);
// Safety: colsToCopy is [0, _columnCount].
charsToCopy = _uncheckedCharOffset(colsToCopy);
// Safety: colsToCopy is [0, _columnCount] due to colsToCopy != 0.
for (; colsToCopy != 0 && _uncheckedIsTrailer(colsToCopy); --colsToCopy)
{
}
}
CATCH_RETURN();
_rowWidth = width;
// If we grow the row width, we have to append a bunch of whitespace.
// `trailingWhitespace` stores that amount.
// Safety: The preceding block left colsToCopy in the range [0, rowWidth].
const uint16_t trailingWhitespace = rowWidth - colsToCopy;
return S_OK;
// Allocate memory for the new `_chars` array.
// Use the provided charsBuffer if possible, otherwise allocate a `_charsHeap`.
std::unique_ptr<wchar_t[]> charsHeap;
std::span chars{ charsBuffer, rowWidth };
const std::span charOffsets{ charOffsetsBuffer, ::base::strict_cast<size_t>(rowWidth) + 1u };
if (const uint16_t charsCapacity = charsToCopy + trailingWhitespace; charsCapacity > rowWidth)
{
charsHeap = std::make_unique_for_overwrite<wchar_t[]>(charsCapacity);
chars = { charsHeap.get(), charsCapacity };
}
// Copy chars and charOffsets over.
{
const auto it = std::copy_n(_chars.begin(), charsToCopy, chars.begin());
std::fill_n(it, trailingWhitespace, L' ');
}
{
const auto it = std::copy_n(_charOffsets.begin(), colsToCopy, charOffsets.begin());
// The _charOffsets array is 1 wider than newWidth indicates.
// This is because the extra column contains the past-the-end index into _chars.
iota_n(it, trailingWhitespace + 1u, charsToCopy);
}
_charsBuffer = charsBuffer;
_charsHeap = std::move(charsHeap);
_chars = chars;
_charOffsets = charOffsets;
_columnCount = rowWidth;
// .resize_trailing_extent() doesn't work if the vector is empty,
// since there's no trailing item that could be extended.
if (_attr.empty())
{
_attr = { rowWidth, fillAttribute };
}
else
{
_attr.resize_trailing_extent(rowWidth);
}
}
void ROW::TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& attr, til::CoordType newWidth)
{
_attr = attr;
_attr.resize_trailing_extent(gsl::narrow<uint16_t>(newWidth));
}
// Routine Description:
@ -78,20 +235,10 @@ bool ROW::Reset(const TextAttribute Attr)
// - column - 0-indexed column index
// Return Value:
// - <none>
void ROW::ClearColumn(const til::CoordType column)
void ROW::ClearCell(const til::CoordType column)
{
THROW_HR_IF(E_INVALIDARG, column >= _charRow.size());
_charRow.ClearCell(column);
}
UnicodeStorage& ROW::GetUnicodeStorage() noexcept
{
return _pParent->GetUnicodeStorage();
}
const UnicodeStorage& ROW::GetUnicodeStorage() const noexcept
{
return _pParent->GetUnicodeStorage();
static constexpr std::wstring_view space{ L" " };
ReplaceCharacters(column, 1, space);
}
// Routine Description:
@ -103,17 +250,17 @@ const UnicodeStorage& ROW::GetUnicodeStorage() const noexcept
// - limitRight - right inclusive column ID for the last write in this row. (optional, will just write to the end of row if nullopt)
// Return Value:
// - iterator to first cell that was not written to this row.
OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType index, const std::optional<bool> wrap, std::optional<til::CoordType> limitRight)
OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType columnBegin, const std::optional<bool> wrap, std::optional<til::CoordType> limitRight)
{
THROW_HR_IF(E_INVALIDARG, index >= _charRow.size());
THROW_HR_IF(E_INVALIDARG, limitRight.value_or(0) >= _charRow.size());
THROW_HR_IF(E_INVALIDARG, columnBegin >= size());
THROW_HR_IF(E_INVALIDARG, limitRight.value_or(0) >= size());
// If we're given a right-side column limit, use it. Otherwise, the write limit is the final column index available in the char row.
const auto finalColumnInRow = limitRight.value_or(_charRow.size() - 1);
const auto finalColumnInRow = limitRight.value_or(size() - 1);
auto currentColor = it->TextAttr();
uint16_t colorUses = 0;
auto colorStarts = gsl::narrow_cast<uint16_t>(index);
auto colorStarts = gsl::narrow_cast<uint16_t>(columnBegin);
auto currentIndex = colorStarts;
while (it && currentIndex <= finalColumnInRow)
@ -131,7 +278,7 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType i
{
// Otherwise, commit this color into the run and save off the new one.
// Now commit the new color runs into the attr row.
_attrRow.Replace(colorStarts, currentIndex, currentColor);
_attr.replace(colorStarts, currentIndex, currentColor);
currentColor = it->TextAttr();
colorUses = 1;
colorStarts = currentIndex;
@ -141,31 +288,47 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType i
// Fill the text if the behavior isn't set to saying there's only a color stored in this iterator.
if (it->TextAttrBehavior() != TextAttributeBehavior::StoredOnly)
{
const auto fillingFirstColumn = currentIndex == 0;
const auto fillingLastColumn = currentIndex == finalColumnInRow;
const auto attr = it->DbcsAttr();
const auto& chars = it->Chars();
// TODO: MSFT: 19452170 - We need to ensure when writing any trailing byte that the one to the left
// is a matching leading byte. Likewise, if we're writing a leading byte, we need to make sure we still have space in this loop
// for the trailing byte coming up before writing it.
// If we're trying to fill the first cell with a trailing byte, pad it out instead by clearing it.
// Don't increment iterator. We'll advance the index and try again with this value on the next round through the loop.
if (currentIndex == 0 && it->DbcsAttr().IsTrailing())
switch (attr)
{
_charRow.ClearCell(currentIndex);
}
// If we're trying to fill the last cell with a leading byte, pad it out instead by clearing it.
// Don't increment iterator. We'll exit because we couldn't write a lead at the end of a line.
else if (fillingLastColumn && it->DbcsAttr().IsLeading())
{
_charRow.ClearCell(currentIndex);
SetDoubleBytePadded(true);
}
// Otherwise, copy the data given and increment the iterator.
else
{
_charRow.DbcsAttrAt(currentIndex) = it->DbcsAttr();
_charRow.GlyphAt(currentIndex) = it->Chars();
case DbcsAttribute::Leading:
if (fillingLastColumn)
{
// The wide char doesn't fit. Pad with whitespace.
// Don't increment the iterator. Instead we'll return from this function and the
// caller can call WriteCells() again on the next row with the same iterator position.
ClearCell(currentIndex);
SetDoubleBytePadded(true);
}
else
{
ReplaceCharacters(currentIndex, 2, chars);
++it;
}
break;
case DbcsAttribute::Trailing:
// Handling the trailing half of wide chars ensures that we correctly restore
// wide characters when a user backs up and restores the viewport via CHAR_INFOs.
if (fillingFirstColumn)
{
// The wide char doesn't fit. Pad with whitespace.
// Ignore the character. There's no correct alternative way to handle this situation.
ClearCell(currentIndex);
}
else
{
ReplaceCharacters(currentIndex - 1, 2, chars);
}
++it;
break;
default:
ReplaceCharacters(currentIndex, 1, chars);
++it;
break;
}
// If we're asked to (un)set the wrap status and we just filled the last column with some text...
@ -191,8 +354,334 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const til::CoordType i
// Now commit the final color into the attr row
if (colorUses)
{
_attrRow.Replace(colorStarts, currentIndex, currentColor);
_attr.replace(colorStarts, currentIndex, currentColor);
}
return it;
}
bool ROW::SetAttrToEnd(const til::CoordType columnBegin, const TextAttribute attr)
{
_attr.replace(_clampedColumnInclusive(columnBegin), _attr.size(), attr);
return true;
}
void ROW::ReplaceAttributes(const til::CoordType beginIndex, const til::CoordType endIndex, const TextAttribute& newAttr)
{
_attr.replace(_clampedColumnInclusive(beginIndex), _clampedColumnInclusive(endIndex), newAttr);
}
void ROW::ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars)
{
const auto colBeg = _clampedUint16(columnBegin);
const auto colEnd = _clampedUint16(columnBegin + width);
if (colBeg >= colEnd || colEnd > _columnCount || chars.empty())
{
return;
}
// Safety:
// * colBeg is now [0, _columnCount)
// * colEnd is now (colBeg, _columnCount]
// Algorithm explanation
//
// Task:
// Replace the characters in cells [colBeg, colEnd) with a single `width`-wide glyph consisting of `chars`.
//
// Problem:
// Imagine that we have the following ROW contents:
// "xxyyzz"
// xx, yy, zz are 2 cell wide glyphs. We want to insert a 2 cell wide glyph ww at colBeg 1:
// ^^
// ww
// An incorrect result would be:
// "xwwyzz"
// The half cut off x and y glyph wouldn't make much sense, so we need to fill them with whitespace:
// " ww zz"
//
// Solution:
// Given the range we want to replace [colBeg, colEnd), we "extend" it to encompass leading (preceding)
// and trailing wide glyphs we partially overwrite resulting in the range [colExtBeg, colExtEnd), where
// colExtBeg <= colBeg and colExtEnd >= colEnd. In other words, the to be replaced range has been "extended".
// The amount of leading whitespace we need to insert is thus colBeg - colExtBeg
// and the amount of trailing whitespace colExtEnd - colEnd.
// Extend range downwards (leading whitespace)
uint16_t colExtBeg = colBeg;
// Safety: colExtBeg is [0, _columnCount], because colBeg is.
const uint16_t chExtBeg = _uncheckedCharOffset(colExtBeg);
// Safety: colExtBeg remains [0, _columnCount] due to colExtBeg != 0.
for (; colExtBeg != 0 && _uncheckedIsTrailer(colExtBeg); --colExtBeg)
{
}
// Extend range upwards (trailing whitespace)
uint16_t colExtEnd = colEnd;
// Safety: colExtEnd cannot be incremented past _columnCount, because the last
// _charOffset at index _columnCount will never get the CharOffsetsTrailer flag.
for (; _uncheckedIsTrailer(colExtEnd); ++colExtEnd)
{
}
// Safety: After the previous loop colExtEnd is [0, _columnCount].
const uint16_t chExtEnd = _uncheckedCharOffset(colExtEnd);
const uint16_t leadingSpaces = colBeg - colExtBeg;
const uint16_t trailingSpaces = colExtEnd - colEnd;
const size_t chExtEndNew = chars.size() + leadingSpaces + trailingSpaces + chExtBeg;
if (chExtEndNew != chExtEnd)
{
_resizeChars(colExtEnd, chExtBeg, chExtEnd, chExtEndNew);
}
// Add leading/trailing whitespace and copy chars
{
auto it = _chars.begin() + chExtBeg;
it = fill_n_small(it, leadingSpaces, L' ');
it = copy_n_small(chars.begin(), chars.size(), it);
it = fill_n_small(it, trailingSpaces, L' ');
}
// Update char offsets with leading/trailing whitespace and the chars columns.
{
auto chPos = chExtBeg;
auto it = _charOffsets.begin() + colExtBeg;
it = iota_n_mut(it, leadingSpaces, chPos);
*it++ = chPos;
it = fill_small(it, _charOffsets.begin() + colEnd, gsl::narrow_cast<uint16_t>(chPos | CharOffsetsTrailer));
chPos = gsl::narrow_cast<uint16_t>(chPos + chars.size());
it = iota_n_mut(it, trailingSpaces, chPos);
}
}
// This function represents the slow path of ReplaceCharacters(),
// as it reallocates the backing buffer and shifts the char offsets.
// The parameters are difficult to explain, but their names are identical to
// local variables in ReplaceCharacters() which I've attempted to document there.
void ROW::_resizeChars(uint16_t colExtEnd, uint16_t chExtBeg, uint16_t chExtEnd, size_t chExtEndNew)
{
const auto diff = chExtEndNew - chExtEnd;
const auto currentLength = _charSize();
const auto newLength = currentLength + diff;
if (newLength <= _chars.size())
{
std::copy_n(_chars.begin() + chExtEnd, currentLength - chExtEnd, _chars.begin() + chExtEndNew);
}
else
{
const auto minCapacity = std::min<size_t>(UINT16_MAX, _chars.size() + (_chars.size() >> 1));
const auto newCapacity = gsl::narrow<uint16_t>(std::max(newLength, minCapacity));
auto charsHeap = std::make_unique_for_overwrite<wchar_t[]>(newCapacity);
const std::span chars{ charsHeap.get(), newCapacity };
std::copy_n(_chars.begin(), chExtBeg, chars.begin());
std::copy_n(_chars.begin() + chExtEnd, currentLength - chExtEnd, chars.begin() + chExtEndNew);
_charsHeap = std::move(charsHeap);
_chars = chars;
}
auto it = _charOffsets.begin() + colExtEnd;
const auto end = _charOffsets.end();
for (; it != end; ++it)
{
*it = gsl::narrow_cast<uint16_t>(*it + diff);
}
}
const til::small_rle<TextAttribute, uint16_t, 1>& ROW::Attributes() const noexcept
{
return _attr;
}
TextAttribute ROW::GetAttrByColumn(const til::CoordType column) const
{
return _attr.at(_clampedUint16(column));
}
std::vector<uint16_t> ROW::GetHyperlinks() const
{
std::vector<uint16_t> ids;
for (const auto& run : _attr.runs())
{
if (run.value.IsHyperlink())
{
ids.emplace_back(run.value.GetHyperlinkId());
}
}
return ids;
}
uint16_t ROW::size() const noexcept
{
return _columnCount;
}
til::CoordType ROW::MeasureLeft() const noexcept
{
const auto text = GetText();
const auto beg = text.begin();
const auto end = text.end();
auto it = beg;
for (; it != end; ++it)
{
if (*it != L' ')
{
break;
}
}
return gsl::narrow_cast<til::CoordType>(it - beg);
}
til::CoordType ROW::MeasureRight() const noexcept
{
const auto text = GetText();
const auto beg = text.begin();
const auto end = text.end();
auto it = end;
for (; it != beg; --it)
{
// it[-1] is safe as `it` is always greater than `beg` (loop invariant).
if (til::at(it, -1) != L' ')
{
break;
}
}
// We're supposed to return the measurement in cells and not characters
// and therefore simply calculating `it - beg` would be wrong.
//
// An example: The row is 10 cells wide and `it` points to the second character.
// `it - beg` would return 1, but it's possible it's actually 1 wide glyph and 8 whitespace.
return gsl::narrow_cast<til::CoordType>(_columnCount - (end - it));
}
bool ROW::ContainsText() const noexcept
{
const auto text = GetText();
const auto beg = text.begin();
const auto end = text.end();
auto it = beg;
for (; it != end; ++it)
{
if (*it != L' ')
{
return true;
}
}
return false;
}
std::wstring_view ROW::GlyphAt(til::CoordType column) const noexcept
{
auto col = _clampedColumn(column);
// Safety: col is [0, _columnCount).
const auto beg = _uncheckedCharOffset(col);
// Safety: col cannot be incremented past _columnCount, because the last
// _charOffset at index _columnCount will never get the CharOffsetsTrailer flag.
while (_uncheckedIsTrailer(++col))
{
}
// Safety: col is now (0, _columnCount].
const auto end = _uncheckedCharOffset(col);
return { _chars.begin() + beg, _chars.begin() + end };
}
DbcsAttribute ROW::DbcsAttrAt(til::CoordType column) const noexcept
{
const auto col = _clampedColumn(column);
auto attr = DbcsAttribute::Single;
// Safety: col is [0, _columnCount).
if (_uncheckedIsTrailer(col))
{
attr = DbcsAttribute::Trailing;
}
// Safety: col+1 is [1, _columnCount].
else if (_uncheckedIsTrailer(::base::strict_cast<size_t>(col) + 1u))
{
attr = DbcsAttribute::Leading;
}
return { attr };
}
std::wstring_view ROW::GetText() const noexcept
{
return { _chars.data(), _charSize() };
}
DelimiterClass ROW::DelimiterClassAt(til::CoordType column, const std::wstring_view& wordDelimiters) const noexcept
{
const auto col = _clampedColumn(column);
// Safety: col is [0, _columnCount).
const auto glyph = _uncheckedChar(_uncheckedCharOffset(col));
if (glyph <= L' ')
{
return DelimiterClass::ControlChar;
}
else if (wordDelimiters.find(glyph) != std::wstring_view::npos)
{
return DelimiterClass::DelimiterChar;
}
else
{
return DelimiterClass::RegularChar;
}
}
template<typename T>
constexpr uint16_t ROW::_clampedUint16(T v) noexcept
{
return static_cast<uint16_t>(std::max(T{ 0 }, std::min(T{ 65535 }, v)));
}
template<typename T>
constexpr uint16_t ROW::_clampedColumn(T v) const noexcept
{
return static_cast<uint16_t>(std::max(T{ 0 }, std::min<T>(_columnCount - 1u, v)));
}
template<typename T>
constexpr uint16_t ROW::_clampedColumnInclusive(T v) const noexcept
{
return static_cast<uint16_t>(std::max(T{ 0 }, std::min<T>(_columnCount, v)));
}
// Safety: off must be [0, _charSize()].
wchar_t ROW::_uncheckedChar(size_t off) const noexcept
{
return til::at(_chars, off);
}
uint16_t ROW::_charSize() const noexcept
{
// Safety: _charOffsets is an array of `_columnCount + 1` entries.
return til::at(_charOffsets, _columnCount);
}
// Safety: col must be [0, _columnCount].
uint16_t ROW::_uncheckedCharOffset(size_t col) const noexcept
{
return til::at(_charOffsets, col) & CharOffsetsMask;
}
// Safety: col must be [0, _columnCount].
bool ROW::_uncheckedIsTrailer(size_t col) const noexcept
{
return WI_IsFlagSet(til::at(_charOffsets, col), CharOffsetsTrailer);
}

View File

@ -20,50 +20,68 @@ Revision History:
#pragma once
#include "AttrRow.hpp"
#include <span>
#include <til/rle.h>
#include "LineRendition.hpp"
#include "OutputCell.hpp"
#include "OutputCellIterator.hpp"
#include "CharRow.hpp"
#include "UnicodeStorage.hpp"
class TextBuffer;
enum class DelimiterClass
{
ControlChar,
DelimiterChar,
RegularChar
};
class ROW final
{
public:
ROW(const til::CoordType rowId, const til::CoordType rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent);
ROW() = default;
ROW(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute);
til::CoordType size() const noexcept { return _rowWidth; }
ROW(const ROW& other) = delete;
ROW& operator=(const ROW& other) = delete;
void SetWrapForced(const bool wrap) noexcept { _wrapForced = wrap; }
bool WasWrapForced() const noexcept { return _wrapForced; }
explicit ROW(ROW&& other) = default;
ROW& operator=(ROW&& other) = default;
void SetDoubleBytePadded(const bool doubleBytePadded) noexcept { _doubleBytePadded = doubleBytePadded; }
bool WasDoubleBytePadded() const noexcept { return _doubleBytePadded; }
friend void swap(ROW& lhs, ROW& rhs) noexcept;
const CharRow& GetCharRow() const noexcept { return _charRow; }
CharRow& GetCharRow() noexcept { return _charRow; }
void SetWrapForced(const bool wrap) noexcept;
bool WasWrapForced() const noexcept;
void SetDoubleBytePadded(const bool doubleBytePadded) noexcept;
bool WasDoubleBytePadded() const noexcept;
void SetLineRendition(const LineRendition lineRendition) noexcept;
LineRendition GetLineRendition() const noexcept;
const ATTR_ROW& GetAttrRow() const noexcept { return _attrRow; }
ATTR_ROW& GetAttrRow() noexcept { return _attrRow; }
void Reset(const TextAttribute& attr);
void Resize(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute);
void TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& attr, til::CoordType newWidth);
LineRendition GetLineRendition() const noexcept { return _lineRendition; }
void SetLineRendition(const LineRendition lineRendition) noexcept { _lineRendition = lineRendition; }
void ClearCell(til::CoordType column);
OutputCellIterator WriteCells(OutputCellIterator it, til::CoordType columnBegin, std::optional<bool> wrap = std::nullopt, std::optional<til::CoordType> limitRight = std::nullopt);
bool SetAttrToEnd(til::CoordType columnBegin, TextAttribute attr);
void ReplaceAttributes(til::CoordType beginIndex, til::CoordType endIndex, const TextAttribute& newAttr);
void ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars);
til::CoordType GetId() const noexcept { return _id; }
void SetId(const til::CoordType id) noexcept { _id = id; }
const til::small_rle<TextAttribute, uint16_t, 1>& Attributes() const noexcept;
TextAttribute GetAttrByColumn(til::CoordType column) const;
std::vector<uint16_t> GetHyperlinks() const;
uint16_t size() const noexcept;
til::CoordType MeasureLeft() const noexcept;
til::CoordType MeasureRight() const noexcept;
bool ContainsText() const noexcept;
std::wstring_view GlyphAt(til::CoordType column) const noexcept;
DbcsAttribute DbcsAttrAt(til::CoordType column) const noexcept;
std::wstring_view GetText() const noexcept;
DelimiterClass DelimiterClassAt(til::CoordType column, const std::wstring_view& wordDelimiters) const noexcept;
bool Reset(const TextAttribute Attr);
[[nodiscard]] HRESULT Resize(const til::CoordType width);
void ClearColumn(const til::CoordType column);
std::wstring GetText() const { return _charRow.GetText(); }
UnicodeStorage& GetUnicodeStorage() noexcept;
const UnicodeStorage& GetUnicodeStorage() const noexcept;
OutputCellIterator WriteCells(OutputCellIterator it, const til::CoordType index, const std::optional<bool> wrap = std::nullopt, std::optional<til::CoordType> limitRight = std::nullopt);
auto AttrBegin() const noexcept { return _attr.begin(); }
auto AttrEnd() const noexcept { return _attr.end(); }
#ifdef UNIT_TESTING
friend constexpr bool operator==(const ROW& a, const ROW& b) noexcept;
@ -71,23 +89,84 @@ public:
#endif
private:
CharRow _charRow;
ATTR_ROW _attrRow;
LineRendition _lineRendition;
til::CoordType _id;
til::CoordType _rowWidth;
// To simplify the detection of wide glyphs, we don't just store the simple character offset as described
// for _charOffsets. Instead we use the most significant bit to indicate whether any column is the
// trailing half of a wide glyph. This simplifies many implementation details via _uncheckedIsTrailer.
static constexpr uint16_t CharOffsetsTrailer = 0x8000;
static constexpr uint16_t CharOffsetsMask = 0x7fff;
template<typename T>
static constexpr uint16_t _clampedUint16(T v) noexcept;
template<typename T>
constexpr uint16_t _clampedColumn(T v) const noexcept;
template<typename T>
constexpr uint16_t _clampedColumnInclusive(T v) const noexcept;
wchar_t _uncheckedChar(size_t off) const noexcept;
uint16_t _charSize() const noexcept;
uint16_t _uncheckedCharOffset(size_t col) const noexcept;
bool _uncheckedIsTrailer(size_t col) const noexcept;
void _init() noexcept;
void _resizeChars(uint16_t colExtEnd, uint16_t chExtBeg, uint16_t chExtEnd, size_t chExtEndNew);
// These fields are a bit "wasteful", but it makes all this a bit more robust against
// programming errors during initial development (which is when this comment was written).
// * _chars and _charsHeap are redundant
// If _charsHeap is stored in _chars, we can still infer that
// _chars was allocated on the heap if _chars != _charsBuffer.
// * _chars doesn't need a size_t size()
// The size may never exceed an uint16_t anyways.
// * _charOffsets doesn't need a size() at all
// The length is already stored in _columns.
// Most text uses only a single wchar_t per codepoint / grapheme cluster.
// That's why TextBuffer allocates a large blob of virtual memory which we can use as
// a simplified chars buffer, without having to allocate any additional heap memory.
// _charsBuffer fits _columnCount characters at most.
wchar_t* _charsBuffer = nullptr;
// ...but if this ROW needs to store more than _columnCount characters
// then it will allocate a larger string on the heap and store it here.
// The capacity of this string on the heap is stored in _chars.size().
std::unique_ptr<wchar_t[]> _charsHeap;
// _chars either refers to our _charsBuffer or _charsHeap, defaulting to the former.
// _chars.size() is NOT the length of the string, but rather its capacity.
// _charOffsets[_columnCount] stores the length.
std::span<wchar_t> _chars;
// _charOffsets accelerates indexing into the above _chars string given a terminal column,
// by storing the character index/offset at which a column's text in _chars starts.
// It stores 1 more item than this row is wide, allowing it to store the
// past-the-end offset, which is thus equal to the length of the string.
//
// For instance given a 4 column ROW containing "abcd" it would store 01234,
// because each of "abcd" are 1 column wide and 1 wchar_t per column.
// Given "a\u732Bd" it would store 01123, because "\u732B" is a wide glyph
// and "11" indicates that both column 1 and 2 start at &_chars[1] (= wide glyph).
// The fact that the next offset is 2 tells us that the glyph is 1 wchar_t long.
// Given "a\uD83D\uDE00d" ("\uD83D\uDE00" is an Emoji) it would store 01134,
// because while it's 2 cells wide as indicated by 2 offsets that are identical (11),
// the next offset is 3, which indicates that the glyph is 3-1 = 2 wchar_t long.
//
// In other words, _charOffsets tells us both the width in chars and width in columns.
// See CharOffsetsTrailer for more information.
std::span<uint16_t> _charOffsets;
// _attr is a run-length-encoded vector of TextAttribute with a decompressed
// length equal to _columnCount (= 1 TextAttribute per column).
til::small_rle<TextAttribute, uint16_t, 1> _attr;
// The width of the row in visual columns.
uint16_t _columnCount = 0;
// Stores double-width/height (DECSWL/DECDWL/DECDHL) attributes.
LineRendition _lineRendition = LineRendition::SingleWidth;
// Occurs when the user runs out of text in a given row and we're forced to wrap the cursor to the next line
bool _wrapForced;
bool _wrapForced = false;
// Occurs when the user runs out of text to support a double byte character and we're forced to the next line
bool _doubleBytePadded;
TextBuffer* _pParent; // non ownership pointer
bool _doubleBytePadded = false;
};
#ifdef UNIT_TESTING
constexpr bool operator==(const ROW& a, const ROW& b) noexcept
{
// comparison is only used in the tests; this should suffice.
return (a._pParent == b._pParent &&
a._id == b._id);
return a._charsBuffer == b._charsBuffer;
}
#endif

View File

@ -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<til::CoordType, til::CoordType>& rowMap, const std::optional<til::CoordType> 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 = til::point{ oldCoord.X, newRowId };
// Put the adjusted coordinate into the map with the original value.
newMap.emplace(newCoord, pair.second);
}
// Swap into the stored map, free the temporary when we exit.
_map.swap(newMap);
}

View File

@ -1,65 +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 <unordered_map>
#include <vector>
#include <til/hash.h>
// std::unordered_map needs help to know how to hash a til::point
namespace std
{
template<>
struct hash<til::point>
{
// 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
size_t operator()(const til::point coord) const noexcept
{
return til::hash(coord);
}
};
}
class UnicodeStorage final
{
public:
using key_type = typename til::point;
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<til::CoordType, til::CoordType>& rowMap, const std::optional<til::CoordType> width);
private:
std::unordered_map<key_type, mapped_type> _map;
#ifdef UNIT_TESTING
friend class UnicodeStorageTests;
friend class TextBufferTests;
#endif
};

View File

@ -11,7 +11,6 @@
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<Import Project="$(SolutionDir)src\common.nugetversions.props" />
<ItemGroup>
<ClCompile Include="..\AttrRow.cpp" />
<ClCompile Include="..\cursor.cpp" />
<ClCompile Include="..\OutputCell.cpp" />
<ClCompile Include="..\OutputCellIterator.cpp" />
@ -24,16 +23,11 @@
<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" />
@ -49,11 +43,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" />

View File

@ -5,7 +5,6 @@
#include "search.h"
#include "CharRow.hpp"
#include "textBuffer.hpp"
#include "../types/inc/Utf16Parser.hpp"
#include "../types/inc/GlyphWidth.hpp"

View File

@ -29,7 +29,6 @@ PRECOMPILED_CXX = 1
PRECOMPILED_INCLUDE = ..\precomp.h
SOURCES= \
..\AttrRow.cpp \
..\cursor.cpp \
..\OutputCell.cpp \
..\OutputCellIterator.cpp \
@ -41,10 +40,6 @@ SOURCES= \
..\textBuffer.cpp \
..\textBufferCellIterator.cpp \
..\textBufferTextIterator.cpp \
..\CharRow.cpp \
..\CharRowCell.cpp \
..\CharRowCellReference.cpp \
..\UnicodeStorage.cpp \
..\search.cpp \
INCLUDES= \

View File

@ -4,7 +4,8 @@
#include "precomp.h"
#include "textBuffer.hpp"
#include "CharRow.hpp"
#include <til/hash.h>
#include "../renderer/base/renderer.hpp"
#include "../types/inc/utils.hpp"
@ -12,7 +13,74 @@
#include "../../types/inc/Utf16Parser.hpp"
#include "../../types/inc/GlyphWidth.hpp"
#pragma hdrstop
namespace
{
struct BufferAllocator
{
BufferAllocator(til::size sz)
{
const auto w = gsl::narrow<uint16_t>(sz.width);
const auto h = gsl::narrow<uint16_t>(sz.height);
const auto charsBytes = w * sizeof(wchar_t);
// The ROW::_indices array stores 1 more item than the buffer is wide.
// That extra column stores the past-the-end _chars pointer.
const auto indicesBytes = w * sizeof(uint16_t) + sizeof(uint16_t);
const auto rowStride = charsBytes + indicesBytes;
// 65535*65535 cells would result in a charsAreaSize of 8GiB.
// --> Use uint64_t so that we can safely do our calculations even on x86.
const auto allocSize = gsl::narrow<size_t>(::base::strict_cast<uint64_t>(rowStride) * ::base::strict_cast<uint64_t>(h));
_buffer = wil::unique_virtualalloc_ptr<std::byte>{ static_cast<std::byte*>(VirtualAlloc(nullptr, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) };
THROW_IF_NULL_ALLOC(_buffer);
_data = std::span{ _buffer.get(), allocSize }.begin();
_rowStride = rowStride;
_indicesOffset = charsBytes;
_width = w;
_height = h;
}
BufferAllocator& operator++() noexcept
{
_data += _rowStride;
return *this;
}
wchar_t* chars() const noexcept
{
return til::bit_cast<wchar_t*>(&*_data);
}
uint16_t* indices() const noexcept
{
return til::bit_cast<uint16_t*>(&*(_data + _indicesOffset));
}
uint16_t width() const noexcept
{
return _width;
}
uint16_t height() const noexcept
{
return _height;
}
wil::unique_virtualalloc_ptr<std::byte>&& take() noexcept
{
return std::move(_buffer);
}
private:
wil::unique_virtualalloc_ptr<std::byte> _buffer;
std::span<std::byte>::iterator _data;
size_t _rowStride;
size_t _indicesOffset;
uint16_t _width;
uint16_t _height;
};
}
using namespace Microsoft::Console;
using namespace Microsoft::Console::Types;
@ -35,24 +103,20 @@ TextBuffer::TextBuffer(const til::size screenBufferSize,
const UINT cursorSize,
const bool isActiveBuffer,
Microsoft::Console::Render::Renderer& renderer) :
_firstRow{ 0 },
_renderer{ renderer },
_currentAttributes{ defaultAttributes },
_cursor{ cursorSize, *this },
_storage{},
_unicodeStorage{},
_isActiveBuffer{ isActiveBuffer },
_renderer{ renderer },
_size{},
_currentHyperlinkId{ 1 },
_currentPatternId{ 0 }
_isActiveBuffer{ isActiveBuffer }
{
// initialize ROWs
_storage.reserve(gsl::narrow<size_t>(screenBufferSize.Y));
for (til::CoordType i = 0; i < screenBufferSize.Y; ++i)
BufferAllocator allocator{ screenBufferSize };
_storage.reserve(allocator.height());
for (til::CoordType i = 0; i < screenBufferSize.Y; ++i, ++allocator)
{
_storage.emplace_back(i, screenBufferSize.X, _currentAttributes, this);
_storage.emplace_back(allocator.chars(), allocator.indices(), allocator.width(), _currentAttributes);
}
_charBuffer = allocator.take();
_UpdateSize();
}
@ -199,10 +263,10 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut
// To figure out if the sequence is valid, we have to look at the character that comes before the current one
const auto coordPrevPosition = _GetPreviousFromCursor();
auto& prevRow = GetRowByOffset(coordPrevPosition.Y);
DbcsAttribute prevDbcsAttr;
DbcsAttribute prevDbcsAttr = DbcsAttribute::Single;
try
{
prevDbcsAttr = prevRow.GetCharRow().DbcsAttrAt(coordPrevPosition.X);
prevDbcsAttr = prevRow.DbcsAttrAt(coordPrevPosition.X);
}
catch (...)
{
@ -229,21 +293,21 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut
// T T Fail, uncorrectable. New trailing byte must have had leading before it.
// Check for only failing portions of the matrix:
if (prevDbcsAttr.IsSingle() && dbcsAttribute.IsTrailing())
if (prevDbcsAttr == DbcsAttribute::Single && dbcsAttribute == DbcsAttribute::Trailing)
{
// N, T failing case (uncorrectable)
fValidSequence = false;
}
else if (prevDbcsAttr.IsLeading())
else if (prevDbcsAttr == DbcsAttribute::Leading)
{
if (dbcsAttribute.IsSingle() || dbcsAttribute.IsLeading())
if (dbcsAttribute == DbcsAttribute::Single || dbcsAttribute == DbcsAttribute::Leading)
{
// L, N and L, L failing cases (correctable)
fValidSequence = false;
fCorrectableByErase = true;
}
}
else if (prevDbcsAttr.IsTrailing() && dbcsAttribute.IsTrailing())
else if (prevDbcsAttr == DbcsAttribute::Trailing && dbcsAttribute == DbcsAttribute::Trailing)
{
// T, T failing case (uncorrectable)
fValidSequence = false;
@ -255,7 +319,7 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut
// Erase previous character into an N type.
try
{
prevRow.ClearColumn(coordPrevPosition.X);
prevRow.ClearCell(coordPrevPosition.X);
}
catch (...)
{
@ -289,7 +353,7 @@ bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute
auto fSuccess = true;
// Now compensate if we don't have enough space for the upcoming double byte sequence
// We only need to compensate for leading bytes
if (dbcsAttribute.IsLeading())
if (dbcsAttribute == DbcsAttribute::Leading)
{
const auto cursorPosition = GetCursor().GetPosition();
const auto lineWidth = GetLineWidth(cursorPosition.Y);
@ -418,12 +482,20 @@ bool TextBuffer::InsertCharacter(const std::wstring_view chars,
auto& Row = GetRowByOffset(iRow);
// Store character and double byte data
auto& charRow = Row.GetCharRow();
try
{
charRow.GlyphAt(iCol) = chars;
charRow.DbcsAttrAt(iCol) = dbcsAttribute;
switch (dbcsAttribute)
{
case DbcsAttribute::Leading:
Row.ReplaceCharacters(iCol, 2, chars);
break;
case DbcsAttribute::Trailing:
Row.ReplaceCharacters(iCol - 1, 2, chars);
break;
default:
Row.ReplaceCharacters(iCol, 1, chars);
break;
}
}
catch (...)
{
@ -432,7 +504,7 @@ bool TextBuffer::InsertCharacter(const std::wstring_view chars,
}
// Store color data
fSuccess = Row.GetAttrRow().SetAttrToEnd(iCol, attr);
fSuccess = Row.SetAttrToEnd(iCol, attr);
if (fSuccess)
{
// Advance the cursor
@ -572,8 +644,7 @@ bool TextBuffer::IncrementCircularBuffer(const bool inVtMode)
// the current background color, but with no meta attributes set.
fillAttributes.SetStandardErase();
}
const auto fSuccess = _storage.at(_firstRow).Reset(fillAttributes);
if (fSuccess)
GetRowByOffset(0).Reset(fillAttributes);
{
// Now proceed to increment.
// Incrementing it will cause the next line down to become the new "top" of the window (the new "0" in logical coordinates)
@ -585,7 +656,7 @@ bool TextBuffer::IncrementCircularBuffer(const bool inVtMode)
_firstRow = 0;
}
}
return fSuccess;
return true;
}
//Routine Description:
@ -610,7 +681,7 @@ til::point TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::C
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 = currRow.GetCharRow().MeasureRight() - 1;
coordEndOfText.X = 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();
@ -621,7 +692,7 @@ til::point TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::C
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 = backupRow.GetCharRow().MeasureRight() - 1;
coordEndOfText.X = backupRow.MeasureRight() - 1;
fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop);
}
@ -779,10 +850,6 @@ void TextBuffer::ScrollRows(const til::CoordType firstRow, const til::CoordType
// - end
std::rotate(_storage.begin() + firstRow, _storage.begin() + firstRow + size, _storage.begin() + firstRow + size + delta);
}
// Renumber the IDs now that we've rearranged where the rows sit within the buffer.
// Refreshing should also delegate to the UnicodeStorage to re-key all the stored unicode sequences (where applicable).
_RefreshRowIDs(std::nullopt);
}
Cursor& TextBuffer::GetCursor() noexcept
@ -898,10 +965,10 @@ void TextBuffer::Reset()
// - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed.
[[nodiscard]] NTSTATUS TextBuffer::ResizeTraditional(const til::size newSize) noexcept
{
RETURN_HR_IF(E_INVALIDARG, newSize.X < 0 || newSize.Y < 0);
try
{
BufferAllocator allocator{ newSize };
const auto currentSize = GetSize().Dimensions();
const auto attributes = GetCurrentAttributes();
@ -913,49 +980,30 @@ void TextBuffer::Reset()
const auto TopRowIndex = (GetFirstRowIndex() + TopRow) % currentSize.Y;
// rotate rows until the top row is at index 0
for (auto i = 0; i < TopRowIndex; i++)
{
_storage.emplace_back(std::move(_storage.front()));
_storage.erase(_storage.begin());
}
std::rotate(_storage.begin(), _storage.begin() + TopRowIndex, _storage.end());
_SetFirstRowIndex(0);
// realloc in the Y direction
// remove rows if we're shrinking
while (_storage.size() > static_cast<size_t>(newSize.Y))
{
_storage.pop_back();
}
// add rows if we're growing
while (_storage.size() < static_cast<size_t>(newSize.Y))
{
_storage.emplace_back(gsl::narrow_cast<til::CoordType>(_storage.size()), newSize.X, attributes, this);
}
_storage.resize(allocator.height());
// Now that we've tampered with the row placement, refresh all the row IDs.
// Also take advantage of the row ID refresh loop to resize the rows in the X dimension
// and cleanup the UnicodeStorage characters that might fall outside the resized buffer.
_RefreshRowIDs(newSize.X);
// realloc in the X direction
for (auto& it : _storage)
{
it.Resize(allocator.chars(), allocator.indices(), allocator.width(), attributes);
++allocator;
}
// Update the cached size value
_UpdateSize();
_charBuffer = allocator.take();
}
CATCH_RETURN();
return S_OK;
}
const UnicodeStorage& TextBuffer::GetUnicodeStorage() const noexcept
{
return _unicodeStorage;
}
UnicodeStorage& TextBuffer::GetUnicodeStorage() noexcept
{
return _unicodeStorage;
}
void TextBuffer::SetAsActiveBuffer(const bool isActiveBuffer) noexcept
{
_isActiveBuffer = isActiveBuffer;
@ -1019,42 +1067,6 @@ void TextBuffer::TriggerNewTextNotification(const std::wstring_view newText)
}
}
// Routine Description:
// - Method to help refresh all the Row IDs after manipulating the row
// by shuffling pointers around.
// - This will also update parent pointers that are stored in depth within the buffer
// (e.g. it will update CharRow parents pointing at Rows that might have been moved around)
// - Optionally takes a new row width if we're resizing to perform a resize operation and cleanup
// any high unicode (UnicodeStorage) runs while we're already looping through the rows.
// Arguments:
// - newRowWidth - Optional new value for the row width.
void TextBuffer::_RefreshRowIDs(std::optional<til::CoordType> newRowWidth)
{
std::unordered_map<til::CoordType, til::CoordType> rowMap;
til::CoordType i = 0;
for (auto& it : _storage)
{
// Build a map so we can update Unicode Storage
rowMap.emplace(it.GetId(), i);
// Update the IDs
it.SetId(i++);
// Also update the char row parent pointers as they can get shuffled up in the rotates.
it.GetCharRow().UpdateParent(&it);
// Resize the rows in the X dimension if we have a new width
if (newRowWidth.has_value())
{
// Realloc in the X direction
THROW_IF_FAILED(it.Resize(newRowWidth.value()));
}
}
// Give the new mapping to Unicode Storage
_unicodeStorage.Remap(rowMap, newRowWidth);
}
// Routine Description:
// - Retrieves the first row from the underlying buffer.
// Arguments:
@ -1066,27 +1078,6 @@ ROW& TextBuffer::_GetFirstRow() noexcept
return GetRowByOffset(0);
}
// Routine Description:
// - Retrieves the row that comes before the given row.
// - Does not wrap around the screen buffer.
// Arguments:
// - The current row.
// Return Value:
// - reference to the previous row
// Note:
// - will throw exception if called with the first row of the text buffer
ROW& TextBuffer::_GetPrevRowNoWrap(const ROW& Row)
{
auto prevRowIndex = Row.GetId() - 1;
if (prevRowIndex < 0)
{
prevRowIndex = TotalRowCount() - 1;
}
THROW_HR_IF(E_FAIL, Row.GetId() == _firstRow);
return _storage.at(prevRowIndex);
}
// Method Description:
// - get delimiter class for buffer cell position
// - used for double click selection and uia word navigation
@ -1095,9 +1086,9 @@ ROW& TextBuffer::_GetPrevRowNoWrap(const ROW& Row)
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
// Return Value:
// - the delimiter class for the given char
DelimiterClass TextBuffer::_GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const
DelimiterClass TextBuffer::_GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const noexcept
{
return GetRowByOffset(pos.Y).GetCharRow().DelimiterClassAt(pos.X, wordDelimiters);
return GetRowByOffset(pos.Y).DelimiterClassAt(pos.X, wordDelimiters);
}
// Method Description:
@ -1161,7 +1152,7 @@ til::point TextBuffer::GetWordStart(const til::point target, const std::wstring_
// - wordDelimiters - what characters are we considering for the separation of words
// Return Value:
// - The til::point for the first character on the current/previous READABLE "word" (inclusive)
til::point TextBuffer::_GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const
til::point TextBuffer::_GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const noexcept
{
auto result = target;
const auto bufferSize = GetSize();
@ -1206,7 +1197,7 @@ til::point TextBuffer::_GetWordStartForAccessibility(const til::point target, co
// - wordDelimiters - what characters are we considering for the separation of words
// Return Value:
// - The til::point for the first character on the current word or delimiter run (stopped by the left margin)
til::point TextBuffer::_GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const
til::point TextBuffer::_GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept
{
auto result = target;
const auto bufferSize = GetSize();
@ -1327,7 +1318,7 @@ til::point TextBuffer::_GetWordEndForAccessibility(const til::point target, cons
// - wordDelimiters - what characters are we considering for the separation of words
// Return Value:
// - The til::point for the last character of the current word or delimiter run (stopped by right margin)
til::point TextBuffer::_GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const
til::point TextBuffer::_GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept
{
const auto bufferSize = GetSize();
@ -1362,7 +1353,7 @@ void TextBuffer::_PruneHyperlinks()
// If the buffer does not contain the same reference, we can remove that hyperlink from our map
// This way, obsolete hyperlink references are cleared from our hyperlink map instead of hanging around
// Get all the hyperlink references in the row we're erasing
const auto hyperlinks = _storage.at(_firstRow).GetAttrRow().GetHyperlinks();
const auto hyperlinks = GetRowByOffset(0).GetHyperlinks();
if (!hyperlinks.empty())
{
@ -1378,7 +1369,7 @@ void TextBuffer::_PruneHyperlinks()
// to see if those references are anywhere else
for (til::CoordType i = 1; i < total; ++i)
{
const auto nextRowRefs = GetRowByOffset(i).GetAttrRow().GetHyperlinks();
const auto nextRowRefs = GetRowByOffset(i).GetHyperlinks();
for (auto id : nextRowRefs)
{
if (firstRowRefs.find(id) != firstRowRefs.end())
@ -1472,7 +1463,7 @@ til::point TextBuffer::GetGlyphStart(const til::point pos, std::optional<til::po
}
// limit is exclusive, so we need to move back to be within valid bounds
if (resultPos != limit && GetCellDataAt(resultPos)->DbcsAttr().IsTrailing())
if (resultPos != limit && GetCellDataAt(resultPos)->DbcsAttr() == DbcsAttribute::Trailing)
{
bufferSize.DecrementInBounds(resultPos, true);
}
@ -1499,7 +1490,7 @@ til::point TextBuffer::GetGlyphEnd(const til::point pos, bool accessibilityMode,
resultPos = limit;
}
if (resultPos != limit && GetCellDataAt(resultPos)->DbcsAttr().IsLeading())
if (resultPos != limit && GetCellDataAt(resultPos)->DbcsAttr() == DbcsAttribute::Leading)
{
bufferSize.IncrementInBounds(resultPos, true);
}
@ -1547,7 +1538,7 @@ bool TextBuffer::MoveToNextGlyph(til::point& pos, bool allowExclusiveEnd, std::o
const bool success{ ++iter };
// Move again if we're on a wide glyph
if (success && iter->DbcsAttr().IsTrailing())
if (success && iter->DbcsAttr() == DbcsAttribute::Trailing)
{
++iter;
}
@ -1579,7 +1570,7 @@ bool TextBuffer::MoveToPreviousGlyph(til::point& pos, std::optional<til::point>
// try to move. If we can't, we're done.
const auto success = bufferSize.DecrementInBounds(resultPos, true);
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr().IsLeading())
if (resultPos != bufferSize.EndExclusive() && GetCellDataAt(resultPos)->DbcsAttr() == DbcsAttribute::Leading)
{
bufferSize.DecrementInBounds(resultPos, true);
}
@ -1726,7 +1717,7 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
// expand left side of rect
til::point targetPoint{ textRow.Left, textRow.Top };
if (GetCellDataAt(targetPoint)->DbcsAttr().IsTrailing())
if (GetCellDataAt(targetPoint)->DbcsAttr() == DbcsAttribute::Trailing)
{
if (targetPoint.X == bufferSize.Left())
{
@ -1741,7 +1732,7 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
// expand right side of rect
targetPoint = { textRow.Right, textRow.Bottom };
if (GetCellDataAt(targetPoint)->DbcsAttr().IsLeading())
if (GetCellDataAt(targetPoint)->DbcsAttr() == DbcsAttribute::Leading)
{
if (targetPoint.X == bufferSize.RightInclusive())
{
@ -1811,7 +1802,7 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
{
const auto& cell = *it;
if (!cell.DbcsAttr().IsTrailing())
if (cell.DbcsAttr() != DbcsAttribute::Trailing)
{
const auto chars = cell.Chars();
selectionText.append(chars);
@ -1911,7 +1902,7 @@ std::wstring TextBuffer::GetPlainText(const til::point& start, const til::point&
for (; it && spanLength > 0; ++it, --spanLength)
{
const auto& cell = *it;
if (!cell.DbcsAttr().IsTrailing())
if (cell.DbcsAttr() != DbcsAttribute::Trailing)
{
const auto chars = cell.Chars();
text.append(chars);
@ -2352,12 +2343,11 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// Fetch the row and its "right" which is the last printable character.
const auto& row = oldBuffer.GetRowByOffset(iOldRow);
const auto cOldColsTotal = oldBuffer.GetLineWidth(iOldRow);
const auto& charRow = row.GetCharRow();
auto iRight = charRow.MeasureRight();
auto iRight = row.MeasureRight();
// If we're starting a new row, try and preserve the line rendition
// from the row in the original buffer.
const auto newBufferPos = newBuffer.GetCursor().GetPosition();
const auto newBufferPos = newCursor.GetPosition();
if (newBufferPos.X == 0)
{
auto& newRow = newBuffer.GetRowByOffset(newBufferPos.Y);
@ -2404,9 +2394,9 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
try
{
// TODO: MSFT: 19446208 - this should just use an iterator and the inserter...
const auto glyph = row.GetCharRow().GlyphAt(iOldCol);
const auto dbcsAttr = row.GetCharRow().DbcsAttrAt(iOldCol);
const auto textAttr = row.GetAttrRow().GetAttrByColumn(iOldCol);
const auto glyph = row.GlyphAt(iOldCol);
const auto dbcsAttr = row.DbcsAttrAt(iOldCol);
const auto textAttr = row.GetAttrByColumn(iOldCol);
if (!newBuffer.InsertCharacter(glyph, dbcsAttr, textAttr))
{
@ -2450,8 +2440,8 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
try
{
// TODO: MSFT: 19446208 - this should just use an iterator and the inserter...
const auto textAttr = row.GetAttrRow().GetAttrByColumn(copyAttrCol);
if (!newRow.GetAttrRow().SetAttrToEnd(newAttrColumn, textAttr))
const auto textAttr = row.GetAttrByColumn(copyAttrCol);
if (!newRow.SetAttrToEnd(newAttrColumn, textAttr))
{
break;
}
@ -2565,8 +2555,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// the last attr when wider.
auto& newRow = newBuffer.GetRowByOffset(newRowY);
const auto newWidth = newBuffer.GetLineWidth(newRowY);
newRow.GetAttrRow() = row.GetAttrRow();
newRow.GetAttrRow().Resize(newWidth);
newRow.TransferAttributes(row.Attributes(), newWidth);
newRowY++;
}

View File

@ -54,7 +54,6 @@ filling in the last row, and updating the screen.
#include "cursor.h"
#include "Row.hpp"
#include "TextAttribute.hpp"
#include "UnicodeStorage.hpp"
#include "../types/inc/Viewport.hpp"
#include "../buffer/out/textBufferCellIterator.hpp"
@ -140,9 +139,6 @@ public:
[[nodiscard]] HRESULT ResizeTraditional(const til::size newSize) noexcept;
const UnicodeStorage& GetUnicodeStorage() const noexcept;
UnicodeStorage& GetUnicodeStorage() noexcept;
void SetAsActiveBuffer(const bool isActiveBuffer) noexcept;
bool IsActiveBuffer() const noexcept;
@ -221,54 +217,42 @@ public:
private:
void _UpdateSize();
Microsoft::Console::Types::Viewport _size;
std::vector<ROW> _storage;
Cursor _cursor;
til::CoordType _firstRow; // indexes top row (not necessarily 0)
TextAttribute _currentAttributes;
// storage location for glyphs that can't fit into the buffer normally
UnicodeStorage _unicodeStorage;
bool _isActiveBuffer;
Microsoft::Console::Render::Renderer& _renderer;
std::unordered_map<uint16_t, std::wstring> _hyperlinkMap;
std::unordered_map<std::wstring, uint16_t> _hyperlinkCustomIdMap;
uint16_t _currentHyperlinkId;
void _RefreshRowIDs(std::optional<til::CoordType> newRowWidth);
void _SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept;
til::point _GetPreviousFromCursor() const noexcept;
void _SetWrapOnCurrentRow() noexcept;
void _AdjustWrapOnCurrentRow(const bool fSet) noexcept;
// Assist with maintaining proper buffer state for Double Byte character sequences
bool _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute);
bool _AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute);
ROW& _GetFirstRow() noexcept;
ROW& _GetPrevRowNoWrap(const ROW& row);
void _ExpandTextRow(til::inclusive_rect& selectionRow) const;
DelimiterClass _GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const;
til::point _GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const;
til::point _GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const;
DelimiterClass _GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const noexcept;
til::point _GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const noexcept;
til::point _GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept;
til::point _GetWordEndForAccessibility(const til::point target, const std::wstring_view wordDelimiters, const til::point limit) const;
til::point _GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const;
til::point _GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept;
void _PruneHyperlinks();
static void _AppendRTFText(std::ostringstream& contentBuilder, const std::wstring_view& text);
Microsoft::Console::Render::Renderer& _renderer;
std::unordered_map<uint16_t, std::wstring> _hyperlinkMap;
std::unordered_map<std::wstring, uint16_t> _hyperlinkCustomIdMap;
uint16_t _currentHyperlinkId = 1;
std::unordered_map<size_t, std::wstring> _idsAndPatterns;
size_t _currentPatternId;
size_t _currentPatternId = 0;
wil::unique_virtualalloc_ptr<std::byte> _charBuffer;
std::vector<ROW> _storage;
TextAttribute _currentAttributes;
til::CoordType _firstRow = 0; // indexes top row (not necessarily 0)
Cursor _cursor;
Microsoft::Console::Types::Viewport _size;
bool _isActiveBuffer = false;
#ifdef UNIT_TESTING
friend class TextBufferTests;

View File

@ -5,7 +5,6 @@
#include "textBufferCellIterator.hpp"
#include "CharRow.hpp"
#include "textBuffer.hpp"
#include "../types/inc/convert.hpp"
#include "../types/inc/viewport.hpp"
@ -37,7 +36,7 @@ TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, til::po
_bounds(limits),
_exceeded(false),
_view({}, {}, {}, TextAttributeBehavior::Stored),
_attrIter(s_GetRow(buffer, pos)->GetAttrRow().cbegin())
_attrIter(s_GetRow(buffer, pos)->AttrBegin())
{
// Throw if the bounds rectangle is not limited to the inside of the given buffer.
THROW_HR_IF(E_INVALIDARG, !buffer.GetSize().IsInBounds(limits));
@ -165,16 +164,15 @@ TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& move
_attrIter += diff;
_view.UpdateTextAttribute(*_attrIter);
const auto& charRow = _pRow->GetCharRow();
_view.UpdateText(charRow.GlyphAt(newX));
_view.UpdateDbcsAttribute(charRow.DbcsAttrAt(newX));
_view.UpdateText(_pRow->GlyphAt(newX));
_view.UpdateDbcsAttribute(_pRow->DbcsAttrAt(newX));
_pos.X = newX;
}
else
{
// cold path (_GenerateView is slow)
_pRow = s_GetRow(_buffer, { newX, newY });
_attrIter = _pRow->GetAttrRow().cbegin() + newX;
_attrIter = _pRow->AttrBegin() + newX;
_pos.X = newX;
_pos.Y = newY;
_GenerateView();
@ -289,12 +287,12 @@ ptrdiff_t TextBufferCellIterator::operator-(const TextBufferCellIterator& it)
// - Sets the coordinate position that this iterator will inspect within the text buffer on dereference.
// Arguments:
// - newPos - The new coordinate position.
void TextBufferCellIterator::_SetPos(const til::point newPos)
void TextBufferCellIterator::_SetPos(const til::point newPos) noexcept
{
if (newPos.Y != _pos.Y)
{
_pRow = s_GetRow(_buffer, newPos);
_attrIter = _pRow->GetAttrRow().cbegin();
_attrIter = _pRow->AttrBegin();
_pos.X = 0;
}
@ -324,10 +322,10 @@ const ROW* TextBufferCellIterator::s_GetRow(const TextBuffer& buffer, const til:
// Routine Description:
// - Updates the internal view. Call after updating row, attribute, or positions.
void TextBufferCellIterator::_GenerateView()
void TextBufferCellIterator::_GenerateView() noexcept
{
_view = OutputCellView(_pRow->GetCharRow().GlyphAt(_pos.X),
_pRow->GetCharRow().DbcsAttrAt(_pos.X),
_view = OutputCellView(_pRow->GlyphAt(_pos.X),
_pRow->DbcsAttrAt(_pos.X),
*_attrIter,
TextAttributeBehavior::Stored);
}

View File

@ -15,8 +15,7 @@ Author(s):
#pragma once
#include "CharRow.hpp"
#include "AttrRow.hpp"
#include "Row.hpp"
#include "OutputCellView.hpp"
#include "../../types/inc/viewport.hpp"
@ -50,14 +49,14 @@ public:
til::point Pos() const noexcept;
protected:
void _SetPos(const til::point newPos);
void _GenerateView();
void _SetPos(const til::point newPos) noexcept;
void _GenerateView() noexcept;
static const ROW* s_GetRow(const TextBuffer& buffer, const til::point pos) noexcept;
til::small_rle<TextAttribute, uint16_t, 1>::const_iterator _attrIter;
OutputCellView _view;
const ROW* _pRow;
ATTR_ROW::const_iterator _attrIter;
const TextBuffer& _buffer;
const Microsoft::Console::Types::Viewport _bounds;
bool _exceeded;

View File

@ -5,7 +5,6 @@
#include "textBufferTextIterator.hpp"
#include "CharRow.hpp"
#include "Row.hpp"
#pragma hdrstop

View File

@ -737,34 +737,22 @@ class ReflowTests
{
auto buffer = std::make_unique<TextBuffer>(testBuffer.size, TextAttribute{ 0x7 }, 0, false, renderer);
til::CoordType i{};
til::CoordType y = 0;
for (const auto& testRow : testBuffer.rows)
{
auto& row{ buffer->GetRowByOffset(i) };
auto& row{ buffer->GetRowByOffset(y) };
auto& charRow{ row.GetCharRow() };
row.SetWrapForced(testRow.wrap);
til::CoordType j{};
for (auto it{ charRow.begin() }; it != charRow.end(); ++it)
til::CoordType x = 0;
for (const auto& ch : testRow.text)
{
// Yes, we're about to manually create a buffer. It is unpleasant.
const auto ch{ til::at(testRow.text, j) };
it->Char() = ch;
if (IsGlyphFullWidth(ch))
{
it->DbcsAttr().SetLeading();
it++;
it->Char() = ch;
it->DbcsAttr().SetTrailing();
}
else
{
it->DbcsAttr().SetSingle();
}
j++;
const til::CoordType width = IsGlyphFullWidth(ch) ? 2 : 1;
row.ReplaceCharacters(x, width, { &ch, 1 });
x += width;
}
i++;
y++;
}
buffer->GetCursor().SetPosition(testBuffer.cursor);
@ -783,42 +771,44 @@ class ReflowTests
VERIFY_ARE_EQUAL(testBuffer.cursor, buffer.GetCursor().GetPosition());
VERIFY_ARE_EQUAL(testBuffer.size, buffer.GetSize().Dimensions());
til::CoordType i{};
til::CoordType y = 0;
for (const auto& testRow : testBuffer.rows)
{
NoThrowString indexString;
const auto& row{ buffer.GetRowByOffset(i) };
const auto& row{ buffer.GetRowByOffset(y) };
const auto& charRow{ row.GetCharRow() };
indexString.Format(L"[Row %d]", i);
indexString.Format(L"[Row %d]", y);
VERIFY_ARE_EQUAL(testRow.wrap, row.WasWrapForced(), indexString);
til::CoordType j{};
for (auto it{ charRow.begin() }; it != charRow.end(); ++it)
til::CoordType x = 0;
til::CoordType j = 0;
for (const auto& ch : testRow.text)
{
indexString.Format(L"[Cell %d, %d; Text line index %d]", it - charRow.begin(), i, j);
// Yes, we're about to manually create a buffer. It is unpleasant.
const auto ch{ til::at(testRow.text, j) };
indexString.Format(L"[Cell %d, %d; Text line index %d]", x, y, j);
if (IsGlyphFullWidth(ch))
{
// Char is full width in test buffer, so
// ensure that real buffer is LEAD, TRAIL (ch)
VERIFY_IS_TRUE(it->DbcsAttr().IsLeading(), indexString);
VERIFY_ARE_EQUAL(ch, it->Char(), indexString);
VERIFY_ARE_EQUAL(row.DbcsAttrAt(x), DbcsAttribute::Leading, indexString);
VERIFY_ARE_EQUAL(ch, row.GlyphAt(x).front(), indexString);
++x;
it++;
VERIFY_IS_TRUE(it->DbcsAttr().IsTrailing(), indexString);
VERIFY_ARE_EQUAL(row.DbcsAttrAt(x), DbcsAttribute::Trailing, indexString);
VERIFY_ARE_EQUAL(ch, row.GlyphAt(x).front(), indexString);
++x;
}
else
{
VERIFY_IS_TRUE(it->DbcsAttr().IsSingle(), indexString);
VERIFY_ARE_EQUAL(row.DbcsAttrAt(x), DbcsAttribute::Single, indexString);
VERIFY_ARE_EQUAL(ch, row.GlyphAt(x).front(), indexString);
++x;
}
VERIFY_ARE_EQUAL(ch, it->Char(), indexString);
j++;
}
i++;
y++;
}
}

View File

@ -14,7 +14,6 @@
<ClCompile Include="ReflowTests.cpp" />
<ClCompile Include="TextColorTests.cpp" />
<ClCompile Include="TextAttributeTests.cpp" />
<ClCompile Include="UnicodeStorageTests.cpp" />
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
@ -42,4 +41,4 @@
<Import Project="$(SolutionDir)src\common.build.post.props" />
<Import Project="$(SolutionDir)src\common.build.tests.props" />
<Import Project="$(SolutionDir)src\common.nugetversions.targets" />
</Project>
</Project>

View File

@ -1,51 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "WexTestClass.h"
#include "../../inc/consoletaeftemplates.hpp"
#include "../UnicodeStorage.hpp"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
class UnicodeStorageTests
{
TEST_CLASS(UnicodeStorageTests);
TEST_METHOD(CanOverwriteEmoji)
{
UnicodeStorage storage;
const til::point coord{ 1, 3 };
const std::vector<wchar_t> newMoon{ 0xD83C, 0xDF11 };
const std::vector<wchar_t> fullMoon{ 0xD83C, 0xDF15 };
// store initial glyph
storage.StoreGlyph(coord, newMoon);
// verify it was stored
auto findIt = storage._map.find(coord);
VERIFY_ARE_NOT_EQUAL(findIt, storage._map.end());
const auto& newMoonGlyph = findIt->second;
VERIFY_ARE_EQUAL(newMoonGlyph.size(), newMoon.size());
for (size_t i = 0; i < newMoon.size(); ++i)
{
VERIFY_ARE_EQUAL(newMoonGlyph.at(i), newMoon.at(i));
}
// overwrite it
storage.StoreGlyph(coord, fullMoon);
// verify the glyph was overwritten
findIt = storage._map.find(coord);
VERIFY_ARE_NOT_EQUAL(findIt, storage._map.end());
const auto& fullMoonGlyph = findIt->second;
VERIFY_ARE_EQUAL(fullMoonGlyph.size(), fullMoon.size());
for (size_t i = 0; i < fullMoon.size(); ++i)
{
VERIFY_ARE_EQUAL(fullMoonGlyph.at(i), fullMoon.at(i));
}
}
};

View File

@ -1735,26 +1735,25 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const auto& textBuffer = _terminal->GetTextBuffer();
std::wstringstream ss;
std::wstring str;
const auto lastRow = textBuffer.GetLastNonSpaceCharacter().Y;
for (auto rowIndex = 0; rowIndex <= lastRow; rowIndex++)
{
const auto& row = textBuffer.GetRowByOffset(rowIndex);
auto rowText = row.GetText();
const auto rowText = row.GetText();
const auto strEnd = rowText.find_last_not_of(UNICODE_SPACE);
if (strEnd != std::string::npos)
if (strEnd != decltype(rowText)::npos)
{
rowText.erase(strEnd + 1);
ss << rowText;
str.append(rowText.substr(strEnd + 1));
}
if (!row.WasWrapForced())
{
ss << UNICODE_CARRIAGERETURN << UNICODE_LINEFEED;
str.append(L"\r\n");
}
}
return hstring(ss.str());
return hstring{ str };
}
// Helper to check if we're on Windows 11 or not. This is used to check if

View File

@ -184,7 +184,7 @@ public:
ULONG GetCursorHeight() const noexcept override;
ULONG GetCursorPixelWidth() const noexcept override;
CursorType GetCursorStyle() const noexcept override;
bool IsCursorDoubleWidth() const override;
bool IsCursorDoubleWidth() const noexcept override;
const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
const bool IsGridLineDrawingAllowed() noexcept override;
const std::wstring GetHyperlinkUri(uint16_t id) const override;

View File

@ -70,11 +70,11 @@ CursorType Terminal::GetCursorStyle() const noexcept
return _activeBuffer().GetCursor().GetType();
}
bool Terminal::IsCursorDoubleWidth() const
bool Terminal::IsCursorDoubleWidth() const noexcept
{
const auto position = _activeBuffer().GetCursor().GetPosition();
const TextBufferTextIterator it(TextBufferCellIterator(_activeBuffer(), position));
return IsGlyphFullWidth(*it);
const auto& buffer = _activeBuffer();
const auto position = buffer.GetCursor().GetPosition();
return buffer.GetRowByOffset(position.y).DbcsAttrAt(position.x) != DbcsAttribute::Single;
}
const std::vector<RenderOverlay> Terminal::GetOverlays() const noexcept

View File

@ -3709,11 +3709,11 @@ void ConptyRoundtripTests::HyperlinkIdConsistency()
auto verifyData = [](TextBuffer& tb) {
// Check that all the linked cells still have the same ID
auto& attrRow = tb.GetRowByOffset(0).GetAttrRow();
auto id = attrRow.GetAttrByColumn(0).GetHyperlinkId();
auto& row = tb.GetRowByOffset(0);
auto id = row.GetAttrByColumn(0).GetHyperlinkId();
for (uint16_t i = 1; i < 4; ++i)
{
VERIFY_ARE_EQUAL(id, attrRow.GetAttrByColumn(i).GetHyperlinkId());
VERIFY_ARE_EQUAL(id, row.GetAttrByColumn(i).GetHyperlinkId());
}
};

View File

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

View File

@ -858,12 +858,11 @@ using Microsoft::Console::VirtualTerminal::StateMachine;
{
const auto TargetPoint = cursor.GetPosition();
auto& Row = textBuffer.GetRowByOffset(TargetPoint.Y);
const auto& charRow = Row.GetCharRow();
try
{
// If we're on top of a trailing cell, clear it and the previous cell.
if (charRow.DbcsAttrAt(TargetPoint.X).IsTrailing())
if (Row.DbcsAttrAt(TargetPoint.X) == DbcsAttribute::Trailing)
{
// Space to clear for 2 cells.
OutputCellIterator it(UNICODE_SPACE, 2);

View File

@ -256,7 +256,7 @@ std::vector<OutputCell> ConsoleImeInfo::s_ConvertToCells(const std::wstring_view
// If it's full width, it's two, and we need to make sure we don't draw the cursor
// right down the middle of the character.
// Otherwise it's one column and we'll push it in with the default empty DbcsAttribute.
DbcsAttribute dbcsAttr;
DbcsAttribute dbcsAttr = DbcsAttribute::Single;
if (IsGlyphFullWidth(glyph))
{
auto leftHalfAttr = drawingAttr;
@ -269,9 +269,9 @@ std::vector<OutputCell> ConsoleImeInfo::s_ConvertToCells(const std::wstring_view
leftHalfAttr.SetRightVerticalDisplayed(false);
}
dbcsAttr.SetLeading();
dbcsAttr = DbcsAttribute::Leading;
cells.emplace_back(glyph, dbcsAttr, leftHalfAttr);
dbcsAttr.SetTrailing();
dbcsAttr = DbcsAttribute::Trailing;
// If we need a left vertical, don't apply it to the right side of the character
if (rightHalfAttr.IsLeftVerticalDisplayed())
@ -346,7 +346,7 @@ std::vector<OutputCell>::const_iterator ConsoleImeInfo::_WriteConversionArea(con
// Get the last cell in the run and if it's a leading byte, move the end position back one so we don't
// try to insert it.
const auto lastCell = lineEnd - 1;
if (lastCell->DbcsAttr().IsLeading())
if (lastCell->DbcsAttr() == DbcsAttribute::Leading)
{
lineEnd--;
}

View File

@ -378,6 +378,6 @@ CHAR_INFO CONSOLE_INFORMATION::AsCharInfo(const OutputCellView& cell) const noex
// (for mapping RGB values to the nearest table value)
const auto& attr = cell.TextAttr();
ci.Attributes = attr.GetLegacyAttributes();
ci.Attributes |= cell.DbcsAttr().GeneratePublicApiAttributeFormat();
ci.Attributes |= GeneratePublicApiAttributeFormat(cell.DbcsAttr());
return ci;
}

View File

@ -154,6 +154,10 @@ class DbcsTests
BEGIN_TEST_METHOD(TestDbcsStdCoutScenario)
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
END_TEST_METHOD()
BEGIN_TEST_METHOD(TestDbcsBackupRestore)
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
END_TEST_METHOD()
};
bool DbcsTests::DbcsTestSetup()
@ -2038,3 +2042,67 @@ void DbcsTests::TestDbcsStdCoutScenario()
VERIFY_ARE_EQUAL(cchReadBack, dwRead, L"We should have read as many characters as we expected (length of original printed line.)");
VERIFY_ARE_EQUAL(String(test), String(psReadBack.get()), L"String should match what we wrote.");
}
// Read/WriteConsoleOutput allow a user to implement a restricted form of buffer "backup" and "restore".
// But what if the saved region clips ("bisects") a wide character? This test ensures that we restore proper
// wide characters when given an unpaired trailing/leading CHAR_INFO in the first/last column of the given region.
// In other words, writing a trailing CHAR_INFO will also automatically write a leading CHAR_INFO in the preceeding cell.
void DbcsTests::TestDbcsBackupRestore()
{
const auto out = GetStdHandle(STD_OUTPUT_HANDLE);
std::array<CHAR_INFO, 16> expected = PrepPattern::DoubledW;
PrepPattern::replaceColorPlaceholders(expected, FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_GREEN);
// DoubledW will show up like this in the top/left corner of the terminal:
// +----------------
// |QいかなZYXWVUTに
//
// Since those 4 Japanese characters probably aren't going to be monospace for you in your editor
// (as they most likely aren't exactly 2 ASCII characters wide), I'll continue referring to them like this:
// +----------------
// |QaabbccZYXWVUTdd
{
SMALL_RECT region{ .Left = 0, .Right = 15 };
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(out, expected.data(), { 16, 1 }, {}, &region));
}
// Make a "backup" of the viewport. The twist is that our backup region only
// copies the trailing/leading half of the first/last glyph respectively like so:
// +----------------
// | abbccZYXWVUTd
std::array<CHAR_INFO, 13> backup{};
constexpr COORD backupSize{ 13, 1 };
SMALL_RECT backupRegion{ .Left = 2, .Right = 14 };
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(out, backup.data(), backupSize, {}, &backupRegion));
// Destroy the text with some narrow ASCII characters, resulting in:
// +----------------
// |Qxxxxxxxxxxxxxxx
{
DWORD ignored;
VERIFY_WIN32_BOOL_SUCCEEDED(FillConsoleOutputCharacterW(out, L'x', 15, { 1, 0 }, &ignored));
}
// Restore our "backup". The trailing half of the first wide glyph (indicated as "a" above)
// as well as the leading half of the last wide glyph ("d"), will automatically get a
// matching leading/trailing half respectively. In other words, this:
// +----------------
// | abbccZYXWVUTd
//
// turns into this:
// +----------------
// | aabbccZYXWVUTdd
//
// and so we restore this, overwriting all the "x" characters in the process:
// +----------------
// |QいかなZYXWVUTに
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(out, backup.data(), backupSize, {}, &backupRegion));
std::array<CHAR_INFO, 16> infos{};
{
SMALL_RECT region{ .Left = 0, .Right = 15 };
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputW(out, infos.data(), { 16, 1 }, {}, &region));
}
DbcsWriteRead::Verify(expected, infos);
}

View File

@ -238,7 +238,6 @@ MODULE_SETUP(ModuleSetup)
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(FreeConsole());
// Wait a moment for the driver to be ready after freeing to attach.
Sleep(1000);
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(AttachConsole(dwFindPid));
auto tries = 0;

View File

@ -152,14 +152,14 @@ std::vector<WORD> ReadOutputAttributes(const SCREEN_INFORMATION& screenInfo,
// If the first thing we read is trailing, pad with a space.
// OR If the last thing we read is leading, pad with a space.
if ((amountRead == 0 && it->DbcsAttr().IsTrailing()) ||
(amountRead == (amountToRead - 1) && it->DbcsAttr().IsLeading()))
if ((amountRead == 0 && it->DbcsAttr() == DbcsAttribute::Trailing) ||
(amountRead == (amountToRead - 1) && it->DbcsAttr() == DbcsAttribute::Leading))
{
retVal.push_back(legacyAttributes);
}
else
{
retVal.push_back(legacyAttributes | it->DbcsAttr().GeneratePublicApiAttributeFormat());
retVal.push_back(legacyAttributes | GeneratePublicApiAttributeFormat(it->DbcsAttr()));
}
amountRead++;
@ -208,15 +208,15 @@ std::wstring ReadOutputStringW(const SCREEN_INFORMATION& screenInfo,
{
// If the first thing we read is trailing, pad with a space.
// OR If the last thing we read is leading, pad with a space.
if ((amountRead == 0 && it->DbcsAttr().IsTrailing()) ||
(amountRead == (amountToRead - 1) && it->DbcsAttr().IsLeading()))
if ((amountRead == 0 && it->DbcsAttr() == DbcsAttribute::Trailing) ||
(amountRead == (amountToRead - 1) && it->DbcsAttr() == DbcsAttribute::Leading))
{
retVal += UNICODE_SPACE;
}
else
{
// Otherwise, add anything that isn't a trailing cell. (Trailings are duplicate copies of the leading.)
if (!it->DbcsAttr().IsTrailing())
if (it->DbcsAttr() != DbcsAttribute::Trailing)
{
retVal += it->Chars();
}

View File

@ -250,7 +250,7 @@ const std::vector<Microsoft::Console::Render::RenderOverlay> RenderData::GetOver
// - <none>
// Return Value:
// - true if the cursor should be drawn twice as wide as usual
bool RenderData::IsCursorDoubleWidth() const
bool RenderData::IsCursorDoubleWidth() const noexcept
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
return gci.GetActiveOutputBuffer().CursorIsDoubleWidth();

View File

@ -42,7 +42,7 @@ public:
ULONG GetCursorHeight() const noexcept override;
CursorType GetCursorStyle() const noexcept override;
ULONG GetCursorPixelWidth() const noexcept override;
bool IsCursorDoubleWidth() const override;
bool IsCursorDoubleWidth() const noexcept override;
const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;

View File

@ -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"
@ -2396,11 +2395,11 @@ OutputCellRect SCREEN_INFORMATION::ReadRect(const Viewport viewport) const
}
// if we're clipping a dbcs char then don't include it, add a space instead
if (span.begin()->DbcsAttr().IsTrailing())
if (span.begin()->DbcsAttr() == DbcsAttribute::Trailing)
{
*span.begin() = paddingCell;
}
if (span.rbegin()->DbcsAttr().IsLeading())
if (span.rbegin()->DbcsAttr() == DbcsAttribute::Leading)
{
*span.rbegin() = paddingCell;
}
@ -2658,7 +2657,7 @@ void SCREEN_INFORMATION::InitializeCursorRowAttributes()
// the current background color, but with no meta attributes set.
auto fillAttributes = GetAttributes();
fillAttributes.SetStandardErase();
row.GetAttrRow().SetAttrToEnd(0, fillAttributes);
row.SetAttrToEnd(0, fillAttributes);
// The row should also be single width to start with.
row.SetLineRendition(LineRendition::SingleWidth);
}
@ -2685,12 +2684,11 @@ Viewport SCREEN_INFORMATION::GetVirtualViewport() const noexcept
// - <none>
// Return Value:
// - true if the character at the cursor's current position is wide
bool SCREEN_INFORMATION::CursorIsDoubleWidth() const
bool SCREEN_INFORMATION::CursorIsDoubleWidth() const noexcept
{
const auto& buffer = GetTextBuffer();
const auto position = buffer.GetCursor().GetPosition();
TextBufferTextIterator it(TextBufferCellIterator(buffer, position));
return IsGlyphFullWidth(*it);
return buffer.GetRowByOffset(position.y).DbcsAttrAt(position.x) != DbcsAttribute::Single;
}
// Method Description:

View File

@ -157,7 +157,7 @@ public:
InputBuffer* const GetActiveInputBuffer() const override;
#pragma endregion
bool CursorIsDoubleWidth() const;
bool CursorIsDoubleWidth() const noexcept;
DWORD OutputMode;
WORD ResizingWindow; // > 0 if we should ignore WM_SIZE messages

View File

@ -360,7 +360,7 @@ bool Selection::HandleKeyboardLineSelectionEvent(const INPUT_KEY_INFO* const pIn
try
{
const auto attr = gci.GetActiveOutputBuffer().GetCellDataAt(coordSelPoint)->DbcsAttr();
if (attr.IsTrailing())
if (attr == DbcsAttribute::Trailing)
{
bufferSize.IncrementInBounds(coordSelPoint);
}
@ -583,7 +583,7 @@ bool Selection::HandleKeyboardLineSelectionEvent(const INPUT_KEY_INFO* const pIn
try
{
const auto attr = gci.GetActiveOutputBuffer().GetCellDataAt(coordSelPoint)->DbcsAttr();
if (attr.IsTrailing())
if (attr == DbcsAttribute::Trailing)
{
// try to move off by highlighting the lead half too.
auto fSuccess = bufferSize.DecrementInBounds(coordSelPoint);
@ -766,7 +766,7 @@ bool Selection::_HandleMarkModeSelectionNav(const INPUT_KEY_INFO* const pInputKe
auto it = ScreenInfo.GetCellLineDataAt(cursorPos);
// calculate next right
if (it->DbcsAttr().IsLeading())
if (it->DbcsAttr() == DbcsAttribute::Leading)
{
iNextRightX = 2;
}
@ -779,16 +779,16 @@ bool Selection::_HandleMarkModeSelectionNav(const INPUT_KEY_INFO* const pInputKe
if (cursorPos.X > 0)
{
it--;
if (it->DbcsAttr().IsTrailing())
if (it->DbcsAttr() == DbcsAttribute::Trailing)
{
iNextLeftX = 2;
}
else if (it->DbcsAttr().IsLeading())
else if (it->DbcsAttr() == DbcsAttribute::Leading)
{
if (cursorPos.X - 1 > 0)
{
it--;
if (it->DbcsAttr().IsTrailing())
if (it->DbcsAttr() == DbcsAttribute::Trailing)
{
iNextLeftX = 3;
}

View File

@ -29,12 +29,12 @@ class OutputCellIteratorTests
OutputCellIterator it(wch, limit);
OutputCellView expectedLead({ &wch, 1 },
DbcsAttribute(DbcsAttribute::Attribute::Leading),
DbcsAttribute::Leading,
InvalidTextAttribute,
TextAttributeBehavior::Current);
OutputCellView expectedTrail({ &wch, 1 },
DbcsAttribute(DbcsAttribute::Attribute::Trailing),
DbcsAttribute::Trailing,
InvalidTextAttribute,
TextAttributeBehavior::Current);
@ -284,7 +284,7 @@ class OutputCellIteratorTests
for (const auto& wch : testText)
{
auto expected = OutputCellView({ &wch, 1 },
DbcsAttribute(DbcsAttribute::Attribute::Leading),
DbcsAttribute::Leading,
InvalidTextAttribute,
TextAttributeBehavior::Current);
@ -293,7 +293,7 @@ class OutputCellIteratorTests
it++;
expected = OutputCellView({ &wch, 1 },
DbcsAttribute(DbcsAttribute::Attribute::Trailing),
DbcsAttribute::Trailing,
InvalidTextAttribute,
TextAttributeBehavior::Current);
@ -341,7 +341,7 @@ class OutputCellIteratorTests
for (const auto& wch : testText)
{
auto expected = OutputCellView({ &wch, 1 },
DbcsAttribute(DbcsAttribute::Attribute::Leading),
DbcsAttribute::Leading,
color,
TextAttributeBehavior::Stored);
@ -350,7 +350,7 @@ class OutputCellIteratorTests
it++;
expected = OutputCellView({ &wch, 1 },
DbcsAttribute(DbcsAttribute::Attribute::Trailing),
DbcsAttribute::Trailing,
color,
TextAttributeBehavior::Stored);
@ -496,7 +496,7 @@ class OutputCellIteratorTests
const auto value = *it;
it++;
if (value.DbcsAttr().IsLeading() || value.DbcsAttr().IsTrailing())
if (value.DbcsAttr() == DbcsAttribute::Leading || value.DbcsAttr() == DbcsAttribute::Trailing)
{
VERIFY_IS_TRUE(it);
it++;

View File

@ -1464,8 +1464,7 @@ void ScreenBufferTests::VtScrollMarginsNewlineColor()
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const auto& row = tbi.GetRowByOffset(y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
for (auto x = 0; x < viewport.RightInclusive(); x++)
{
const auto& attr = attrs[x];
@ -1532,8 +1531,7 @@ void ScreenBufferTests::VtNewlinePastViewport()
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const auto& row = tbi.GetRowByOffset(y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
for (auto x = 0; x < viewport.RightInclusive(); x++)
{
const auto& attr = attrs[x];
@ -1542,8 +1540,7 @@ void ScreenBufferTests::VtNewlinePastViewport()
}
const auto& row = tbi.GetRowByOffset(viewport.BottomInclusive());
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
for (auto x = 0; x < viewport.RightInclusive(); x++)
{
const auto& attr = attrs[x];
@ -1609,8 +1606,7 @@ void ScreenBufferTests::VtNewlinePastEndOfBuffer()
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const auto& row = tbi.GetRowByOffset(y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
for (auto x = 0; x < viewport.RightInclusive(); x++)
{
const auto& attr = attrs[x];
@ -1619,8 +1615,7 @@ void ScreenBufferTests::VtNewlinePastEndOfBuffer()
}
const auto& row = tbi.GetRowByOffset(viewport.BottomInclusive());
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
for (auto x = 0; x < viewport.RightInclusive(); x++)
{
const auto& attr = attrs[x];
@ -2477,8 +2472,7 @@ void ScreenBufferTests::TestAltBufferVtDispatching()
// Recall we didn't print an 'X' to the main buffer, so there's no
// char to inspect the attributes of.
const auto& altRow = alternate.GetTextBuffer().GetRowByOffset(altCursor.GetPosition().Y);
const auto altAttrRow = &altRow.GetAttrRow();
const std::vector<TextAttribute> altAttrs{ altAttrRow->begin(), altAttrRow->end() };
const std::vector<TextAttribute> altAttrs{ altRow.AttrBegin(), altRow.AttrEnd() };
const auto altAttrA = altAttrs[altCursor.GetPosition().X - 1];
VERIFY_ARE_EQUAL(expectedRgb, altAttrA);
}
@ -2572,8 +2566,7 @@ void ScreenBufferTests::SetDefaultsIndividuallyBothDefault()
VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition());
const auto& row = tbi.GetRowByOffset(0);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[0];
const auto attrB = attrs[1];
const auto attrC = attrs[2];
@ -2663,8 +2656,7 @@ void ScreenBufferTests::SetDefaultsTogether()
VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition());
const auto& row = tbi.GetRowByOffset(0);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[0];
const auto attrB = attrs[1];
const auto attrC = attrs[2];
@ -2727,8 +2719,7 @@ void ScreenBufferTests::ReverseResetWithDefaultBackground()
VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition());
const auto& row = tbi.GetRowByOffset(0);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[0];
const auto attrB = attrs[1];
const auto attrC = attrs[2];
@ -2790,8 +2781,7 @@ void ScreenBufferTests::BackspaceDefaultAttrs()
VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition());
const auto& row = tbi.GetRowByOffset(0);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[0];
const auto attrB = attrs[1];
@ -2869,8 +2859,7 @@ void ScreenBufferTests::BackspaceDefaultAttrsWriteCharsLegacy()
VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition());
const auto& row = tbi.GetRowByOffset(0);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[0];
const auto attrB = attrs[1];
@ -2923,7 +2912,6 @@ void ScreenBufferTests::BackspaceDefaultAttrsInPrompt()
const auto viewport = si.GetViewport();
const auto& row = tbi.GetRowByOffset(cursor.GetPosition().Y);
const auto attrRow = &row.GetAttrRow();
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
@ -2931,7 +2919,7 @@ void ScreenBufferTests::BackspaceDefaultAttrsInPrompt()
L"Make sure the row contains what we're expecting before we start."
L"It should entirely be filled with defaults"));
const std::vector<TextAttribute> initialAttrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> initialAttrs{ row.AttrBegin(), row.AttrEnd() };
for (auto x = 0; x <= viewport.RightInclusive(); x++)
{
const auto& attr = initialAttrs[x];
@ -2948,7 +2936,7 @@ void ScreenBufferTests::BackspaceDefaultAttrsInPrompt()
til::point expectedCursor{ 1, 0 };
VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition());
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
for (auto x = 0; x <= viewport.RightInclusive(); x++)
{
const auto& attr = attrs[x];
@ -2992,8 +2980,7 @@ void ScreenBufferTests::SetGlobalColorTable()
VERIFY_ARE_EQUAL(expectedCursor, mainCursor.GetPosition());
{
const auto& row = mainBuffer.GetTextBuffer().GetRowByOffset(mainCursor.GetPosition().Y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[0];
LOG_ATTR(attrA);
VERIFY_ARE_EQUAL(originalRed, renderSettings.GetAttributeColors(attrA).second);
@ -3018,8 +3005,7 @@ void ScreenBufferTests::SetGlobalColorTable()
VERIFY_ARE_EQUAL(expectedCursor, altCursor.GetPosition());
{
const auto& row = altBuffer.GetTextBuffer().GetRowByOffset(altCursor.GetPosition().Y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[0];
LOG_ATTR(attrA);
VERIFY_ARE_EQUAL(originalRed, renderSettings.GetAttributeColors(attrA).second);
@ -3033,8 +3019,7 @@ void ScreenBufferTests::SetGlobalColorTable()
VERIFY_ARE_EQUAL(til::point(2, 0), altCursor.GetPosition());
{
const auto& row = altBuffer.GetTextBuffer().GetRowByOffset(altCursor.GetPosition().Y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[0];
const auto attrB = attrs[1];
LOG_ATTR(attrA);
@ -3056,8 +3041,7 @@ void ScreenBufferTests::SetGlobalColorTable()
VERIFY_ARE_EQUAL(til::point(2, 0), mainCursor.GetPosition());
{
const auto& row = mainBuffer.GetTextBuffer().GetRowByOffset(mainCursor.GetPosition().Y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[0];
const auto attrB = attrs[1];
LOG_ATTR(attrA);
@ -3099,8 +3083,7 @@ void ScreenBufferTests::SetColorTableThreeDigits()
VERIFY_ARE_EQUAL(expectedCursor, mainCursor.GetPosition());
{
const auto& row = mainBuffer.GetTextBuffer().GetRowByOffset(mainCursor.GetPosition().Y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[0];
LOG_ATTR(attrA);
VERIFY_ARE_EQUAL(originalRed, renderSettings.GetAttributeColors(attrA).second);
@ -3125,8 +3108,7 @@ void ScreenBufferTests::SetColorTableThreeDigits()
VERIFY_ARE_EQUAL(expectedCursor, altCursor.GetPosition());
{
const auto& row = altBuffer.GetTextBuffer().GetRowByOffset(altCursor.GetPosition().Y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[0];
LOG_ATTR(attrA);
VERIFY_ARE_EQUAL(originalRed, renderSettings.GetAttributeColors(attrA).second);
@ -3143,8 +3125,7 @@ void ScreenBufferTests::SetColorTableThreeDigits()
VERIFY_ARE_EQUAL(til::point(2, 0), altCursor.GetPosition());
{
const auto& row = altBuffer.GetTextBuffer().GetRowByOffset(altCursor.GetPosition().Y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrB = attrs[1];
// TODO MSFT:20105972 - attrA and attrB should both be the same color now
LOG_ATTR(attrB);
@ -3346,19 +3327,12 @@ void ScreenBufferTests::DeleteCharsNearEndOfLine()
VERIFY_ARE_EQUAL(til::point(mainView.Width() - 1, 0), mainCursor.GetPosition());
Log::Comment(NoThrowString().Format(
L"row_i=[%s]",
tbi.GetRowByOffset(0).GetText().c_str()));
mainCursor.SetPosition({ mainView.Width() - static_cast<til::CoordType>(dx), 0 });
mainCursor.SetPosition({ mainView.Width() - dx, 0 });
std::wstringstream ss;
ss << L"\x1b[" << numCharsToDelete << L"P"; // Delete N chars
stateMachine.ProcessString(ss.str());
Log::Comment(NoThrowString().Format(
L"row_f=[%s]",
tbi.GetRowByOffset(0).GetText().c_str()));
VERIFY_ARE_EQUAL(til::point(mainView.Width() - static_cast<til::CoordType>(dx), 0), mainCursor.GetPosition());
VERIFY_ARE_EQUAL(til::point(mainView.Width() - dx, 0), mainCursor.GetPosition());
auto iter = tbi.GetCellDataAt({ 0, 0 });
auto expectedNumSpaces = std::min(dx, numCharsToDelete);
for (auto x = 0; x < mainView.Width() - expectedNumSpaces; x++)
@ -3414,14 +3388,11 @@ void ScreenBufferTests::DeleteCharsNearEndOfLineSimpleFirstCase()
// Place the cursor on the 'D'
mainCursor.SetPosition({ 3, 0 });
Log::Comment(NoThrowString().Format(L"before=[%s]", tbi.GetRowByOffset(0).GetText().c_str()));
// Delete 3 chars - [D, E, F]
std::wstringstream ss;
ss << L"\x1b[" << 3 << L"P";
stateMachine.ProcessString(ss.str());
Log::Comment(NoThrowString().Format(L"after =[%s]", tbi.GetRowByOffset(0).GetText().c_str()));
// Cursor shouldn't have moved
VERIFY_ARE_EQUAL(til::point(3, 0), mainCursor.GetPosition());
@ -3475,15 +3446,11 @@ void ScreenBufferTests::DeleteCharsNearEndOfLineSimpleSecondCase()
// Place the cursor on the 'C'
mainCursor.SetPosition({ 2, 0 });
Log::Comment(NoThrowString().Format(L"before=[%s]", tbi.GetRowByOffset(0).GetText().c_str()));
// Delete 4 chars - [C, D, E, F]
std::wstringstream ss;
ss << L"\x1b[" << 4 << L"P";
stateMachine.ProcessString(ss.str());
Log::Comment(NoThrowString().Format(L"after =[%s]", tbi.GetRowByOffset(0).GetText().c_str()));
VERIFY_ARE_EQUAL(til::point(2, 0), mainCursor.GetPosition());
auto iter = tbi.GetCellDataAt({ 0, 0 });
@ -3540,8 +3507,7 @@ void ScreenBufferTests::DontResetColorsAboveVirtualBottom()
VERIFY_ARE_EQUAL(2, cursor.GetPosition().X);
{
const auto& row = tbi.GetRowByOffset(cursor.GetPosition().Y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[0];
const auto attrB = attrs[1];
LOG_ATTR(attrA);
@ -3571,8 +3537,7 @@ void ScreenBufferTests::DontResetColorsAboveVirtualBottom()
VERIFY_ARE_EQUAL(3, cursor.GetPosition().X);
{
const auto& row = tbi.GetRowByOffset(cursor.GetPosition().Y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[0];
const auto attrB = attrs[1];
const auto attrC = attrs[1];
@ -3885,8 +3850,6 @@ void ScreenBufferTests::InsertChars()
auto before = si.GetTextBuffer().GetRowByOffset(insertLine).GetText();
stateMachine.ProcessString(L"\x1b[5@");
auto after = si.GetTextBuffer().GetRowByOffset(insertLine).GetText();
Log::Comment(before.c_str(), L"Before");
Log::Comment(after.c_str(), L" After");
// Verify cursor didn't move.
VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from insert operation.");
@ -3926,8 +3889,6 @@ void ScreenBufferTests::InsertChars()
before = si.GetTextBuffer().GetRowByOffset(insertLine).GetText();
stateMachine.ProcessString(L"\x1b[5@");
after = si.GetTextBuffer().GetRowByOffset(insertLine).GetText();
Log::Comment(before.c_str(), L"Before");
Log::Comment(after.c_str(), L" After");
// Verify cursor didn't move.
VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from insert operation.");
@ -3964,8 +3925,6 @@ void ScreenBufferTests::InsertChars()
before = si.GetTextBuffer().GetRowByOffset(insertLine).GetText();
stateMachine.ProcessString(L"\x1b[100@");
after = si.GetTextBuffer().GetRowByOffset(insertLine).GetText();
Log::Comment(before.c_str(), L"Before");
Log::Comment(after.c_str(), L" After");
// Verify cursor didn't move.
VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from insert operation.");
@ -4045,8 +4004,6 @@ void ScreenBufferTests::DeleteChars()
auto before = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText();
stateMachine.ProcessString(L"\x1b[5P");
auto after = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText();
Log::Comment(before.c_str(), L"Before");
Log::Comment(after.c_str(), L" After");
// Verify cursor didn't move.
VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from delete operation.");
@ -4086,8 +4043,6 @@ void ScreenBufferTests::DeleteChars()
before = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText();
stateMachine.ProcessString(L"\x1b[5P");
after = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText();
Log::Comment(before.c_str(), L"Before");
Log::Comment(after.c_str(), L" After");
// Verify cursor didn't move.
VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from delete operation.");
@ -4124,8 +4079,6 @@ void ScreenBufferTests::DeleteChars()
before = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText();
stateMachine.ProcessString(L"\x1b[100P");
after = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText();
Log::Comment(before.c_str(), L"Before");
Log::Comment(after.c_str(), L" After");
// Verify cursor didn't move.
VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from delete operation.");
@ -6888,8 +6841,7 @@ void ScreenBufferTests::TestWriteConsoleVTQuirkMode()
const auto verifyLastAttribute = [&](const TextAttribute& expected) {
const auto& row = mainBuffer.GetTextBuffer().GetRowByOffset(cursor.GetPosition().Y);
const auto attrRow = &row.GetAttrRow();
auto iter{ attrRow->begin() };
auto iter{ row.AttrBegin() };
iter += cursor.GetPosition().X - 1;
VERIFY_ARE_EQUAL(expected, *iter);
};

View File

@ -343,68 +343,6 @@ class SelectionTests
VERIFY_ARE_EQUAL(srOriginal.Left + sDeltaLeft, srSelection.Left);
VERIFY_ARE_EQUAL(srOriginal.Right + sDeltaRight, srSelection.Right);
}
TEST_METHOD(TestBisectSelection)
{
m_state->FillTextBufferBisect();
// From CommonState, this is what rows look like:
// positions of き are at 0, 27-28, 39-40, 67-68, 79
// きABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789ききABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789き
// きABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789ききABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789き
// きABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789ききABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789き
// きABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789ききABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789き
// 1a. Start position is trailing half and is at beginning of row
// start from position Column 0, Row 2
// selection is 5 characters long
// the left edge should move one to the right (+1) to not select the trailing byte
// right edge shouldn't move
TestBisectSelectionDelta(0, 2, 5, 1, 0);
// 1b. Start position is trailing half and is elsewhere in the row
// start from position Column 28, Row 2, which is the position of a trailing き in the mid row
// selection is 5 characters long
// the left edge should move one to the left (-1) to select the leading byte also
// right edge shouldn't move
TestBisectSelectionDelta(28, 2, 5, -1, 0);
// 1c. Start position is trailing half and is beginning of buffer
// start from position Column 0, Row 0 which is a trailing byte
// selection is 5 characters long
// the left edge should move one to the right (+1) to not select the trailing byte
// right edge shouldn't move
TestBisectSelectionDelta(0, 0, 5, 1, 0);
// 2a. End position is leading half and is at end of row
// start from position 10 before end of row (80 length row)
// row is 2
// selection is 9 characters long
// the left edge shouldn't move
// the right edge should move one to the left (-1) to not select the leading byte
TestBisectSelectionDelta(70, 2, 9, 0, -1);
// 2b. End position is leading half and is elsewhere in the row
// start from 10 before trailing き in the mid row (pos 68 - 10 = 58)
// row is 2
// selection is 10 characters long
// the left edge shouldn't move
// the right edge should not move, because it is already on the trailing byte
TestBisectSelectionDelta(58, 2, 10, 0, 0);
// 2c. End position is leading half and is at end of buffer
// start from position 10 before end of row (80 length row)
// row is 300 (or 299 for the index)
// selection is 9 characters long
// the left edge shouldn't move
// the right edge should move one to the left (-1) to not select the leading byte
TestBisectSelectionDelta(70, 299, 9, 0, -1);
}
};
class SelectionInputTests

View File

@ -11,7 +11,6 @@
#include "../buffer/out/textBuffer.hpp"
#include "../buffer/out/textBufferCellIterator.hpp"
#include "../buffer/out/textBufferTextIterator.hpp"
#include "../buffer/out/CharRow.hpp"
#include "input.h"
@ -455,8 +454,8 @@ void TextBufferIteratorTests::AsCharInfoCell()
const auto& row = outputBuffer._textBuffer->GetRowByOffset(it._pos.Y);
const auto wcharExpected = *row.GetCharRow().GlyphAt(it._pos.X).begin();
const auto attrExpected = row.GetAttrRow().GetAttrByColumn(it._pos.X);
const auto wcharExpected = *row.GlyphAt(it._pos.X).begin();
const auto attrExpected = row.GetAttrByColumn(it._pos.X);
const auto cellActual = gci.AsCharInfo(*it);
const auto wcharActual = cellActual.Char.UnicodeChar;
@ -475,7 +474,7 @@ void TextBufferIteratorTests::DereferenceOperatorText()
const auto& row = outputBuffer._textBuffer->GetRowByOffset(it._pos.Y);
const auto wcharExpected = row.GetCharRow().GlyphAt(it._pos.X);
const auto wcharExpected = row.GlyphAt(it._pos.X);
const auto wcharActual = *it;
VERIFY_ARE_EQUAL(*wcharExpected.begin(), *wcharActual.begin());
@ -490,9 +489,9 @@ void TextBufferIteratorTests::DereferenceOperatorCell()
const auto& row = outputBuffer._textBuffer->GetRowByOffset(it._pos.Y);
const auto textExpected = (std::wstring_view)row.GetCharRow().GlyphAt(it._pos.X);
const auto dbcsExpected = row.GetCharRow().DbcsAttrAt(it._pos.X);
const auto attrExpected = row.GetAttrRow().GetAttrByColumn(it._pos.X);
const auto textExpected = (std::wstring_view)row.GlyphAt(it._pos.X);
const auto dbcsExpected = row.DbcsAttrAt(it._pos.X);
const auto attrExpected = row.GetAttrByColumn(it._pos.X);
const auto cellActual = *it;
const auto textActual = cellActual.Chars();

View File

@ -2,6 +2,9 @@
// Licensed under the MIT license.
#include "precomp.h"
#include <til/hash.h>
#include "WexTestClass.h"
#include "../inc/consoletaeftemplates.hpp"
@ -9,7 +12,6 @@
#include "globals.h"
#include "../buffer/out/textBuffer.hpp"
#include "../buffer/out/CharRow.hpp"
#include "input.h"
#include "_stream.h"
@ -73,8 +75,6 @@ class TextBufferTests
til::CoordType GetBufferHeight();
TEST_METHOD(TestBufferRowByOffset);
TEST_METHOD(TestWrapFlag);
TEST_METHOD(TestWrapThroughWriteLine);
@ -87,8 +87,9 @@ class TextBufferTests
const til::CoordType cLeft,
const til::CoordType cRight);
TEST_METHOD(TestBoundaryMeasuresEmptyString);
TEST_METHOD(TestBoundaryMeasuresFullString);
TEST_METHOD(TestBoundaryMeasuresRegularString);
TEST_METHOD(TestBoundaryMeasuresFloatingString);
TEST_METHOD(TestCopyProperties);
@ -145,6 +146,7 @@ class TextBufferTests
TEST_METHOD(ResizeTraditionalHighUnicodeColumnRemoval);
TEST_METHOD(TestBurrito);
TEST_METHOD(TestOverwriteChars);
TEST_METHOD(TestAppendRTFText);
@ -181,19 +183,6 @@ til::CoordType TextBufferTests::GetBufferHeight()
return GetTbi().GetSize().Height();
}
void TextBufferTests::TestBufferRowByOffset()
{
auto& textBuffer = GetTbi();
auto csBufferHeight = GetBufferHeight();
VERIFY_IS_TRUE(csBufferHeight > 20);
auto sId = csBufferHeight / 2 - 5;
const auto& row = textBuffer.GetRowByOffset(sId);
VERIFY_ARE_EQUAL(row.GetId(), sId);
}
void TextBufferTests::TestWrapFlag()
{
auto& textBuffer = GetTbi();
@ -310,12 +299,12 @@ void TextBufferTests::DoBoundaryTest(PCWCHAR const pwszInputString,
{
auto& textBuffer = GetTbi();
auto& charRow = textBuffer._GetFirstRow().GetCharRow();
auto& row = textBuffer._GetFirstRow();
// copy string into buffer
for (til::CoordType i = 0; i < cLength; ++i)
{
charRow.GlyphAt(i) = { &pwszInputString[i], 1 };
row.ReplaceCharacters(i, 1, { &pwszInputString[i], 1 });
}
// space pad the rest of the string
@ -323,19 +312,37 @@ void TextBufferTests::DoBoundaryTest(PCWCHAR const pwszInputString,
{
for (auto cStart = cLength; cStart < cMax; cStart++)
{
charRow.ClearGlyph(cStart);
row.ClearCell(cStart);
}
}
// left edge should be 0 since there are no leading spaces
VERIFY_ARE_EQUAL(charRow.MeasureLeft(), cLeft);
VERIFY_ARE_EQUAL(row.MeasureLeft(), cLeft);
// right edge should be one past the index of the last character or the string length
VERIFY_ARE_EQUAL(charRow.MeasureRight(), cRight);
VERIFY_ARE_EQUAL(row.MeasureRight(), cRight);
}
void TextBufferTests::TestBoundaryMeasuresEmptyString()
{
const auto csBufferWidth = GetBufferWidth();
// length 0, left 80, right 0
const auto pwszLazyDog = L"";
DoBoundaryTest(pwszLazyDog, 0, csBufferWidth, 80, 0);
}
void TextBufferTests::TestBoundaryMeasuresFullString()
{
const auto csBufferWidth = GetBufferWidth();
// length 0, left 80, right 0
const std::wstring str(csBufferWidth, L'X');
DoBoundaryTest(str.data(), csBufferWidth, csBufferWidth, 0, 80);
}
void TextBufferTests::TestBoundaryMeasuresRegularString()
{
auto csBufferWidth = GetBufferWidth();
const auto csBufferWidth = GetBufferWidth();
// length 44, left 0, right 44
const auto pwszLazyDog = L"The quick brown fox jumps over the lazy dog.";
@ -344,7 +351,7 @@ void TextBufferTests::TestBoundaryMeasuresRegularString()
void TextBufferTests::TestBoundaryMeasuresFloatingString()
{
auto csBufferWidth = GetBufferWidth();
const auto csBufferWidth = GetBufferWidth();
// length 5 spaces + 4 chars + 5 spaces = 14, left 5, right 9
const auto pwszOffsets = L" C:\\> ";
@ -402,18 +409,15 @@ void TextBufferTests::TestInsertCharacter()
// create some sample test data
const auto wch = L'Z';
const std::wstring_view wchTest(&wch, 1);
DbcsAttribute dbcsAttribute;
dbcsAttribute.SetTrailing();
const auto dbcsAttribute = DbcsAttribute::Leading;
const auto wAttrTest = BACKGROUND_INTENSITY | FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE;
auto TestAttributes = TextAttribute(wAttrTest);
auto& charRow = Row.GetCharRow();
charRow.DbcsAttrAt(coordCursorBefore.X).SetLeading();
// ensure that the buffer didn't start with these fields
VERIFY_ARE_NOT_EQUAL(charRow.GlyphAt(coordCursorBefore.X), wchTest);
VERIFY_ARE_NOT_EQUAL(charRow.DbcsAttrAt(coordCursorBefore.X), dbcsAttribute);
VERIFY_ARE_NOT_EQUAL(Row.GlyphAt(coordCursorBefore.X), wchTest);
VERIFY_ARE_NOT_EQUAL(Row.DbcsAttrAt(coordCursorBefore.X), dbcsAttribute);
auto attr = Row.GetAttrRow().GetAttrByColumn(coordCursorBefore.X);
auto attr = Row.GetAttrByColumn(coordCursorBefore.X);
VERIFY_ARE_NOT_EQUAL(attr, TestAttributes);
@ -421,10 +425,10 @@ void TextBufferTests::TestInsertCharacter()
textBuffer.InsertCharacter(wchTest, dbcsAttribute, TestAttributes);
// ensure that the buffer position where the cursor WAS contains the test items
VERIFY_ARE_EQUAL(charRow.GlyphAt(coordCursorBefore.X), wchTest);
VERIFY_ARE_EQUAL(charRow.DbcsAttrAt(coordCursorBefore.X), dbcsAttribute);
VERIFY_ARE_EQUAL(Row.GlyphAt(coordCursorBefore.X), wchTest);
VERIFY_ARE_EQUAL(Row.DbcsAttrAt(coordCursorBefore.X), dbcsAttribute);
attr = Row.GetAttrRow().GetAttrByColumn(coordCursorBefore.X);
attr = Row.GetAttrByColumn(coordCursorBefore.X);
VERIFY_ARE_EQUAL(attr, TestAttributes);
// ensure that the cursor moved to a new position (X or Y or both have changed)
@ -531,7 +535,7 @@ void TextBufferTests::TestLastNonSpace(const til::CoordType cursorPosY)
auto coordExpected = textBuffer.GetCursor().GetPosition();
// Try to get the X position from the current cursor position.
coordExpected.X = static_cast<til::CoordType>(textBuffer.GetRowByOffset(coordExpected.Y).GetCharRow().MeasureRight()) - 1;
coordExpected.X = textBuffer.GetRowByOffset(coordExpected.Y).MeasureRight() - 1;
// If we went negative, this row was empty and we need to continue seeking upward...
// - As long as X is negative (empty rows)
@ -539,7 +543,7 @@ void TextBufferTests::TestLastNonSpace(const til::CoordType cursorPosY)
while (coordExpected.X < 0 && coordExpected.Y > 0)
{
coordExpected.Y--;
coordExpected.X = static_cast<til::CoordType>(textBuffer.GetRowByOffset(coordExpected.Y).GetCharRow().MeasureRight()) - 1;
coordExpected.X = textBuffer.GetRowByOffset(coordExpected.Y).MeasureRight() - 1;
}
VERIFY_ARE_EQUAL(coordLastNonSpace.X, coordExpected.X);
@ -618,12 +622,10 @@ void TextBufferTests::TestIncrementCircularBuffer()
// fill first row with some stuff
auto& FirstRow = textBuffer._GetFirstRow();
auto& charRow = FirstRow.GetCharRow();
const auto stuff = L'A';
charRow.GlyphAt(0) = { &stuff, 1 };
FirstRow.ReplaceCharacters(0, 1, { L"A" });
// ensure it does say that it contains text
VERIFY_IS_TRUE(FirstRow.GetCharRow().ContainsText());
VERIFY_IS_TRUE(FirstRow.ContainsText());
// try increment
textBuffer.IncrementCircularBuffer();
@ -633,7 +635,7 @@ void TextBufferTests::TestIncrementCircularBuffer()
VERIFY_ARE_NOT_EQUAL(textBuffer._GetFirstRow(), FirstRow); // the old first row is no longer the first
// ensure old first row has been emptied
VERIFY_IS_FALSE(FirstRow.GetCharRow().ContainsText());
VERIFY_IS_FALSE(FirstRow.ContainsText());
}
}
@ -658,8 +660,7 @@ void TextBufferTests::TestMixedRgbAndLegacyForeground()
const auto x = cursor.GetPosition().X;
const auto y = cursor.GetPosition().Y;
const auto& row = tbi.GetRowByOffset(y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[x - 2];
const auto attrB = attrs[x - 1];
Log::Comment(NoThrowString().Format(
@ -703,8 +704,7 @@ void TextBufferTests::TestMixedRgbAndLegacyBackground()
const auto x = cursor.GetPosition().X;
const auto y = cursor.GetPosition().Y;
const auto& row = tbi.GetRowByOffset(y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[x - 2];
const auto attrB = attrs[x - 1];
Log::Comment(NoThrowString().Format(
@ -746,8 +746,7 @@ void TextBufferTests::TestMixedRgbAndLegacyUnderline()
const auto x = cursor.GetPosition().X;
const auto y = cursor.GetPosition().Y;
const auto& row = tbi.GetRowByOffset(y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[x - 2];
const auto attrB = attrs[x - 1];
Log::Comment(NoThrowString().Format(
@ -796,8 +795,7 @@ void TextBufferTests::TestMixedRgbAndLegacyBrightness()
const auto x = cursor.GetPosition().X;
const auto y = cursor.GetPosition().Y;
const auto& row = tbi.GetRowByOffset(y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[x - 2];
const auto attrB = attrs[x - 1];
Log::Comment(NoThrowString().Format(
@ -856,9 +854,8 @@ void TextBufferTests::TestRgbEraseLine()
VERIFY_ARE_EQUAL(y, 0);
const auto& row = tbi.GetRowByOffset(y);
const auto attrRow = &row.GetAttrRow();
const auto len = tbi.GetSize().Width();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attr0 = attrs[0];
@ -908,8 +905,7 @@ void TextBufferTests::TestUnintense()
VERIFY_ARE_EQUAL(y, 0);
const auto& row = tbi.GetRowByOffset(y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[x - 2];
const auto attrB = attrs[x - 1];
@ -960,8 +956,7 @@ void TextBufferTests::TestUnintenseRgb()
VERIFY_ARE_EQUAL(y, 0);
const auto& row = tbi.GetRowByOffset(y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[x - 2];
const auto attrB = attrs[x - 1];
@ -1020,8 +1015,7 @@ void TextBufferTests::TestComplexUnintense()
VERIFY_ARE_EQUAL(y, 0);
const auto& row = tbi.GetRowByOffset(y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[x - 6];
const auto attrB = attrs[x - 5];
const auto attrC = attrs[x - 4];
@ -1103,8 +1097,7 @@ void TextBufferTests::CopyAttrs()
VERIFY_ARE_EQUAL(y, 0);
const auto& row = tbi.GetRowByOffset(0);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[0];
const auto attrB = attrs[1];
@ -1155,8 +1148,7 @@ void TextBufferTests::EmptySgrTest()
VERIFY_IS_TRUE(x >= 3);
const auto& row = tbi.GetRowByOffset(y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[x - 3];
const auto attrB = attrs[x - 2];
const auto attrC = attrs[x - 1];
@ -1217,8 +1209,7 @@ void TextBufferTests::TestReverseReset()
VERIFY_IS_TRUE(x >= 3);
const auto& row = tbi.GetRowByOffset(y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[x - 3];
const auto attrB = attrs[x - 2];
const auto attrC = attrs[x - 1];
@ -1321,9 +1312,9 @@ void TextBufferTests::CopyLastAttr()
const auto& row2 = tbi.GetRowByOffset(y + 2);
const auto& row3 = tbi.GetRowByOffset(y + 3);
const std::vector<TextAttribute> attrs1{ row1.GetAttrRow().begin(), row1.GetAttrRow().end() };
const std::vector<TextAttribute> attrs2{ row2.GetAttrRow().begin(), row2.GetAttrRow().end() };
const std::vector<TextAttribute> attrs3{ row3.GetAttrRow().begin(), row3.GetAttrRow().end() };
const std::vector<TextAttribute> attrs1{ row1.AttrBegin(), row1.AttrEnd() };
const std::vector<TextAttribute> attrs2{ row2.AttrBegin(), row2.AttrEnd() };
const std::vector<TextAttribute> attrs3{ row3.AttrBegin(), row3.AttrEnd() };
const auto attr1A = attrs1[0];
@ -1382,8 +1373,7 @@ void TextBufferTests::TestRgbThenIntense()
const auto x = cursor.GetPosition().X;
const auto y = cursor.GetPosition().Y;
const auto& row = tbi.GetRowByOffset(y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[x - 2];
const auto attrB = attrs[x - 1];
Log::Comment(NoThrowString().Format(
@ -1435,8 +1425,7 @@ void TextBufferTests::TestResetClearsIntensity()
const auto x = cursor.GetPosition().X;
const auto y = cursor.GetPosition().Y;
const auto& row = tbi.GetRowByOffset(y);
const auto attrRow = &row.GetAttrRow();
const std::vector<TextAttribute> attrs{ attrRow->begin(), attrRow->end() };
const std::vector<TextAttribute> attrs{ row.AttrBegin(), row.AttrEnd() };
const auto attrA = attrs[x0];
const auto attrB = attrs[x0 + 1];
const auto attrC = attrs[x0 + 2];
@ -1853,13 +1842,12 @@ void TextBufferTests::ResizeTraditionalRotationPreservesHighUnicode()
// Get a position inside the buffer
const til::point pos{ 2, 1 };
auto position = _buffer->_storage[pos.Y].GetCharRow().GlyphAt(pos.X);
// Fill it up with a sequence that will have to hit the high unicode storage.
// This is the negative squared latin capital letter B emoji: 🅱
// It's encoded in UTF-16, as needed by the buffer.
const auto bButton = L"\xD83C\xDD71";
position = bButton;
_buffer->_storage[pos.Y].ReplaceCharacters(pos.X, 2, bButton);
// Read back the text at that position and ensure that it matches what we wrote.
const auto readBack = _buffer->GetTextDataAt(pos);
@ -1895,13 +1883,12 @@ void TextBufferTests::ScrollBufferRotationPreservesHighUnicode()
// Get a position inside the buffer
const til::point pos{ 2, 1 };
auto position = _buffer->_storage[pos.Y].GetCharRow().GlyphAt(pos.X);
// Fill it up with a sequence that will have to hit the high unicode storage.
// This is the fire emoji: 🔥
// It's encoded in UTF-16, as needed by the buffer.
const auto fire = L"\xD83D\xDD25";
position = fire;
_buffer->_storage[pos.Y].ReplaceCharacters(pos.X, 2, fire);
// Read back the text at that position and ensure that it matches what we wrote.
const auto readBack = _buffer->GetTextDataAt(pos);
@ -1935,27 +1922,22 @@ void TextBufferTests::ResizeTraditionalHighUnicodeRowRemoval()
// Get a position inside the buffer in the bottom row
const til::point pos{ 0, bufferSize.Y - 1 };
auto position = _buffer->_storage[pos.Y].GetCharRow().GlyphAt(pos.X);
// Fill it up with a sequence that will have to hit the high unicode storage.
// This is the eggplant emoji: 🍆
// It's encoded in UTF-16, as needed by the buffer.
const auto emoji = L"\xD83C\xDF46";
position = emoji;
_buffer->_storage[pos.Y].ReplaceCharacters(pos.X, 2, emoji);
// Read back the text at that position and ensure that it matches what we wrote.
const auto readBack = _buffer->GetTextDataAt(pos);
const auto readBackText = *readBack;
VERIFY_ARE_EQUAL(String(emoji), String(readBackText.data(), gsl::narrow<int>(readBackText.size())));
VERIFY_ARE_EQUAL(1u, _buffer->GetUnicodeStorage()._map.size(), L"There should be one item in the map.");
// Perform resize to trim off the row of the buffer that included the emoji
til::size trimmedBufferSize{ bufferSize.X, bufferSize.Y - 1 };
VERIFY_NT_SUCCESS(_buffer->ResizeTraditional(trimmedBufferSize));
VERIFY_IS_TRUE(_buffer->GetUnicodeStorage()._map.empty(), L"The map should now be empty.");
}
// This tests that columns removed from the buffer while resizing traditionally will also drop the high unicode
@ -1968,29 +1950,24 @@ void TextBufferTests::ResizeTraditionalHighUnicodeColumnRemoval()
const TextAttribute attr{ 0x7f };
auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, false, _renderer);
// Get a position inside the buffer in the last column
const til::point pos{ bufferSize.X - 1, 0 };
auto position = _buffer->_storage[pos.Y].GetCharRow().GlyphAt(pos.X);
// Get a position inside the buffer in the last column (-2 as the inserted character is 2 columns wide).
const til::point pos{ bufferSize.X - 2, 0 };
// Fill it up with a sequence that will have to hit the high unicode storage.
// This is the peach emoji: 🍑
// It's encoded in UTF-16, as needed by the buffer.
const auto emoji = L"\xD83C\xDF51";
position = emoji;
_buffer->_storage[pos.Y].ReplaceCharacters(pos.X, 2, emoji);
// Read back the text at that position and ensure that it matches what we wrote.
const auto readBack = _buffer->GetTextDataAt(pos);
const auto readBackText = *readBack;
VERIFY_ARE_EQUAL(String(emoji), String(readBackText.data(), gsl::narrow<int>(readBackText.size())));
VERIFY_ARE_EQUAL(1u, _buffer->GetUnicodeStorage()._map.size(), L"There should be one item in the map.");
// Perform resize to trim off the column of the buffer that included the emoji
til::size trimmedBufferSize{ bufferSize.X - 1, bufferSize.Y };
VERIFY_NT_SUCCESS(_buffer->ResizeTraditional(trimmedBufferSize));
VERIFY_IS_TRUE(_buffer->GetUnicodeStorage()._map.empty(), L"The map should now be empty.");
}
void TextBufferTests::TestBurrito()
@ -2014,6 +1991,62 @@ void TextBufferTests::TestBurrito()
VERIFY_IS_FALSE(afterBurritoIter);
}
void TextBufferTests::TestOverwriteChars()
{
til::size bufferSize{ 10, 3 };
UINT cursorSize = 12;
TextAttribute attr{ 0x7f };
TextBuffer buffer{ bufferSize, attr, cursorSize, false, _renderer };
auto& row = buffer.GetRowByOffset(0);
// scientist emoji U+1F9D1 U+200D U+1F52C
#define complex1 L"\U0001F9D1\U0000200D\U0001F52C"
// technologist emoji U+1F9D1 U+200D U+1F4BB
#define complex2 L"\U0001F9D1\U0000200D\U0001F4BB"
#define simple L"X"
// Test overwriting narrow chars with wide chars at the begin/end of a row.
row.ReplaceCharacters(0, 2, complex1);
row.ReplaceCharacters(8, 2, complex1);
VERIFY_ARE_EQUAL(complex1 L" " complex1, row.GetText());
// Test overwriting wide chars with wide chars slightly shifted left/right.
row.ReplaceCharacters(1, 2, complex1);
row.ReplaceCharacters(7, 2, complex1);
VERIFY_ARE_EQUAL(L" " complex1 L" " complex1 L" ", row.GetText());
// Test overwriting wide chars with wide chars.
row.ReplaceCharacters(1, 2, complex2);
row.ReplaceCharacters(7, 2, complex2);
VERIFY_ARE_EQUAL(L" " complex2 L" " complex2 L" ", row.GetText());
// Test overwriting wide chars with narrow chars.
row.ReplaceCharacters(1, 1, simple);
row.ReplaceCharacters(8, 1, simple);
VERIFY_ARE_EQUAL(L" " simple L" " simple L" ", row.GetText());
// Test clearing narrow/wide chars.
row.ReplaceCharacters(0, 1, simple);
row.ReplaceCharacters(1, 2, complex2);
row.ReplaceCharacters(3, 1, simple);
row.ReplaceCharacters(6, 1, simple);
row.ReplaceCharacters(7, 2, complex2);
row.ReplaceCharacters(9, 1, simple);
VERIFY_ARE_EQUAL(simple complex2 simple L" " simple complex2 simple, row.GetText());
row.ClearCell(0);
row.ClearCell(1);
row.ClearCell(3);
row.ClearCell(6);
row.ClearCell(8);
row.ClearCell(9);
VERIFY_ARE_EQUAL(L" ", row.GetText());
#undef simple
#undef complex2
#undef complex1
}
void TextBufferTests::TestAppendRTFText()
{
{
@ -2270,11 +2303,12 @@ void TextBufferTests::GetGlyphBoundaries()
// clang-format off
const std::vector<ExpectedResult> expected = {
{ L"Buffer Start", { 0, 0 }, { 2, 0 }, { 1, 0 } },
{ L"Line Start", { 0, 1 }, { 2, 1 }, { 1, 1 } },
{ L"General Case", { 1, 1 }, { 3, 1 }, { 2, 1 } },
{ L"Line End", { 9, 1 }, { 0, 2 }, { 0, 2 } },
{ L"Buffer End", { 9, 9 }, { 0, 10 }, { 0, 10 } },
{ L"Buffer Start", { 0, 0 }, { 2, 0 }, { 1, 0 } },
{ L"Line Start", { 0, 1 }, { 2, 1 }, { 1, 1 } },
{ L"General Case 1", { 1, 1 }, { 3, 1 }, { 2, 1 } },
{ L"Line End", { 8, 1 }, { 0, 2 }, { 9, 1 } },
{ L"General Case 2", { 7, 1 }, { 9, 1 }, { 8, 1 } },
{ L"Buffer End", { 9, 9 }, { 0, 10 }, { 0, 10 } },
};
// clang-format on
@ -2644,14 +2678,14 @@ void TextBufferTests::HyperlinkTrim()
const auto id = _buffer->GetHyperlinkId(url, customId);
TextAttribute newAttr{ 0x7f };
newAttr.SetHyperlinkId(id);
_buffer->GetRowByOffset(pos.Y).GetAttrRow().SetAttrToEnd(pos.X, newAttr);
_buffer->GetRowByOffset(pos.Y).SetAttrToEnd(pos.X, newAttr);
_buffer->AddHyperlinkToMap(url, id);
// Set a different hyperlink id somewhere else in the buffer
const til::point otherPos{ 70, 5 };
const auto otherId = _buffer->GetHyperlinkId(otherUrl, otherCustomId);
newAttr.SetHyperlinkId(otherId);
_buffer->GetRowByOffset(otherPos.Y).GetAttrRow().SetAttrToEnd(otherPos.X, newAttr);
_buffer->GetRowByOffset(otherPos.Y).SetAttrToEnd(otherPos.X, newAttr);
_buffer->AddHyperlinkToMap(otherUrl, otherId);
// Increment the circular buffer
@ -2688,12 +2722,12 @@ void TextBufferTests::NoHyperlinkTrim()
const auto id = _buffer->GetHyperlinkId(url, customId);
TextAttribute newAttr{ 0x7f };
newAttr.SetHyperlinkId(id);
_buffer->GetRowByOffset(pos.Y).GetAttrRow().SetAttrToEnd(pos.X, newAttr);
_buffer->GetRowByOffset(pos.Y).SetAttrToEnd(pos.X, newAttr);
_buffer->AddHyperlinkToMap(url, id);
// Set the same hyperlink id somewhere else in the buffer
const til::point otherPos{ 70, 5 };
_buffer->GetRowByOffset(otherPos.Y).GetAttrRow().SetAttrToEnd(otherPos.X, newAttr);
_buffer->GetRowByOffset(otherPos.Y).SetAttrToEnd(otherPos.X, newAttr);
// Increment the circular buffer
_buffer->IncrementCircularBuffer();

View File

@ -325,7 +325,7 @@ public:
return 12ul;
}
bool IsCursorDoubleWidth() const override
bool IsCursorDoubleWidth() const noexcept override
{
return false;
}

View File

@ -24,7 +24,6 @@ unit testing projects in the codebase without a bunch of overhead.
#include "../host/globals.h"
#include "../host/inputReadHandleData.h"
#include "../buffer/out/CharRow.hpp"
#include "../interactivity/inc/ServiceLocator.hpp"
class CommonState
@ -241,26 +240,7 @@ public:
for (til::CoordType iRow = 0; iRow < cRowsToFill; iRow++)
{
ROW& row = textBuffer.GetRowByOffset(iRow);
FillRow(&row);
}
textBuffer.GetCursor().SetYPosition(cRowsToFill);
}
void FillTextBufferBisect()
{
CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
// fill with some text that fills the whole row and has bisecting double byte characters
const auto cRowsToFill = s_csBufferHeight;
VERIFY_IS_TRUE(gci.HasActiveOutputBuffer());
TextBuffer& textBuffer = gci.GetActiveOutputBuffer().GetTextBuffer();
for (til::CoordType iRow = 0; iRow < cRowsToFill; iRow++)
{
ROW& row = textBuffer.GetRowByOffset(iRow);
FillBisect(&row);
FillRow(&row, iRow & 1);
}
textBuffer.GetCursor().SetYPosition(cRowsToFill);
@ -278,54 +258,72 @@ private:
std::unique_ptr<TextBuffer> m_backupTextBufferInfo;
std::unique_ptr<INPUT_READ_HANDLE_DATA> m_readHandle;
void FillRow(ROW* pRow)
struct TestString
{
std::wstring_view string;
bool wide = false;
};
static void applyTestString(ROW* pRow, const auto& testStrings)
{
uint16_t x = 0;
for (const auto& t : testStrings)
{
if (t.wide)
{
pRow->ReplaceCharacters(x, 2, t.string);
x += 2;
}
else
{
for (const auto& ch : t.string)
{
pRow->ReplaceCharacters(x, 1, { &ch, 1 });
x += 1;
}
}
}
}
void FillRow(ROW* pRow, bool wrapForced)
{
// fill a row
// 9 characters, 6 spaces. 15 total
// か = \x304b
// き = \x304d
const PCWSTR pwszText = L"AB"
L"\x304b\x304b"
L"C"
L"\x304d\x304d"
L"DE ";
const size_t length = wcslen(pwszText);
std::vector<DbcsAttribute> attrs(length, DbcsAttribute());
// set double-byte/double-width attributes
attrs[2].SetLeading();
attrs[3].SetTrailing();
attrs[5].SetLeading();
attrs[6].SetTrailing();
static constexpr std::array testStrings{
TestString{ L"AB" },
TestString{ L"\x304b", true },
TestString{ L"C" },
TestString{ L"\x304d", true },
TestString{ L"DE " },
};
CharRow& charRow = pRow->GetCharRow();
OverwriteColumns(pwszText, pwszText + length, attrs.cbegin(), charRow.begin());
applyTestString(pRow, testStrings);
// set some colors
TextAttribute Attr = TextAttribute(0);
pRow->GetAttrRow().Reset(Attr);
// A = bright red on dark gray
// This string starts at index 0
Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY);
pRow->GetAttrRow().SetAttrToEnd(0, Attr);
auto Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY);
pRow->SetAttrToEnd(0, Attr);
// BかC = dark gold on bright blue
// This string starts at index 1
Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY);
pRow->GetAttrRow().SetAttrToEnd(1, Attr);
pRow->SetAttrToEnd(1, Attr);
// き = bright white on dark purple
// This string starts at index 5
Attr = TextAttribute(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_BLUE);
pRow->GetAttrRow().SetAttrToEnd(5, Attr);
pRow->SetAttrToEnd(5, Attr);
// DE = black on dark green
// This string starts at index 7
Attr = TextAttribute(BACKGROUND_GREEN);
pRow->GetAttrRow().SetAttrToEnd(7, Attr);
pRow->SetAttrToEnd(7, Attr);
// odd rows forced a wrap
if (pRow->GetId() % 2 != 0)
if (wrapForced)
{
pRow->SetWrapForced(true);
}
@ -334,41 +332,4 @@ private:
pRow->SetWrapForced(false);
}
}
void FillBisect(ROW* pRow)
{
const CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
// length 80 string of text with bisecting characters at the beginning and end.
// positions of き(\x304d) are at 0, 27-28, 39-40, 67-68, 79
auto pwszText =
L"\x304d"
L"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
L"\x304d\x304d"
L"0123456789"
L"\x304d\x304d"
L"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
L"\x304d\x304d"
L"0123456789"
L"\x304d";
const size_t length = wcslen(pwszText);
std::vector<DbcsAttribute> attrs(length, DbcsAttribute());
// set double-byte/double-width attributes
attrs[0].SetTrailing();
attrs[27].SetLeading();
attrs[28].SetTrailing();
attrs[39].SetLeading();
attrs[40].SetTrailing();
attrs[67].SetLeading();
attrs[68].SetTrailing();
attrs[79].SetLeading();
CharRow& charRow = pRow->GetCharRow();
OverwriteColumns(pwszText, pwszText + length, attrs.cbegin(), charRow.begin());
// everything gets default attributes
pRow->GetAttrRow().Reset(gci.GetActiveOutputBuffer().GetAttributes());
pRow->SetWrapForced(true);
}
};

View File

@ -360,18 +360,13 @@ class UiaTextRangeTests
// fill first half of text buffer with text
for (auto i = 0; i < _pTextBuffer->TotalRowCount() / 2; ++i)
{
const std::wstring_view glyph{ i % 2 == 0 ? L" " : L"X" };
auto& row = _pTextBuffer->GetRowByOffset(i);
auto& charRow = row.GetCharRow();
for (auto& cell : charRow)
const auto width = row.size();
for (uint16_t x = 0; x < width; ++x)
{
if (i % 2 == 0)
{
cell.Char() = L' ';
}
else
{
cell.Char() = L'X';
}
row.ReplaceCharacters(x, 1, glyph);
}
}
@ -495,20 +490,12 @@ class UiaTextRangeTests
for (auto i = 0; i < _pTextBuffer->TotalRowCount(); ++i)
{
auto& row = _pTextBuffer->GetRowByOffset(i);
auto& charRow = row.GetCharRow();
for (auto j = 0; j < charRow.size(); ++j)
const auto width = row.size();
for (uint16_t x = 0; x < width; ++x)
{
// every 5th cell is a space, otherwise a letter
// this is used to simulate words
auto cell = charRow.GlyphAt(j);
if (j % 5 == 0)
{
cell = L" ";
}
else
{
cell = L"x";
}
const std::wstring_view glyph{ x % 5 == 0 ? L" " : L"x" };
row.ReplaceCharacters(x, 1, glyph);
}
}

View File

@ -849,7 +849,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
// If we're on the first cluster to be added and it's marked as "trailing"
// (a.k.a. the right half of a two column character), then we need some special handling.
if (_clusterBuffer.empty() && it->DbcsAttr().IsTrailing())
if (_clusterBuffer.empty() && it->DbcsAttr() == DbcsAttribute::Trailing)
{
// Move left to the one so the whole character can be struck correctly.
--screenPoint.X;

View File

@ -22,7 +22,6 @@ Author(s):
#include "thread.hpp"
#include "../../buffer/out/textBuffer.hpp"
#include "../../buffer/out/CharRow.hpp"
// fwdecl unittest classes
#ifdef UNIT_TESTING

View File

@ -52,7 +52,7 @@ namespace Microsoft::Console::Render
virtual ULONG GetCursorHeight() const noexcept = 0;
virtual CursorType GetCursorStyle() const noexcept = 0;
virtual ULONG GetCursorPixelWidth() const noexcept = 0;
virtual bool IsCursorDoubleWidth() const = 0;
virtual bool IsCursorDoubleWidth() const noexcept = 0;
virtual const std::vector<RenderOverlay> GetOverlays() const noexcept = 0;

View File

@ -702,15 +702,13 @@ void AdaptDispatch::_SelectiveEraseRect(TextBuffer& textBuffer, const til::rect&
for (auto row = eraseRect.top; row < eraseRect.bottom; row++)
{
auto& rowBuffer = textBuffer.GetRowByOffset(row);
const auto& attrs = rowBuffer.GetAttrRow();
auto& chars = rowBuffer.GetCharRow();
for (auto col = eraseRect.left; col < eraseRect.right; col++)
{
// Only unprotected cells are affected.
if (!attrs.GetAttrByColumn(col).IsProtected())
if (!rowBuffer.GetAttrByColumn(col).IsProtected())
{
// The text is cleared but the attributes are left as is.
chars.ClearGlyph(col);
rowBuffer.ClearCell(col);
textBuffer.TriggerRedraw(Viewport::FromCoord({ col, row }));
}
}
@ -800,10 +798,9 @@ void AdaptDispatch::_ChangeRectAttributes(TextBuffer& textBuffer, const til::rec
for (auto row = changeRect.top; row < changeRect.bottom; row++)
{
auto& rowBuffer = textBuffer.GetRowByOffset(row);
auto& attrs = rowBuffer.GetAttrRow();
for (auto col = changeRect.left; col < changeRect.right; col++)
{
auto attr = attrs.GetAttrByColumn(col);
auto attr = rowBuffer.GetAttrByColumn(col);
auto characterAttributes = attr.GetCharacterAttributes();
characterAttributes &= changeOps.andAttrMask;
characterAttributes ^= changeOps.xorAttrMask;
@ -816,7 +813,7 @@ void AdaptDispatch::_ChangeRectAttributes(TextBuffer& textBuffer, const til::rec
{
attr.SetBackground(*changeOps.background);
}
attrs.Replace(col, col + 1, attr);
rowBuffer.ReplaceAttributes(col, col + 1, attr);
}
}
textBuffer.TriggerRedraw(Viewport::FromExclusive(changeRect));