Compare commits

...

21 commits

Author SHA1 Message Date
Dustin Howett 5bdb5e6caa Break a huge amount of the console
Writing CHAR_INFO doesn't work
RowImage is half-baked
Row.hpp contains eight reimplementations of the same slgotihm
move shit around
RowImage.split is fucking madness
look for TODO(DH) everywhere

however, termbench performance doubles for non-colored text.
2021-07-30 12:41:30 -05:00
Dustin Howett d2f0f50651 Make the writers fun templates 2021-07-29 16:54:26 -05:00
Dustin Howett 271132edb4 Remove the OCI sources 2021-07-29 16:44:47 -05:00
Dustin Howett 7c5b39a041 OCI-DEP: Remove some more OCI 2021-07-29 13:25:12 -05:00
Dustin Howett 34035509a9 OCI-DEP: Remove all Attribute (Region/Count) fills 2021-07-28 15:40:32 -05:00
Dustin Howett a13f6237ff WriteStringCOntiguous + WriteCharsLegacy[Measurement] 2021-07-28 15:12:55 -05:00
Dustin Howett 97702a1d9d WCL-HAX: fix wrap-repeat and buffer reuse bug 2021-07-27 18:48:26 -05:00
Dustin Howett d4cdbc9b91 Update for Chester's TBCI change 2021-07-27 18:48:01 -05:00
Dustin Howett 596a8155ca Move Attr, Cwid, Data into nested type. Unify Damage interface to support returning damage for one column (replace off+size with min/max damage) and for multiple cols. 2021-07-27 18:46:44 -05:00
Dustin Howett 58ad0a34fc remove dead code, non-rle code, and de-tuplise 2021-07-27 13:53:51 -05:00
Dustin Howett 8f4c4f4916 and nuke the local copy of rle 2021-07-27 13:53:51 -05:00
Dustin Howett 8133f2856d tidy 2021-07-27 13:53:51 -05:00
Dustin Howett f64660ac2f new cleaner variable names 2021-07-27 13:53:51 -05:00
Dustin Howett b1a981daa9 port to Leonard's RLE 2021-07-27 13:53:48 -05:00
Dustin L. Howett 7d8df11ede wishlist 2021-07-27 13:51:49 -05:00
Dustin L. Howett 89fde46a94 Clean up the RLE defines 2021-07-27 13:51:49 -05:00
Dustin L. Howett 00af538278 HAX: rle-based row 2021-07-27 13:51:46 -05:00
Dustin Howett 227ce8ff20 beef prevention 2021-07-27 13:42:54 -05:00
Dustin Howett 99d9bac51d Kill GlyphAt (writable and const) 2021-07-27 13:42:52 -05:00
Dustin Howett 97a2dc6878 Rewrite Reflow 2021-07-27 13:41:53 -05:00
Dustin Howett abf66b2ff8 NO TESTS: Add OututCellIterator that takes TextBufferCellIterator 2021-07-27 13:38:05 -05:00
45 changed files with 1072 additions and 2962 deletions

View file

@ -31,6 +31,8 @@ public:
using const_iterator = rle_vector::const_iterator;
ATTR_ROW(uint16_t width, TextAttribute attr);
ATTR_ROW(rle_vector&& v) :
_data(std::move(v)) {}
~ATTR_ROW() = default;
@ -57,11 +59,11 @@ public:
friend bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept;
friend class ROW;
rle_vector _data;
private:
void Reset(const TextAttribute attr);
rle_vector _data;
#ifdef UNIT_TESTING
friend class CommonState;
#endif

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(size_t rowWidth, ROW* const pParent) noexcept :
_data(rowWidth, value_type()),
_pParent{ FAIL_FAST_IF_NULL(pParent) }
{
}
#pragma warning(pop)
// Routine Description:
// - gets the size of the row, in glyph cells
// Arguments:
// - <none>
// Return Value:
// - the size of the row
size_t CharRow::size() const noexcept
{
return _data.size();
}
// Routine Description:
// - Sets all properties of the CharRowBase to default values
// Arguments:
// - sRowWidth - The width of the row.
// Return Value:
// - <none>
void CharRow::Reset() noexcept
{
for (auto& cell : _data)
{
cell.Reset();
}
}
// Routine Description:
// - resizes the width of the CharRowBase
// Arguments:
// - newSize - the new width of the character and attributes rows
// Return Value:
// - S_OK on success, otherwise relevant error code
[[nodiscard]] HRESULT CharRow::Resize(const size_t newSize) noexcept
{
try
{
const value_type insertVals;
_data.resize(newSize, insertVals);
}
CATCH_RETURN();
return S_OK;
}
typename CharRow::iterator CharRow::begin() noexcept
{
return _data.begin();
}
typename CharRow::const_iterator CharRow::cbegin() const noexcept
{
return _data.cbegin();
}
typename CharRow::iterator CharRow::end() noexcept
{
return _data.end();
}
typename CharRow::const_iterator CharRow::cend() const noexcept
{
return _data.cend();
}
// Routine Description:
// - Inspects the current internal string to find the left edge of it
// Arguments:
// - <none>
// Return Value:
// - The calculated left boundary of the internal string.
size_t CharRow::MeasureLeft() const noexcept
{
const_iterator it = _data.cbegin();
while (it != _data.cend() && it->IsSpace())
{
++it;
}
return it - _data.cbegin();
}
// Routine Description:
// - Inspects the current internal string to find the right edge of it
// Arguments:
// - <none>
// Return Value:
// - The calculated right boundary of the internal string.
size_t CharRow::MeasureRight() const
{
const_reverse_iterator it = _data.crbegin();
while (it != _data.crend() && it->IsSpace())
{
++it;
}
return _data.crend() - it;
}
void CharRow::ClearCell(const size_t column)
{
_data.at(column).Reset();
}
// Routine Description:
// - Tells you whether or not this row contains any valid text.
// Arguments:
// - <none>
// Return Value:
// - True if there is valid text in this row. False otherwise.
bool CharRow::ContainsText() const noexcept
{
for (const value_type& cell : _data)
{
if (!cell.IsSpace())
{
return true;
}
}
return false;
}
// Routine Description:
// - gets the attribute at the specified column
// Arguments:
// - column - the column to get the attribute for
// Return Value:
// - the attribute
// Note: will throw exception if column is out of bounds
const DbcsAttribute& CharRow::DbcsAttrAt(const size_t column) const
{
return _data.at(column).DbcsAttr();
}
// Routine Description:
// - gets the attribute at the specified column
// Arguments:
// - column - the column to get the attribute for
// Return Value:
// - the attribute
// Note: will throw exception if column is out of bounds
DbcsAttribute& CharRow::DbcsAttrAt(const size_t column)
{
return _data.at(column).DbcsAttr();
}
// Routine Description:
// - resets text data at column
// Arguments:
// - column - column index to clear text data from
// Return Value:
// - <none>
// Note: will throw exception if column is out of bounds
void CharRow::ClearGlyph(const size_t column)
{
_data.at(column).EraseChars();
}
// Routine Description:
// - returns text data at column as a const reference.
// Arguments:
// - column - column to get text data for
// Return Value:
// - text data at column
// - Note: will throw exception if column is out of bounds
const CharRow::reference CharRow::GlyphAt(const size_t column) const
{
THROW_HR_IF(E_INVALIDARG, column >= _data.size());
return { const_cast<CharRow&>(*this), column };
}
// Routine Description:
// - returns text data at column as a reference.
// Arguments:
// - column - column to get text data for
// Return Value:
// - text data at column
// - Note: will throw exception if column is out of bounds
CharRow::reference CharRow::GlyphAt(const size_t column)
{
THROW_HR_IF(E_INVALIDARG, column >= _data.size());
return { *this, column };
}
std::wstring CharRow::GetText() const
{
std::wstring wstr;
wstr.reserve(_data.size());
for (size_t i = 0; i < _data.size(); ++i)
{
const auto glyph = GlyphAt(i);
if (!DbcsAttrAt(i).IsTrailing())
{
for (const auto wch : glyph)
{
wstr.push_back(wch);
}
}
}
return wstr;
}
// Method Description:
// - get delimiter class for a position in the char row
// - used for double click selection and uia word navigation
// Arguments:
// - column: column to get text data for
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
// Return Value:
// - the delimiter class for the given char
const DelimiterClass CharRow::DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const
{
THROW_HR_IF(E_INVALIDARG, column >= _data.size());
const auto glyph = *GlyphAt(column).begin();
if (glyph <= UNICODE_SPACE)
{
return DelimiterClass::ControlChar;
}
else if (wordDelimiters.find(glyph) != std::wstring_view::npos)
{
return DelimiterClass::DelimiterChar;
}
else
{
return DelimiterClass::RegularChar;
}
}
UnicodeStorage& CharRow::GetUnicodeStorage() noexcept
{
return _pParent->GetUnicodeStorage();
}
const UnicodeStorage& CharRow::GetUnicodeStorage() const noexcept
{
return _pParent->GetUnicodeStorage();
}
// Routine Description:
// - calculates the key used by the given column of the char row to store glyph data in UnicodeStorage
// Arguments:
// - column - the column to generate the key for
// Return Value:
// - the COORD key for data access from UnicodeStorage for the column
COORD CharRow::GetStorageKey(const size_t column) const noexcept
{
return { gsl::narrow<SHORT>(column), _pParent->GetId() };
}
// Routine Description:
// - Updates the pointer to the parent row (which might change if we shuffle the rows around)
// Arguments:
// - pParent - Pointer to the parent row
void CharRow::UpdateParent(ROW* const pParent)
{
_pParent = FAIL_FAST_IF_NULL(pParent);
}

View file

@ -1,115 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- CharRow.hpp
Abstract:
- contains data structure for UCS2 encoded character data of a row
Author(s):
- Michael Niksa (miniksa) 10-Apr-2014
- Paul Campbell (paulcam) 10-Apr-2014
Revision History:
- From components of output.h/.c
by Therese Stowell (ThereseS) 1990-1991
- Pulled into its own file from textBuffer.hpp/cpp (AustDi, 2017)
--*/
#pragma once
#include "DbcsAttribute.hpp"
#include "CharRowCellReference.hpp"
#include "CharRowCell.hpp"
#include "UnicodeStorage.hpp"
class ROW;
enum class DelimiterClass
{
ControlChar,
DelimiterChar,
RegularChar
};
// the characters of one row of screen buffer
// we keep the following values so that we don't write
// more pixels to the screen than we have to:
// left is initialized to screenbuffer width. right is
// initialized to zero.
//
// [ foo.bar 12-12-61 ]
// ^ ^ ^ ^
// | | | |
// Chars Left Right end of Chars buffer
class CharRow final
{
public:
using glyph_type = typename wchar_t;
using value_type = typename CharRowCell;
using iterator = typename boost::container::small_vector_base<value_type>::iterator;
using const_iterator = typename boost::container::small_vector_base<value_type>::const_iterator;
using const_reverse_iterator = typename boost::container::small_vector_base<value_type>::const_reverse_iterator;
using reference = typename CharRowCellReference;
CharRow(size_t rowWidth, ROW* const pParent) noexcept;
size_t size() const noexcept;
[[nodiscard]] HRESULT Resize(const size_t newSize) noexcept;
size_t MeasureLeft() const noexcept;
size_t MeasureRight() const;
bool ContainsText() const noexcept;
const DbcsAttribute& DbcsAttrAt(const size_t column) const;
DbcsAttribute& DbcsAttrAt(const size_t column);
void ClearGlyph(const size_t column);
const DelimiterClass DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const;
// working with glyphs
const reference GlyphAt(const size_t column) const;
reference GlyphAt(const size_t column);
// iterators
iterator begin() noexcept;
const_iterator cbegin() const noexcept;
const_iterator begin() const noexcept { return cbegin(); }
iterator end() noexcept;
const_iterator cend() const noexcept;
const_iterator end() const noexcept { return cend(); }
UnicodeStorage& GetUnicodeStorage() noexcept;
const UnicodeStorage& GetUnicodeStorage() const noexcept;
COORD GetStorageKey(const size_t column) const noexcept;
void UpdateParent(ROW* const pParent);
friend CharRowCellReference;
friend class ROW;
private:
void Reset() noexcept;
void ClearCell(const size_t column);
std::wstring GetText() const;
protected:
// storage for glyph data and dbcs attributes
boost::container::small_vector<value_type, 120> _data;
// ROW that this CharRow belongs to
ROW* _pParent;
};
template<typename InputIt1, typename InputIt2>
void OverwriteColumns(InputIt1 startChars, InputIt1 endChars, InputIt2 startAttrs, CharRow::iterator outIt)
{
std::transform(startChars,
endChars,
startAttrs,
outIt,
[](const wchar_t wch, const DbcsAttribute attr) {
return CharRow::value_type{ wch, attr };
});
}

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

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 size_t index) noexcept :
_parent{ parent },
_index{ index }
{
}
~CharRowCellReference() = default;
CharRowCellReference(const CharRowCellReference&) noexcept = default;
CharRowCellReference(CharRowCellReference&&) noexcept = default;
void operator=(const CharRowCellReference&) = delete;
void operator=(CharRowCellReference&&) = delete;
void operator=(const std::wstring_view chars);
operator std::wstring_view() const;
const_iterator begin() const;
const_iterator end() const;
friend bool operator==(const CharRowCellReference& ref, const std::vector<wchar_t>& glyph);
friend bool operator==(const std::vector<wchar_t>& glyph, const CharRowCellReference& ref);
private:
// what char row the object belongs to
CharRow& _parent;
// the index of the cell in the parent char row
const size_t _index;
CharRowCell& _cellData();
const CharRowCell& _cellData() const;
std::wstring_view _glyphData() const;
};
bool operator==(const CharRowCellReference& ref, const std::vector<wchar_t>& glyph);
bool operator==(const std::vector<wchar_t>& glyph, const CharRowCellReference& ref);

View file

