mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-19 18:11:39 -05:00
Initialize rows lazily (#15524)
For a 120x9001 terminal, a01500f reduced the private working set of
conhost by roughly 0.7MB, presumably due to tighter `ROW` packing, but
also increased it by 2.1MB due to the addition of the `_charOffsets`
array on each `ROW` instance. An option to fix this would be to only
allocate a `_charOffsets` if the first wide or complex Unicode glyph
is encountered. But on one hand this would be quite western-centric
and unfairly hurt most languages that exist and on another we can get
rid of the `_charOffsets` array entirely in the future by injecting
ZWNJs if a write begins with a combining glyph and just recount each
row from the start. That's still faster than fragmented memory.
This commit goes a different way and instead reduces the working
set of conhost after it launches from 7MB down to just 2MB,
by only committing ROWs when they're first used.
Finally, it adds a "scratchpad" row which can be used to build
more complex contents, for instance to horizontally scroll them.
## Validation Steps Performed
* Traditional resize
* Horizontal shrinking works ✅
* Vertical shrinking works ✅ and cursor stays in the viewport ✅
* Reflow works ✅
* Filling the buffer with ASCII works ✅ and no leaks ✅
* Filling the buffer with complex Unicode works ✅ and no leaks ✅
* `^[[3J` erases scrollback ✅
* Test `ScrollRows` with a positive delta ✅
* I don't know how to test `Reset`. ❔ Unit tests use it though
This commit is contained in:
1
.github/actions/spelling/expect/expect.txt
vendored
1
.github/actions/spelling/expect/expect.txt
vendored
@@ -440,6 +440,7 @@ DECMSR
|
|||||||
DECNKM
|
DECNKM
|
||||||
DECNRCM
|
DECNRCM
|
||||||
DECOM
|
DECOM
|
||||||
|
decommit
|
||||||
DECPCTERM
|
DECPCTERM
|
||||||
DECPS
|
DECPS
|
||||||
DECRARA
|
DECRARA
|
||||||
|
|||||||
@@ -81,12 +81,9 @@ ROW::ROW(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, c
|
|||||||
_charOffsets{ charOffsetsBuffer, ::base::strict_cast<size_t>(rowWidth) + 1u },
|
_charOffsets{ charOffsetsBuffer, ::base::strict_cast<size_t>(rowWidth) + 1u },
|
||||||
_attr{ rowWidth, fillAttribute },
|
_attr{ rowWidth, fillAttribute },
|
||||||
_columnCount{ rowWidth }
|
_columnCount{ rowWidth }
|
||||||
{
|
|
||||||
if (_chars.data())
|
|
||||||
{
|
{
|
||||||
_init();
|
_init();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void ROW::SetWrapForced(const bool wrap) noexcept
|
void ROW::SetWrapForced(const bool wrap) noexcept
|
||||||
{
|
{
|
||||||
@@ -147,6 +144,15 @@ void ROW::TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& a
|
|||||||
_attr.resize_trailing_extent(gsl::narrow<uint16_t>(newWidth));
|
_attr.resize_trailing_extent(gsl::narrow<uint16_t>(newWidth));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ROW::CopyFrom(const ROW& source)
|
||||||
|
{
|
||||||
|
til::CoordType begin = 0;
|
||||||
|
CopyTextFrom(0, til::CoordTypeMax, source, begin, til::CoordTypeMax);
|
||||||
|
TransferAttributes(source.Attributes(), _columnCount);
|
||||||
|
_lineRendition = source._lineRendition;
|
||||||
|
_wrapForced = source._wrapForced;
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the previous possible cursor position, preceding the given column.
|
// Returns the previous possible cursor position, preceding the given column.
|
||||||
// Returns 0 if column is less than or equal to 0.
|
// Returns 0 if column is less than or equal to 0.
|
||||||
til::CoordType ROW::NavigateToPrevious(til::CoordType column) const noexcept
|
til::CoordType ROW::NavigateToPrevious(til::CoordType column) const noexcept
|
||||||
@@ -445,7 +451,7 @@ catch (...)
|
|||||||
charsConsumed = ch - chBeg;
|
charsConsumed = ch - chBeg;
|
||||||
}
|
}
|
||||||
|
|
||||||
til::CoordType ROW::CopyRangeFrom(til::CoordType columnBegin, til::CoordType columnLimit, const ROW& other, til::CoordType& otherBegin, til::CoordType otherLimit)
|
til::CoordType ROW::CopyTextFrom(til::CoordType columnBegin, til::CoordType columnLimit, const ROW& other, til::CoordType& otherBegin, til::CoordType otherLimit)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const auto otherColBeg = other._clampedColumnInclusive(otherBegin);
|
const auto otherColBeg = other._clampedColumnInclusive(otherBegin);
|
||||||
@@ -464,8 +470,11 @@ try
|
|||||||
}
|
}
|
||||||
|
|
||||||
WriteHelper h{ *this, columnBegin, columnLimit, chars };
|
WriteHelper h{ *this, columnBegin, columnLimit, chars };
|
||||||
if (!h.IsValid())
|
// If we were to copy text from ourselves, we'd overwrite
|
||||||
|
// our _charOffsets and break Finish() which reads from it.
|
||||||
|
if (!h.IsValid() || this == &other)
|
||||||
{
|
{
|
||||||
|
assert(false); // You probably shouldn't call this function in the first place.
|
||||||
return h.colBeg;
|
return h.colBeg;
|
||||||
}
|
}
|
||||||
// Any valid charOffsets array is at least 2 elements long (the 1st element is the start offset and the 2nd
|
// Any valid charOffsets array is at least 2 elements long (the 1st element is the start offset and the 2nd
|
||||||
@@ -477,7 +486,7 @@ try
|
|||||||
otherBegin = other.size();
|
otherBegin = other.size();
|
||||||
return h.colBeg;
|
return h.colBeg;
|
||||||
}
|
}
|
||||||
h.CopyRangeFrom(charOffsets);
|
h.CopyTextFrom(charOffsets);
|
||||||
h.Finish();
|
h.Finish();
|
||||||
|
|
||||||
otherBegin += h.colEnd - h.colBeg;
|
otherBegin += h.colEnd - h.colBeg;
|
||||||
@@ -489,7 +498,7 @@ catch (...)
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[msvc::forceinline]] void ROW::WriteHelper::CopyRangeFrom(const std::span<const uint16_t>& charOffsets) noexcept
|
[[msvc::forceinline]] void ROW::WriteHelper::CopyTextFrom(const std::span<const uint16_t>& charOffsets) noexcept
|
||||||
{
|
{
|
||||||
// Since our `charOffsets` input is already in columns (just like the `ROW::_charOffsets`),
|
// Since our `charOffsets` input is already in columns (just like the `ROW::_charOffsets`),
|
||||||
// we can directly look up the end char-offset, but...
|
// we can directly look up the end char-offset, but...
|
||||||
|
|||||||
@@ -60,6 +60,26 @@ struct RowWriteState
|
|||||||
class ROW final
|
class ROW final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
// The implicit agreement between ROW and TextBuffer is that TextBuffer supplies ROW with a charsBuffer of at
|
||||||
|
// least `columns * sizeof(wchar_t)` bytes and a charOffsetsBuffer of at least `(columns + 1) * sizeof(uint16_t)`
|
||||||
|
// bytes (see ROW::_charOffsets for why it needs space for 1 additional offset).
|
||||||
|
// These methods exists to make this agreement explicit and serve as a reminder.
|
||||||
|
//
|
||||||
|
// TextBuffer calculates the distance in bytes between two ROWs (_bufferRowStride) as the sum of these values.
|
||||||
|
// As such it's important that we return sizes with a minimum alignment of alignof(ROW).
|
||||||
|
static constexpr size_t CalculateRowSize() noexcept
|
||||||
|
{
|
||||||
|
return sizeof(ROW);
|
||||||
|
}
|
||||||
|
static constexpr size_t CalculateCharsBufferSize(size_t columns) noexcept
|
||||||
|
{
|
||||||
|
return (columns * sizeof(wchar_t) + 16) & ~15;
|
||||||
|
}
|
||||||
|
static constexpr size_t CalculateCharOffsetsBufferSize(size_t columns) noexcept
|
||||||
|
{
|
||||||
|
return (columns * sizeof(uint16_t) + 16) & ~15;
|
||||||
|
}
|
||||||
|
|
||||||
ROW() = default;
|
ROW() = default;
|
||||||
ROW(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute);
|
ROW(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute);
|
||||||
|
|
||||||
@@ -78,6 +98,7 @@ public:
|
|||||||
|
|
||||||
void Reset(const TextAttribute& attr);
|
void Reset(const TextAttribute& attr);
|
||||||
void TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& attr, til::CoordType newWidth);
|
void TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& attr, til::CoordType newWidth);
|
||||||
|
void CopyFrom(const ROW& source);
|
||||||
|
|
||||||
til::CoordType NavigateToPrevious(til::CoordType column) const noexcept;
|
til::CoordType NavigateToPrevious(til::CoordType column) const noexcept;
|
||||||
til::CoordType NavigateToNext(til::CoordType column) const noexcept;
|
til::CoordType NavigateToNext(til::CoordType column) const noexcept;
|
||||||
@@ -88,7 +109,7 @@ public:
|
|||||||
void ReplaceAttributes(til::CoordType beginIndex, til::CoordType endIndex, const TextAttribute& newAttr);
|
void ReplaceAttributes(til::CoordType beginIndex, til::CoordType endIndex, const TextAttribute& newAttr);
|
||||||
void ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars);
|
void ReplaceCharacters(til::CoordType columnBegin, til::CoordType width, const std::wstring_view& chars);
|
||||||
void ReplaceText(RowWriteState& state);
|
void ReplaceText(RowWriteState& state);
|
||||||
til::CoordType CopyRangeFrom(til::CoordType columnBegin, til::CoordType columnLimit, const ROW& other, til::CoordType& otherBegin, til::CoordType otherLimit);
|
til::CoordType CopyTextFrom(til::CoordType columnBegin, til::CoordType columnLimit, const ROW& other, til::CoordType& otherBegin, til::CoordType otherLimit);
|
||||||
|
|
||||||
til::small_rle<TextAttribute, uint16_t, 1>& Attributes() noexcept;
|
til::small_rle<TextAttribute, uint16_t, 1>& Attributes() noexcept;
|
||||||
const til::small_rle<TextAttribute, uint16_t, 1>& Attributes() const noexcept;
|
const til::small_rle<TextAttribute, uint16_t, 1>& Attributes() const noexcept;
|
||||||
@@ -121,7 +142,7 @@ private:
|
|||||||
bool IsValid() const noexcept;
|
bool IsValid() const noexcept;
|
||||||
void ReplaceCharacters(til::CoordType width) noexcept;
|
void ReplaceCharacters(til::CoordType width) noexcept;
|
||||||
void ReplaceText() noexcept;
|
void ReplaceText() noexcept;
|
||||||
void CopyRangeFrom(const std::span<const uint16_t>& charOffsets) noexcept;
|
void CopyTextFrom(const std::span<const uint16_t>& charOffsets) noexcept;
|
||||||
void Finish();
|
void Finish();
|
||||||
|
|
||||||
// Parent pointer.
|
// Parent pointer.
|
||||||
|
|||||||
@@ -42,10 +42,166 @@ TextBuffer::TextBuffer(til::size screenBufferSize,
|
|||||||
// Guard against resizing the text buffer to 0 columns/rows, which would break being able to insert text.
|
// Guard against resizing the text buffer to 0 columns/rows, which would break being able to insert text.
|
||||||
screenBufferSize.width = std::max(screenBufferSize.width, 1);
|
screenBufferSize.width = std::max(screenBufferSize.width, 1);
|
||||||
screenBufferSize.height = std::max(screenBufferSize.height, 1);
|
screenBufferSize.height = std::max(screenBufferSize.height, 1);
|
||||||
_charBuffer = _allocateBuffer(screenBufferSize, _currentAttributes, _storage);
|
_reserve(screenBufferSize, defaultAttributes);
|
||||||
_UpdateSize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextBuffer::~TextBuffer()
|
||||||
|
{
|
||||||
|
if (_buffer)
|
||||||
|
{
|
||||||
|
_destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// I put these functions in a block at the start of the class, because they're the most
|
||||||
|
// fundamental aspect of TextBuffer: It implements the basic gap buffer text storage.
|
||||||
|
// It's also fairly tricky code.
|
||||||
|
#pragma region buffer management
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
|
||||||
|
#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1).
|
||||||
|
|
||||||
|
// MEM_RESERVEs memory sufficient to store height-many ROW structs,
|
||||||
|
// as well as their ROW::_chars and ROW::_charOffsets buffers.
|
||||||
|
//
|
||||||
|
// We use explicit virtual memory allocations to not taint the general purpose allocator
|
||||||
|
// with our huge allocation, as well as to be able to reduce the private working set of
|
||||||
|
// the application by only committing what we actually need. This reduces conhost's
|
||||||
|
// memory usage from ~7MB down to just ~2MB at startup in the general case.
|
||||||
|
void TextBuffer::_reserve(til::size screenBufferSize, const TextAttribute& defaultAttributes)
|
||||||
|
{
|
||||||
|
const auto w = gsl::narrow<uint16_t>(screenBufferSize.width);
|
||||||
|
const auto h = gsl::narrow<uint16_t>(screenBufferSize.height);
|
||||||
|
|
||||||
|
constexpr auto rowSize = ROW::CalculateRowSize();
|
||||||
|
const auto charsBufferSize = ROW::CalculateCharsBufferSize(w);
|
||||||
|
const auto charOffsetsBufferSize = ROW::CalculateCharOffsetsBufferSize(w);
|
||||||
|
const auto rowStride = rowSize + charsBufferSize + charOffsetsBufferSize;
|
||||||
|
assert(rowStride % alignof(ROW) == 0);
|
||||||
|
|
||||||
|
// 65535*65535 cells would result in a allocSize of 8GiB.
|
||||||
|
// --> Use uint64_t so that we can safely do our calculations even on x86.
|
||||||
|
// We allocate 1 additional row, which will be used for GetScratchpadRow().
|
||||||
|
const auto rowCount = ::base::strict_cast<uint64_t>(h) + 1;
|
||||||
|
const auto allocSize = gsl::narrow<size_t>(rowCount * rowStride);
|
||||||
|
|
||||||
|
// NOTE: Modifications to this block of code might have to be mirrored over to ResizeTraditional().
|
||||||
|
// It constructs a temporary TextBuffer and then extracts the members below, overwriting itself.
|
||||||
|
_buffer = wil::unique_virtualalloc_ptr<std::byte>{
|
||||||
|
static_cast<std::byte*>(THROW_LAST_ERROR_IF_NULL(VirtualAlloc(nullptr, allocSize, MEM_RESERVE, PAGE_READWRITE)))
|
||||||
|
};
|
||||||
|
_bufferEnd = _buffer.get() + allocSize;
|
||||||
|
_commitWatermark = _buffer.get();
|
||||||
|
_initialAttributes = defaultAttributes;
|
||||||
|
_bufferRowStride = rowStride;
|
||||||
|
_bufferOffsetChars = rowSize;
|
||||||
|
_bufferOffsetCharOffsets = rowSize + charsBufferSize;
|
||||||
|
_width = w;
|
||||||
|
_height = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MEM_COMMITs the memory and constructs all ROWs up to and including the given row pointer.
|
||||||
|
// It's expected that the caller verifies the parameter. It goes hand in hand with _getRowByOffsetDirect().
|
||||||
|
//
|
||||||
|
// Declaring this function as noinline allows _getRowByOffsetDirect() to be inlined,
|
||||||
|
// which improves overall TextBuffer performance by ~6%. And all it cost is this annotation.
|
||||||
|
// The compiler doesn't understand the likelihood of our branches. (PGO does, but that's imperfect.)
|
||||||
|
__declspec(noinline) void TextBuffer::_commit(const std::byte* row)
|
||||||
|
{
|
||||||
|
const auto rowEnd = row + _bufferRowStride;
|
||||||
|
const auto remaining = gsl::narrow_cast<uintptr_t>(_bufferEnd - _commitWatermark);
|
||||||
|
const auto minimum = gsl::narrow_cast<uintptr_t>(rowEnd - _commitWatermark);
|
||||||
|
const auto ideal = minimum + _bufferRowStride * _commitReadAheadRowCount;
|
||||||
|
const auto size = std::min(remaining, ideal);
|
||||||
|
|
||||||
|
THROW_LAST_ERROR_IF_NULL(VirtualAlloc(_commitWatermark, size, MEM_COMMIT, PAGE_READWRITE));
|
||||||
|
|
||||||
|
_construct(_commitWatermark + size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destructs and MEM_DECOMMITs all previously constructed ROWs.
|
||||||
|
// You can use this (or rather the Reset() method) to fully clear the TextBuffer.
|
||||||
|
void TextBuffer::_decommit() noexcept
|
||||||
|
{
|
||||||
|
_destroy();
|
||||||
|
VirtualFree(_buffer.get(), 0, MEM_DECOMMIT);
|
||||||
|
_commitWatermark = _buffer.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructs ROWs up to (excluding) the ROW pointed to by `until`.
|
||||||
|
void TextBuffer::_construct(const std::byte* until) noexcept
|
||||||
|
{
|
||||||
|
for (; _commitWatermark < until; _commitWatermark += _bufferRowStride)
|
||||||
|
{
|
||||||
|
const auto row = reinterpret_cast<ROW*>(_commitWatermark);
|
||||||
|
const auto chars = reinterpret_cast<wchar_t*>(_commitWatermark + _bufferOffsetChars);
|
||||||
|
const auto indices = reinterpret_cast<uint16_t*>(_commitWatermark + _bufferOffsetCharOffsets);
|
||||||
|
std::construct_at(row, chars, indices, _width, _initialAttributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroys all previously constructed ROWs.
|
||||||
|
// Be careful! This doesn't reset any of the members, in particular the _commitWatermark.
|
||||||
|
void TextBuffer::_destroy() const noexcept
|
||||||
|
{
|
||||||
|
for (auto it = _buffer.get(); it < _commitWatermark; it += _bufferRowStride)
|
||||||
|
{
|
||||||
|
std::destroy_at(reinterpret_cast<ROW*>(it));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is "direct" because it trusts the caller to properly wrap the "offset"
|
||||||
|
// parameter modulo the _height of the buffer, etc. But keep in mind that a offset=0
|
||||||
|
// is the GetScratchpadRow() and not the GetRowByOffset(0). That one is offset=1.
|
||||||
|
ROW& TextBuffer::_getRowByOffsetDirect(size_t offset)
|
||||||
|
{
|
||||||
|
const auto row = _buffer.get() + _bufferRowStride * offset;
|
||||||
|
THROW_HR_IF(E_UNEXPECTED, row < _buffer.get() || row >= _bufferEnd);
|
||||||
|
|
||||||
|
if (row >= _commitWatermark)
|
||||||
|
{
|
||||||
|
_commit(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return *reinterpret_cast<ROW*>(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves a row from the buffer by its offset from the first row of the text buffer
|
||||||
|
// (what corresponds to the top row of the screen buffer).
|
||||||
|
const ROW& TextBuffer::GetRowByOffset(const til::CoordType index) const
|
||||||
|
{
|
||||||
|
// The const_cast is safe because "const" never had any meaning in C++ in the first place.
|
||||||
|
#pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3).
|
||||||
|
return const_cast<TextBuffer*>(this)->GetRowByOffset(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves a row from the buffer by its offset from the first row of the text buffer
|
||||||
|
// (what corresponds to the top row of the screen buffer).
|
||||||
|
ROW& TextBuffer::GetRowByOffset(const til::CoordType index)
|
||||||
|
{
|
||||||
|
// Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows.
|
||||||
|
auto offset = (_firstRow + index) % _height;
|
||||||
|
|
||||||
|
// Support negative wrap around. This way an index of -1 will
|
||||||
|
// wrap to _rowCount-1 and make implementing scrolling easier.
|
||||||
|
if (offset < 0)
|
||||||
|
{
|
||||||
|
offset += _height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We add 1 to the row offset, because row "0" is the one returned by GetScratchpadRow().
|
||||||
|
return _getRowByOffsetDirect(gsl::narrow_cast<size_t>(offset) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a row filled with whitespace and the current attributes, for you to freely use.
|
||||||
|
ROW& TextBuffer::GetScratchpadRow()
|
||||||
|
{
|
||||||
|
return _getRowByOffsetDirect(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning(pop)
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
// - Copies properties from another text buffer into this one.
|
// - Copies properties from another text buffer into this one.
|
||||||
// - This is primarily to copy properties that would otherwise not be specified during CreateInstance
|
// - This is primarily to copy properties that would otherwise not be specified during CreateInstance
|
||||||
@@ -66,35 +222,7 @@ void TextBuffer::CopyProperties(const TextBuffer& OtherBuffer) noexcept
|
|||||||
// - Total number of rows in the buffer
|
// - Total number of rows in the buffer
|
||||||
til::CoordType TextBuffer::TotalRowCount() const noexcept
|
til::CoordType TextBuffer::TotalRowCount() const noexcept
|
||||||
{
|
{
|
||||||
return gsl::narrow_cast<til::CoordType>(_storage.size());
|
return _height;
|
||||||
}
|
|
||||||
|
|
||||||
// Routine Description:
|
|
||||||
// - Retrieves a row from the buffer by its offset from the first row of the text buffer (what corresponds to
|
|
||||||
// the top row of the screen buffer)
|
|
||||||
// Arguments:
|
|
||||||
// - Number of rows down from the first row of the buffer.
|
|
||||||
// Return Value:
|
|
||||||
// - const reference to the requested row. Asserts if out of bounds.
|
|
||||||
const ROW& TextBuffer::GetRowByOffset(const til::CoordType index) const noexcept
|
|
||||||
{
|
|
||||||
// Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows.
|
|
||||||
const auto offsetIndex = gsl::narrow_cast<size_t>(_firstRow + index) % _storage.size();
|
|
||||||
return til::at(_storage, offsetIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Routine Description:
|
|
||||||
// - Retrieves a row from the buffer by its offset from the first row of the text buffer (what corresponds to
|
|
||||||
// the top row of the screen buffer)
|
|
||||||
// Arguments:
|
|
||||||
// - Number of rows down from the first row of the buffer.
|
|
||||||
// Return Value:
|
|
||||||
// - reference to the requested row. Asserts if out of bounds.
|
|
||||||
ROW& TextBuffer::GetRowByOffset(const til::CoordType index) noexcept
|
|
||||||
{
|
|
||||||
// Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows.
|
|
||||||
const auto offsetIndex = gsl::narrow_cast<size_t>(_firstRow + index) % _storage.size();
|
|
||||||
return til::at(_storage, offsetIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
@@ -483,7 +611,7 @@ bool TextBuffer::InsertCharacter(const wchar_t wch, const DbcsAttribute dbcsAttr
|
|||||||
// - <none> - Always sets to wrap
|
// - <none> - Always sets to wrap
|
||||||
//Return Value:
|
//Return Value:
|
||||||
// - <none>
|
// - <none>
|
||||||
void TextBuffer::_SetWrapOnCurrentRow() noexcept
|
void TextBuffer::_SetWrapOnCurrentRow()
|
||||||
{
|
{
|
||||||
_AdjustWrapOnCurrentRow(true);
|
_AdjustWrapOnCurrentRow(true);
|
||||||
}
|
}
|
||||||
@@ -495,7 +623,7 @@ void TextBuffer::_SetWrapOnCurrentRow() noexcept
|
|||||||
// - fSet - True if this row has a wrap. False otherwise.
|
// - fSet - True if this row has a wrap. False otherwise.
|
||||||
//Return Value:
|
//Return Value:
|
||||||
// - <none>
|
// - <none>
|
||||||
void TextBuffer::_AdjustWrapOnCurrentRow(const bool fSet) noexcept
|
void TextBuffer::_AdjustWrapOnCurrentRow(const bool fSet)
|
||||||
{
|
{
|
||||||
// The vertical position of the cursor represents the current row we're manipulating.
|
// The vertical position of the cursor represents the current row we're manipulating.
|
||||||
const auto uiCurrentRowOffset = GetCursor().GetPosition().y;
|
const auto uiCurrentRowOffset = GetCursor().GetPosition().y;
|
||||||
@@ -651,7 +779,7 @@ til::point TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::C
|
|||||||
// Return Value:
|
// Return Value:
|
||||||
// - Coordinate position in screen coordinates of the character just before the cursor.
|
// - Coordinate position in screen coordinates of the character just before the cursor.
|
||||||
// - NOTE: Will return 0,0 if already in the top left corner
|
// - NOTE: Will return 0,0 if already in the top left corner
|
||||||
til::point TextBuffer::_GetPreviousFromCursor() const noexcept
|
til::point TextBuffer::_GetPreviousFromCursor() const
|
||||||
{
|
{
|
||||||
auto coordPosition = GetCursor().GetPosition();
|
auto coordPosition = GetCursor().GetPosition();
|
||||||
|
|
||||||
@@ -683,43 +811,7 @@ const til::CoordType TextBuffer::GetFirstRowIndex() const noexcept
|
|||||||
|
|
||||||
const Viewport TextBuffer::GetSize() const noexcept
|
const Viewport TextBuffer::GetSize() const noexcept
|
||||||
{
|
{
|
||||||
return _size;
|
return Viewport::FromDimensions({ _width, _height });
|
||||||
}
|
|
||||||
|
|
||||||
wil::unique_virtualalloc_ptr<std::byte> TextBuffer::_allocateBuffer(til::size sz, const TextAttribute& attributes, std::vector<ROW>& rows)
|
|
||||||
{
|
|
||||||
const auto w = gsl::narrow<uint16_t>(sz.width);
|
|
||||||
const auto h = gsl::narrow<uint16_t>(sz.height);
|
|
||||||
|
|
||||||
const auto charsBytes = w * sizeof(wchar_t);
|
|
||||||
// The ROW::_indices array stores 1 more item than the buffer is wide.
|
|
||||||
// That extra column stores the past-the-end _chars pointer.
|
|
||||||
const auto indicesBytes = w * sizeof(uint16_t) + sizeof(uint16_t);
|
|
||||||
const auto rowStride = charsBytes + indicesBytes;
|
|
||||||
// 65535*65535 cells would result in a charsAreaSize of 8GiB.
|
|
||||||
// --> Use uint64_t so that we can safely do our calculations even on x86.
|
|
||||||
const auto allocSize = gsl::narrow<size_t>(::base::strict_cast<uint64_t>(rowStride) * ::base::strict_cast<uint64_t>(h));
|
|
||||||
|
|
||||||
auto buffer = wil::unique_virtualalloc_ptr<std::byte>{ static_cast<std::byte*>(VirtualAlloc(nullptr, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) };
|
|
||||||
THROW_IF_NULL_ALLOC(buffer);
|
|
||||||
|
|
||||||
auto data = std::span{ buffer.get(), allocSize }.begin();
|
|
||||||
|
|
||||||
rows.resize(h);
|
|
||||||
for (auto& row : rows)
|
|
||||||
{
|
|
||||||
const auto chars = til::bit_cast<wchar_t*>(&*data);
|
|
||||||
const auto indices = til::bit_cast<uint16_t*>(&*(data + charsBytes));
|
|
||||||
row = { chars, indices, w, attributes };
|
|
||||||
data += rowStride;
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextBuffer::_UpdateSize()
|
|
||||||
{
|
|
||||||
_size = Viewport::FromDimensions({ _storage.at(0).size(), gsl::narrow<til::CoordType>(_storage.size()) });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBuffer::_SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept
|
void TextBuffer::_SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept
|
||||||
@@ -727,27 +819,21 @@ void TextBuffer::_SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept
|
|||||||
_firstRow = FirstRowIndex;
|
_firstRow = FirstRowIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBuffer::ScrollRows(const til::CoordType firstRow, const til::CoordType size, const til::CoordType delta)
|
void TextBuffer::ScrollRows(const til::CoordType firstRow, til::CoordType size, const til::CoordType delta)
|
||||||
{
|
{
|
||||||
// If we don't have to move anything, leave early.
|
|
||||||
if (delta == 0)
|
if (delta == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// OK. We're about to play games by moving rows around within the deque to
|
// Since the for() loop uses !=, we must ensure that size is positive.
|
||||||
// scroll a massive region in a faster way than copying things.
|
// A negative size doesn't make any sense anyways.
|
||||||
// To make this easier, first correct the circular buffer to have the first row be 0 again.
|
size = std::max(0, size);
|
||||||
if (_firstRow != 0)
|
|
||||||
{
|
|
||||||
// Rotate the buffer to put the first row at the front.
|
|
||||||
std::rotate(_storage.begin(), _storage.begin() + _firstRow, _storage.end());
|
|
||||||
|
|
||||||
// The first row is now at the top.
|
til::CoordType y = 0;
|
||||||
_firstRow = 0;
|
til::CoordType end = 0;
|
||||||
}
|
til::CoordType step = 0;
|
||||||
|
|
||||||
// Rotate just the subsection specified
|
|
||||||
if (delta < 0)
|
if (delta < 0)
|
||||||
{
|
{
|
||||||
// The layout is like this:
|
// The layout is like this:
|
||||||
@@ -757,33 +843,20 @@ void TextBuffer::ScrollRows(const til::CoordType firstRow, const til::CoordType
|
|||||||
// | 0 begin
|
// | 0 begin
|
||||||
// | 1
|
// | 1
|
||||||
// | 2
|
// | 2
|
||||||
// | 3 A. begin + firstRow + delta (because delta is negative)
|
// | 3 A. firstRow + delta (because delta is negative)
|
||||||
// | 4
|
// | 4
|
||||||
// | 5 B. begin + firstRow
|
// | 5 B. firstRow
|
||||||
// | 6
|
// | 6
|
||||||
// | 7
|
// | 7
|
||||||
// | 8 C. begin + firstRow + size
|
// | 8 C. firstRow + size
|
||||||
// | 9
|
// | 9
|
||||||
// | 10
|
// | 10
|
||||||
// | 11
|
// | 11
|
||||||
// - end
|
// - end
|
||||||
// We want B to slide up to A (the negative delta) and everything from [B,C) to slide up with it.
|
// We want B to slide up to A (the negative delta) and everything from [B,C) to slide up with it.
|
||||||
// So the final layout will be
|
y = firstRow;
|
||||||
// --- (storage) ----
|
end = firstRow + size;
|
||||||
// | 0 begin
|
step = 1;
|
||||||
// | 1
|
|
||||||
// | 2
|
|
||||||
// | 5
|
|
||||||
// | 6
|
|
||||||
// | 7
|
|
||||||
// | 3
|
|
||||||
// | 4
|
|
||||||
// | 8
|
|
||||||
// | 9
|
|
||||||
// | 10
|
|
||||||
// | 11
|
|
||||||
// - end
|
|
||||||
std::rotate(_storage.begin() + firstRow + delta, _storage.begin() + firstRow, _storage.begin() + firstRow + size);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -796,31 +869,23 @@ void TextBuffer::ScrollRows(const til::CoordType firstRow, const til::CoordType
|
|||||||
// | 2
|
// | 2
|
||||||
// | 3
|
// | 3
|
||||||
// | 4
|
// | 4
|
||||||
// | 5 A. begin + firstRow
|
// | 5 A. firstRow
|
||||||
// | 6
|
// | 6
|
||||||
// | 7
|
// | 7
|
||||||
// | 8 B. begin + firstRow + size
|
// | 8 B. firstRow + size
|
||||||
// | 9
|
// | 9
|
||||||
// | 10 C. begin + firstRow + size + delta
|
// | 10 C. firstRow + size + delta
|
||||||
// | 11
|
// | 11
|
||||||
// - end
|
// - end
|
||||||
// We want B-1 to slide down to C-1 (the positive delta) and everything from [A, B) to slide down with it.
|
// We want B-1 to slide down to C-1 (the positive delta) and everything from [A, B) to slide down with it.
|
||||||
// So the final layout will be
|
y = firstRow + size - 1;
|
||||||
// --- (storage) ----
|
end = firstRow - 1;
|
||||||
// | 0 begin
|
step = -1;
|
||||||
// | 1
|
}
|
||||||
// | 2
|
|
||||||
// | 3
|
for (; y != end; y += step)
|
||||||
// | 4
|
{
|
||||||
// | 8
|
GetRowByOffset(y + delta).CopyFrom(GetRowByOffset(y));
|
||||||
// | 9
|
|
||||||
// | 5
|
|
||||||
// | 6
|
|
||||||
// | 7
|
|
||||||
// | 10
|
|
||||||
// | 11
|
|
||||||
// - end
|
|
||||||
std::rotate(_storage.begin() + firstRow, _storage.begin() + firstRow + size, _storage.begin() + firstRow + size + delta);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -869,7 +934,7 @@ void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition, cons
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBuffer::ResetLineRenditionRange(const til::CoordType startRow, const til::CoordType endRow) noexcept
|
void TextBuffer::ResetLineRenditionRange(const til::CoordType startRow, const til::CoordType endRow)
|
||||||
{
|
{
|
||||||
for (auto row = startRow; row < endRow; row++)
|
for (auto row = startRow; row < endRow; row++)
|
||||||
{
|
{
|
||||||
@@ -877,37 +942,37 @@ void TextBuffer::ResetLineRenditionRange(const til::CoordType startRow, const ti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LineRendition TextBuffer::GetLineRendition(const til::CoordType row) const noexcept
|
LineRendition TextBuffer::GetLineRendition(const til::CoordType row) const
|
||||||
{
|
{
|
||||||
return GetRowByOffset(row).GetLineRendition();
|
return GetRowByOffset(row).GetLineRendition();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextBuffer::IsDoubleWidthLine(const til::CoordType row) const noexcept
|
bool TextBuffer::IsDoubleWidthLine(const til::CoordType row) const
|
||||||
{
|
{
|
||||||
return GetLineRendition(row) != LineRendition::SingleWidth;
|
return GetLineRendition(row) != LineRendition::SingleWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
til::CoordType TextBuffer::GetLineWidth(const til::CoordType row) const noexcept
|
til::CoordType TextBuffer::GetLineWidth(const til::CoordType row) const
|
||||||
{
|
{
|
||||||
// Use shift right to quickly divide the width by 2 for double width lines.
|
// Use shift right to quickly divide the width by 2 for double width lines.
|
||||||
const auto scale = IsDoubleWidthLine(row) ? 1 : 0;
|
const auto scale = IsDoubleWidthLine(row) ? 1 : 0;
|
||||||
return GetSize().Width() >> scale;
|
return GetSize().Width() >> scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
til::point TextBuffer::ClampPositionWithinLine(const til::point position) const noexcept
|
til::point TextBuffer::ClampPositionWithinLine(const til::point position) const
|
||||||
{
|
{
|
||||||
const auto rightmostColumn = GetLineWidth(position.y) - 1;
|
const auto rightmostColumn = GetLineWidth(position.y) - 1;
|
||||||
return { std::min(position.x, rightmostColumn), position.y };
|
return { std::min(position.x, rightmostColumn), position.y };
|
||||||
}
|
}
|
||||||
|
|
||||||
til::point TextBuffer::ScreenToBufferPosition(const til::point position) const noexcept
|
til::point TextBuffer::ScreenToBufferPosition(const til::point position) const
|
||||||
{
|
{
|
||||||
// Use shift right to quickly divide the X pos by 2 for double width lines.
|
// Use shift right to quickly divide the X pos by 2 for double width lines.
|
||||||
const auto scale = IsDoubleWidthLine(position.y) ? 1 : 0;
|
const auto scale = IsDoubleWidthLine(position.y) ? 1 : 0;
|
||||||
return { position.x >> scale, position.y };
|
return { position.x >> scale, position.y };
|
||||||
}
|
}
|
||||||
|
|
||||||
til::point TextBuffer::BufferToScreenPosition(const til::point position) const noexcept
|
til::point TextBuffer::BufferToScreenPosition(const til::point position) const
|
||||||
{
|
{
|
||||||
// Use shift left to quickly multiply the X pos by 2 for double width lines.
|
// Use shift left to quickly multiply the X pos by 2 for double width lines.
|
||||||
const auto scale = IsDoubleWidthLine(position.y) ? 1 : 0;
|
const auto scale = IsDoubleWidthLine(position.y) ? 1 : 0;
|
||||||
@@ -917,14 +982,10 @@ til::point TextBuffer::BufferToScreenPosition(const til::point position) const n
|
|||||||
// Routine Description:
|
// Routine Description:
|
||||||
// - Resets the text contents of this buffer with the default character
|
// - Resets the text contents of this buffer with the default character
|
||||||
// and the default current color attributes
|
// and the default current color attributes
|
||||||
void TextBuffer::Reset()
|
void TextBuffer::Reset() noexcept
|
||||||
{
|
{
|
||||||
const auto attr = GetCurrentAttributes();
|
_decommit();
|
||||||
|
_initialAttributes = _currentAttributes;
|
||||||
for (auto& row : _storage)
|
|
||||||
{
|
|
||||||
row.Reset(attr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
@@ -941,55 +1002,34 @@ void TextBuffer::Reset()
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
til::CoordType TopRow = 0; // new top row of the screen buffer
|
TextBuffer newBuffer{ newSize, _currentAttributes, 0, false, _renderer };
|
||||||
if (newSize.height <= GetCursor().GetPosition().y)
|
const auto cursorRow = GetCursor().GetPosition().y;
|
||||||
{
|
const auto copyableRows = std::min<til::CoordType>(_height, newSize.height);
|
||||||
TopRow = GetCursor().GetPosition().y - newSize.height + 1;
|
til::CoordType srcRow = 0;
|
||||||
}
|
til::CoordType dstRow = 0;
|
||||||
const auto TopRowIndex = gsl::narrow_cast<size_t>(_firstRow + TopRow) % _storage.size();
|
|
||||||
|
|
||||||
std::vector<ROW> newStorage;
|
if (cursorRow >= newSize.height)
|
||||||
auto newBuffer = _allocateBuffer(newSize, _currentAttributes, newStorage);
|
|
||||||
|
|
||||||
// This basically imitates a std::rotate_copy(first, mid, last), but uses ROW::CopyRangeFrom() to do the copying.
|
|
||||||
{
|
{
|
||||||
const auto first = _storage.begin();
|
srcRow = cursorRow - newSize.height + 1;
|
||||||
const auto last = _storage.end();
|
|
||||||
const auto mid = first + TopRowIndex;
|
|
||||||
auto dest = newStorage.begin();
|
|
||||||
|
|
||||||
std::span<ROW> sourceRanges[]{
|
|
||||||
{ mid, last },
|
|
||||||
{ first, mid },
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ensure we don't copy more from `_storage` than fit into `newStorage`.
|
|
||||||
if (sourceRanges[0].size() > newStorage.size())
|
|
||||||
{
|
|
||||||
sourceRanges[0] = sourceRanges[0].subspan(0, newStorage.size());
|
|
||||||
}
|
|
||||||
if (const auto remaining = newStorage.size() - sourceRanges[0].size(); sourceRanges[1].size() > remaining)
|
|
||||||
{
|
|
||||||
sourceRanges[1] = sourceRanges[1].subspan(0, remaining);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& sourceRange : sourceRanges)
|
for (; dstRow < copyableRows; ++dstRow, ++srcRow)
|
||||||
{
|
{
|
||||||
for (const auto& oldRow : sourceRange)
|
newBuffer.GetRowByOffset(dstRow).CopyFrom(GetRowByOffset(srcRow));
|
||||||
{
|
|
||||||
til::CoordType begin = 0;
|
|
||||||
dest->CopyRangeFrom(0, til::CoordTypeMax, oldRow, begin, til::CoordTypeMax);
|
|
||||||
dest->TransferAttributes(oldRow.Attributes(), newSize.width);
|
|
||||||
++dest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_charBuffer = std::move(newBuffer);
|
// NOTE: Keep this in sync with _reserve().
|
||||||
_storage = std::move(newStorage);
|
_buffer = std::move(newBuffer._buffer);
|
||||||
|
_bufferEnd = newBuffer._bufferEnd;
|
||||||
|
_commitWatermark = newBuffer._commitWatermark;
|
||||||
|
_initialAttributes = newBuffer._initialAttributes;
|
||||||
|
_bufferRowStride = newBuffer._bufferRowStride;
|
||||||
|
_bufferOffsetChars = newBuffer._bufferOffsetChars;
|
||||||
|
_bufferOffsetCharOffsets = newBuffer._bufferOffsetCharOffsets;
|
||||||
|
_width = newBuffer._width;
|
||||||
|
_height = newBuffer._height;
|
||||||
|
|
||||||
_SetFirstRowIndex(0);
|
_SetFirstRowIndex(0);
|
||||||
_UpdateSize();
|
|
||||||
}
|
}
|
||||||
CATCH_RETURN();
|
CATCH_RETURN();
|
||||||
|
|
||||||
@@ -1059,17 +1099,6 @@ void TextBuffer::TriggerNewTextNotification(const std::wstring_view newText)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
|
||||||
// - Retrieves the first row from the underlying buffer.
|
|
||||||
// Arguments:
|
|
||||||
// - <none>
|
|
||||||
// Return Value:
|
|
||||||
// - reference to the first row.
|
|
||||||
ROW& TextBuffer::_GetFirstRow() noexcept
|
|
||||||
{
|
|
||||||
return GetRowByOffset(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
// - get delimiter class for buffer cell position
|
// - get delimiter class for buffer cell position
|
||||||
// - used for double click selection and uia word navigation
|
// - used for double click selection and uia word navigation
|
||||||
@@ -1078,7 +1107,7 @@ ROW& TextBuffer::_GetFirstRow() noexcept
|
|||||||
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
|
// - wordDelimiters: the delimiters defined as a part of the DelimiterClass::DelimiterChar
|
||||||
// Return Value:
|
// Return Value:
|
||||||
// - the delimiter class for the given char
|
// - the delimiter class for the given char
|
||||||
DelimiterClass TextBuffer::_GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const noexcept
|
DelimiterClass TextBuffer::_GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const
|
||||||
{
|
{
|
||||||
return GetRowByOffset(pos.y).DelimiterClassAt(pos.x, wordDelimiters);
|
return GetRowByOffset(pos.y).DelimiterClassAt(pos.x, wordDelimiters);
|
||||||
}
|
}
|
||||||
@@ -1144,7 +1173,7 @@ til::point TextBuffer::GetWordStart(const til::point target, const std::wstring_
|
|||||||
// - wordDelimiters - what characters are we considering for the separation of words
|
// - wordDelimiters - what characters are we considering for the separation of words
|
||||||
// Return Value:
|
// Return Value:
|
||||||
// - The til::point for the first character on the current/previous READABLE "word" (inclusive)
|
// - The til::point for the first character on the current/previous READABLE "word" (inclusive)
|
||||||
til::point TextBuffer::_GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const noexcept
|
til::point TextBuffer::_GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const
|
||||||
{
|
{
|
||||||
auto result = target;
|
auto result = target;
|
||||||
const auto bufferSize = GetSize();
|
const auto bufferSize = GetSize();
|
||||||
@@ -1189,7 +1218,7 @@ til::point TextBuffer::_GetWordStartForAccessibility(const til::point target, co
|
|||||||
// - wordDelimiters - what characters are we considering for the separation of words
|
// - wordDelimiters - what characters are we considering for the separation of words
|
||||||
// Return Value:
|
// Return Value:
|
||||||
// - The til::point for the first character on the current word or delimiter run (stopped by the left margin)
|
// - The til::point for the first character on the current word or delimiter run (stopped by the left margin)
|
||||||
til::point TextBuffer::_GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept
|
til::point TextBuffer::_GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const
|
||||||
{
|
{
|
||||||
auto result = target;
|
auto result = target;
|
||||||
const auto bufferSize = GetSize();
|
const auto bufferSize = GetSize();
|
||||||
@@ -1310,7 +1339,7 @@ til::point TextBuffer::_GetWordEndForAccessibility(const til::point target, cons
|
|||||||
// - wordDelimiters - what characters are we considering for the separation of words
|
// - wordDelimiters - what characters are we considering for the separation of words
|
||||||
// Return Value:
|
// Return Value:
|
||||||
// - The til::point for the last character of the current word or delimiter run (stopped by right margin)
|
// - The til::point for the last character of the current word or delimiter run (stopped by right margin)
|
||||||
til::point TextBuffer::_GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept
|
til::point TextBuffer::_GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const
|
||||||
{
|
{
|
||||||
const auto bufferSize = GetSize();
|
const auto bufferSize = GetSize();
|
||||||
|
|
||||||
|
|||||||
@@ -72,14 +72,21 @@ public:
|
|||||||
const UINT cursorSize,
|
const UINT cursorSize,
|
||||||
const bool isActiveBuffer,
|
const bool isActiveBuffer,
|
||||||
Microsoft::Console::Render::Renderer& renderer);
|
Microsoft::Console::Render::Renderer& renderer);
|
||||||
TextBuffer(const TextBuffer& a) = delete;
|
|
||||||
|
TextBuffer(const TextBuffer&) = delete;
|
||||||
|
TextBuffer(TextBuffer&&) = delete;
|
||||||
|
TextBuffer& operator=(const TextBuffer&) = delete;
|
||||||
|
TextBuffer& operator=(TextBuffer&&) = delete;
|
||||||
|
|
||||||
|
~TextBuffer();
|
||||||
|
|
||||||
// Used for duplicating properties to another text buffer
|
// Used for duplicating properties to another text buffer
|
||||||
void CopyProperties(const TextBuffer& OtherBuffer) noexcept;
|
void CopyProperties(const TextBuffer& OtherBuffer) noexcept;
|
||||||
|
|
||||||
// row manipulation
|
// row manipulation
|
||||||
const ROW& GetRowByOffset(const til::CoordType index) const noexcept;
|
ROW& GetScratchpadRow();
|
||||||
ROW& GetRowByOffset(const til::CoordType index) noexcept;
|
const ROW& GetRowByOffset(til::CoordType index) const;
|
||||||
|
ROW& GetRowByOffset(til::CoordType index);
|
||||||
|
|
||||||
TextBufferCellIterator GetCellDataAt(const til::point at) const;
|
TextBufferCellIterator GetCellDataAt(const til::point at) const;
|
||||||
TextBufferCellIterator GetCellLineDataAt(const til::point at) const;
|
TextBufferCellIterator GetCellLineDataAt(const til::point at) const;
|
||||||
@@ -129,16 +136,16 @@ public:
|
|||||||
void SetCurrentAttributes(const TextAttribute& currentAttributes) noexcept;
|
void SetCurrentAttributes(const TextAttribute& currentAttributes) noexcept;
|
||||||
|
|
||||||
void SetCurrentLineRendition(const LineRendition lineRendition, const TextAttribute& fillAttributes);
|
void SetCurrentLineRendition(const LineRendition lineRendition, const TextAttribute& fillAttributes);
|
||||||
void ResetLineRenditionRange(const til::CoordType startRow, const til::CoordType endRow) noexcept;
|
void ResetLineRenditionRange(const til::CoordType startRow, const til::CoordType endRow);
|
||||||
LineRendition GetLineRendition(const til::CoordType row) const noexcept;
|
LineRendition GetLineRendition(const til::CoordType row) const;
|
||||||
bool IsDoubleWidthLine(const til::CoordType row) const noexcept;
|
bool IsDoubleWidthLine(const til::CoordType row) const;
|
||||||
|
|
||||||
til::CoordType GetLineWidth(const til::CoordType row) const noexcept;
|
til::CoordType GetLineWidth(const til::CoordType row) const;
|
||||||
til::point ClampPositionWithinLine(const til::point position) const noexcept;
|
til::point ClampPositionWithinLine(const til::point position) const;
|
||||||
til::point ScreenToBufferPosition(const til::point position) const noexcept;
|
til::point ScreenToBufferPosition(const til::point position) const;
|
||||||
til::point BufferToScreenPosition(const til::point position) const noexcept;
|
til::point BufferToScreenPosition(const til::point position) const;
|
||||||
|
|
||||||
void Reset();
|
void Reset() noexcept;
|
||||||
|
|
||||||
[[nodiscard]] HRESULT ResizeTraditional(const til::size newSize) noexcept;
|
[[nodiscard]] HRESULT ResizeTraditional(const til::size newSize) noexcept;
|
||||||
|
|
||||||
@@ -219,23 +226,26 @@ public:
|
|||||||
interval_tree::IntervalTree<til::point, size_t> GetPatterns(const til::CoordType firstRow, const til::CoordType lastRow) const;
|
interval_tree::IntervalTree<til::point, size_t> GetPatterns(const til::CoordType firstRow, const til::CoordType lastRow) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static wil::unique_virtualalloc_ptr<std::byte> _allocateBuffer(til::size sz, const TextAttribute& attributes, std::vector<ROW>& rows);
|
void _reserve(til::size screenBufferSize, const TextAttribute& defaultAttributes);
|
||||||
|
void _commit(const std::byte* row);
|
||||||
|
void _decommit() noexcept;
|
||||||
|
void _construct(const std::byte* until) noexcept;
|
||||||
|
void _destroy() const noexcept;
|
||||||
|
ROW& _getRowByOffsetDirect(size_t offset);
|
||||||
|
|
||||||
void _UpdateSize();
|
|
||||||
void _SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept;
|
void _SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept;
|
||||||
til::point _GetPreviousFromCursor() const noexcept;
|
til::point _GetPreviousFromCursor() const;
|
||||||
void _SetWrapOnCurrentRow() noexcept;
|
void _SetWrapOnCurrentRow();
|
||||||
void _AdjustWrapOnCurrentRow(const bool fSet) noexcept;
|
void _AdjustWrapOnCurrentRow(const bool fSet);
|
||||||
// Assist with maintaining proper buffer state for Double Byte character sequences
|
// Assist with maintaining proper buffer state for Double Byte character sequences
|
||||||
bool _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute);
|
bool _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute);
|
||||||
bool _AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute);
|
bool _AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute);
|
||||||
ROW& _GetFirstRow() noexcept;
|
|
||||||
void _ExpandTextRow(til::inclusive_rect& selectionRow) const;
|
void _ExpandTextRow(til::inclusive_rect& selectionRow) const;
|
||||||
DelimiterClass _GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const noexcept;
|
DelimiterClass _GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const;
|
||||||
til::point _GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const noexcept;
|
til::point _GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const;
|
||||||
til::point _GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept;
|
til::point _GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const;
|
||||||
til::point _GetWordEndForAccessibility(const til::point target, const std::wstring_view wordDelimiters, const til::point limit) const;
|
til::point _GetWordEndForAccessibility(const til::point target, const std::wstring_view wordDelimiters, const til::point limit) const;
|
||||||
til::point _GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept;
|
til::point _GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const;
|
||||||
void _PruneHyperlinks();
|
void _PruneHyperlinks();
|
||||||
|
|
||||||
static void _AppendRTFText(std::ostringstream& contentBuilder, const std::wstring_view& text);
|
static void _AppendRTFText(std::ostringstream& contentBuilder, const std::wstring_view& text);
|
||||||
@@ -249,13 +259,67 @@ private:
|
|||||||
std::unordered_map<size_t, std::wstring> _idsAndPatterns;
|
std::unordered_map<size_t, std::wstring> _idsAndPatterns;
|
||||||
size_t _currentPatternId = 0;
|
size_t _currentPatternId = 0;
|
||||||
|
|
||||||
wil::unique_virtualalloc_ptr<std::byte> _charBuffer;
|
// This block describes the state of the underlying virtual memory buffer that holds all ROWs, text and attributes.
|
||||||
std::vector<ROW> _storage;
|
// Initially memory is only allocated with MEM_RESERVE to reduce the private working set of conhost.
|
||||||
|
// ROWs are laid out like this in memory:
|
||||||
|
// ROW <-- sizeof(ROW), stores
|
||||||
|
// (padding)
|
||||||
|
// ROW::_charsBuffer <-- _width * sizeof(wchar_t)
|
||||||
|
// (padding)
|
||||||
|
// ROW::_charOffsets <-- (_width + 1) * sizeof(uint16_t)
|
||||||
|
// (padding)
|
||||||
|
// ...
|
||||||
|
// Padding may exist for alignment purposes.
|
||||||
|
//
|
||||||
|
// The base (start) address of the memory arena.
|
||||||
|
wil::unique_virtualalloc_ptr<std::byte> _buffer;
|
||||||
|
// The past-the-end pointer of the memory arena.
|
||||||
|
std::byte* _bufferEnd = nullptr;
|
||||||
|
// The range between _buffer (inclusive) and _commitWatermark (exclusive) is the range of
|
||||||
|
// memory that has already been committed via MEM_COMMIT and contains ready-to-use ROWs.
|
||||||
|
//
|
||||||
|
// The problem is that calling VirtualAlloc(MEM_COMMIT) on each ROW one by one is extremely expensive, which forces
|
||||||
|
// us to commit ROWs in batches and avoid calling it on already committed ROWs. Let's say we commit memory in
|
||||||
|
// batches of 128 ROWs. One option to know whether a ROW has already been committed is to allocate a vector<uint8_t>
|
||||||
|
// of size `(height + 127) / 128` and mark the corresponding slot as 1 if that 128-sized batch has been committed.
|
||||||
|
// That way we know not to commit it again. But ROWs aren't accessed randomly. Instead, they're usually accessed
|
||||||
|
// fairly linearly from row 1 to N. As such we can just commit ROWs up to the point of the highest accessed ROW
|
||||||
|
// plus some read-ahead of 128 ROWs. This is exactly what _commitWatermark stores: The highest accessed ROW plus
|
||||||
|
// some read-ahead. It's the amount of memory that has been committed and is ready to use.
|
||||||
|
//
|
||||||
|
// _commitWatermark will always be a multiple of _bufferRowStride away from _buffer.
|
||||||
|
// In other words, _commitWatermark itself will either point exactly onto the next ROW
|
||||||
|
// that should be committed or be equal to _bufferEnd when all ROWs are committed.
|
||||||
|
std::byte* _commitWatermark = nullptr;
|
||||||
|
// This will MEM_COMMIT 128 rows more than we need, to avoid us from having to call VirtualAlloc too often.
|
||||||
|
// This equates to roughly the following commit chunk sizes at these column counts:
|
||||||
|
// * 80 columns (the usual minimum) = 60KB chunks, 4.1MB buffer at 9001 rows
|
||||||
|
// * 120 columns (the most common) = 80KB chunks, 5.6MB buffer at 9001 rows
|
||||||
|
// * 400 columns (the usual maximum) = 220KB chunks, 15.5MB buffer at 9001 rows
|
||||||
|
// There's probably a better metric than this. (This comment was written when ROW had both,
|
||||||
|
// a _chars array containing text and a _charOffsets array contain column-to-text indices.)
|
||||||
|
static constexpr size_t _commitReadAheadRowCount = 128;
|
||||||
|
// Before TextBuffer was made to use virtual memory it initialized the entire memory arena with the initial
|
||||||
|
// attributes right away. To ensure it continues to work the way it used to, this stores these initial attributes.
|
||||||
|
TextAttribute _initialAttributes;
|
||||||
|
// ROW ---------------+--+--+
|
||||||
|
// (padding) | | v _bufferOffsetChars
|
||||||
|
// ROW::_charsBuffer | |
|
||||||
|
// (padding) | v _bufferOffsetCharOffsets
|
||||||
|
// ROW::_charOffsets |
|
||||||
|
// (padding) v _bufferRowStride
|
||||||
|
size_t _bufferRowStride = 0;
|
||||||
|
size_t _bufferOffsetChars = 0;
|
||||||
|
size_t _bufferOffsetCharOffsets = 0;
|
||||||
|
// The width of the buffer in columns.
|
||||||
|
uint16_t _width = 0;
|
||||||
|
// The height of the buffer in rows, excluding the scratchpad row.
|
||||||
|
uint16_t _height = 0;
|
||||||
|
|
||||||
TextAttribute _currentAttributes;
|
TextAttribute _currentAttributes;
|
||||||
til::CoordType _firstRow = 0; // indexes top row (not necessarily 0)
|
til::CoordType _firstRow = 0; // indexes top row (not necessarily 0)
|
||||||
|
|
||||||
Cursor _cursor;
|
Cursor _cursor;
|
||||||
Microsoft::Console::Types::Viewport _size;
|
|
||||||
|
|
||||||
bool _isActiveBuffer = false;
|
bool _isActiveBuffer = false;
|
||||||
|
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ ptrdiff_t TextBufferCellIterator::operator-(const TextBufferCellIterator& it)
|
|||||||
// - Sets the coordinate position that this iterator will inspect within the text buffer on dereference.
|
// - Sets the coordinate position that this iterator will inspect within the text buffer on dereference.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// - newPos - The new coordinate position.
|
// - newPos - The new coordinate position.
|
||||||
void TextBufferCellIterator::_SetPos(const til::point newPos) noexcept
|
void TextBufferCellIterator::_SetPos(const til::point newPos)
|
||||||
{
|
{
|
||||||
if (newPos.y != _pos.y)
|
if (newPos.y != _pos.y)
|
||||||
{
|
{
|
||||||
@@ -315,7 +315,7 @@ void TextBufferCellIterator::_SetPos(const til::point newPos) noexcept
|
|||||||
// - pos - Position inside screen buffer bounds to retrieve row
|
// - pos - Position inside screen buffer bounds to retrieve row
|
||||||
// Return Value:
|
// Return Value:
|
||||||
// - Pointer to the underlying CharRow structure
|
// - Pointer to the underlying CharRow structure
|
||||||
const ROW* TextBufferCellIterator::s_GetRow(const TextBuffer& buffer, const til::point pos) noexcept
|
const ROW* TextBufferCellIterator::s_GetRow(const TextBuffer& buffer, const til::point pos)
|
||||||
{
|
{
|
||||||
return &buffer.GetRowByOffset(pos.y);
|
return &buffer.GetRowByOffset(pos.y);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ public:
|
|||||||
til::point Pos() const noexcept;
|
til::point Pos() const noexcept;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void _SetPos(const til::point newPos) noexcept;
|
void _SetPos(const til::point newPos);
|
||||||
void _GenerateView() noexcept;
|
void _GenerateView() noexcept;
|
||||||
static const ROW* s_GetRow(const TextBuffer& buffer, const til::point pos) noexcept;
|
static const ROW* s_GetRow(const TextBuffer& buffer, const til::point pos);
|
||||||
|
|
||||||
til::small_rle<TextAttribute, uint16_t, 1>::const_iterator _attrIter;
|
til::small_rle<TextAttribute, uint16_t, 1>::const_iterator _attrIter;
|
||||||
OutputCellView _view;
|
OutputCellView _view;
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ public:
|
|||||||
ULONG GetCursorHeight() const noexcept override;
|
ULONG GetCursorHeight() const noexcept override;
|
||||||
ULONG GetCursorPixelWidth() const noexcept override;
|
ULONG GetCursorPixelWidth() const noexcept override;
|
||||||
CursorType GetCursorStyle() const noexcept override;
|
CursorType GetCursorStyle() const noexcept override;
|
||||||
bool IsCursorDoubleWidth() const noexcept override;
|
bool IsCursorDoubleWidth() const override;
|
||||||
const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
|
const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
|
||||||
const bool IsGridLineDrawingAllowed() noexcept override;
|
const bool IsGridLineDrawingAllowed() noexcept override;
|
||||||
const std::wstring GetHyperlinkUri(uint16_t id) const override;
|
const std::wstring GetHyperlinkUri(uint16_t id) const override;
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ CursorType Terminal::GetCursorStyle() const noexcept
|
|||||||
return _activeBuffer().GetCursor().GetType();
|
return _activeBuffer().GetCursor().GetType();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Terminal::IsCursorDoubleWidth() const noexcept
|
bool Terminal::IsCursorDoubleWidth() const
|
||||||
{
|
{
|
||||||
const auto& buffer = _activeBuffer();
|
const auto& buffer = _activeBuffer();
|
||||||
const auto position = buffer.GetCursor().GetPosition();
|
const auto position = buffer.GetCursor().GetPosition();
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ const std::vector<Microsoft::Console::Render::RenderOverlay> RenderData::GetOver
|
|||||||
// - <none>
|
// - <none>
|
||||||
// Return Value:
|
// Return Value:
|
||||||
// - true if the cursor should be drawn twice as wide as usual
|
// - true if the cursor should be drawn twice as wide as usual
|
||||||
bool RenderData::IsCursorDoubleWidth() const noexcept
|
bool RenderData::IsCursorDoubleWidth() const
|
||||||
{
|
{
|
||||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||||
return gci.GetActiveOutputBuffer().CursorIsDoubleWidth();
|
return gci.GetActiveOutputBuffer().CursorIsDoubleWidth();
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public:
|
|||||||
ULONG GetCursorHeight() const noexcept override;
|
ULONG GetCursorHeight() const noexcept override;
|
||||||
CursorType GetCursorStyle() const noexcept override;
|
CursorType GetCursorStyle() const noexcept override;
|
||||||
ULONG GetCursorPixelWidth() const noexcept override;
|
ULONG GetCursorPixelWidth() const noexcept override;
|
||||||
bool IsCursorDoubleWidth() const noexcept override;
|
bool IsCursorDoubleWidth() const override;
|
||||||
|
|
||||||
const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
|
const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
|
||||||
|
|
||||||
|
|||||||
@@ -2609,7 +2609,7 @@ Viewport SCREEN_INFORMATION::GetVirtualViewport() const noexcept
|
|||||||
// - <none>
|
// - <none>
|
||||||
// Return Value:
|
// Return Value:
|
||||||
// - true if the character at the cursor's current position is wide
|
// - true if the character at the cursor's current position is wide
|
||||||
bool SCREEN_INFORMATION::CursorIsDoubleWidth() const noexcept
|
bool SCREEN_INFORMATION::CursorIsDoubleWidth() const
|
||||||
{
|
{
|
||||||
const auto& buffer = GetTextBuffer();
|
const auto& buffer = GetTextBuffer();
|
||||||
const auto position = buffer.GetCursor().GetPosition();
|
const auto position = buffer.GetCursor().GetPosition();
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ public:
|
|||||||
InputBuffer* const GetActiveInputBuffer() const override;
|
InputBuffer* const GetActiveInputBuffer() const override;
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
bool CursorIsDoubleWidth() const noexcept;
|
bool CursorIsDoubleWidth() const;
|
||||||
|
|
||||||
DWORD OutputMode;
|
DWORD OutputMode;
|
||||||
WORD ResizingWindow; // > 0 if we should ignore WM_SIZE messages
|
WORD ResizingWindow; // > 0 if we should ignore WM_SIZE messages
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ void TextBufferTests::TestWrapFlag()
|
|||||||
{
|
{
|
||||||
auto& textBuffer = GetTbi();
|
auto& textBuffer = GetTbi();
|
||||||
|
|
||||||
auto& Row = textBuffer._GetFirstRow();
|
auto& Row = textBuffer.GetRowByOffset(0);
|
||||||
|
|
||||||
// no wrap by default
|
// no wrap by default
|
||||||
VERIFY_IS_FALSE(Row.WasWrapForced());
|
VERIFY_IS_FALSE(Row.WasWrapForced());
|
||||||
@@ -207,7 +207,7 @@ void TextBufferTests::TestWrapThroughWriteLine()
|
|||||||
auto& textBuffer = GetTbi();
|
auto& textBuffer = GetTbi();
|
||||||
|
|
||||||
auto VerifyWrap = [&](bool expected) {
|
auto VerifyWrap = [&](bool expected) {
|
||||||
auto& Row = textBuffer._GetFirstRow();
|
auto& Row = textBuffer.GetRowByOffset(0);
|
||||||
|
|
||||||
if (expected)
|
if (expected)
|
||||||
{
|
{
|
||||||
@@ -278,7 +278,7 @@ void TextBufferTests::TestDoubleBytePadFlag()
|
|||||||
{
|
{
|
||||||
auto& textBuffer = GetTbi();
|
auto& textBuffer = GetTbi();
|
||||||
|
|
||||||
auto& Row = textBuffer._GetFirstRow();
|
auto& Row = textBuffer.GetRowByOffset(0);
|
||||||
|
|
||||||
// no padding by default
|
// no padding by default
|
||||||
VERIFY_IS_FALSE(Row.WasDoubleBytePadded());
|
VERIFY_IS_FALSE(Row.WasDoubleBytePadded());
|
||||||
@@ -300,7 +300,7 @@ void TextBufferTests::DoBoundaryTest(PCWCHAR const pwszInputString,
|
|||||||
{
|
{
|
||||||
auto& textBuffer = GetTbi();
|
auto& textBuffer = GetTbi();
|
||||||
|
|
||||||
auto& row = textBuffer._GetFirstRow();
|
auto& row = textBuffer.GetRowByOffset(0);
|
||||||
|
|
||||||
// copy string into buffer
|
// copy string into buffer
|
||||||
for (til::CoordType i = 0; i < cLength; ++i)
|
for (til::CoordType i = 0; i < cLength; ++i)
|
||||||
@@ -622,7 +622,7 @@ void TextBufferTests::TestIncrementCircularBuffer()
|
|||||||
textBuffer._firstRow = iRowToTestIndex;
|
textBuffer._firstRow = iRowToTestIndex;
|
||||||
|
|
||||||
// fill first row with some stuff
|
// fill first row with some stuff
|
||||||
auto& FirstRow = textBuffer._GetFirstRow();
|
auto& FirstRow = textBuffer.GetRowByOffset(0);
|
||||||
FirstRow.ReplaceCharacters(0, 1, { L"A" });
|
FirstRow.ReplaceCharacters(0, 1, { L"A" });
|
||||||
|
|
||||||
// ensure it does say that it contains text
|
// ensure it does say that it contains text
|
||||||
@@ -633,7 +633,7 @@ void TextBufferTests::TestIncrementCircularBuffer()
|
|||||||
|
|
||||||
// validate that first row has moved
|
// validate that first row has moved
|
||||||
VERIFY_ARE_EQUAL(textBuffer._firstRow, iNextRowIndex); // first row has incremented
|
VERIFY_ARE_EQUAL(textBuffer._firstRow, iNextRowIndex); // first row has incremented
|
||||||
VERIFY_ARE_NOT_EQUAL(textBuffer._GetFirstRow(), FirstRow); // the old first row is no longer the first
|
VERIFY_ARE_NOT_EQUAL(textBuffer.GetRowByOffset(0), FirstRow); // the old first row is no longer the first
|
||||||
|
|
||||||
// ensure old first row has been emptied
|
// ensure old first row has been emptied
|
||||||
VERIFY_IS_FALSE(FirstRow.ContainsText());
|
VERIFY_IS_FALSE(FirstRow.ContainsText());
|
||||||
@@ -1847,7 +1847,7 @@ void TextBufferTests::ResizeTraditionalRotationPreservesHighUnicode()
|
|||||||
// This is the negative squared latin capital letter B emoji: 🅱
|
// This is the negative squared latin capital letter B emoji: 🅱
|
||||||
// It's encoded in UTF-16, as needed by the buffer.
|
// It's encoded in UTF-16, as needed by the buffer.
|
||||||
const auto bButton = L"\xD83C\xDD71";
|
const auto bButton = L"\xD83C\xDD71";
|
||||||
_buffer->_storage[pos.y].ReplaceCharacters(pos.x, 2, bButton);
|
_buffer->GetRowByOffset(pos.y).ReplaceCharacters(pos.x, 2, bButton);
|
||||||
|
|
||||||
// Read back the text at that position and ensure that it matches what we wrote.
|
// Read back the text at that position and ensure that it matches what we wrote.
|
||||||
const auto readBack = _buffer->GetTextDataAt(pos);
|
const auto readBack = _buffer->GetTextDataAt(pos);
|
||||||
@@ -1888,7 +1888,7 @@ void TextBufferTests::ScrollBufferRotationPreservesHighUnicode()
|
|||||||
// This is the fire emoji: 🔥
|
// This is the fire emoji: 🔥
|
||||||
// It's encoded in UTF-16, as needed by the buffer.
|
// It's encoded in UTF-16, as needed by the buffer.
|
||||||
const auto fire = L"\xD83D\xDD25";
|
const auto fire = L"\xD83D\xDD25";
|
||||||
_buffer->_storage[pos.y].ReplaceCharacters(pos.x, 2, fire);
|
_buffer->GetRowByOffset(pos.y).ReplaceCharacters(pos.x, 2, fire);
|
||||||
|
|
||||||
// Read back the text at that position and ensure that it matches what we wrote.
|
// Read back the text at that position and ensure that it matches what we wrote.
|
||||||
const auto readBack = _buffer->GetTextDataAt(pos);
|
const auto readBack = _buffer->GetTextDataAt(pos);
|
||||||
@@ -1902,11 +1902,7 @@ void TextBufferTests::ScrollBufferRotationPreservesHighUnicode()
|
|||||||
// Scroll the row with our data by delta.
|
// Scroll the row with our data by delta.
|
||||||
_buffer->ScrollRows(pos.y, 1, delta);
|
_buffer->ScrollRows(pos.y, 1, delta);
|
||||||
|
|
||||||
// Retrieve the text at the old and new positions.
|
|
||||||
const auto shouldBeEmptyText = *_buffer->GetTextDataAt(pos);
|
|
||||||
const auto shouldBeFireText = *_buffer->GetTextDataAt(newPos);
|
const auto shouldBeFireText = *_buffer->GetTextDataAt(newPos);
|
||||||
|
|
||||||
VERIFY_ARE_EQUAL(String(L" "), String(shouldBeEmptyText.data(), gsl::narrow<int>(shouldBeEmptyText.size())));
|
|
||||||
VERIFY_ARE_EQUAL(String(fire), String(shouldBeFireText.data(), gsl::narrow<int>(shouldBeFireText.size())));
|
VERIFY_ARE_EQUAL(String(fire), String(shouldBeFireText.data(), gsl::narrow<int>(shouldBeFireText.size())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1927,7 +1923,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeRowRemoval()
|
|||||||
// This is the eggplant emoji: 🍆
|
// This is the eggplant emoji: 🍆
|
||||||
// It's encoded in UTF-16, as needed by the buffer.
|
// It's encoded in UTF-16, as needed by the buffer.
|
||||||
const auto emoji = L"\xD83C\xDF46";
|
const auto emoji = L"\xD83C\xDF46";
|
||||||
_buffer->_storage[pos.y].ReplaceCharacters(pos.x, 2, emoji);
|
_buffer->GetRowByOffset(pos.y).ReplaceCharacters(pos.x, 2, emoji);
|
||||||
|
|
||||||
// Read back the text at that position and ensure that it matches what we wrote.
|
// Read back the text at that position and ensure that it matches what we wrote.
|
||||||
const auto readBack = _buffer->GetTextDataAt(pos);
|
const auto readBack = _buffer->GetTextDataAt(pos);
|
||||||
@@ -1957,7 +1953,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeColumnRemoval()
|
|||||||
// This is the peach emoji: 🍑
|
// This is the peach emoji: 🍑
|
||||||
// It's encoded in UTF-16, as needed by the buffer.
|
// It's encoded in UTF-16, as needed by the buffer.
|
||||||
const auto emoji = L"\xD83C\xDF51";
|
const auto emoji = L"\xD83C\xDF51";
|
||||||
_buffer->_storage[pos.y].ReplaceCharacters(pos.x, 2, emoji);
|
_buffer->GetRowByOffset(pos.y).ReplaceCharacters(pos.x, 2, emoji);
|
||||||
|
|
||||||
// Read back the text at that position and ensure that it matches what we wrote.
|
// Read back the text at that position and ensure that it matches what we wrote.
|
||||||
const auto readBack = _buffer->GetTextDataAt(pos);
|
const auto readBack = _buffer->GetTextDataAt(pos);
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ public:
|
|||||||
return 12ul;
|
return 12ul;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsCursorDoubleWidth() const noexcept override
|
bool IsCursorDoubleWidth() const override
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ namespace Microsoft::Console::Render
|
|||||||
virtual ULONG GetCursorHeight() const noexcept = 0;
|
virtual ULONG GetCursorHeight() const noexcept = 0;
|
||||||
virtual CursorType GetCursorStyle() const noexcept = 0;
|
virtual CursorType GetCursorStyle() const noexcept = 0;
|
||||||
virtual ULONG GetCursorPixelWidth() const noexcept = 0;
|
virtual ULONG GetCursorPixelWidth() const noexcept = 0;
|
||||||
virtual bool IsCursorDoubleWidth() const noexcept = 0;
|
virtual bool IsCursorDoubleWidth() const = 0;
|
||||||
virtual const std::vector<RenderOverlay> GetOverlays() const noexcept = 0;
|
virtual const std::vector<RenderOverlay> GetOverlays() const noexcept = 0;
|
||||||
virtual const bool IsGridLineDrawingAllowed() noexcept = 0;
|
virtual const bool IsGridLineDrawingAllowed() noexcept = 0;
|
||||||
virtual const std::wstring_view GetConsoleTitle() const noexcept = 0;
|
virtual const std::wstring_view GetConsoleTitle() const noexcept = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user