@ -27,14 +27,12 @@ public:
};
DbcsAttribute() noexcept :
_attribute{ Attribute::Single },
_glyphStored{ false }
_attribute{ Attribute::Single }
{
}
DbcsAttribute(const Attribute attribute) noexcept :
_attribute{ attribute },
_glyphStored{ false }
_attribute{ attribute }
{
}
@ -58,16 +56,6 @@ public:
return IsLeading() || IsTrailing();
}
constexpr bool IsGlyphStored() const noexcept
{
return _glyphStored;
}
void SetGlyphStored(const bool stored) noexcept
{
_glyphStored = stored;
}
void SetSingle() noexcept
{
_attribute = Attribute::Single;
@ -86,7 +74,6 @@ public:
void Reset() noexcept
{
SetSingle();
SetGlyphStored(false);
}
WORD GeneratePublicApiAttributeFormat() const noexcept
@ -127,7 +114,6 @@ public:
private:
Attribute _attribute : 2;
bool _glyphStored : 1;
#ifdef UNIT_TESTING
friend class TextBufferTests;

View file

@ -1,546 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "OutputCellIterator.hpp"
#include "../../types/inc/convert.hpp"
#include "../../types/inc/Utf16Parser.hpp"
#include "../../types/inc/GlyphWidth.hpp"
#include "../../inc/conattrs.hpp"
static constexpr TextAttribute InvalidTextAttribute{ INVALID_COLOR, INVALID_COLOR };
// Routine Description:
// - This is a fill-mode iterator for one particular wchar. It will repeat forever if fillLimit is 0.
// Arguments:
// - wch - The character to use for filling
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
OutputCellIterator::OutputCellIterator(const wchar_t& wch, const size_t fillLimit) noexcept :
_mode(Mode::Fill),
_currentView(s_GenerateView(wch)),
_run(),
_attr(InvalidTextAttribute),
_pos(0),
_distance(0),
_fillLimit(fillLimit)
{
}
// Routine Description:
// - This is a fill-mode iterator for one particular color. It will repeat forever if fillLimit is 0.
// Arguments:
// - attr - The color attribute to use for filling
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
OutputCellIterator::OutputCellIterator(const TextAttribute& attr, const size_t fillLimit) noexcept :
_mode(Mode::Fill),
_currentView(s_GenerateView(attr)),
_run(),
_attr(InvalidTextAttribute),
_pos(0),
_distance(0),
_fillLimit(fillLimit)
{
}
// Routine Description:
// - This is a fill-mode iterator for one particular character and color. It will repeat forever if fillLimit is 0.
// Arguments:
// - wch - The character to use for filling
// - attr - The color attribute to use for filling
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
OutputCellIterator::OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit) noexcept :
_mode(Mode::Fill),
_currentView(s_GenerateView(wch, attr)),
_run(),
_attr(InvalidTextAttribute),
_pos(0),
_distance(0),
_fillLimit(fillLimit)
{
}
// Routine Description:
// - This is a fill-mode iterator for one particular CHAR_INFO. It will repeat forever if fillLimit is 0.
// Arguments:
// - charInfo - The legacy character and color data to use for filling (uses Unicode portion of text data)
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
OutputCellIterator::OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit) noexcept :
_mode(Mode::Fill),
_currentView(s_GenerateView(charInfo)),
_run(),
_attr(InvalidTextAttribute),
_pos(0),
_distance(0),
_fillLimit(fillLimit)
{
}
// Routine Description:
// - This is an iterator over a range of text only. No color data will be modified as the text is inserted.
// Arguments:
// - utf16Text - UTF-16 text range
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) :
_mode(Mode::LooseTextOnly),
_currentView(s_GenerateView(utf16Text)),
_run(utf16Text),
_attr(InvalidTextAttribute),
_pos(0),
_distance(0),
_fillLimit(0)
{
}
// Routine Description:
// - This is an iterator over a range text that will apply the same color to every position.
// Arguments:
// - utf16Text - UTF-16 text range
// - attribute - Color to apply over the entire range
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute attribute) :
_mode(Mode::Loose),
_currentView(s_GenerateView(utf16Text, attribute)),
_run(utf16Text),
_attr(attribute),
_distance(0),
_pos(0),
_fillLimit(0)
{
}
// Routine Description:
// - This is an iterator over legacy colors only. The text is not modified.
// Arguments:
// - legacyAttrs - One legacy color item per cell
OutputCellIterator::OutputCellIterator(const gsl::span<const WORD> legacyAttrs) noexcept :
_mode(Mode::LegacyAttr),
_currentView(s_GenerateViewLegacyAttr(til::at(legacyAttrs, 0))),
_run(legacyAttrs),
_attr(InvalidTextAttribute),
_distance(0),
_pos(0),
_fillLimit(0)
{
}
// Routine Description:
// - This is an iterator over legacy cell data. We will use the unicode text and the legacy color attribute.
// Arguments:
// - charInfos - Multiple cell with unicode text and legacy color data.
OutputCellIterator::OutputCellIterator(const gsl::span<const CHAR_INFO> charInfos) noexcept :
_mode(Mode::CharInfo),
_currentView(s_GenerateView(til::at(charInfos, 0))),
_run(charInfos),
_attr(InvalidTextAttribute),
_distance(0),
_pos(0),
_fillLimit(0)
{
}
// Routine Description:
// - This is an iterator over existing OutputCells with full text and color data.
// Arguments:
// - cells - Multiple cells in a run
OutputCellIterator::OutputCellIterator(const gsl::span<const OutputCell> cells) :
_mode(Mode::Cell),
_currentView(s_GenerateView(til::at(cells, 0))),
_run(cells),
_attr(InvalidTextAttribute),
_distance(0),
_pos(0),
_fillLimit(0)
{
}
// Routine Description:
// - Specifies whether this iterator is valid for dereferencing (still valid underlying data)
// Return Value:
// - True if the views on dereference are valid. False if it shouldn't be dereferenced.
OutputCellIterator::operator bool() const noexcept
{
try
{
switch (_mode)
{
case Mode::Loose:
case Mode::LooseTextOnly:
{
// In lieu of using start and end, this custom iterator type simply becomes bool false
// when we run out of items to iterate over.
return _pos < std::get<std::wstring_view>(_run).length();
}
case Mode::Fill:
{
if (_fillLimit > 0)
{
return _pos < _fillLimit;
}
return true;
}
case Mode::Cell:
{
return _pos < std::get<gsl::span<const OutputCell>>(_run).size();
}
case Mode::CharInfo:
{
return _pos < std::get<gsl::span<const CHAR_INFO>>(_run).size();
}
case Mode::LegacyAttr:
{
return _pos < std::get<gsl::span<const WORD>>(_run).size();
}
default:
FAIL_FAST_HR(E_NOTIMPL);
}
}
CATCH_FAIL_FAST();
}
// Routine Description:
// - Advances the iterator one position over the underlying data source.
// Return Value:
// - Reference to self after advancement.
OutputCellIterator& OutputCellIterator::operator++()
{
// Keep track of total distance moved (cells filled)
_distance++;
switch (_mode)
{
case Mode::Loose:
{
if (!_TryMoveTrailing())
{
// When walking through a text sequence, we need to move forward by the number of wchar_ts consumed in the previous view
// in case we had a surrogate pair (or wider complex sequence) in the previous view.
_pos += _currentView.Chars().size();
if (operator bool())
{
_currentView = s_GenerateView(std::get<std::wstring_view>(_run).substr(_pos), _attr);
}
}
break;
}
case Mode::LooseTextOnly:
{
if (!_TryMoveTrailing())
{
// When walking through a text sequence, we need to move forward by the number of wchar_ts consumed in the previous view
// in case we had a surrogate pair (or wider complex sequence) in the previous view.
_pos += _currentView.Chars().size();
if (operator bool())
{
_currentView = s_GenerateView(std::get<std::wstring_view>(_run).substr(_pos));
}
}
break;
}
case Mode::Fill:
{
if (!_TryMoveTrailing())
{
if (_currentView.DbcsAttr().IsTrailing())
{
auto dbcsAttr = _currentView.DbcsAttr();
dbcsAttr.SetLeading();
_currentView = OutputCellView(_currentView.Chars(),
dbcsAttr,
_currentView.TextAttr(),
_currentView.TextAttrBehavior());
}
if (_fillLimit > 0)
{
// We walk forward by one because we fill with the same cell over and over no matter what
_pos++;
}
}
break;
}
case Mode::Cell:
{
// Walk forward by one because cells are assumed to be in the form they needed to be
_pos++;
if (operator bool())
{
_currentView = s_GenerateView(til::at(std::get<gsl::span<const OutputCell>>(_run), _pos));
}
break;
}
case Mode::CharInfo:
{
// Walk forward by one because charinfos are just the legacy version of cells and prealigned to columns
_pos++;
if (operator bool())
{
_currentView = s_GenerateView(til::at(std::get<gsl::span<const CHAR_INFO>>(_run), _pos));
}
break;
}
case Mode::LegacyAttr:
{
// Walk forward by one because color attributes apply cell by cell (no complex text information)
_pos++;
if (operator bool())
{
_currentView = s_GenerateViewLegacyAttr(til::at(std::get<gsl::span<const WORD>>(_run), _pos));
}
break;
}
default:
FAIL_FAST_HR(E_NOTIMPL);
}
return (*this);
}
// Routine Description:
// - Advances the iterator one position over the underlying data source.
// Return Value:
// - Reference to self after advancement.
OutputCellIterator OutputCellIterator::operator++(int)
{
auto temp(*this);
operator++();
return temp;
}
// Routine Description:
// - Reference the view to fully-formed output cell data representing the underlying data source.
// Return Value:
// - Reference to the view
const OutputCellView& OutputCellIterator::operator*() const noexcept
{
return _currentView;
}
// Routine Description:
// - Get pointer to the view to fully-formed output cell data representing the underlying data source.
// Return Value:
// - Pointer to the view
const OutputCellView* OutputCellIterator::operator->() const noexcept
{
return &_currentView;
}
// Routine Description:
// - Checks the current view. If it is a leading half, it updates the current
// view to the trailing half of the same glyph.
// - This helps us to draw glyphs that are two columns wide by "doubling"
// the view that is returned so it will consume two cells.
// Return Value:
// - True if we just turned a lead half into a trailing half (and caller doesn't
// need to further update the view).
// - False if this wasn't applicable and the caller should update the view.
bool OutputCellIterator::_TryMoveTrailing() noexcept
{
if (_currentView.DbcsAttr().IsLeading())
{
auto dbcsAttr = _currentView.DbcsAttr();
dbcsAttr.SetTrailing();
_currentView = OutputCellView(_currentView.Chars(),
dbcsAttr,
_currentView.TextAttr(),
_currentView.TextAttrBehavior());
return true;
}
else
{
return false;
}
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and specify that the attributes shouldn't be changed.
// Arguments:
// - view - View representing characters corresponding to a single glyph
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view)
{
return s_GenerateView(view, InvalidTextAttribute, TextAttributeBehavior::Current);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
// Arguments:
// - view - View representing characters corresponding to a single glyph
// - attr - Color attributes to apply to the text
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
const TextAttribute attr)
{
return s_GenerateView(view, attr, TextAttributeBehavior::Stored);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
// Arguments:
// - view - View representing characters corresponding to a single glyph
// - attr - Color attributes to apply to the text
// - behavior - Behavior of the given text attribute (used when writing)
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
const TextAttribute attr,
const TextAttributeBehavior behavior)
{
const auto glyph = Utf16Parser::ParseNext(view);
DbcsAttribute dbcsAttr;
if (IsGlyphFullWidth(glyph))
{
dbcsAttr.SetLeading();
}
return OutputCellView(glyph, dbcsAttr, attr, behavior);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
// Arguments:
// - wch - View representing a single UTF-16 character (that can be represented without surrogates)
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch) noexcept
{
const auto glyph = std::wstring_view(&wch, 1);
DbcsAttribute dbcsAttr;
if (IsGlyphFullWidth(wch))
{
dbcsAttr.SetLeading();
}
return OutputCellView(glyph, dbcsAttr, InvalidTextAttribute, TextAttributeBehavior::Current);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
// Arguments:
// - attr - View representing a single color
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const TextAttribute& attr) noexcept
{
return OutputCellView({}, {}, attr, TextAttributeBehavior::StoredOnly);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
// Arguments:
// - wch - View representing a single UTF-16 character (that can be represented without surrogates)
// - attr - View representing a single color
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch, const TextAttribute& attr) noexcept
{
const auto glyph = std::wstring_view(&wch, 1);
DbcsAttribute dbcsAttr;
if (IsGlyphFullWidth(wch))
{
dbcsAttr.SetLeading();
}
return OutputCellView(glyph, dbcsAttr, attr, TextAttributeBehavior::Stored);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
// Arguments:
// - legacyAttr - View representing a single legacy color
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateViewLegacyAttr(const WORD& legacyAttr) noexcept
{
WORD cleanAttr = legacyAttr;
WI_ClearAllFlags(cleanAttr, COMMON_LVB_SBCSDBCS); // don't use legacy lead/trailing byte flags for colors
const TextAttribute attr(cleanAttr);
return s_GenerateView(attr);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
// Arguments:
// - charInfo - character and attribute pair representing a single cell
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const CHAR_INFO& charInfo) noexcept
{
const auto glyph = std::wstring_view(&charInfo.Char.UnicodeChar, 1);
DbcsAttribute dbcsAttr;
if (WI_IsFlagSet(charInfo.Attributes, COMMON_LVB_LEADING_BYTE))
{
dbcsAttr.SetLeading();
}
else if (WI_IsFlagSet(charInfo.Attributes, COMMON_LVB_TRAILING_BYTE))
{
dbcsAttr.SetTrailing();
}
const TextAttribute textAttr(charInfo.Attributes);
const auto behavior = TextAttributeBehavior::Stored;
return OutputCellView(glyph, dbcsAttr, textAttr, behavior);
}
// Routine Description:
// - Static function to create a view.
// - It's pulled out statically so it can be used during construction with just the given
// variables (so OutputCellView doesn't need an empty default constructor)
// Arguments:
// - cell - A reference to the cell for which we will make the read-only view
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const OutputCell& cell)
{
return OutputCellView(cell.Chars(), cell.DbcsAttr(), cell.TextAttr(), cell.TextAttrBehavior());
}
// Routine Description:
// - Gets the distance between two iterators relative to the input data given in.
// Return Value:
// - The number of items of the input run consumed between these two iterators.
ptrdiff_t OutputCellIterator::GetInputDistance(OutputCellIterator other) const noexcept
{
return _pos - other._pos;
}
// Routine Description:
// - Gets the distance between two iterators relative to the number of cells inserted.
// Return Value:
// - The number of cells in the backing buffer filled between these two iterators.
ptrdiff_t OutputCellIterator::GetCellDistance(OutputCellIterator other) const noexcept
{
return _distance - other._distance;
}

View file

@ -1,125 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- OutputCellIterator.hpp
Abstract:
- Read-only view into an entire batch of data to be written into the output buffer.
- This is done for performance reasons (avoid heap allocs and copies).
Author:
- Michael Niksa (MiNiksa) 06-Oct-2018
Revision History:
- Based on work from OutputCell.hpp/cpp by Austin Diviness (AustDi)
--*/
#pragma once
#include "TextAttribute.hpp"
#include "OutputCell.hpp"
#include "OutputCellView.hpp"
class OutputCellIterator final
{
public:
using iterator_category = std::input_iterator_tag;
using value_type = OutputCellView;
using difference_type = ptrdiff_t;
using pointer = OutputCellView*;
using reference = OutputCellView&;
OutputCellIterator(const wchar_t& wch, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const TextAttribute& attr, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const std::wstring_view utf16Text);
OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute attribute);
OutputCellIterator(const gsl::span<const WORD> legacyAttributes) noexcept;
OutputCellIterator(const gsl::span<const CHAR_INFO> charInfos) noexcept;
OutputCellIterator(const gsl::span<const OutputCell> cells);
~OutputCellIterator() = default;
OutputCellIterator& operator=(const OutputCellIterator& it) = default;
operator bool() const noexcept;
ptrdiff_t GetCellDistance(OutputCellIterator other) const noexcept;
ptrdiff_t GetInputDistance(OutputCellIterator other) const noexcept;
friend ptrdiff_t operator-(OutputCellIterator one, OutputCellIterator two) = delete;
OutputCellIterator& operator++();
OutputCellIterator operator++(int);
const OutputCellView& operator*() const noexcept;
const OutputCellView* operator->() const noexcept;
private:
enum class Mode
{
// Loose mode is where we're given text and attributes in a raw sort of form
// like while data is being inserted from an API call.
Loose,
// Loose mode with only text is where we're given just text and we want
// to use the attribute already in the buffer when writing
LooseTextOnly,
// Fill mode is where we were given one thing and we just need to keep giving
// that back over and over for eternity.
Fill,
// Given a run of legacy attributes, convert each of them and insert only attribute data.
LegacyAttr,
// CharInfo mode is where we've been given a pair of text and attribute for each
// cell in the legacy format from an API call.
CharInfo,
// Cell mode is where we have an already fully structured cell data usually
// from accessing/copying data already put into the OutputBuffer.
Cell,
};
Mode _mode;
gsl::span<const WORD> _legacyAttrs;
std::variant<
std::wstring_view,
gsl::span<const WORD>,
gsl::span<const CHAR_INFO>,
gsl::span<const OutputCell>,
std::monostate>
_run;
TextAttribute _attr;
bool _TryMoveTrailing() noexcept;
static OutputCellView s_GenerateView(const std::wstring_view view);
static OutputCellView s_GenerateView(const std::wstring_view view,
const TextAttribute attr);
static OutputCellView s_GenerateView(const std::wstring_view view,
const TextAttribute attr,
const TextAttributeBehavior behavior);
static OutputCellView s_GenerateView(const wchar_t& wch) noexcept;
static OutputCellView s_GenerateViewLegacyAttr(const WORD& legacyAttr) noexcept;
static OutputCellView s_GenerateView(const TextAttribute& attr) noexcept;
static OutputCellView s_GenerateView(const wchar_t& wch, const TextAttribute& attr) noexcept;
static OutputCellView s_GenerateView(const CHAR_INFO& charInfo) noexcept;
static OutputCellView s_GenerateView(const OutputCell& cell);
OutputCellView _currentView;
size_t _pos;
size_t _distance;
size_t _fillLimit;
};

View file

@ -42,19 +42,6 @@ gsl::span<OutputCell> OutputCellRect::GetRow(const size_t row)
return gsl::span<OutputCell>(_FindRowOffset(row), _cols);
}
// Routine Description:
// - Gets a read-only iterator view over a single row of the rectangle.
// Arguments:
// - row - The Y position or row index in the buffer.
// Return Value:
// - Read-only iterator of OutputCells
OutputCellIterator OutputCellRect::GetRowIter(const size_t row) const
{
const gsl::span<const OutputCell> view(_FindRowOffset(row), _cols);
return OutputCellIterator(view);
}
// Routine Description:
// - Internal helper to find the pointer to the specific row offset in the giant
// contiguous block of memory allocated for this rectangle.

View file

@ -21,10 +21,8 @@ Revision History:
#pragma once
#include "DbcsAttribute.hpp"
#include "TextAttribute.hpp"
#include "OutputCell.hpp"
#include "OutputCellIterator.hpp"
class OutputCellRect final
{
@ -33,7 +31,6 @@ public:
OutputCellRect(const size_t rows, const size_t cols);
gsl::span<OutputCell> GetRow(const size_t row);
OutputCellIterator GetRowIter(const size_t row) const;
size_t Height() const noexcept;
size_t Width() const noexcept;

View file

@ -3,7 +3,6 @@
#include "precomp.h"
#include "Row.hpp"
#include "CharRow.hpp"
#include "textBuffer.hpp"
#include "../types/inc/convert.hpp"
@ -16,15 +15,16 @@
// - pParent - the text buffer that this row belongs to
// Return Value:
// - constructed object
ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent) :
_id{ rowId },
_rowWidth{ rowWidth },
_charRow{ rowWidth, this },
_attrRow{ rowWidth, fillAttribute },
ROW::ROW(const SHORT /*rowId*/, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const /*pParent*/) :
_data{
std::wstring(rowWidth, UNICODE_SPACE),
{ { gsl::narrow_cast<uint8_t>(1), gsl::narrow_cast<uint16_t>(rowWidth) } },
{ rowWidth, fillAttribute },
rowWidth
},
_lineRendition{ LineRendition::SingleWidth },
_wrapForced{ false },
_doubleBytePadded{ false },
_pParent{ pParent }
_doubleBytePadded{ false }
{
}
@ -39,10 +39,11 @@ bool ROW::Reset(const TextAttribute Attr)
_lineRendition = LineRendition::SingleWidth;
_wrapForced = false;
_doubleBytePadded = false;
_charRow.Reset();
_data._cwid.replace(0, _data._width, { 1, _data._width }); // replace entire RLE with one run
_data._data.replace(0, _data._width, _data._width, UNICODE_SPACE);
try
{
_attrRow.Reset(Attr);
_data._attrRow.Reset(Attr);
}
catch (...)
{
@ -60,14 +61,20 @@ bool ROW::Reset(const TextAttribute Attr)
// - S_OK if successful, otherwise relevant error
[[nodiscard]] HRESULT ROW::Resize(const unsigned short width)
{
RETURN_IF_FAILED(_charRow.Resize(width));
_data._data.resize(width, L' ');
auto oldEnd{ _data._cwid.size() };
_data._cwid.resize_trailing_extent(width);
if (width > oldEnd)
{
_data._cwid.replace(oldEnd, width, { 1, gsl::narrow_cast<uint16_t>(width - oldEnd) });
}
try
{
_attrRow.Resize(width);
_data._attrRow.Resize(width);
}
CATCH_RETURN();
_rowWidth = width;
_data._width = width;
return S_OK;
}
@ -80,20 +87,12 @@ bool ROW::Reset(const TextAttribute Attr)
// - <none>
void ROW::ClearColumn(const size_t column)
{
THROW_HR_IF(E_INVALIDARG, column >= _charRow.size());
_charRow.ClearCell(column);
}
UnicodeStorage& ROW::GetUnicodeStorage() noexcept
{
return _pParent->GetUnicodeStorage();
}
const UnicodeStorage& ROW::GetUnicodeStorage() const noexcept
{
return _pParent->GetUnicodeStorage();
THROW_HR_IF(E_INVALIDARG, column >= _data._width);
WriteGlyphAtMeasured(column, 1, L" ");
}
#if 0
TODO (DH)
// Routine Description:
// - writes cell data to the row
// Arguments:
@ -105,11 +104,11 @@ const UnicodeStorage& ROW::GetUnicodeStorage() const noexcept
// - iterator to first cell that was not written to this row.
OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, const std::optional<bool> wrap, std::optional<size_t> limitRight)
{
THROW_HR_IF(E_INVALIDARG, index >= _charRow.size());
THROW_HR_IF(E_INVALIDARG, limitRight.value_or(0) >= _charRow.size());
THROW_HR_IF(E_INVALIDARG, index >= _data._width);
THROW_HR_IF(E_INVALIDARG, limitRight.value_or(0) >= _data._width);
// If we're given a right-side column limit, use it. Otherwise, the write limit is the final column index available in the char row.
const auto finalColumnInRow = limitRight.value_or(_charRow.size() - 1);
const auto finalColumnInRow = limitRight.value_or(_data._width - 1);
auto currentColor = it->TextAttr();
uint16_t colorUses = 0;
@ -131,7 +130,7 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
{
// Otherwise, commit this color into the run and save off the new one.
// Now commit the new color runs into the attr row.
_attrRow.Replace(colorStarts, currentIndex, currentColor);
_data._attrRow.Replace(colorStarts, currentIndex, currentColor);
currentColor = it->TextAttr();
colorUses = 1;
colorStarts = currentIndex;
@ -151,21 +150,31 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
// Don't increment iterator. We'll advance the index and try again with this value on the next round through the loop.
if (currentIndex == 0 && it->DbcsAttr().IsTrailing())
{
_charRow.ClearCell(currentIndex);
ClearColumn(currentIndex);
it.AddCellDistanceFault(1); // we couldn't fit a cell here but we skipped a column :|
}
// If we're trying to fill the last cell with a leading byte, pad it out instead by clearing it.
// Don't increment iterator. We'll exit because we couldn't write a lead at the end of a line.
else if (fillingLastColumn && it->DbcsAttr().IsLeading())
{
_charRow.ClearCell(currentIndex);
ClearColumn(currentIndex);
it.AddCellDistanceFault(1); // we couldn't fit a cell here but we skipped a column :|
SetDoubleBytePadded(true);
}
// Otherwise, copy the data given and increment the iterator.
else
{
_charRow.DbcsAttrAt(currentIndex) = it->DbcsAttr();
_charRow.GlyphAt(currentIndex) = it->Chars();
++it;
if (!it->DbcsAttr().IsTrailing())
{
uint16_t d = it->DbcsAttr().IsSingle() ? 1 : 2;
WriteGlyphAtMeasured(currentIndex, d, it->Chars());
currentIndex += d - 1;
while (d > 0)
{ // TODO(DH) FFS
++it;
--d;
}
}
}
// If we're asked to (un)set the wrap status and we just filled the last column with some text...
@ -191,8 +200,9 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
// Now commit the final color into the attr row
if (colorUses)
{
_attrRow.Replace(colorStarts, currentIndex, currentColor);
_data._attrRow.Replace(colorStarts, currentIndex, currentColor);
}
return it;
}
#endif

View file

@ -23,18 +23,157 @@ Revision History:
#include "AttrRow.hpp"
#include "LineRendition.hpp"
#include "OutputCell.hpp"
#include "OutputCellIterator.hpp"
#include "CharRow.hpp"
#include "UnicodeStorage.hpp"
#include "unicode.hpp"
#pragma warning(push)
#pragma warning(disable : 4267)
class TextBuffer;
using RowMeasurementBuffer = til::small_rle<uint8_t, uint16_t, 3>;
struct DamageRanges
{
size_t dataOffset;
size_t dataLength;
uint16_t firstColumn;
uint16_t lastColumnExclusive;
};
template<typename TRuns>
static DamageRanges DamageRangesForColumnInMeasurementBuffer(const TRuns& cwid, size_t col)
{
size_t currentCol{ 0 };
size_t currentWchar{ 0 };
auto it{ cwid.runs().cbegin() };
while (it != cwid.runs().cend())
{
// Each compressed pair tells us how many columns x N wchar_t
const auto colsCoveredByRun{ it->value * it->length };
if (currentCol + colsCoveredByRun > col)
{
// We want to break out of the loop to manually handle this run, because
// we've determined that it is the run that covers the column of interest.
break;
}
currentCol += colsCoveredByRun;
currentWchar += it->length;
it++;
}
if (it == cwid.runs().cend())
{
// this is an interesting case- somebody requested a column we cannot answer for.
// The string might actually have data, and the caller might be interested in where that data is.
// Ideally, we would return the index of the first char out-of-bounds, and the length of the remaining data as a single unit.
// We can't answer for how much space it takes up, though.
__debugbreak();
return { 0, 0, 0u, 0u };
//return { currentWchar, _data.size() - currentWchar, 0u, 0u };
}
// currentWchar is how many wchar_t we are into the string before processing this run
// currentCol is how many columns we've covered before processing this run
// We are *guaranteed* that the hit is in this run -- no need to check it->length
// col-currentCol is how many columns are left unaccounted for (how far into this run we need to go)
const auto colsLeftToCountInCurrentRun{ col - currentCol };
currentWchar += colsLeftToCountInCurrentRun / it->value; // one wch per column unit -- rounds down (correct behavior)
size_t lenInWchars{ 1 }; // the first hit takes up one wchar
// We use this to determine if we have exhausted every column this run can cough up.
// colsLeftToCountInCurrentRun is 0-indexed, but colsConsumedByRun is 1-indexed (index 0 consumes N columns, etc.)
// therefore, we reindex colsLeftToCountInCurrentRun and compare it to colsConsumedByRun
const auto colsConsumedFromRun{ colsLeftToCountInCurrentRun + it->value };
const auto colsCoveredByRun{ it->value * it->length };
// If we *have* consumed every column this run can provide, we must check the run after it:
// if it contributes "0" columns, it is actually a set of trailing code units.
if (colsConsumedFromRun >= colsCoveredByRun && it != cwid.runs().cend())
{
const auto nextRunIt{ it + 1 };
if (nextRunIt != cwid.runs().cend() && nextRunIt->value == 0)
{
// we were at the boundary of a column run, so if the next one is 0 it tells us that each
// wchar after it is a trailer
lenInWchars += nextRunIt->length;
}
}
return {
currentWchar, // wchar start
lenInWchars, // wchar size
gsl::narrow_cast<uint16_t>(col - (colsLeftToCountInCurrentRun % it->value)), // Column damage to the left (where we overlapped the right of a wide glyph)
gsl::narrow_cast<uint16_t>(col - (colsLeftToCountInCurrentRun % it->value) + it->value), // Column damage to the right (where we overlapped the left of a wide glyph)
};
}
class ROW;
struct RowImage
{
std::wstring _data;
RowMeasurementBuffer _cwid;
ATTR_ROW _attrRow;
uint16_t _width;
friend class ROW;
friend class TextBuffer;
RowImage() :
_data{}, _cwid{}, _attrRow{ {} }, _width{ 0 } {}
RowImage(const std::wstring& data, const RowMeasurementBuffer& cwid, ATTR_ROW attrRow, uint16_t width):
_data{data}, _cwid{cwid}, _attrRow{std::move(attrRow)}, _width{width} {}
// exclusive
std::tuple<RowImage, RowImage> split(uint16_t col) const
{
if (col >= _width)
{
return { *this, RowImage{} };
}
else if (col == 0)
{
return { RowImage{}, *this };
}
auto yes_more_fucking_damage_ranges = DamageRangesForColumnInMeasurementBuffer(_cwid, col);
// here's a dumb decision: when you split along a wide char, you get spaces over its damage on the left side.
// X X XX X X X
// ^ split
// X X S <- left
// XX X X X <- right
auto chopped_off = col == yes_more_fucking_damage_ranges.lastColumnExclusive ? 0 : /*didn't get whole thing*/ yes_more_fucking_damage_ranges.lastColumnExclusive - col;
auto chopped_on = col == yes_more_fucking_damage_ranges.lastColumnExclusive ? yes_more_fucking_damage_ranges.dataLength : 0;
auto lwid = _cwid.slice(0, yes_more_fucking_damage_ranges.dataOffset + chopped_on);
for (auto z{ chopped_off }; z > 0; --z)
{
lwid.append(1);
}
RowImage left{
_data.substr(0, yes_more_fucking_damage_ranges.dataOffset + chopped_on) + std::wstring(chopped_off, UNICODE_SPACE),
lwid,
_attrRow._data.slice(0, yes_more_fucking_damage_ranges.lastColumnExclusive),
col
};
RowImage right{
_data.substr(yes_more_fucking_damage_ranges.dataOffset + chopped_on),
_cwid.slice(yes_more_fucking_damage_ranges.dataOffset + chopped_on, _data.length()),
// we use first col here to catch the overlap
_attrRow._data.slice(yes_more_fucking_damage_ranges.firstColumn, _width),
::base::ClampSub(_width, col),
};
return { left, right };
}
};
enum class DelimiterClass
{
ControlChar,
DelimiterChar,
RegularChar
};
class ROW final
{
public:
ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent);
size_t size() const noexcept { return _rowWidth; }
size_t size() const noexcept { return _data._width; }
void SetWrapForced(const bool wrap) noexcept { _wrapForced = wrap; }
bool WasWrapForced() const noexcept { return _wrapForced; }
@ -42,52 +181,357 @@ public:
void SetDoubleBytePadded(const bool doubleBytePadded) noexcept { _doubleBytePadded = doubleBytePadded; }
bool WasDoubleBytePadded() const noexcept { return _doubleBytePadded; }
const CharRow& GetCharRow() const noexcept { return _charRow; }
CharRow& GetCharRow() noexcept { return _charRow; }
const ATTR_ROW& GetAttrRow() const noexcept { return _attrRow; }
ATTR_ROW& GetAttrRow() noexcept { return _attrRow; }
const ATTR_ROW& GetAttrRow() const noexcept { return _data._attrRow; }
ATTR_ROW& GetAttrRow() noexcept { return _data._attrRow; }
LineRendition GetLineRendition() const noexcept { return _lineRendition; }
void SetLineRendition(const LineRendition lineRendition) noexcept { _lineRendition = lineRendition; }
SHORT GetId() const noexcept { return _id; }
void SetId(const SHORT id) noexcept { _id = id; }
bool Reset(const TextAttribute Attr);
[[nodiscard]] HRESULT Resize(const unsigned short width);
void ClearColumn(const size_t column);
std::wstring GetText() const { return _charRow.GetText(); }
UnicodeStorage& GetUnicodeStorage() noexcept;
const UnicodeStorage& GetUnicodeStorage() const noexcept;
OutputCellIterator WriteCells(OutputCellIterator it, const size_t index, const std::optional<bool> wrap = std::nullopt, std::optional<size_t> limitRight = std::nullopt);
std::wstring GetText() const { return _data._data; }
#ifdef UNIT_TESTING
friend constexpr bool operator==(const ROW& a, const ROW& b) noexcept;
friend class RowTests;
#endif
struct RowData
{
std::wstring _data;
RowMeasurementBuffer _cwid;
ATTR_ROW _attrRow;
unsigned short _width;
DamageRanges _damageForColumn(size_t col) const
{
return DamageRangesForColumnInMeasurementBuffer(_cwid, col);
}
DamageRanges _damageForColumns(size_t col, size_t ncols) const
{
// When we want to replace a column, or set of columns, with a glyph, we need to:
// * Figure out the physical extent of the character in that cell (UTF-16 code units).
// * Figure out the columnar extent of the character in that cell (how many columns it covers).
// * In the simple case (1->1, 2->2), there will be no damage.
// * In the complex case (2->1, 1->2, 2->2 with middle overlap), there *WILL* be damage.
// * Replace the physical character data in that cell with the new character data.
// * Insert padding characters to the left and right to account for damage.
//
// ## DAMAGE
// Damage is measured in the number of columns to the left
// and right of the new glyph that are now NO LONGER VALID because
// they were double-width characters that are being cut in half,
// or single-width characters that are collateral damage from stomping
// them with a double-width character.
auto damage{ _damageForColumn(col) };
const auto lastDamage{ _damageForColumn(col + ncols - 1 /*inclusive*/) };
// *INVARIANT* the beginning of the next column range must have a different beginning byte
// This column began at a different data index, so we have to delete its data too.
// Since it's contiguous, just increment len.
damage.dataLength = lastDamage.dataOffset + lastDamage.dataLength - damage.dataOffset;
damage.lastColumnExclusive = lastDamage.lastColumnExclusive;
return damage;
}
void _strikeDamageAndAdjust(size_t col, size_t ncols, size_t incomingCodeUnitCount, DamageRanges& range)
{
(void)incomingCodeUnitCount;
const bool damaged{ range.firstColumn < col || col + ncols < range.lastColumnExclusive };
if (damaged)
{
const auto damagedColumns{ range.lastColumnExclusive - range.firstColumn };
_data.replace(range.dataOffset, range.dataLength, damagedColumns, UNICODE_SPACE);
_cwid.replace(range.dataOffset, range.dataOffset + range.dataLength, { uint8_t{ 1 }, gsl::narrow_cast<uint16_t>(damagedColumns) });
// We may have replaced surrogate pairs/etc with fewer/more code units.
range.dataLength = damagedColumns;
}
}
};
private:
CharRow _charRow;
ATTR_ROW _attrRow;
RowData _data;
LineRendition _lineRendition;
SHORT _id;
unsigned short _rowWidth;
// Occurs when the user runs out of text in a given row and we're forced to wrap the cursor to the next line
bool _wrapForced;
// Occurs when the user runs out of text to support a double byte character and we're forced to the next line
bool _doubleBytePadded;
TextBuffer* _pParent; // non ownership pointer
public:
std::wstring_view GlyphAt(size_t col) const
{
const auto lookup{ _data._damageForColumn(col) };
return { _data._data.data() + lookup.dataOffset, lookup.dataLength };
}
std::pair<size_t, size_t> WriteGlyphAtMeasured(size_t col, size_t ncols, std::wstring_view glyph)
{
const auto [begin, len, minDamageColumn, maxDamageColumnExclusive]{ _data._damageForColumns(col, ncols) };
if (minDamageColumn == col && maxDamageColumnExclusive == col + ncols)
{
// We are only damaging as many columns as we are introducing -- no spillover (!)
// We can replace the code units in the data directly, and we can replace the
// column counts with [col, 0, 0...] (with as many zeroes as we need to account
// for any code units past the first.)
_data._data.replace(begin, len, glyph);
typename decltype(_data._cwid)::rle_type newRuns[]{
{ gsl::narrow_cast<uint8_t>(ncols), 1 },
{ 0, gsl::narrow_cast<uint16_t>(glyph.size() - 1) },
};
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), gsl::make_span(&newRuns[0], glyph.size() == 1 ? 1 : 2));
}
else
{
// We are damaging multiple columns -- oops. We need to insert replacement characters
// to get us from the leftmost side of the damaged glyph up to the leftmost side of
// our newly-inserted region. We also need to insert replacement characters from the
// rightmost side of our glyph to the rightmost side of the glyph that was once in
// that column.
// Left side count : col - minDamageColumn
// Right side count: maxDamageColumn - (col + ncols)
const auto replacementCodeUnits{ (col - minDamageColumn) + glyph.size() + (maxDamageColumnExclusive - (col + ncols)) };
std::wstring replacement(replacementCodeUnits, UNICODE_SPACE);
replacement.replace(col - minDamageColumn, glyph.size(), glyph);
// New advances:
// Our glyph and all its trailers
// v-----v
// [1, ..., 1, X, 0, 0, 1, ..., 1]
// ^-------^ ^-------^
// Each replacement space char
// is one column wide. We have
// to insert [1]s for each
// damaged column.
boost::container::small_vector<typename decltype(_data._cwid)::rle_type, 4> newRuns;
if (col - minDamageColumn)
{
newRuns.emplace_back((uint8_t)1, gsl::narrow_cast<uint16_t>(col - minDamageColumn));
}
newRuns.emplace_back(gsl::narrow_cast<uint8_t>(ncols), (uint16_t)1);
if (glyph.size() > 1)
{
newRuns.emplace_back((uint8_t)0, gsl::narrow_cast<uint16_t>(glyph.size() - 1)); // trailers
}
if (maxDamageColumnExclusive - (col + ncols))
{
newRuns.emplace_back((uint8_t)1, gsl::narrow_cast<uint16_t>(maxDamageColumnExclusive - (col + ncols)));
}
_data._data.replace(begin, len, replacement);
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), gsl::make_span(newRuns));
}
// Distance from requested column to final
_maxc = std::max(_maxc, maxDamageColumnExclusive);
return { begin + glyph.size(), col + ncols };
}
DbcsAttribute DbcsAttrAt(size_t col) const
{
const auto [begin, len, first, lastE] = _data._damageForColumn(col);
if (lastE - first == 1)
{
// The glyph under this column is only onw column wide.
return DbcsAttribute{ DbcsAttribute::Attribute::Single };
}
else if (first != col)
{
// The glyph under this column is >1 col wide, and we're bisecting it
return DbcsAttribute{ DbcsAttribute::Attribute::Trailing };
}
else
{
// The glyph under this column is >1 col wide, and we're at the head
return DbcsAttribute{ DbcsAttribute::Attribute::Leading };
}
}
std::tuple<size_t, uint16_t, uint16_t> WriteStringAtMeasured(uint16_t col, uint16_t colCount, const std::wstring_view& string, const RowMeasurementBuffer& measurements)
{
size_t incomingLastColumn{ std::min<size_t>(_data._width - col, colCount) };
auto incomingLastColumnOffsets{ DamageRangesForColumnInMeasurementBuffer(measurements, incomingLastColumn - 1 /*inclusive*/) };
auto codeUnitsToConsume{ incomingLastColumnOffsets.dataOffset };
auto columnsToConsume{ incomingLastColumn };
const auto [begin, len, minDamageColumn, maxDamageColumnExclusive]{ _data._damageForColumns(col, incomingLastColumn) };
// If these don't match, we are cutting a multi-cell glyph.
if (incomingLastColumnOffsets.lastColumnExclusive == incomingLastColumn)
{
// Since they *do* match, we should consume this part of the string too.
codeUnitsToConsume += incomingLastColumnOffsets.dataLength;
}
else
{
// Only consume up to the final cell (the one we cut in half)
columnsToConsume = incomingLastColumnOffsets.firstColumn;
// **INVARIANT** we only get here if we had to cut off the incoming text, and that only
// happens because we had to clamp the read buffer against our width. This means that the
// incoming text definitely had a wide glyph that would not fit against the end of our
// buffer.
// THEREFORE: col+incomingLastColumn was our final column (exclusive)
// which means that maxDamageColumnExclusive can be upgraded to be our width.
// OPTIMIZATION: If we mark our last column as damaged, it will automatically get stomped
// with spaces.
// NO NO NO NO NO NO NO NO NO NO TODO(DH)
// We can't do this without changing the damaged buffer region to delete the character
// from _data at the same time. Use the non-optimial path.
ClearColumn(_data._width - 1);
SetDoubleBytePadded(true);
// NO - we already marked this column when we calculated damage above
//++columnsWrittenAfterInsertionPoint;
}
auto mss = measurements.slice(0, gsl::narrow_cast<uint16_t>(codeUnitsToConsume));
if (minDamageColumn == col && maxDamageColumnExclusive == col + incomingLastColumn)
{
// We are only damaging as many columns as we are introducing -- no spillover (!)
// We can replace the code units in the data directly, and we can replace the
// column counts with [col, 0, 0...] (with as many zeroes as we need to account
// for any code units past the first.)
_data._data.replace(begin, len, &*string.cbegin(), codeUnitsToConsume);
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), mss.runs());
}
else
{
// We are damaging multiple columns -- oops. We need to insert replacement characters
// to get us from the leftmost side of the damaged glyph up to the leftmost side of
// our newly-inserted region. We also need to insert replacement characters from the
// rightmost side of our glyph to the rightmost side of the glyph that was once in
// that column.
// Left side count : col - minDamageColumn
// Right side count: maxDamageColumn - (col + ncols)
const auto replacementCodeUnits{ (col - minDamageColumn) + codeUnitsToConsume + (maxDamageColumnExclusive - (col + incomingLastColumn)) };
std::wstring replacement(replacementCodeUnits, UNICODE_SPACE);
replacement.replace(col - minDamageColumn, codeUnitsToConsume, &*string.cbegin(), codeUnitsToConsume);
// New advances:
// Our glyph and all its trailers
// v-----v
// [1, ..., 1, X, 0, 0, 1, ..., 1]
// ^-------^ ^-------^
// Each replacement space char
// is one column wide. We have
// to insert [1]s for each
// damaged column.
mss.replace(0, 0, { uint8_t{ 1 }, gsl::narrow_cast<uint16_t>(col - minDamageColumn) });
mss.replace(mss.size(), mss.size(), { uint8_t{ 1 }, gsl::narrow_cast<uint16_t>(maxDamageColumnExclusive - (col + incomingLastColumn)) });
_data._data.replace(begin, len, replacement);
_data._cwid.replace(gsl::narrow_cast<uint16_t>(begin), gsl::narrow_cast<uint16_t>(begin + len), mss.runs()); //gsl::make_span(newRuns));
}
return {
codeUnitsToConsume,
gsl::narrow_cast<uint16_t>(maxDamageColumnExclusive - col),
gsl::narrow_cast<uint16_t>(columnsToConsume)
};
}
size_t Fill(size_t col, size_t count, wchar_t ch, uint8_t w)
{
const auto charsFitOrRemain = std::min((_data._width - col) / w, count);
const auto columnsRequired{ charsFitOrRemain * w };
auto damage = _data._damageForColumns(col, columnsRequired);
// If we are filling over the left/right halves of a character
// This is a bit wasteful since it can grow/shrink the buffers and we're about
// to do it again, but I was trying to be expedient.
_data._strikeDamageAndAdjust(col, columnsRequired, charsFitOrRemain, damage);
const auto [begin, len, min, max] = damage;
_data._data.replace(begin, len, charsFitOrRemain, ch);
_data._cwid.replace(begin, begin + len, { w, gsl::narrow_cast<uint16_t>(charsFitOrRemain) });
const auto doubleBytePadded{
w > 1 // We had a wide glyph...
&& max != _data._width // ...and didn't reach the edge
&& count > charsFitOrRemain // ...but we had spare characters, so we wanted to
};
if (doubleBytePadded)
{
const uint16_t remaining{ gsl::narrow_cast<uint16_t>(_data._width - max) };
// overflow: add spaces
_data._data.replace(begin + charsFitOrRemain, _data._data.size() - begin + charsFitOrRemain, remaining, UNICODE_SPACE);
_data._cwid.replace(begin + charsFitOrRemain, _data._cwid.size(), { uint8_t{ 1u }, gsl::narrow_cast<uint16_t>(remaining) });
}
if (max == _data._width || doubleBytePadded)
{
// TODO(DH): Evaluate the above condition
// We only want to do this if we touched or near-touched the lat col
SetDoubleBytePadded(doubleBytePadded);
SetWrapForced(false);
}
return charsFitOrRemain;
}
// Method Description:
// - get delimiter class for a position in the char row
// - used for double click selection and uia word navigation
// Arguments:
// - column: column to get text data for
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
// Return Value:
// - the delimiter class for the given char
const DelimiterClass DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const
{
THROW_HR_IF(E_INVALIDARG, column >= _data._width);
const auto glyph = *GlyphAt(column).begin();
if (glyph <= UNICODE_SPACE)
{
return DelimiterClass::ControlChar;
}
else if (wordDelimiters.find(glyph) != std::wstring_view::npos)
{
return DelimiterClass::DelimiterChar;
}
else
{
return DelimiterClass::RegularChar;
}
}
RowImage Dump(uint16_t left, uint16_t size)
{
auto [begin, len, min, max] = _data._damageForColumns(left, size);
return RowImage{
_data._data.substr(begin, len),
_data._cwid.slice(begin, begin + len),
ATTR_ROW{ _data._attrRow._data.slice(min, max) },
::base::MakeClampedNum(max) - min
};
}
void Reinsert(uint16_t left, const RowImage& ri)
{
auto damage{ _data._damageForColumns(left, ri._width) };
_data._strikeDamageAndAdjust(left, ri._width, ri._data.size(), damage);
_data._data.replace(damage.dataOffset, damage.dataLength, ri._data);
_data._cwid.replace(damage.dataOffset, damage.dataOffset + damage.dataLength, ri._cwid.runs());
_data._attrRow._data.replace(damage.firstColumn, damage.lastColumnExclusive, ri._attrRow._data.runs());
}
uint16_t _maxc{};
size_t MeasureRight() const
{
return _maxc;
}
};
#ifdef UNIT_TESTING
constexpr bool operator==(const ROW& a, const ROW& b) noexcept
{
// comparison is only used in the tests; this should suffice.
return (a._pParent == b._pParent &&
a._id == b._id);
return (a._data == b._data &&
a._cwid == b._cwid &&
a._attrRow == b._attrRow &&
a._rowWidth == b._rowWidth &&
a._wrapForced == b._wrapForced &&
a._doubleBytePadded == b._doubleBytePadded);
}
#endif
#pragma warning(pop)

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<SHORT, SHORT>& rowMap, const std::optional<SHORT> width)
{
// Make a temporary map to hold all the new row positioning
std::unordered_map<key_type, mapped_type> newMap;
// Walk through every stored item.
for (const auto& pair : _map)
{
// Extract the old coordinate position
const auto oldCoord = pair.first;
// Only try to short-circuit based on width if we were told it changed
// by being given a new width value.
if (width.has_value())
{
// Get the column ID
const auto oldColId = oldCoord.X;
// If the column index is at/beyond the row width, don't bother copying it to the new map.
if (oldColId >= width.value())
{
continue;
}
}
// Get the row ID from the position as that's what we need to remap
const auto oldRowId = oldCoord.Y;
// Use the mapping given to convert the old row ID to the new row ID
const auto mapIter = rowMap.find(oldRowId);
// If there's no mapping to a new row, don't bother copying it to the new map. The row is gone.
if (mapIter == rowMap.end())
{
continue;
}
const auto newRowId = mapIter->second;
// Generate a new coordinate with the same X as the old one, but a new Y value.
const auto newCoord = COORD{ oldCoord.X, newRowId };
// Put the adjusted coordinate into the map with the original value.
newMap.emplace(newCoord, pair.second);
}
// Swap into the stored map, free the temporary when we exit.
_map.swap(newMap);
}

View file

@ -1,67 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- UnicodeStorage.hpp
Abstract:
- dynamic storage location for glyphs that can't normally fit in the output buffer
Author(s):
- Austin Diviness (AustDi) 02-May-2018
--*/
#pragma once
#include <vector>
#include <unordered_map>
#include <climits>
// std::unordered_map needs help to know how to hash a COORD
namespace std
{
template<>
struct hash<COORD>
{
// Routine Description:
// - hashes a coord. coord will be hashed by storing the x and y values consecutively in the lower
// bits of a size_t.
// Arguments:
// - coord - the coord to hash
// Return Value:
// - the hashed coord
constexpr size_t operator()(const COORD& coord) const noexcept
{
size_t retVal = coord.Y;
const size_t xCoord = coord.X;
retVal |= xCoord << (sizeof(coord.Y) * CHAR_BIT);
return retVal;
}
};
}
class UnicodeStorage final
{
public:
using key_type = typename COORD;
using mapped_type = typename std::vector<wchar_t>;
UnicodeStorage() noexcept;
const mapped_type& GetText(const key_type key) const;
void StoreGlyph(const key_type key, const mapped_type& glyph);
void Erase(const key_type key) noexcept;
void Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width);
private:
std::unordered_map<key_type, mapped_type> _map;
#ifdef UNIT_TESTING
friend class UnicodeStorageTests;
friend class TextBufferTests;
#endif
};

View file

@ -13,7 +13,6 @@
<ClCompile Include="..\AttrRow.cpp" />
<ClCompile Include="..\cursor.cpp" />
<ClCompile Include="..\OutputCell.cpp" />
<ClCompile Include="..\OutputCellIterator.cpp" />
<ClCompile Include="..\OutputCellRect.cpp" />
<ClCompile Include="..\OutputCellView.cpp" />
<ClCompile Include="..\Row.cpp" />
@ -23,22 +22,16 @@
<ClCompile Include="..\textBuffer.cpp" />
<ClCompile Include="..\textBufferCellIterator.cpp" />
<ClCompile Include="..\textBufferTextIterator.cpp" />
<ClCompile Include="..\CharRow.cpp" />
<ClCompile Include="..\CharRowCell.cpp" />
<ClCompile Include="..\CharRowCellReference.cpp" />
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\UnicodeStorage.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\AttrRow.hpp" />
<ClInclude Include="..\cursor.h" />
<ClInclude Include="..\DbcsAttribute.hpp" />
<ClInclude Include="..\ICharRow.hpp" />
<ClInclude Include="..\LineRendition.hpp" />
<ClInclude Include="..\OutputCell.hpp" />
<ClInclude Include="..\OutputCellIterator.hpp" />
<ClInclude Include="..\OutputCellRect.hpp" />
<ClInclude Include="..\OutputCellView.hpp" />
<ClInclude Include="..\Row.hpp" />
@ -48,11 +41,7 @@
<ClInclude Include="..\textBuffer.hpp" />
<ClInclude Include="..\textBufferCellIterator.hpp" />
<ClInclude Include="..\textBufferTextIterator.hpp" />
<ClInclude Include="..\CharRow.hpp" />
<ClInclude Include="..\CharRowCell.hpp" />
<ClInclude Include="..\CharRowCellReference.hpp" />
<ClInclude Include="..\precomp.h" />
<ClInclude Include="..\UnicodeStorage.hpp" />
</ItemGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />

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

@ -41,10 +41,6 @@ SOURCES= \
..\textBuffer.cpp \
..\textBufferCellIterator.cpp \
..\textBufferTextIterator.cpp \
..\CharRow.cpp \
..\CharRowCell.cpp \
..\CharRowCellReference.cpp \
..\UnicodeStorage.cpp \
..\search.cpp \
INCLUDES= \

View file

@ -4,11 +4,11 @@
#include "precomp.h"
#include "textBuffer.hpp"
#include "CharRow.hpp"
#include "../types/inc/utils.hpp"
#include "../types/inc/convert.hpp"
#include "../../types/inc/GlyphWidth.hpp"
#include "../types/inc/Utf16Parser.hpp"
#pragma hdrstop
@ -35,7 +35,6 @@ TextBuffer::TextBuffer(const COORD screenBufferSize,
_currentAttributes{ defaultAttributes },
_cursor{ cursorSize, *this },
_storage{},
_unicodeStorage{},
_renderTarget{ renderTarget },
_size{},
_currentHyperlinkId{ 1 },
@ -184,277 +183,6 @@ TextBufferCellIterator TextBuffer::GetCellDataAt(const COORD at, const Viewport
return TextBufferCellIterator(*this, at, limit);
}
//Routine Description:
// - Corrects and enforces consistent double byte character state (KAttrs line) within a row of the text buffer.
// - This will take the given double byte information and check that it will be consistent when inserted into the buffer
// at the current cursor position.
// - It will correct the buffer (by erasing the character prior to the cursor) if necessary to make a consistent state.
//Arguments:
// - dbcsAttribute - Double byte information associated with the character about to be inserted into the buffer
//Return Value:
// - True if it is valid to insert a character with the given double byte attributes. False otherwise.
bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute)
{
// To figure out if the sequence is valid, we have to look at the character that comes before the current one
const COORD coordPrevPosition = _GetPreviousFromCursor();
ROW& prevRow = GetRowByOffset(coordPrevPosition.Y);
DbcsAttribute prevDbcsAttr;
try
{
prevDbcsAttr = prevRow.GetCharRow().DbcsAttrAt(coordPrevPosition.X);
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
return false;
}
bool fValidSequence = true; // Valid until proven otherwise
bool fCorrectableByErase = false; // Can't be corrected until proven otherwise
// Here's the matrix of valid items:
// N = None (single byte)
// L = Lead (leading byte of double byte sequence
// T = Trail (trailing byte of double byte sequence
// Prev Curr Result
// N N OK.
// N L OK.
// N T Fail, uncorrectable. Trailing byte must have had leading before it.
// L N Fail, OK with erase. Lead needs trailing pair. Can erase lead to correct.
// L L Fail, OK with erase. Lead needs trailing pair. Can erase prev lead to correct.
// L T OK.
// T N OK.
// T L OK.
// T T Fail, uncorrectable. New trailing byte must have had leading before it.
// Check for only failing portions of the matrix:
if (prevDbcsAttr.IsSingle() && dbcsAttribute.IsTrailing())
{
// N, T failing case (uncorrectable)
fValidSequence = false;
}
else if (prevDbcsAttr.IsLeading())
{
if (dbcsAttribute.IsSingle() || dbcsAttribute.IsLeading())
{
// L, N and L, L failing cases (correctable)
fValidSequence = false;
fCorrectableByErase = true;
}
}
else if (prevDbcsAttr.IsTrailing() && dbcsAttribute.IsTrailing())
{
// T, T failing case (uncorrectable)
fValidSequence = false;
}
// If it's correctable by erase, erase the previous character
if (fCorrectableByErase)
{
// Erase previous character into an N type.
try
{
prevRow.ClearColumn(coordPrevPosition.X);
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
return false;
}
// Sequence is now N N or N L, which are both okay. Set sequence back to valid.
fValidSequence = true;
}
return fValidSequence;
}
//Routine Description:
// - Call before inserting a character into the buffer.
// - This will ensure a consistent double byte state (KAttrs line) within the text buffer
// - It will attempt to correct the buffer if we're inserting an unexpected double byte character type
// and it will pad out the buffer if we're going to split a double byte sequence across two rows.
//Arguments:
// - dbcsAttribute - Double byte information associated with the character about to be inserted into the buffer
//Return Value:
// - true if we successfully prepared the buffer and moved the cursor
// - false otherwise (out of memory)
bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute)
{
// This function corrects most errors. If this is false, we had an uncorrectable one which
// older versions of conhost simply let pass by unflinching.
LOG_HR_IF(E_NOT_VALID_STATE, !(_AssertValidDoubleByteSequence(dbcsAttribute))); // Shouldn't be uncorrectable sequences unless something is very wrong.
bool fSuccess = true;
// Now compensate if we don't have enough space for the upcoming double byte sequence
// We only need to compensate for leading bytes
if (dbcsAttribute.IsLeading())
{
const auto cursorPosition = GetCursor().GetPosition();
const auto lineWidth = GetLineWidth(cursorPosition.Y);
// If we're about to lead on the last column in the row, we need to add a padding space
if (cursorPosition.X == lineWidth - 1)
{
// set that we're wrapping for double byte reasons
auto& row = GetRowByOffset(cursorPosition.Y);
row.SetDoubleBytePadded(true);
// then move the cursor forward and onto the next row
fSuccess = IncrementCursor();
}
}
return fSuccess;
}
// Routine Description:
// - Writes cells to the output buffer. Writes at the cursor.
// Arguments:
// - givenIt - Iterator representing output cell data to write
// Return Value:
// - The final position of the iterator
OutputCellIterator TextBuffer::Write(const OutputCellIterator givenIt)
{
const auto& cursor = GetCursor();
const auto target = cursor.GetPosition();
const auto finalIt = Write(givenIt, target);
return finalIt;
}
// Routine Description:
// - Writes cells to the output buffer.
// Arguments:
// - givenIt - Iterator representing output cell data to write
// - target - the row/column to start writing the text to
// - wrap - change the wrap flag if we hit the end of the row while writing and there's still more data
// Return Value:
// - The final position of the iterator
OutputCellIterator TextBuffer::Write(const OutputCellIterator givenIt,
const COORD target,
const std::optional<bool> wrap)
{
// Make mutable copy so we can walk.
auto it = givenIt;
// Make mutable target so we can walk down lines.
auto lineTarget = target;
// Get size of the text buffer so we can stay in bounds.
const auto size = GetSize();
// While there's still data in the iterator and we're still targeting in bounds...
while (it && size.IsInBounds(lineTarget))
{
// Attempt to write as much data as possible onto this line.
// NOTE: if wrap = true/false, we want to set the line's wrap to true/false (respectively) if we reach the end of the line
it = WriteLine(it, lineTarget, wrap);
// Move to the next line down.
lineTarget.X = 0;
++lineTarget.Y;
}
return it;
}
// Routine Description:
// - Writes one line of text to the output buffer.
// Arguments:
// - givenIt - The iterator that will dereference into cell data to insert
// - target - Coordinate targeted within output buffer
// - wrap - change the wrap flag if we hit the end of the row while writing and there's still more data in the iterator.
// - limitRight - Optionally restrict the right boundary for writing (e.g. stop writing earlier than the end of line)
// Return Value:
// - The iterator, but advanced to where we stopped writing. Use to find input consumed length or cells written length.
OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt,
const COORD target,
const std::optional<bool> wrap,
std::optional<size_t> limitRight)
{
// If we're not in bounds, exit early.
if (!GetSize().IsInBounds(target))
{
return givenIt;
}
// Get the row and write the cells
ROW& row = GetRowByOffset(target.Y);
const auto newIt = row.WriteCells(givenIt, target.X, wrap, limitRight);
// Take the cell distance written and notify that it needs to be repainted.
const auto written = newIt.GetCellDistance(givenIt);
const Viewport paint = Viewport::FromDimensions(target, { gsl::narrow<SHORT>(written), 1 });
_NotifyPaint(paint);
return newIt;
}
//Routine Description:
// - Inserts one codepoint into the buffer at the current cursor position and advances the cursor as appropriate.
//Arguments:
// - chars - The codepoint to insert
// - dbcsAttribute - Double byte information associated with the codepoint
// - bAttr - Color data associated with the character
//Return Value:
// - true if we successfully inserted the character
// - false otherwise (out of memory)
bool TextBuffer::InsertCharacter(const std::wstring_view chars,
const DbcsAttribute dbcsAttribute,
const TextAttribute attr)
{
// Ensure consistent buffer state for double byte characters based on the character type we're about to insert
bool fSuccess = _PrepareForDoubleByteSequence(dbcsAttribute);
if (fSuccess)
{
// Get the current cursor position
short const iRow = GetCursor().GetPosition().Y; // row stored as logical position, not array position
short const iCol = GetCursor().GetPosition().X; // column logical and array positions are equal.
// Get the row associated with the given logical position
ROW& Row = GetRowByOffset(iRow);
// Store character and double byte data
CharRow& charRow = Row.GetCharRow();
try
{
charRow.GlyphAt(iCol) = chars;
charRow.DbcsAttrAt(iCol) = dbcsAttribute;
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
return false;
}
// Store color data
fSuccess = Row.GetAttrRow().SetAttrToEnd(iCol, attr);
if (fSuccess)
{
// Advance the cursor
fSuccess = IncrementCursor();
}
}
return fSuccess;
}
//Routine Description:
// - Inserts one ucs2 codepoint into the buffer at the current cursor position and advances the cursor as appropriate.
//Arguments:
// - wch - The codepoint to insert
// - dbcsAttribute - Double byte information associated with the codepoint
// - bAttr - Color data associated with the character
//Return Value:
// - true if we successfully inserted the character
// - false otherwise (out of memory)
bool TextBuffer::InsertCharacter(const wchar_t wch, const DbcsAttribute dbcsAttribute, const TextAttribute attr)
{
return InsertCharacter({ &wch, 1 }, dbcsAttribute, attr);
}
//Routine Description:
// - Finds the current row in the buffer (as indicated by the cursor position)
// and specifies that we have forced a line wrap on that row
@ -606,7 +334,7 @@ COORD TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Consol
const auto& currRow = GetRowByOffset(coordEndOfText.Y);
// The X position of the end of the valid text is the Right draw boundary (which is one beyond the final valid character)
coordEndOfText.X = gsl::narrow<short>(currRow.GetCharRow().MeasureRight()) - 1;
coordEndOfText.X = gsl::narrow<short>(currRow.MeasureRight()) - 1;
// If the X coordinate turns out to be -1, the row was empty, we need to search backwards for the real end of text.
const auto viewportTop = viewport.Top();
@ -617,7 +345,7 @@ COORD TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Consol
const auto& backupRow = GetRowByOffset(coordEndOfText.Y);
// We need to back up to the previous row if this line is empty, AND there are more rows
coordEndOfText.X = gsl::narrow<short>(backupRow.GetCharRow().MeasureRight()) - 1;
coordEndOfText.X = gsl::narrow<short>(backupRow.MeasureRight()) - 1;
fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop);
}
@ -777,7 +505,6 @@ void TextBuffer::ScrollRows(const SHORT firstRow, const SHORT size, const SHORT
}
// Renumber the IDs now that we've rearranged where the rows sit within the buffer.
// Refreshing should also delegate to the UnicodeStorage to re-key all the stored unicode sequences (where applicable).
_RefreshRowIDs(std::nullopt);
}
@ -814,13 +541,11 @@ void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition)
// And if it's no longer single width, the right half of the row should be erased.
if (lineRendition != LineRendition::SingleWidth)
{
const auto fillChar = L' ';
auto fillAttrs = GetCurrentAttributes();
fillAttrs.SetStandardErase();
const size_t fillOffset = GetLineWidth(rowIndex);
const size_t fillLength = GetSize().Width() - fillOffset;
const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillLength };
row.WriteCells(fillData, fillOffset, false);
FillWithCharacterAndAttributeLinear(til::point{ fillOffset, ::base::saturated_cast<size_t>(cursorPosition.Y) }, fillLength, UNICODE_SPACE, fillAttrs);
// We also need to make sure the cursor is clamped within the new width.
GetCursor().SetPosition(ClampPositionWithinLine(cursorPosition));
}
@ -930,8 +655,7 @@ void TextBuffer::Reset()
}
// Now that we've tampered with the row placement, refresh all the row IDs.
// Also take advantage of the row ID refresh loop to resize the rows in the X dimension
// and cleanup the UnicodeStorage characters that might fall outside the resized buffer.
// Also take advantage of the row ID refresh loop to resize the rows in the X dimension.
_RefreshRowIDs(newSize.X);
// Update the cached size value
@ -942,40 +666,16 @@ void TextBuffer::Reset()
return S_OK;
}
const UnicodeStorage& TextBuffer::GetUnicodeStorage() const noexcept
{
return _unicodeStorage;
}
UnicodeStorage& TextBuffer::GetUnicodeStorage() noexcept
{
return _unicodeStorage;
}
// Routine Description:
// - Method to help refresh all the Row IDs after manipulating the row
// by shuffling pointers around.
// - This will also update parent pointers that are stored in depth within the buffer
// (e.g. it will update CharRow parents pointing at Rows that might have been moved around)
// - Optionally takes a new row width if we're resizing to perform a resize operation and cleanup
// any high unicode (UnicodeStorage) runs while we're already looping through the rows.
// - Optionally takes a new row width if we're resizing to perform a resize operation
// Arguments:
// - newRowWidth - Optional new value for the row width.
void TextBuffer::_RefreshRowIDs(std::optional<SHORT> newRowWidth)
{
std::unordered_map<SHORT, SHORT> rowMap;
SHORT i = 0;
for (auto& it : _storage)
{
// Build a map so we can update Unicode Storage
rowMap.emplace(it.GetId(), i);
// Update the IDs
it.SetId(i++);
// Also update the char row parent pointers as they can get shuffled up in the rotates.
it.GetCharRow().UpdateParent(&it);
// Resize the rows in the X dimension if we have a new width
if (newRowWidth.has_value())
{
@ -983,9 +683,6 @@ void TextBuffer::_RefreshRowIDs(std::optional<SHORT> newRowWidth)
THROW_IF_FAILED(it.Resize(newRowWidth.value()));
}
}
// Give the new mapping to Unicode Storage
_unicodeStorage.Remap(rowMap, newRowWidth);
}
void TextBuffer::_NotifyPaint(const Viewport& viewport) const
@ -1004,27 +701,6 @@ ROW& TextBuffer::_GetFirstRow()
return GetRowByOffset(0);
}
// Routine Description:
// - Retrieves the row that comes before the given row.
// - Does not wrap around the screen buffer.
// Arguments:
// - The current row.
// Return Value:
// - reference to the previous row
// Note:
// - will throw exception if called with the first row of the text buffer
ROW& TextBuffer::_GetPrevRowNoWrap(const ROW& Row)
{
int prevRowIndex = Row.GetId() - 1;
if (prevRowIndex < 0)
{
prevRowIndex = TotalRowCount() - 1;
}
THROW_HR_IF(E_FAIL, Row.GetId() == _firstRow);
return _storage.at(prevRowIndex);
}
// Method Description:
// - Retrieves this buffer's current render target.
// Arguments:
@ -1046,7 +722,7 @@ Microsoft::Console::Render::IRenderTarget& TextBuffer::GetRenderTarget() noexcep
// - the delimiter class for the given char
const DelimiterClass TextBuffer::_GetDelimiterClassAt(const COORD pos, const std::wstring_view wordDelimiters) const
{
return GetRowByOffset(pos.Y).GetCharRow().DelimiterClassAt(pos.X, wordDelimiters);
return GetRowByOffset(pos.Y).DelimiterClassAt(pos.X, wordDelimiters);
}
// Method Description:
@ -2138,8 +1814,13 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// Fetch the row and its "right" which is the last printable character.
const ROW& row = oldBuffer.GetRowByOffset(iOldRow);
const short cOldColsTotal = oldBuffer.GetLineWidth(iOldRow);
const CharRow& charRow = row.GetCharRow();
short iRight = gsl::narrow_cast<short>(charRow.MeasureRight());
short iRight = gsl::narrow_cast<short>(row.MeasureRight());
if (iRight == 0 && !row.WasWrapForced())
{
// don't beef it if iRight=0 on iterator
newBuffer.NewlineCursor();
continue;
}
// If we're starting a new row, try and preserve the line rendition
// from the row in the original buffer.
@ -2174,33 +1855,26 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
}
}
// Loop through every character in the current row (up to
// the "right" boundary, which is one past the final valid
// character)
for (short iOldCol = 0; iOldCol < iRight; iOldCol++)
#if 0
OutputCellIterator it{ oldBuffer.GetCellDataAt({ 0, iOldRow }, Viewport::FromDimensions({ 0, iOldRow }, iRight, 1)) };
auto preCurPos{ newBuffer.GetCursor().GetPosition() };
const auto last = newBuffer.Write(it, preCurPos, true); // true - wrap if we don't fit!
const auto distance = last.GetCellDistance(it);
#endif
auto preCurPos{ newBuffer.GetCursor().GetPosition() };
const auto distance = 5;
if (iOldRow == cOldCursorPos.Y)
{
if (iOldCol == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
{
cNewCursorPos = newCursor.GetPosition();
fFoundCursorPos = true;
}
try
{
// TODO: MSFT: 19446208 - this should just use an iterator and the inserter...
const auto glyph = row.GetCharRow().GlyphAt(iOldCol);
const auto dbcsAttr = row.GetCharRow().DbcsAttrAt(iOldCol);
const auto textAttr = row.GetAttrRow().GetAttrByColumn(iOldCol);
if (!newBuffer.InsertCharacter(glyph, dbcsAttr, textAttr))
{
hr = E_OUTOFMEMORY;
break;
}
}
CATCH_RETURN();
cNewCursorPos = preCurPos;
// we started at (0, NewY) -- walk forward by the old position's X to figure out where we land in the new line
newBuffer.GetSize().MoveInBounds(cOldCursorPos.X, cNewCursorPos);
fFoundCursorPos = true;
}
newBuffer.GetSize().MoveInBounds(distance, preCurPos);
newBuffer.GetCursor().SetPosition(preCurPos);
// If we found the old row that the caller was interested in, set the
// out value of that parameter to the cursor's current Y position (the
// new location of the _end_ of that row in the buffer).
@ -2232,7 +1906,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// Only do so if we were not forced to wrap. If we did
// force a word wrap, then the existing line break was
// only because we ran out of space.
if (iRight < cOldColsTotal && !row.WasWrapForced())
if (distance < cOldColsTotal && !row.WasWrapForced())
{
if (iRight == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
{
@ -2563,3 +2237,224 @@ PointTree TextBuffer::GetPatterns(const size_t firstRow, const size_t lastRow) c
PointTree result(std::move(intervals));
return result;
}
TextBuffer::WriteResult TextBuffer::FillWithAttributeRectangular(const til::rectangle& region, const TextAttribute& attribute)
{
return _WriteRectangular(
region,
[&](WriteResult& result, auto&& at, auto&& size) {
auto& row = GetRowByOffset(at.y());
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), size), attribute);
result.columnsWritten += size;
});
}
TextBuffer::WriteResult TextBuffer::FillWithAttributeLinear(til::point at, size_t count, const TextAttribute& attribute)
{
auto out = _WriteLinear(
at,
[&](WriteResult& result, auto&& at, auto&& size) -> bool {
const auto w{ ::base::ClampMin(::base::saturated_cast<uint16_t>(size), count) };
auto& row = GetRowByOffset(at.y());
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), size), attribute);
result.columnsWritten += w.RawValue();
const Viewport paint = Viewport::FromDimensions(at, { w.Cast<SHORT>(), 1 });
_NotifyPaint(paint);
return (count -= w.RawValue()) > 0; // keep going if data remains
});
return out;
}
// TODO(DH): make this a member so that IsGlyphFullWidth can be a member ref
static std::tuple<RowMeasurementBuffer, uint16_t> _MeasureStringAndCountColumns(std::wstring_view string) //const noexcept
{
RowMeasurementBuffer measurements;
uint16_t colCount{};
while (!string.empty())
{
auto codepoint = Utf16Parser::ParseNext(string);
string = string.substr(codepoint.size());
uint8_t measurement{ IsGlyphFullWidth(codepoint) ? 2u : 1u };
measurements.replace(measurements.size(), measurements.size(), { measurement, gsl::narrow_cast<uint16_t>(1) });
if (codepoint.size() > 1)
{
measurements.replace(measurements.size(), measurements.size(), typename decltype(measurements)::rle_type{ uint8_t{ 0 }, gsl::narrow_cast<uint16_t>(codepoint.size() - 1) });
}
colCount += measurement;
}
return { std::move(measurements), colCount };
}
struct no_attributes
{
};
template<typename T>
TextBuffer::WriteResult TextBuffer::_WriteMeasuredStringLinearWithAttributes(const til::point& at, std::wstring_view string, RowMeasurementBuffer measurements, [[maybe_unused]] const T& attributes)
{
uint16_t colCount = std::accumulate(
measurements.runs().cbegin(),
measurements.runs().cend(),
uint16_t{},
[](auto&& v, auto&& r) -> uint16_t { return v + (r.value * r.length); });
const auto out = _WriteLinear(
at,
[&](WriteResult& result, auto&& at, auto&& /* size */) {
auto& row = GetRowByOffset(at.y());
const auto [consumedWchar, consumedDestCols, consumedSourceCols] = row.WriteStringAtMeasured(at.x<uint16_t>(), colCount, string, measurements);
if constexpr (std::is_same<T, TextAttribute>::value)
{
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), consumedDestCols), attributes);
}
#if 0
else if constexpr (std::is_same<T, ATTR_ROW::rle_vector>::value)
{
// user has passed us a packed representation of attributes
// It is a programming error to pass one whose total length does
// not match the measurement buffer
const auto attrRunSlice{ attributes.slice(0, consumedSourceCols) };
attrRunSlice.resize_trailing_extent(consumedDestCols); // this may be very wrong. TODO(DH)
// intent: I believe that this will take the attribute from the cell before the one we had to cut off
// and then extend it to cover our empty column?
row.GetAttrRow()._data.replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), consumedSourceCols), attrRunSlice.runs());
}
#endif
const Viewport paint = Viewport::FromDimensions(at, { ::base::saturated_cast<SHORT>(consumedDestCols), 1 });
_NotifyPaint(paint);
colCount -= consumedSourceCols;
result.charactersRead += consumedWchar;
result.charactersWritten += consumedWchar;
result.columnsWritten += consumedDestCols;
result.columnsRead += consumedSourceCols;
// unchecked substr
//string = std::wstring_view{ &*string.begin() + consumedWchar, string.size() - consumedWchar };
//string = til::unchecked_substr(string, consumedWchar);
string = string.substr(consumedWchar);
measurements = measurements.slice(gsl::narrow_cast<uint16_t>(consumedWchar), measurements.size());
return !string.empty();
});
return out;
}
TextBuffer::WriteResult TextBuffer::WriteMeasuredStringLinear(const til::point& at, const std::wstring_view& string, RowMeasurementBuffer measurements)
{
return _WriteMeasuredStringLinearWithAttributes(at, string, std::move(measurements), _currentAttributes);
}
TextBuffer::WriteResult TextBuffer::WriteStringLinearWithAttributes(const til::point& at, const std::wstring_view& string, const TextAttribute& attr)
{
auto [measurements, colCount] = _MeasureStringAndCountColumns(string);
return _WriteMeasuredStringLinearWithAttributes(at, string, std::move(measurements), attr);
}
TextBuffer::WriteResult TextBuffer::WriteStringLinearKeepAttributes(const til::point& at, const std::wstring_view& string)
{
auto [measurements, colCount] = _MeasureStringAndCountColumns(string);
return _WriteMeasuredStringLinearWithAttributes(at, string, std::move(measurements), no_attributes{});
}
template<typename T>
TextBuffer::WriteResult TextBuffer::_FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, [[maybe_unused]] const T& attr)
{
const uint8_t width{ IsGlyphFullWidth(character) ? uint8_t{ 2u } : uint8_t{ 1u } };
auto out = _WriteRectangular(
region,
[&](WriteResult& /* result */, auto&& at, auto&& size) {
auto& row = GetRowByOffset(at.y());
row.Fill(region.left(), size / width, character, width);
if constexpr (std::is_same<T, TextAttribute>::value)
{
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), size), attr);
}
});
return out;
}
template<typename T>
TextBuffer::WriteResult TextBuffer::_FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, [[maybe_unused]] const T& attr)
{
const uint8_t width{ IsGlyphFullWidth(character) ? uint8_t{ 2u } : uint8_t{ 1u } };
return _WriteLinear(
at,
[&](WriteResult& result, auto&& at, auto&& size) -> bool {
// size is how many *columns* we have left to fill, k?
const auto w{ ::base::ClampMin(::base::saturated_cast<uint16_t>(size), count * width) };
auto& row = GetRowByOffset(at.y());
const auto consumedWchars = row.Fill(at.x(), count, character, width);
if constexpr (std::is_same<T, TextAttribute>::value)
{
row.GetAttrRow().Replace(::base::saturated_cast<uint16_t>(at.x()), ::base::ClampAdd<uint16_t>(::base::saturated_cast<uint16_t>(at.x()), w), attr);
}
result.charactersWritten += consumedWchars;
result.columnsWritten += w.RawValue();
const Viewport paint = Viewport::FromDimensions(at, { w.Cast<SHORT>(), 1 });
_NotifyPaint(paint);
return (count -= consumedWchars) > 0; // keep going if data remains
});
}
TextBuffer::WriteResult TextBuffer::FillWithCharacterRectangular(const til::rectangle& region, const wchar_t character)
{
return _FillWithCharacterAndAttributeRectangular(region, character, no_attributes{});
}
TextBuffer::WriteResult TextBuffer::FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const TextAttribute& attr)
{
return _FillWithCharacterAndAttributeRectangular(region, character, attr);
}
TextBuffer::WriteResult TextBuffer::FillWithCharacterLinear(const til::point& at, size_t count, const wchar_t character)
{
return _FillWithCharacterAndAttributeLinear(at, count, character, no_attributes{});
}
TextBuffer::WriteResult TextBuffer::FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const TextAttribute& attr)
{
return _FillWithCharacterAndAttributeLinear(at, count, character, attr);
}
TextBuffer::WriteResult TextBuffer::WriteRowImage(const til::point at, const RowImage& ri)
{
auto& row{ GetRowByOffset(at.y()) };
row.Reinsert(at.x<uint16_t>(), ri);
return {};
}
template<typename TLambda>
TextBuffer::WriteResult TextBuffer::_WriteRectangular(const til::rectangle& rect, TLambda&& thing)
{
WriteResult out{};
for (auto y{ rect.top() }; y < rect.bottom(); ++y)
{
thing(out, til::point{ rect.left(), y }, rect.width());
}
_NotifyPaint(Viewport::FromDimensions(rect.origin(), rect.size()));
return out;
}
template<typename TLambda>
TextBuffer::WriteResult TextBuffer::_WriteLinear(const til::point& start, TLambda&& thing)
{
auto point{ start };
WriteResult out{};
while (_size.IsInBounds(point) && thing(out, point, _size.Width() - point.x()))
{
point = { 0, point.y() };
};
return out;
}

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"
@ -86,19 +85,6 @@ public:
TextBufferTextIterator GetTextDataAt(const COORD at, const Microsoft::Console::Types::Viewport limit) const;
// Text insertion functions
OutputCellIterator Write(const OutputCellIterator givenIt);
OutputCellIterator Write(const OutputCellIterator givenIt,
const COORD target,
const std::optional<bool> wrap = true);
OutputCellIterator WriteLine(const OutputCellIterator givenIt,
const COORD target,
const std::optional<bool> setWrap = std::nullopt,
const std::optional<size_t> limitRight = std::nullopt);
bool InsertCharacter(const wchar_t wch, const DbcsAttribute dbcsAttribute, const TextAttribute attr);
bool InsertCharacter(const std::wstring_view chars, const DbcsAttribute dbcsAttribute, const TextAttribute attr);
bool IncrementCursor();
bool NewlineCursor();
@ -136,9 +122,6 @@ public:
[[nodiscard]] HRESULT ResizeTraditional(const COORD newSize) noexcept;
const UnicodeStorage& GetUnicodeStorage() const noexcept;
UnicodeStorage& GetUnicodeStorage() noexcept;
Microsoft::Console::Render::IRenderTarget& GetRenderTarget() noexcept;
const COORD GetWordStart(const COORD target, const std::wstring_view wordDelimiters, bool accessibilityMode = false) const;
@ -200,6 +183,30 @@ public:
void CopyPatterns(const TextBuffer& OtherBuffer);
interval_tree::IntervalTree<til::point, size_t> GetPatterns(const size_t firstRow, const size_t lastRow) const;
struct WriteResult
{
size_t charactersWritten;
size_t charactersRead;
size_t columnsWritten;
size_t columnsRead;
};
WriteResult WriteMeasuredStringLinear(const til::point& at, const std::wstring_view& string, RowMeasurementBuffer measurements);
WriteResult WriteStringLinearWithAttributes(const til::point& at, const std::wstring_view& string, const TextAttribute& attr);
WriteResult WriteStringLinearKeepAttributes(const til::point& at, const std::wstring_view& string);
WriteResult FillWithCharacterRectangular(const til::rectangle& region, const wchar_t character);
WriteResult FillWithCharacterLinear(const til::point& at, size_t count, const wchar_t character);
WriteResult FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const TextAttribute& attr);
WriteResult FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const TextAttribute& attr);
WriteResult FillWithAttributeRectangular(const til::rectangle& region, const TextAttribute& attribute);
WriteResult FillWithAttributeLinear(const til::point at, size_t count, const TextAttribute& attribute);
WriteResult WriteRowImage(const til::point at, const RowImage& ri);
private:
void _UpdateSize();
Microsoft::Console::Types::Viewport _size;
@ -210,9 +217,6 @@ private:
TextAttribute _currentAttributes;
// storage location for glyphs that can't fit into the buffer normally
UnicodeStorage _unicodeStorage;
std::unordered_map<uint16_t, std::wstring> _hyperlinkMap;
std::unordered_map<std::wstring, uint16_t> _hyperlinkCustomIdMap;
uint16_t _currentHyperlinkId;
@ -230,12 +234,7 @@ private:
void _NotifyPaint(const Microsoft::Console::Types::Viewport& viewport) const;
// Assist with maintaining proper buffer state for Double Byte character sequences
bool _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute);
bool _AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute);
ROW& _GetFirstRow();
ROW& _GetPrevRowNoWrap(const ROW& row);
void _ExpandTextRow(SMALL_RECT& selectionRow) const;
@ -247,6 +246,21 @@ private:
void _PruneHyperlinks();
template<typename TLambda>
WriteResult _WriteRectangular(const til::rectangle& rect, TLambda&& thing);
template<typename TLambda>
WriteResult _WriteLinear(const til::point& start, TLambda&& thing);
template<typename T>
WriteResult _FillWithCharacterAndAttributeRectangular(const til::rectangle& region, const wchar_t character, const T& attr);
template<typename T>
WriteResult _FillWithCharacterAndAttributeLinear(const til::point& at, size_t count, const wchar_t character, const T& attr);
template<typename T>
WriteResult _WriteMeasuredStringLinearWithAttributes(const til::point& at, std::wstring_view string, RowMeasurementBuffer measurements, const T& attributes);
std::unordered_map<size_t, std::wstring> _idsAndPatterns;
size_t _currentPatternId;

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"
@ -165,9 +164,8 @@ TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& move
_attrIter += diff;
_view.UpdateTextAttribute(*_attrIter);
const CharRow& charRow = _pRow->GetCharRow();
_view.UpdateText(charRow.GlyphAt(newX));
_view.UpdateDbcsAttribute(charRow.DbcsAttrAt(newX));
_view.UpdateText(_pRow->GlyphAt(newX));
_view.UpdateDbcsAttribute(_pRow->DbcsAttrAt(newX));
_pos.X = newX;
}
else
@ -326,8 +324,8 @@ const ROW* TextBufferCellIterator::s_GetRow(const TextBuffer& buffer, const COOR
// - Updates the internal view. Call after updating row, attribute, or positions.
void TextBufferCellIterator::_GenerateView()
{
_view = OutputCellView(_pRow->GetCharRow().GlyphAt(_pos.X),
_pRow->GetCharRow().DbcsAttrAt(_pos.X),
_view = OutputCellView(_pRow->GlyphAt(_pos.X),
_pRow->DbcsAttrAt(_pos.X),
*_attrIter,
TextAttributeBehavior::Stored);
}

View file

@ -15,12 +15,12 @@ Author(s):
#pragma once
#include "CharRow.hpp"
#include "AttrRow.hpp"
#include "OutputCellView.hpp"
#include "../../types/inc/viewport.hpp"
class TextBuffer;
class ROW;
class TextBufferCellIterator
{
@ -58,8 +58,8 @@ protected:
const ROW* _pRow;
ATTR_ROW::const_iterator _attrIter;
const TextBuffer& _buffer;
const Microsoft::Console::Types::Viewport _bounds;
std::reference_wrapper<const TextBuffer> _buffer;
Microsoft::Console::Types::Viewport _bounds;
bool _exceeded;
COORD _pos;

View file

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

View file

@ -336,9 +336,7 @@ void CommandListPopup::_drawList()
size_t lStringLength = Width();
for (SHORT i = 0; i < Height(); ++i)
{
const OutputCellIterator spaces(UNICODE_SPACE, _attributes, lStringLength);
const auto result = _screenInfo.Write(spaces, WriteCoord);
lStringLength = result.GetCellDistance(spaces);
_screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(WriteCoord, Width(), UNICODE_SPACE, _attributes);
WriteCoord.Y += 1i16;
}
@ -431,10 +429,7 @@ void CommandListPopup::_drawList()
TextAttribute inverted = _attributes;
inverted.Invert();
const OutputCellIterator it(inverted, lStringLength);
const auto done = _screenInfo.Write(it, WriteCoord);
lStringLength = done.GetCellDistance(it);
_screenInfo.GetTextBuffer().FillWithAttributeRectangular(til::rectangle{ WriteCoord, til::size{ Width(), 1 } }, inverted);
}
WriteCoord.Y += 1;
@ -529,21 +524,13 @@ void CommandListPopup::_updateHighlight(const SHORT OldCurrentCommand, const SHO
}
COORD WriteCoord;
WriteCoord.X = _region.Left + 1i16;
size_t lStringLength = Width();
WriteCoord.Y = _region.Top + 1i16 + OldCurrentCommand - TopIndex;
const OutputCellIterator it(_attributes, lStringLength);
const auto done = _screenInfo.Write(it, WriteCoord);
lStringLength = done.GetCellDistance(it);
_screenInfo.GetTextBuffer().FillWithAttributeRectangular(til::rectangle{ WriteCoord, til::size{ Width(), 1 } }, _attributes);
// highlight new command
WriteCoord.Y = _region.Top + 1i16 + NewCurrentCommand - TopIndex;
// inverted attributes
TextAttribute inverted = _attributes;
inverted.Invert();
const OutputCellIterator itAttr(inverted, lStringLength);
const auto doneAttr = _screenInfo.Write(itAttr, WriteCoord);
lStringLength = done.GetCellDistance(itAttr);
_screenInfo.GetTextBuffer().FillWithAttributeRectangular(til::rectangle{ WriteCoord, til::size{ Width(), 1 } }, inverted);
}

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"
@ -91,10 +89,48 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region)
return E_INVALIDARG;
}
const OutputCellIterator it(attrs);
const auto done = screenInfo.Write(it, target);
//const OutputCellIterator it(attrs);
//const auto done = screenInfo.Write(it, target);
__debugbreak();
// TODO(DH) restore writing runs of attributes
////////////////
#if 0
static rle_container rle_encode(const basic_container_view& from)
{
if (from.empty())
{
return {};
}
used = done.GetCellDistance(it);
rle_container to;
value_type value = from.front();
size_type length = 0;
for (auto v : from)
{
if (v != value)
{
to.emplace_back(value, length);
value = v;
length = 0;
}
length++;
}
if (length)
{
to.emplace_back(value, length);
}
return to;
}
#endif
////////////////
used = 0;
//used = done.GetCellDistance(it);
return S_OK;
}
@ -133,9 +169,8 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region)
try
{
OutputCellIterator it(chars);
const auto finished = screenInfo.Write(it, target);
used = finished.GetInputDistance(it);
const auto result = screenInfo.GetTextBuffer().WriteStringLinearKeepAttributes(target, chars);
used = result.charactersWritten;
}
CATCH_RETURN();
@ -218,11 +253,8 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region)
try
{
TextAttribute useThisAttr(attribute);
const OutputCellIterator it(useThisAttr, lengthToWrite);
const auto done = screenBuffer.Write(it, startingCoordinate);
cellsModified = done.GetCellDistance(it);
const auto result = screenBuffer.GetTextBuffer().FillWithAttributeLinear(startingCoordinate, lengthToWrite, TextAttribute{ attribute });
cellsModified = result.columnsWritten;
if (screenBuffer.HasAccessibilityEventing())
{
@ -279,12 +311,11 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region)
HRESULT hr = S_OK;
try
{
const OutputCellIterator it(character, lengthToWrite);
// when writing to the buffer, specifically unset wrap if we get to the last column.
// a fill operation should UNSET wrap in that scenario. See GH #1126 for more details.
const auto done = screenInfo.Write(it, startingCoordinate, false);
cellsModified = done.GetInputDistance(it);
// TODO(DH) maintain above fix to clear wrap ^^^
const auto result = screenInfo.GetTextBuffer().FillWithCharacterLinear(startingCoordinate, lengthToWrite, character);
cellsModified = result.columnsWritten;
// Notify accessibility
if (screenInfo.HasAccessibilityEventing())

View file

@ -20,6 +20,7 @@
#include "../types/inc/Viewport.hpp"
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../types/inc/Utf16Parser.hpp"
#pragma hdrstop
using namespace Microsoft::Console::Types;
@ -339,7 +340,9 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
COORD CursorPosition = cursor.GetPosition();
NTSTATUS Status = STATUS_SUCCESS;
SHORT XPosition;
WCHAR LocalBuffer[LOCAL_BUFFER_SIZE];
std::wstring local;
local.reserve(LOCAL_BUFFER_SIZE);
RowMeasurementBuffer measurements;
size_t TempNumSpaces = 0;
const bool fUnprocessed = WI_IsFlagClear(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT);
const bool fWrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
@ -351,6 +354,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
const size_t BufferSize = *pcb;
*pcb = 0;
const wchar_t* const base = pwchRealUnicode;
const wchar_t* lpString = pwchRealUnicode;
COORD coordScreenBufferSize = screenInfo.GetBufferSize().Dimensions();
@ -387,8 +391,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
// As an optimization, collect characters in buffer and print out all at once.
XPosition = cursor.GetPosition().X;
size_t i = 0;
wchar_t* LocalBufPtr = LocalBuffer;
while (*pcb < BufferSize && i < LOCAL_BUFFER_SIZE && XPosition < coordScreenBufferSize.X)
while (*pcb < BufferSize && XPosition < coordScreenBufferSize.X)
{
#pragma prefast(suppress : 26019, "Buffer is taken in multiples of 2. Validation is ok.")
const wchar_t Char = *lpString;
@ -396,19 +399,21 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
// WCL-NOTE: to be identical to lpString. They are incremented in lockstep, never separately, and lpString
// WCL-NOTE: is initialized from pwchRealUnicode.
const wchar_t RealUnicodeChar = *pwchRealUnicode;
const auto cbuf{ Utf16Parser::ParseNext(std::wstring_view{ pwchRealUnicode, (BufferSize / sizeof(WCHAR)) - static_cast<size_t>(pwchRealUnicode - base) }) };
if (IS_GLYPH_CHAR(RealUnicodeChar) || fUnprocessed)
{
// WCL-NOTE: This operates on a single code unit instead of a whole codepoint. It will mis-measure surrogate pairs.
if (IsGlyphFullWidth(Char))
if (IsGlyphFullWidth(cbuf))
{
if (i < (LOCAL_BUFFER_SIZE - 1) && XPosition < (coordScreenBufferSize.X - 1))
if (XPosition < (coordScreenBufferSize.X - 1))
{
*LocalBufPtr++ = Char;
local.append(cbuf);
measurements.append(uint8_t{ 2u });
// cursor adjusted by 2 because the char is double width
XPosition += 2;
i += 1;
pwchBuffer++;
i += cbuf.size();
pwchBuffer += cbuf.size();
}
else
{
@ -417,11 +422,15 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
}
else
{
*LocalBufPtr = Char;
LocalBufPtr++;
local.append(cbuf);
measurements.append(uint8_t{ 1u });
XPosition++;
i++;
pwchBuffer++;
i += cbuf.size();
pwchBuffer += cbuf.size();
}
for (auto z = cbuf.size(); z > 1; --z)
{
measurements.append(uint8_t{ 0 });
}
}
else
@ -459,11 +468,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
goto EndWhile;
}
for (ULONG j = 0; j < TabSize && i < LOCAL_BUFFER_SIZE; j++, i++)
{
*LocalBufPtr = UNICODE_SPACE;
LocalBufPtr++;
}
local.append(TabSize, UNICODE_SPACE);
pwchBuffer++;
break;
@ -477,32 +482,30 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
if ((dwFlags & WC_PRINTABLE_CONTROL_CHARS) && (IS_CONTROL_CHAR(RealUnicodeChar)))
{
CtrlChar:
if (i < (LOCAL_BUFFER_SIZE - 1))
//if (i < (LOCAL_BUFFER_SIZE - 1))
{
// WCL-NOTE: We do not properly measure that there is space for two characters
// WCL-NOTE: left on the screen.
*LocalBufPtr = (WCHAR)'^';
LocalBufPtr++;
local.append(1, L'^');
XPosition++;
i++;
*LocalBufPtr = (WCHAR)(RealUnicodeChar + (WCHAR)'@');
LocalBufPtr++;
local.append(1, (WCHAR)(RealUnicodeChar + (WCHAR)'@'));
XPosition++;
i++;
pwchBuffer++;
}
else
{
goto EndWhile;
}
//else
//{
//goto EndWhile;
//}
}
else
{
if (Char == UNICODE_NULL)
{
*LocalBufPtr = UNICODE_SPACE;
local.append(1, UNICODE_SPACE);
}
else
{
@ -513,11 +516,13 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
GetStringTypeW(CT_CTYPE1, &RealUnicodeChar, 1, &CharType);
if (WI_IsFlagSet(CharType, C1_CNTRL))
{
WCHAR ch{};
ConvertOutputToUnicode(gci.OutputCP,
(LPSTR)&RealUnicodeChar,
1,
LocalBufPtr,
&ch,
1);
local.append(1, ch);
}
else
{
@ -525,20 +530,19 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
// WCL-NOTE: 1. Normal characters are handled via the early check for IS_GLYPH_CHAR
// WCL-NOTE: 2. Control characters are handled via the CtrlChar label (if WC_PRINTABLE_CONTROL_CHARS is on)
// WCL-NOTE: And if they are control characters they will trigger the C1_CNTRL check above.
*LocalBufPtr = Char;
local.append(1, Char);
}
}
LocalBufPtr++;
XPosition++;
i++;
pwchBuffer++;
}
}
}
lpString++;
pwchRealUnicode++;
*pcb += sizeof(WCHAR);
lpString += cbuf.size();
pwchRealUnicode += cbuf.size();
*pcb += cbuf.size() * sizeof(WCHAR);
}
EndWhile:
if (i != 0)
@ -553,8 +557,12 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
}
// line was wrapped if we're writing up to the end of the current row
OutputCellIterator it(std::wstring_view(LocalBuffer, i), Attributes);
const auto itEnd = screenInfo.Write(it);
//OutputCellIterator it(std::wstring_view{ local }, Attributes);
//const auto itEnd = screenInfo.Write(it);
measurements.resize_trailing_extent(gsl::narrow_cast<uint16_t>(local.size()));
auto result = screenInfo.GetTextBuffer().WriteMeasuredStringLinear(CursorPosition, local, measurements);
local.clear();
measurements.resize_trailing_extent(0);
// Notify accessibility
if (screenInfo.HasAccessibilityEventing())
@ -564,7 +572,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
// The number of "spaces" or "cells" we have consumed needs to be reported and stored for later
// when/if we need to erase the command line.
TempNumSpaces += itEnd.GetCellDistance(it);
TempNumSpaces += result.columnsWritten; //itEnd.GetCellDistance(it);
// WCL-NOTE: We are using the "estimated" X position delta instead of the actual delta from
// WCL-NOTE: the iterator. It is not clear why. If they differ, the cursor ends up in the
// WCL-NOTE: wrong place (typically inside another character).
@ -702,7 +710,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
{
try
{
screenInfo.Write(OutputCellIterator(UNICODE_SPACE, Attributes, 1), CursorPosition);
screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(CursorPosition, 1, UNICODE_SPACE, Attributes);
Status = STATUS_SUCCESS;
}
CATCH_LOG();
@ -720,7 +728,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
{
try
{
screenInfo.Write(OutputCellIterator(UNICODE_SPACE, Attributes, 1), CursorPosition);
screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(CursorPosition, 1, UNICODE_SPACE, Attributes);
Status = STATUS_SUCCESS;
}
CATCH_LOG();
@ -742,7 +750,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
{
try
{
screenInfo.Write(OutputCellIterator(UNICODE_SPACE, Attributes, 1), cursor.GetPosition());
screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(cursor.GetPosition(), 1, UNICODE_SPACE, Attributes);
}
CATCH_LOG();
}
@ -797,9 +805,8 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
try
{
const OutputCellIterator it(UNICODE_SPACE, Attributes, NumChars);
const auto done = screenInfo.Write(it, cursor.GetPosition());
NumChars = done.GetCellDistance(it);
const auto result = screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(cursor.GetPosition(), NumChars, UNICODE_SPACE, Attributes);
NumChars = result.columnsWritten;
}
CATCH_LOG();
@ -850,22 +857,18 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
{
const COORD TargetPoint = cursor.GetPosition();
ROW& Row = textBuffer.GetRowByOffset(TargetPoint.Y);
const CharRow& charRow = Row.GetCharRow();
try
{
// If we're on top of a trailing cell, clear it and the previous cell.
if (charRow.DbcsAttrAt(TargetPoint.X).IsTrailing())
if (Row.DbcsAttrAt(TargetPoint.X).IsTrailing())
{
// Space to clear for 2 cells.
OutputCellIterator it(UNICODE_SPACE, 2);
// Back target point up one.
auto writeTarget = TargetPoint;
writeTarget.X--;
// Write 2 clear cells.
screenInfo.Write(it, writeTarget);
screenInfo.GetTextBuffer().FillWithCharacterLinear(writeTarget, 2, UNICODE_SPACE);
}
}
catch (...)

View file

@ -228,7 +228,7 @@ void DeleteCommandLine(COOKED_READ_DATA& cookedReadData, const bool fUpdateField
try
{
cookedReadData.ScreenInfo().Write(OutputCellIterator(UNICODE_SPACE, CharsToWrite), coordOriginalCursor);
cookedReadData.ScreenInfo().GetTextBuffer().FillWithCharacterLinear(coordOriginalCursor, CharsToWrite, UNICODE_SPACE);
}
CATCH_LOG();

View file

@ -109,11 +109,10 @@ void ConversionAreaInfo::SetAttributes(const TextAttribute& attr)
// Arguments:
// - text - Text to insert into the conversion area buffer
// - column - Column to start at (X position)
void ConversionAreaInfo::WriteText(const std::vector<OutputCell>& text,
void ConversionAreaInfo::WriteText(const RowImage& text,
const SHORT column)
{
gsl::span<const OutputCell> view(text.data(), text.size());
_screenBuffer->Write(view, { column, 0 });
_screenBuffer->GetTextBuffer().WriteRowImage(til::point{ column, 0 }, text);
}
// Routine Description:

View file

@ -61,7 +61,7 @@ public:
void SetWindowInfo(const SMALL_RECT view) noexcept;
void Paint() const noexcept;
void WriteText(const std::vector<OutputCell>& text, const SHORT column);
void WriteText(const RowImage& text, const SHORT column);
void SetAttributes(const TextAttribute& attr);
const TextBuffer& GetTextBuffer() const noexcept;

View file

@ -217,76 +217,60 @@ TextAttribute ConsoleImeInfo::s_RetrieveAttributeAt(const size_t pos,
// - colorArray - Array of color values provided by the IME.
// Return Value:
// - Vector of OutputCells where each one represents one cell of the output buffer.
std::vector<OutputCell> ConsoleImeInfo::s_ConvertToCells(const std::wstring_view text,
const gsl::span<const BYTE> attributes,
const gsl::span<const WORD> colorArray)
RowImage ConsoleImeInfo::s_ConvertToCells(std::wstring_view text,
const gsl::span<const BYTE> attributes,
const gsl::span<const WORD> colorArray)
{
RowImage e;
std::vector<OutputCell> cells;
// - Convert incoming wchar_t stream into UTF-16 units.
const auto glyphs = Utf16Parser::Parse(text);
// - Walk through all of the grouped up text, match up the correct attribute to it, and make a new cell.
size_t attributesUsed = 0;
for (const auto& parsedGlyph : glyphs)
size_t attributesUsed{};
while (!text.empty())
{
const std::wstring_view glyph{ parsedGlyph.data(), parsedGlyph.size() };
// Collect up attributes that apply to this glyph range.
auto drawingAttr = s_RetrieveAttributeAt(attributesUsed, attributes, colorArray);
attributesUsed++;
const auto glyph{ Utf16Parser::ParseNext(text) };
text = text.substr(glyph.size());
const uint8_t width{ gsl::narrow_cast<uint8_t>(IsGlyphFullWidth(glyph) ? 2u : 1u) };
// The IME gave us an attribute for every glyph position in a surrogate pair.
// But the only important information will be the cursor position.
// Check all additional attributes to see if the cursor resides on top of them.
for (size_t i = 1; i < glyph.size(); i++)
{
TextAttribute additionalAttr = s_RetrieveAttributeAt(attributesUsed, attributes, colorArray);
attributesUsed++;
if (additionalAttr.IsLeftVerticalDisplayed())
{
drawingAttr.SetLeftVerticalDisplayed(true);
}
if (additionalAttr.IsRightVerticalDisplayed())
{
drawingAttr.SetRightVerticalDisplayed(true);
}
// add glyph
e._data.append(glyph);
// register its width
e._cwid.append(width);
for (auto z{ glyph.size() }; z > 1; --z)
{ // add a trailing 0-width for every surrogate pair trail
e._cwid.append(0);
}
// We have to determine if the glyph range is 1 column or two.
// If it's full width, it's two, and we need to make sure we don't draw the cursor
// right down the middle of the character.
// Otherwise it's one column and we'll push it in with the default empty DbcsAttribute.
DbcsAttribute dbcsAttr;
if (IsGlyphFullWidth(glyph))
auto drawingAttr = s_RetrieveAttributeAt(attributesUsed++, attributes, colorArray);
if (width == 1 || !(drawingAttr.IsLeftVerticalDisplayed() || drawingAttr.IsRightVerticalDisplayed()))
{
auto leftHalfAttr = drawingAttr;
auto rightHalfAttr = drawingAttr;
// Don't draw lines in the middle of full width glyphs.
// If we need a right vertical, don't apply it to the left side of the character
if (leftHalfAttr.IsRightVerticalDisplayed())
for (auto z{ width }; z > 0; --z) // add an attribute for each cell covered
{
leftHalfAttr.SetRightVerticalDisplayed(false);
e._attrRow._data.append(drawingAttr);
}
dbcsAttr.SetLeading();
cells.emplace_back(glyph, dbcsAttr, leftHalfAttr);
dbcsAttr.SetTrailing();
// If we need a left vertical, don't apply it to the right side of the character
if (rightHalfAttr.IsLeftVerticalDisplayed())
{
rightHalfAttr.SetLeftVerticalDisplayed(false);
}
cells.emplace_back(glyph, dbcsAttr, rightHalfAttr);
}
else
{
cells.emplace_back(glyph, dbcsAttr, drawingAttr);
}
}
// the width was >1 and the attribute contained the left or the right cursor indicator; split them into [ ___ ] (left, middle, right)
auto first = drawingAttr;
first.SetRightVerticalDisplayed(false);
auto middle = drawingAttr;
middle.SetLeftVerticalDisplayed(false);
middle.SetRightVerticalDisplayed(false);
auto last = drawingAttr;
last.SetLeftVerticalDisplayed(false);
return cells;
e._attrRow._data.append(first);
for (auto z{ width }; z > 2; --z)
{
e._attrRow._data.append(middle);
}
e._attrRow._data.append(last);
}
e._width += width;
}
return e;
}
// Routine Description:
@ -308,81 +292,55 @@ std::vector<OutputCell> ConsoleImeInfo::s_ConvertToCells(const std::wstring_view
// However, if text couldn't fit in our line (full-width character starting at the very last cell)
// then we will give back the same begin and update the position for the next call to try again.
// If the viewport is deemed too small, we'll skip past it and advance begin past the entire full-width character.
std::vector<OutputCell>::const_iterator ConsoleImeInfo::_WriteConversionArea(const std::vector<OutputCell>::const_iterator begin,
const std::vector<OutputCell>::const_iterator end,
COORD& pos,
const Microsoft::Console::Types::Viewport view,
SCREEN_INFORMATION& screenInfo)
void ConsoleImeInfo::_WriteConversionArea(const RowImage& cells,
COORD pos,
const Microsoft::Console::Types::Viewport view,
SCREEN_INFORMATION& screenInfo)
{
// The position in the viewport where we will start inserting cells for this conversion area
// NOTE: We might exit early if there's not enough space to fit here, so we take a copy of
// the original and increment it up front.
const auto insertionPos = pos;
// Advance the cursor position to set up the next call for success (insert the next conversion area
// at the beginning of the following line)
pos.X = view.Left();
pos.Y++;
// The index of the last column in the viewport. (view is inclusive)
const auto finalViewColumn = view.RightInclusive();
// The maximum number of cells we can insert into a line.
const auto lineWidth = finalViewColumn - insertionPos.X + 1; // +1 because view was inclusive
// The iterator to the beginning position to form our line
const auto lineBegin = begin;
// The total number of cells we could insert.
const auto size = end - begin;
FAIL_FAST_IF(size <= 0); // It's a programming error to have <= 0 cells to insert.
// The end is the smaller of the remaining number of cells or the amount of line cells we can write before
// hitting the right edge of the viewport
auto lineEnd = lineBegin + std::min(size, (ptrdiff_t)lineWidth);
// We must attempt to compensate for ending on a leading byte. We can't split a full-width character across lines.
// As such, if the last item is a leading byte, back the end up by one.
FAIL_FAST_IF(lineEnd <= lineBegin); // We should have at least 1 space we can back up.
// Get the last cell in the run and if it's a leading byte, move the end position back one so we don't
// try to insert it.
const auto lastCell = lineEnd - 1;
if (lastCell->DbcsAttr().IsLeading())
auto remain{ cells };
do
{
lineEnd--;
}
// The maximum number of cells we can insert into a line.
const auto lineWidth = finalViewColumn - pos.X + 1; // +1 because view was inclusive
// Copy out the substring into a vector.
const std::vector<OutputCell> lineVec(lineBegin, lineEnd);
auto [draw, left] = cells.split(::base::saturated_cast<uint16_t>(lineWidth));
// Add a conversion area to the internal state to hold this line.
THROW_IF_FAILED(_AddConversionArea());
// Add a conversion area to the internal state to hold this line.
THROW_IF_FAILED(_AddConversionArea());
// Get the added conversion area.
auto& area = ConvAreaCompStr.back();
// Get the added conversion area.
auto& area = ConvAreaCompStr.back();
// Write our text into the conversion area.
area.WriteText(lineVec, insertionPos.X);
area.WriteText(draw, pos.X);
// Set the viewport and positioning parameters for the conversion area to describe to the renderer
// the appropriate location to overlay this conversion area on top of the main screen buffer inside the viewport.
const SMALL_RECT region{ insertionPos.X, 0, gsl::narrow<SHORT>(insertionPos.X + lineVec.size() - 1), 0 };
area.SetWindowInfo(region);
area.SetViewPos({ 0 - view.Left(), insertionPos.Y - view.Top() });
// Set the viewport and positioning parameters for the conversion area to describe to the renderer
// the appropriate location to overlay this conversion area on top of the main screen buffer inside the viewport.
const SMALL_RECT region{ pos.X, 0, gsl::narrow<SHORT>(pos.X + draw._width - 1), 0 };
area.SetWindowInfo(region);
area.SetViewPos({ 0 - view.Left(), pos.Y - view.Top() });
// Make it visible and paint it.
area.SetHidden(false);
area.Paint();
// Make it visible and paint it.
area.SetHidden(false);
area.Paint();
// Notify accessibility that we have updated the text in this display region within the viewport.
if (screenInfo.HasAccessibilityEventing())
{
screenInfo.NotifyAccessibilityEventing(insertionPos.X, insertionPos.Y, gsl::narrow<SHORT>(insertionPos.X + lineVec.size() - 1), insertionPos.Y);
}
// Notify accessibility that we have updated the text in this display region within the viewport.
if (screenInfo.HasAccessibilityEventing())
{
screenInfo.NotifyAccessibilityEventing(pos.X, pos.Y, gsl::narrow<SHORT>(pos.X + draw._width - 1), pos.Y);
}
// Hand back the iterator representing the end of what we used to be fed into the beginning of the next call.
return lineEnd;
if (remain._data.empty())
break;
// Advance the cursor position to set up the next call for success (insert the next conversion area
// at the beginning of the following line)
pos.X = view.Left();
pos.Y++;
remain = std::move(left);
} while (true);
}
// Routine Description:
@ -432,17 +390,7 @@ void ConsoleImeInfo::_WriteUndeterminedChars(const std::wstring_view text,
const auto view = screenInfo.GetViewport();
// Set cursor position relative to viewport
// Set up our iterators. We will walk through the entire set of cells from beginning to end.
// The first time, we will give the iterators as the whole span and the begin
// will be moved forward by the conversion area write to set up the next call.
auto begin = cells.cbegin();
const auto end = cells.cend();
// Write over and over updating the beginning iterator until we reach the end.
do
{
begin = _WriteConversionArea(begin, end, pos, view, screenInfo);
} while (begin < end);
_WriteConversionArea(cells, pos, view, screenInfo);
}
// Routine Description:

View file

@ -22,6 +22,7 @@ Revision History:
#include "../buffer/out/OutputCell.hpp"
#include "../buffer/out/TextAttribute.hpp"
#include "../renderer/inc/FontInfo.hpp"
#include "../buffer/out/Row.hpp"
#include "../types/inc/viewport.hpp"
#include "conareainfo.h"
@ -73,15 +74,14 @@ private:
const gsl::span<const BYTE> attributes,
const gsl::span<const WORD> colorArray);
static std::vector<OutputCell> s_ConvertToCells(const std::wstring_view text,
const gsl::span<const BYTE> attributes,
const gsl::span<const WORD> colorArray);
static RowImage s_ConvertToCells(std::wstring_view text,
const gsl::span<const BYTE> attributes,
const gsl::span<const WORD> colorArray);
std::vector<OutputCell>::const_iterator _WriteConversionArea(const std::vector<OutputCell>::const_iterator begin,
const std::vector<OutputCell>::const_iterator end,
COORD& pos,
const Microsoft::Console::Types::Viewport view,
SCREEN_INFORMATION& screenInfo);
void _WriteConversionArea(const RowImage& cells,
COORD pos,
const Microsoft::Console::Types::Viewport view,
SCREEN_INFORMATION& screenInfo);
bool _isSavedCursorVisible;

View file

@ -1010,8 +1010,9 @@ void EventsToUnicode(_Inout_ std::deque<std::unique_ptr<IInputEvent>>& inEvents,
const auto charInfos = gsl::span<const CHAR_INFO>(subspan.data(), subspan.size());
// Make the iterator and write to the target position.
OutputCellIterator it(charInfos);
storageBuffer.Write(it, target);
__debugbreak();
//OutputCellIterator it(charInfos);
//storageBuffer.Write(it, target);
}
// Since we've managed to write part of the request, return the clamped part that we actually used.

View file

@ -2206,8 +2206,9 @@ void DoSrvPrivateMoveToBottom(SCREEN_INFORMATION& screenInfo)
fillAttrs.SetStandardErase();
}
const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillLength };
screenInfo.Write(fillData, startPosition, false);
//const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillLength };
//screenInfo.Write(fillData, startPosition, false);
screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(startPosition, fillLength, fillChar, fillAttrs);
// Notify accessibility
if (screenInfo.HasAccessibilityEventing())

View file

@ -104,8 +104,9 @@ static void _CopyRectangle(SCREEN_INFORMATION& screenInfo,
do
{
const auto data = OutputCell(*screenInfo.GetCellDataAt(sourcePos));
screenInfo.Write(OutputCellIterator({ &data, 1 }), targetPos);
__debugbreak();
//const auto data = OutputCell(*screenInfo.GetCellDataAt(sourcePos));
//screenInfo.Write(OutputCellIterator({ &data, 1 }), targetPos);
source.WalkInBounds(sourcePos, walkDirection);
} while (target.WalkInBounds(targetPos, walkDirection));
@ -339,8 +340,8 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
const SMALL_RECT scrollRectGiven,
const std::optional<SMALL_RECT> clipRectGiven,
const COORD destinationOriginGiven,
const wchar_t fillCharGiven,
const TextAttribute fillAttrsGiven)
wchar_t fillChar,
TextAttribute fillAttrs)
{
// ------ 1. PREP SOURCE ------
// Set up the source viewport.
@ -380,15 +381,12 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
return;
}
// Determine the cell we will use to fill in any revealed/uncovered space.
// We generally use exactly what was given to us.
OutputCellIterator fillData(fillCharGiven, fillAttrsGiven);
// However, if the character is null and we were given a null attribute (represented as legacy 0),
// then we'll just fill with spaces and whatever the buffer's default colors are.
if (fillCharGiven == UNICODE_NULL && fillAttrsGiven == TextAttribute{ 0 })
if (fillChar == UNICODE_NULL && fillAttrs == TextAttribute{ 0 })
{
fillData = OutputCellIterator(UNICODE_SPACE, screenInfo.GetAttributes());
fillChar = UNICODE_SPACE;
fillAttrs = screenInfo.GetAttributes();
}
// ------ 4. PREP TARGET ------
@ -447,10 +445,9 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
const auto remaining = Viewport::Subtract(fill, target);
// Apply the fill data to each of the viewports we're given here.
for (size_t i = 0; i < remaining.size(); i++)
for (const auto& view : remaining)
{
const auto& view = remaining.at(i);
screenInfo.WriteRect(fillData, view);
screenInfo.GetTextBuffer().FillWithCharacterAndAttributeRectangular(til::rectangle{ view.Origin(), til::size{ view.Dimensions() } }, fillChar, fillAttrs);
// If we're scrolling an area that encompasses the full buffer width,
// then the filled rows should also have their line rendition reset.

View file

@ -90,50 +90,46 @@ void Popup::_DrawBorder()
COORD WriteCoord;
WriteCoord.X = _region.Left;
WriteCoord.Y = _region.Top;
_screenInfo.Write(OutputCellIterator(_attributes, Width() + 2), WriteCoord);
_screenInfo.GetTextBuffer().FillWithAttributeRectangular(_region, _attributes);
// draw upper left corner
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_DOWN_AND_RIGHT, 1), WriteCoord);
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, 1, UNICODE_BOX_DRAW_LIGHT_DOWN_AND_RIGHT);
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, 1, UNICODE_BOX_DRAW_LIGHT_DOWN_AND_RIGHT);
// draw upper bar
WriteCoord.X += 1;
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_HORIZONTAL, Width()), WriteCoord);
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, Width(), UNICODE_BOX_DRAW_LIGHT_HORIZONTAL);
// draw upper right corner
WriteCoord.X = _region.Right;
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_DOWN_AND_LEFT, 1), WriteCoord);
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, 1, UNICODE_BOX_DRAW_LIGHT_DOWN_AND_LEFT);
for (SHORT i = 0; i < Height(); i++)
{
WriteCoord.Y += 1;
WriteCoord.X = _region.Left;
// fill attributes
_screenInfo.Write(OutputCellIterator(_attributes, Width() + 2), WriteCoord);
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_VERTICAL, 1), WriteCoord);
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, 1, UNICODE_BOX_DRAW_LIGHT_VERTICAL);
WriteCoord.X = _region.Right;
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_VERTICAL, 1), WriteCoord);
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, 1, UNICODE_BOX_DRAW_LIGHT_VERTICAL);
}
// Draw bottom line.
// Fill attributes of top line.
WriteCoord.X = _region.Left;
WriteCoord.Y = _region.Bottom;
_screenInfo.Write(OutputCellIterator(_attributes, Width() + 2), WriteCoord);
// Draw bottom left corner.
WriteCoord.X = _region.Left;
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_UP_AND_RIGHT, 1), WriteCoord);
WriteCoord.Y = _region.Bottom;
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, 1, UNICODE_BOX_DRAW_LIGHT_UP_AND_RIGHT);
// Draw lower bar.
WriteCoord.X += 1;
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_HORIZONTAL, Width()), WriteCoord);
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, Width(), UNICODE_BOX_DRAW_LIGHT_HORIZONTAL);
// draw lower right corner
WriteCoord.X = _region.Right;
_screenInfo.Write(OutputCellIterator(UNICODE_BOX_DRAW_LIGHT_UP_AND_LEFT, 1), WriteCoord);
_screenInfo.GetTextBuffer().FillWithCharacterLinear(WriteCoord, 1, UNICODE_BOX_DRAW_LIGHT_UP_AND_LEFT);
}
// Routine Description:
@ -151,9 +147,7 @@ void Popup::_DrawPrompt(const UINT id)
size_t lStringLength = Width();
for (SHORT i = 0; i < Height(); i++)
{
const OutputCellIterator it(UNICODE_SPACE, _attributes, lStringLength);
const auto done = _screenInfo.Write(it, WriteCoord);
lStringLength = done.GetCellDistance(it);
_screenInfo.GetTextBuffer().FillWithCharacterAndAttributeLinear(WriteCoord, lStringLength, UNICODE_SPACE, _attributes);
WriteCoord.Y += 1;
}

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"
@ -2251,10 +2250,10 @@ void SCREEN_INFORMATION::SetViewport(const Viewport& newViewport,
// i.e. the current background color, but with no meta attributes set.
auto fillAttributes = GetAttributes();
fillAttributes.SetStandardErase();
auto fillPosition = COORD{ 0, _viewport.Top() };
auto fillLength = gsl::narrow_cast<size_t>(_viewport.Height() * GetBufferSize().Width());
auto fillData = OutputCellIterator{ fillAttributes, fillLength };
Write(fillData, fillPosition, false);
// TODO(DH): The original code moved Left to 0 and Right to Width
_textBuffer->FillWithAttributeRectangular(til::rectangle{ _viewport.Origin(), til::size{ _viewport.Dimensions() } }, fillAttributes);
// TODO(DH): THIS ALSO CLEARED THE WRAP ON ALL ROWS (!)
//Write(fillData, fillPosition, false);
// Also reset the line rendition for the erased rows.
_textBuffer->ResetLineRenditionRange(_viewport.Top(), _viewport.BottomExclusive());
@ -2335,37 +2334,8 @@ OutputCellRect SCREEN_INFORMATION::ReadRect(const Viewport viewport) const
return result;
}
// Routine Description:
// - Writes cells to the output buffer at the cursor position.
// Arguments:
// - it - Iterator representing output cell data to write.
// Return Value:
// - the iterator at its final position
// Note:
// - will throw exception on error.
OutputCellIterator SCREEN_INFORMATION::Write(const OutputCellIterator it)
{
return _textBuffer->Write(it);
}
// Routine Description:
// - Writes cells to the output buffer.
// Arguments:
// - it - Iterator representing output cell data to write.
// - target - The position to start writing at
// - wrap - change the wrap flag if we hit the end of the row while writing and there's still more data
// Return Value:
// - the iterator at its final position
// Note:
// - will throw exception on error.
OutputCellIterator SCREEN_INFORMATION::Write(const OutputCellIterator it,
const COORD target,
const std::optional<bool> wrap)
{
// NOTE: if wrap = true/false, we want to set the line's wrap to true/false (respectively) if we reach the end of the line
return _textBuffer->Write(it, target, wrap);
}
#if 0
TODO(DH)
// Routine Description:
// - This routine writes a rectangular region into the screen buffer.
// Arguments:
@ -2389,6 +2359,7 @@ OutputCellIterator SCREEN_INFORMATION::WriteRect(const OutputCellIterator it,
return iter;
}
#endif
// Routine Description:
// - This routine writes a rectangular region into the screen buffer.
@ -2402,6 +2373,10 @@ OutputCellIterator SCREEN_INFORMATION::WriteRect(const OutputCellIterator it,
void SCREEN_INFORMATION::WriteRect(const OutputCellRect& data,
const COORD location)
{
(void)data;
(void)location;
__debugbreak();
#if 0
for (size_t i = 0; i < data.Height(); i++)
{
const auto iter = data.GetRowIter(i);
@ -2410,8 +2385,10 @@ void SCREEN_INFORMATION::WriteRect(const OutputCellRect& data,
point.X = location.X;
point.Y = location.Y + static_cast<short>(i);
_textBuffer->WriteLine(iter, point);
__debugbreak();
//_textBuffer->WriteLine(iter, point);
}
#endif
}
// Routine Description:

View file

@ -135,14 +135,7 @@ public:
TextBufferTextIterator GetTextLineDataAt(const COORD at) const;
TextBufferTextIterator GetTextDataAt(const COORD at, const Microsoft::Console::Types::Viewport limit) const;
OutputCellIterator Write(const OutputCellIterator it);
OutputCellIterator Write(const OutputCellIterator it,
const COORD target,
const std::optional<bool> wrap = true);
OutputCellIterator WriteRect(const OutputCellIterator it,
const Microsoft::Console::Types::Viewport viewport);
size_t WriteMeasuredStringLinear(std::wstring_view string, const RowMeasurementBuffer& measurements);
void WriteRect(const OutputCellRect& data,
const COORD location);

View file

@ -388,25 +388,7 @@ void Selection::ColorSelection(const SMALL_RECT& srRect, const TextAttribute att
// Read selection rectangle, assumed already clipped to buffer.
SCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer();
COORD coordTargetSize;
coordTargetSize.X = CalcWindowSizeX(srRect);
coordTargetSize.Y = CalcWindowSizeY(srRect);
COORD coordTarget;
coordTarget.X = srRect.Left;
coordTarget.Y = srRect.Top;
// Now color the selection a line at a time.
for (; (coordTarget.Y < srRect.Top + coordTargetSize.Y); ++coordTarget.Y)
{
const size_t cchWrite = gsl::narrow<size_t>(coordTargetSize.X);
try
{
screenInfo.Write(OutputCellIterator(attr, cchWrite), coordTarget);
}
CATCH_LOG();
}
screenInfo.GetTextBuffer().FillWithAttributeRectangular(srRect, attr);
}
// Routine Description:

View file

@ -1,514 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "WexTestClass.h"
#include "../../inc/consoletaeftemplates.hpp"
#include "../buffer/out/outputCellIterator.hpp"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
static constexpr TextAttribute InvalidTextAttribute{ INVALID_COLOR, INVALID_COLOR };
class OutputCellIteratorTests
{
CommonState* m_state;
TEST_CLASS(OutputCellIteratorTests);
TEST_METHOD(CharacterFillDoubleWidth)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const wchar_t wch = L'\x30a2'; // katakana A
const size_t limit = 5;
OutputCellIterator it(wch, limit);
OutputCellView expectedLead({ &wch, 1 },
DbcsAttribute(DbcsAttribute::Attribute::Leading),
InvalidTextAttribute,
TextAttributeBehavior::Current);
OutputCellView expectedTrail({ &wch, 1 },
DbcsAttribute(DbcsAttribute::Attribute::Trailing),
InvalidTextAttribute,
TextAttributeBehavior::Current);
for (size_t i = 0; i < limit; i++)
{
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expectedLead, *it);
it++;
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expectedTrail, *it);
it++;
}
VERIFY_IS_FALSE(it);
}
TEST_METHOD(CharacterFillLimited)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const wchar_t wch = L'Q';
const size_t limit = 5;
OutputCellIterator it(wch, limit);
OutputCellView expected({ &wch, 1 },
DbcsAttribute{},
InvalidTextAttribute,
TextAttributeBehavior::Current);
for (size_t i = 0; i < limit; i++)
{
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
}
VERIFY_IS_FALSE(it);
}
TEST_METHOD(CharacterFillUnlimited)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const wchar_t wch = L'Q';
OutputCellIterator it(wch);
OutputCellView expected({ &wch, 1 },
DbcsAttribute{},
InvalidTextAttribute,
TextAttributeBehavior::Current);
for (size_t i = 0; i < SHORT_MAX; i++)
{
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
}
VERIFY_IS_TRUE(it);
}
TEST_METHOD(AttributeFillLimited)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const TextAttribute attr(FOREGROUND_RED | BACKGROUND_BLUE);
const size_t limit = 5;
OutputCellIterator it(attr, limit);
OutputCellView expected({},
{},
attr,
TextAttributeBehavior::StoredOnly);
for (size_t i = 0; i < limit; i++)
{
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
}
VERIFY_IS_FALSE(it);
}
TEST_METHOD(AttributeFillUnlimited)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const TextAttribute attr(FOREGROUND_RED | BACKGROUND_BLUE);
OutputCellIterator it(attr);
OutputCellView expected({},
{},
attr,
TextAttributeBehavior::StoredOnly);
for (size_t i = 0; i < SHORT_MAX; i++)
{
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
}
VERIFY_IS_TRUE(it);
}
TEST_METHOD(TextAndAttributeFillLimited)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const wchar_t wch = L'Q';
const TextAttribute attr(FOREGROUND_RED | BACKGROUND_BLUE);
const size_t limit = 5;
OutputCellIterator it(wch, attr, limit);
OutputCellView expected({ &wch, 1 },
{},
attr,
TextAttributeBehavior::Stored);
for (size_t i = 0; i < limit; i++)
{
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
}
VERIFY_IS_FALSE(it);
}
TEST_METHOD(TextAndAttributeFillUnlimited)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const wchar_t wch = L'Q';
const TextAttribute attr(FOREGROUND_RED | BACKGROUND_BLUE);
OutputCellIterator it(wch, attr);
OutputCellView expected({ &wch, 1 },
{},
attr,
TextAttributeBehavior::Stored);
for (size_t i = 0; i < SHORT_MAX; i++)
{
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
}
VERIFY_IS_TRUE(it);
}
TEST_METHOD(CharInfoFillLimited)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
CHAR_INFO ci;
ci.Char.UnicodeChar = L'Q';
ci.Attributes = FOREGROUND_RED | BACKGROUND_BLUE;
const size_t limit = 5;
OutputCellIterator it(ci, limit);
OutputCellView expected({ &ci.Char.UnicodeChar, 1 },
{},
TextAttribute(ci.Attributes),
TextAttributeBehavior::Stored);
for (size_t i = 0; i < limit; i++)
{
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
}
VERIFY_IS_FALSE(it);
}
TEST_METHOD(CharInfoFillUnlimited)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
CHAR_INFO ci;
ci.Char.UnicodeChar = L'Q';
ci.Attributes = FOREGROUND_RED | BACKGROUND_BLUE;
OutputCellIterator it(ci);
OutputCellView expected({ &ci.Char.UnicodeChar, 1 },
{},
TextAttribute(ci.Attributes),
TextAttributeBehavior::Stored);
for (size_t i = 0; i < SHORT_MAX; i++)
{
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
}
VERIFY_IS_TRUE(it);
}
TEST_METHOD(StringData)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const std::wstring testText(L"The quick brown fox jumps over the lazy dog.");
OutputCellIterator it(testText);
for (const auto& wch : testText)
{
OutputCellView expected({ &wch, 1 },
{},
InvalidTextAttribute,
TextAttributeBehavior::Current);
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
}
VERIFY_IS_FALSE(it);
}
TEST_METHOD(FullWidthStringData)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const std::wstring testText(L"\x30a2\x30a3\x30a4\x30a5\x30a6");
OutputCellIterator it(testText);
for (const auto& wch : testText)
{
auto expected = OutputCellView({ &wch, 1 },
DbcsAttribute(DbcsAttribute::Attribute::Leading),
InvalidTextAttribute,
TextAttributeBehavior::Current);
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
expected = OutputCellView({ &wch, 1 },
DbcsAttribute(DbcsAttribute::Attribute::Trailing),
InvalidTextAttribute,
TextAttributeBehavior::Current);
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
}
VERIFY_IS_FALSE(it);
}
TEST_METHOD(StringDataWithColor)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const std::wstring testText(L"The quick brown fox jumps over the lazy dog.");
const TextAttribute color(FOREGROUND_GREEN | FOREGROUND_INTENSITY);
OutputCellIterator it(testText, color);
for (const auto& wch : testText)
{
OutputCellView expected({ &wch, 1 },
{},
color,
TextAttributeBehavior::Stored);
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
}
VERIFY_IS_FALSE(it);
}
TEST_METHOD(FullWidthStringDataWithColor)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const std::wstring testText(L"\x30a2\x30a3\x30a4\x30a5\x30a6");
const TextAttribute color(FOREGROUND_GREEN | FOREGROUND_INTENSITY);
OutputCellIterator it(testText, color);
for (const auto& wch : testText)
{
auto expected = OutputCellView({ &wch, 1 },
DbcsAttribute(DbcsAttribute::Attribute::Leading),
color,
TextAttributeBehavior::Stored);
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
expected = OutputCellView({ &wch, 1 },
DbcsAttribute(DbcsAttribute::Attribute::Trailing),
color,
TextAttributeBehavior::Stored);
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
}
VERIFY_IS_FALSE(it);
}
TEST_METHOD(LegacyColorDataRun)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const std::vector<WORD> colors{ FOREGROUND_GREEN, FOREGROUND_RED | BACKGROUND_BLUE, FOREGROUND_BLUE | FOREGROUND_INTENSITY, BACKGROUND_GREEN };
const gsl::span<const WORD> view{ colors.data(), colors.size() };
OutputCellIterator it(view);
for (const auto& color : colors)
{
auto expected = OutputCellView({},
{},
TextAttribute{ color },
TextAttributeBehavior::StoredOnly);
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
}
VERIFY_IS_FALSE(it);
}
TEST_METHOD(LegacyCharInfoRun)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
std::vector<CHAR_INFO> charInfos;
for (auto i = 0; i < 5; i++)
{
CHAR_INFO ci;
ci.Char.UnicodeChar = static_cast<wchar_t>(L'A' + i);
ci.Attributes = gsl::narrow<WORD>(i);
charInfos.push_back(ci);
}
const gsl::span<const CHAR_INFO> view{ charInfos.data(), charInfos.size() };
OutputCellIterator it(view);
for (const auto& ci : charInfos)
{
auto expected = OutputCellView({ &ci.Char.UnicodeChar, 1 },
{},
TextAttribute{ ci.Attributes },
TextAttributeBehavior::Stored);
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
}
VERIFY_IS_FALSE(it);
}
TEST_METHOD(OutputCellRun)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
std::vector<OutputCell> cells;
for (auto i = 0; i < 5; i++)
{
const std::wstring pair(L"\xd834\xdd1e");
OutputCell cell(pair, {}, TextAttribute{ gsl::narrow<WORD>(i) });
cells.push_back(cell);
}
const gsl::span<const OutputCell> view{ cells.data(), cells.size() };
OutputCellIterator it(view);
for (const auto& cell : cells)
{
auto expected = OutputCellView(cell.Chars(),
cell.DbcsAttr(),
cell.TextAttr(),
cell.TextAttrBehavior());
VERIFY_IS_TRUE(it);
VERIFY_ARE_EQUAL(expected, *it);
it++;
}
VERIFY_IS_FALSE(it);
}
TEST_METHOD(DistanceStandard)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const std::wstring testText(L"The quick brown fox jumps over the lazy dog.");
OutputCellIterator it(testText);
const auto original = it;
ptrdiff_t expected = 0;
for (const auto& wch : testText)
{
wch; // unused
VERIFY_IS_TRUE(it);
it++;
expected++;
}
VERIFY_IS_FALSE(it);
VERIFY_ARE_EQUAL(expected, it.GetCellDistance(original));
VERIFY_ARE_EQUAL(expected, it.GetInputDistance(original));
}
TEST_METHOD(DistanceFullWidth)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
const std::wstring testText(L"QWER\x30a2\x30a3\x30a4\x30a5\x30a6TYUI");
OutputCellIterator it(testText);
const auto original = it;
ptrdiff_t cellsExpected = 0;
ptrdiff_t inputExpected = 0;
for (const auto& wch : testText)
{
wch; // unused
VERIFY_IS_TRUE(it);
const auto value = *it;
it++;
if (value.DbcsAttr().IsLeading() || value.DbcsAttr().IsTrailing())
{
VERIFY_IS_TRUE(it);
it++;
cellsExpected++;
}
cellsExpected++;
inputExpected++;
}
VERIFY_IS_FALSE(it);
VERIFY_ARE_EQUAL(cellsExpected, it.GetCellDistance(original));
VERIFY_ARE_EQUAL(inputExpected, it.GetInputDistance(original));
}
};

View file

@ -93,8 +93,6 @@ class TextBufferTests
TEST_METHOD(TestCopyProperties);
TEST_METHOD(TestInsertCharacter);
TEST_METHOD(TestIncrementCursor);
TEST_METHOD(TestNewlineCursor);
@ -386,50 +384,6 @@ void TextBufferTests::TestCopyProperties()
VERIFY_IS_TRUE(testTextBuffer->GetCursor().GetDelay());
}
void TextBufferTests::TestInsertCharacter()
{
TextBuffer& textBuffer = GetTbi();
// get starting cursor position
COORD const coordCursorBefore = textBuffer.GetCursor().GetPosition();
// Get current row from the buffer
ROW& Row = textBuffer.GetRowByOffset(coordCursorBefore.Y);
// create some sample test data
const auto wch = L'Z';
const std::wstring_view wchTest(&wch, 1);
DbcsAttribute dbcsAttribute;
dbcsAttribute.SetTrailing();
WORD const wAttrTest = BACKGROUND_INTENSITY | FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE;
TextAttribute TestAttributes = TextAttribute(wAttrTest);
CharRow& charRow = Row.GetCharRow();
charRow.DbcsAttrAt(coordCursorBefore.X).SetLeading();
// ensure that the buffer didn't start with these fields
VERIFY_ARE_NOT_EQUAL(charRow.GlyphAt(coordCursorBefore.X), wchTest);
VERIFY_ARE_NOT_EQUAL(charRow.DbcsAttrAt(coordCursorBefore.X), dbcsAttribute);
auto attr = Row.GetAttrRow().GetAttrByColumn(coordCursorBefore.X);
VERIFY_ARE_NOT_EQUAL(attr, TestAttributes);
// now apply the new data to the buffer
textBuffer.InsertCharacter(wchTest, dbcsAttribute, TestAttributes);
// ensure that the buffer position where the cursor WAS contains the test items
VERIFY_ARE_EQUAL(charRow.GlyphAt(coordCursorBefore.X), wchTest);
VERIFY_ARE_EQUAL(charRow.DbcsAttrAt(coordCursorBefore.X), dbcsAttribute);
attr = Row.GetAttrRow().GetAttrByColumn(coordCursorBefore.X);
VERIFY_ARE_EQUAL(attr, TestAttributes);
// ensure that the cursor moved to a new position (X or Y or both have changed)
VERIFY_IS_TRUE((coordCursorBefore.X != textBuffer.GetCursor().GetPosition().X) ||
(coordCursorBefore.Y != textBuffer.GetCursor().GetPosition().Y));
// the proper advancement of the cursor (e.g. which position it goes to) is validated in other tests
}
void TextBufferTests::TestIncrementCursor()
{
TextBuffer& textBuffer = GetTbi();

View file

@ -520,6 +520,22 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
_total_length = new_size;
}
void append(const value_type& value)
{
++_total_length;
if (!_runs.empty())
{
auto& back{ _runs.back() };
if (back.value == value)
{
++back.length;
return;
}
}
// Either runs was empty, or we have to append a new run.
_runs.emplace_back(value, S{ 1 });
}
constexpr bool operator==(const basic_rle& other) const noexcept
{
return _total_length == other._total_length && _runs == other._runs;

View file

@ -23,7 +23,6 @@ Author(s):
#include "thread.hpp"
#include "../../buffer/out/textBuffer.hpp"
#include "../../buffer/out/CharRow.hpp"
namespace Microsoft::Console::Render
{

View file

@ -26,7 +26,7 @@ namespace Microsoft::Console::Types
~Viewport() {}
constexpr Viewport() noexcept :
_sr({ 0, 0, -1, -1 }){};
Viewport(const Viewport& other) noexcept;
Viewport(const Viewport& other) = default;
Viewport(Viewport&&) = default;
Viewport& operator=(const Viewport&) & = default;
Viewport& operator=(Viewport&&) & = default;

View file

@ -11,11 +11,6 @@ Viewport::Viewport(const SMALL_RECT sr) noexcept :
{
}
Viewport::Viewport(const Viewport& other) noexcept :
_sr(other._sr)
{
}
Viewport Viewport::Empty() noexcept
{
return Viewport();