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
|
||||
DECNRCM
|
||||
DECOM
|
||||
decommit
|
||||
DECPCTERM
|
||||
DECPS
|
||||
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 },
|
||||
_attr{ rowWidth, fillAttribute },
|
||||
_columnCount{ rowWidth }
|
||||
{
|
||||
if (_chars.data())
|
||||
{
|
||||
_init();
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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 0 if column is less than or equal to 0.
|
||||
til::CoordType ROW::NavigateToPrevious(til::CoordType column) const noexcept
|
||||
@@ -445,7 +451,7 @@ catch (...)
|
||||
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
|
||||
{
|
||||
const auto otherColBeg = other._clampedColumnInclusive(otherBegin);
|
||||
@@ -464,8 +470,11 @@ try
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
// 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();
|
||||
return h.colBeg;
|
||||
}
|
||||
h.CopyRangeFrom(charOffsets);
|
||||
h.CopyTextFrom(charOffsets);
|
||||
h.Finish();
|
||||
|
||||
otherBegin += h.colEnd - h.colBeg;
|
||||
@@ -489,7 +498,7 @@ catch (...)
|
||||
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`),
|
||||
// we can directly look up the end char-offset, but...
|
||||
|
||||
@@ -60,6 +60,26 @@ struct RowWriteState
|
||||
class ROW final
|
||||
{
|
||||
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(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute);
|
||||
|
||||
@@ -78,6 +98,7 @@ public:
|
||||
|
||||
void Reset(const TextAttribute& attr);
|
||||
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 NavigateToNext(til::CoordType column) const noexcept;
|
||||
@@ -88,7 +109,7 @@ public:
|
||||
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 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;
|
||||
const til::small_rle<TextAttribute, uint16_t, 1>& Attributes() const noexcept;
|
||||
@@ -121,7 +142,7 @@ private:
|
||||
bool IsValid() const noexcept;
|
||||
void ReplaceCharacters(til::CoordType width) 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();
|
||||
|
||||
// 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.
|
||||
screenBufferSize.width = std::max(screenBufferSize.width, 1);
|
||||
screenBufferSize.height = std::max(screenBufferSize.height, 1);
|
||||
_charBuffer = _allocateBuffer(screenBufferSize, _currentAttributes, _storage);
|
||||
_UpdateSize();
|
||||
_reserve(screenBufferSize, defaultAttributes);
|
||||
}
|
||||
|
||||
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:
|
||||
// - Copies properties from another text buffer into this one.
|
||||
// - 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
|
||||
til::CoordType TextBuffer::TotalRowCount() const noexcept
|
||||
{
|
||||
return gsl::narrow_cast<til::CoordType>(_storage.size());
|
||||
}
|
||||
|
||||
// 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);
|
||||
return _height;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -483,7 +611,7 @@ bool TextBuffer::InsertCharacter(const wchar_t wch, const DbcsAttribute dbcsAttr
|
||||
// - <none> - Always sets to wrap
|
||||
//Return Value:
|
||||
// - <none>
|
||||
void TextBuffer::_SetWrapOnCurrentRow() noexcept
|
||||
void TextBuffer::_SetWrapOnCurrentRow()
|
||||
{
|
||||
_AdjustWrapOnCurrentRow(true);
|
||||
}
|
||||
@@ -495,7 +623,7 @@ void TextBuffer::_SetWrapOnCurrentRow() noexcept
|
||||
// - fSet - True if this row has a wrap. False otherwise.
|
||||
//Return Value:
|
||||
// - <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.
|
||||
const auto uiCurrentRowOffset = GetCursor().GetPosition().y;
|
||||
@@ -651,7 +779,7 @@ til::point TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::C
|
||||
// Return Value:
|
||||
// - Coordinate position in screen coordinates of the character just before the cursor.
|
||||
// - 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();
|
||||
|
||||
@@ -683,43 +811,7 @@ const til::CoordType TextBuffer::GetFirstRowIndex() const noexcept
|
||||
|
||||
const Viewport TextBuffer::GetSize() const noexcept
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
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()) });
|
||||
return Viewport::FromDimensions({ _width, _height });
|
||||
}
|
||||
|
||||
void TextBuffer::_SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept
|
||||
@@ -727,27 +819,21 @@ void TextBuffer::_SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept
|
||||
_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)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// OK. We're about to play games by moving rows around within the deque to
|
||||
// scroll a massive region in a faster way than copying things.
|
||||
// To make this easier, first correct the circular buffer to have the first row be 0 again.
|
||||
if (_firstRow != 0)
|
||||
{
|
||||
// Rotate the buffer to put the first row at the front.
|
||||
std::rotate(_storage.begin(), _storage.begin() + _firstRow, _storage.end());
|
||||
// Since the for() loop uses !=, we must ensure that size is positive.
|
||||
// A negative size doesn't make any sense anyways.
|
||||
size = std::max(0, size);
|
||||
|
||||
// The first row is now at the top.
|
||||
_firstRow = 0;
|
||||
}
|
||||
til::CoordType y = 0;
|
||||
til::CoordType end = 0;
|
||||
til::CoordType step = 0;
|
||||
|
||||
// Rotate just the subsection specified
|
||||
if (delta < 0)
|
||||
{
|
||||
// The layout is like this:
|
||||
@@ -757,33 +843,20 @@ void TextBuffer::ScrollRows(const til::CoordType firstRow, const til::CoordType
|
||||
// | 0 begin
|
||||
// | 1
|
||||
// | 2
|
||||
// | 3 A. begin + firstRow + delta (because delta is negative)
|
||||
// | 3 A. firstRow + delta (because delta is negative)
|
||||
// | 4
|
||||
// | 5 B. begin + firstRow
|
||||
// | 5 B. firstRow
|
||||
// | 6
|
||||
// | 7
|
||||
// | 8 C. begin + firstRow + size
|
||||
// | 8 C. firstRow + size
|
||||
// | 9
|
||||
// | 10
|
||||
// | 11
|
||||
// - end
|
||||
// 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
|
||||
// --- (storage) ----
|
||||
// | 0 begin
|
||||
// | 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);
|
||||
y = firstRow;
|
||||
end = firstRow + size;
|
||||
step = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -796,31 +869,23 @@ void TextBuffer::ScrollRows(const til::CoordType firstRow, const til::CoordType
|
||||
// | 2
|
||||
// | 3
|
||||
// | 4
|
||||
// | 5 A. begin + firstRow
|
||||
// | 5 A. firstRow
|
||||
// | 6
|
||||
// | 7
|
||||
// | 8 B. begin + firstRow + size
|
||||
// | 8 B. firstRow + size
|
||||
// | 9
|
||||
// | 10 C. begin + firstRow + size + delta
|
||||
// | 10 C. firstRow + size + delta
|
||||
// | 11
|
||||
// - end
|
||||
// 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
|
||||
// --- (storage) ----
|
||||
// | 0 begin
|
||||
// | 1
|
||||
// | 2
|
||||
// | 3
|
||||
// | 4
|
||||
// | 8
|
||||
// | 9
|
||||
// | 5
|
||||
// | 6
|
||||
// | 7
|
||||
// | 10
|
||||
// | 11
|
||||
// - end
|
||||
std::rotate(_storage.begin() + firstRow, _storage.begin() + firstRow + size, _storage.begin() + firstRow + size + delta);
|
||||
y = firstRow + size - 1;
|
||||
end = firstRow - 1;
|
||||
step = -1;
|
||||
}
|
||||
|
||||
for (; y != end; y += step)
|
||||
{
|
||||
GetRowByOffset(y + delta).CopyFrom(GetRowByOffset(y));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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++)
|
||||
{
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
bool TextBuffer::IsDoubleWidthLine(const til::CoordType row) const noexcept
|
||||
bool TextBuffer::IsDoubleWidthLine(const til::CoordType row) const
|
||||
{
|
||||
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.
|
||||
const auto scale = IsDoubleWidthLine(row) ? 1 : 0;
|
||||
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;
|
||||
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.
|
||||
const auto scale = IsDoubleWidthLine(position.y) ? 1 : 0;
|
||||
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.
|
||||
const auto scale = IsDoubleWidthLine(position.y) ? 1 : 0;
|
||||
@@ -917,14 +982,10 @@ til::point TextBuffer::BufferToScreenPosition(const til::point position) const n
|
||||
// Routine Description:
|
||||
// - Resets the text contents of this buffer with the default character
|
||||
// and the default current color attributes
|
||||
void TextBuffer::Reset()
|
||||
void TextBuffer::Reset() noexcept
|
||||
{
|
||||
const auto attr = GetCurrentAttributes();
|
||||
|
||||
for (auto& row : _storage)
|
||||
{
|
||||
row.Reset(attr);
|
||||
}
|
||||
_decommit();
|
||||
_initialAttributes = _currentAttributes;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -941,55 +1002,34 @@ void TextBuffer::Reset()
|
||||
|
||||
try
|
||||
{
|
||||
til::CoordType TopRow = 0; // new top row of the screen buffer
|
||||
if (newSize.height <= GetCursor().GetPosition().y)
|
||||
{
|
||||
TopRow = GetCursor().GetPosition().y - newSize.height + 1;
|
||||
}
|
||||
const auto TopRowIndex = gsl::narrow_cast<size_t>(_firstRow + TopRow) % _storage.size();
|
||||
TextBuffer newBuffer{ newSize, _currentAttributes, 0, false, _renderer };
|
||||
const auto cursorRow = GetCursor().GetPosition().y;
|
||||
const auto copyableRows = std::min<til::CoordType>(_height, newSize.height);
|
||||
til::CoordType srcRow = 0;
|
||||
til::CoordType dstRow = 0;
|
||||
|
||||
std::vector<ROW> newStorage;
|
||||
auto newBuffer = _allocateBuffer(newSize, _currentAttributes, newStorage);
|
||||
|
||||
// This basically imitates a std::rotate_copy(first, mid, last), but uses ROW::CopyRangeFrom() to do the copying.
|
||||
if (cursorRow >= newSize.height)
|
||||
{
|
||||
const auto first = _storage.begin();
|
||||
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);
|
||||
srcRow = cursorRow - newSize.height + 1;
|
||||
}
|
||||
|
||||
for (const auto& sourceRange : sourceRanges)
|
||||
for (; dstRow < copyableRows; ++dstRow, ++srcRow)
|
||||
{
|
||||
for (const auto& oldRow : sourceRange)
|
||||
{
|
||||
til::CoordType begin = 0;
|
||||
dest->CopyRangeFrom(0, til::CoordTypeMax, oldRow, begin, til::CoordTypeMax);
|
||||
dest->TransferAttributes(oldRow.Attributes(), newSize.width);
|
||||
++dest;
|
||||
}
|
||||
}
|
||||
newBuffer.GetRowByOffset(dstRow).CopyFrom(GetRowByOffset(srcRow));
|
||||
}
|
||||
|
||||
_charBuffer = std::move(newBuffer);
|
||||
_storage = std::move(newStorage);
|
||||
// NOTE: Keep this in sync with _reserve().
|
||||
_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);
|
||||
_UpdateSize();
|
||||
}
|
||||
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:
|
||||
// - get delimiter class for buffer cell position
|
||||
// - 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
|
||||
// Return Value:
|
||||
// - 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);
|
||||
}
|
||||
@@ -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
|
||||
// Return Value:
|
||||
// - The til::point for the first character on the current/previous READABLE "word" (inclusive)
|
||||
til::point TextBuffer::_GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const noexcept
|
||||
til::point TextBuffer::_GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const
|
||||
{
|
||||
auto result = target;
|
||||
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
|
||||
// Return Value:
|
||||
// - The til::point for the first character on the current word or delimiter run (stopped by the left margin)
|
||||
til::point TextBuffer::_GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept
|
||||
til::point TextBuffer::_GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const
|
||||
{
|
||||
auto result = target;
|
||||
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
|
||||
// Return Value:
|
||||
// - The til::point for the last character of the current word or delimiter run (stopped by right margin)
|
||||
til::point TextBuffer::_GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept
|
||||
til::point TextBuffer::_GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const
|
||||
{
|
||||
const auto bufferSize = GetSize();
|
||||
|
||||
|
||||
@@ -72,14 +72,21 @@ public:
|
||||
const UINT cursorSize,
|
||||
const bool isActiveBuffer,
|
||||
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
|
||||
void CopyProperties(const TextBuffer& OtherBuffer) noexcept;
|
||||
|
||||
// row manipulation
|
||||
const ROW& GetRowByOffset(const til::CoordType index) const noexcept;
|
||||
ROW& GetRowByOffset(const til::CoordType index) noexcept;
|
||||
ROW& GetScratchpadRow();
|
||||
const ROW& GetRowByOffset(til::CoordType index) const;
|
||||
ROW& GetRowByOffset(til::CoordType index);
|
||||
|
||||
TextBufferCellIterator GetCellDataAt(const til::point at) const;
|
||||
TextBufferCellIterator GetCellLineDataAt(const til::point at) const;
|
||||
@@ -129,16 +136,16 @@ public:
|
||||
void SetCurrentAttributes(const TextAttribute& currentAttributes) noexcept;
|
||||
|
||||
void SetCurrentLineRendition(const LineRendition lineRendition, const TextAttribute& fillAttributes);
|
||||
void ResetLineRenditionRange(const til::CoordType startRow, const til::CoordType endRow) noexcept;
|
||||
LineRendition GetLineRendition(const til::CoordType row) const noexcept;
|
||||
bool IsDoubleWidthLine(const til::CoordType row) const noexcept;
|
||||
void ResetLineRenditionRange(const til::CoordType startRow, const til::CoordType endRow);
|
||||
LineRendition GetLineRendition(const til::CoordType row) const;
|
||||
bool IsDoubleWidthLine(const til::CoordType row) const;
|
||||
|
||||
til::CoordType GetLineWidth(const til::CoordType row) const noexcept;
|
||||
til::point ClampPositionWithinLine(const til::point position) const noexcept;
|
||||
til::point ScreenToBufferPosition(const til::point position) const noexcept;
|
||||
til::point BufferToScreenPosition(const til::point position) const noexcept;
|
||||
til::CoordType GetLineWidth(const til::CoordType row) const;
|
||||
til::point ClampPositionWithinLine(const til::point position) const;
|
||||
til::point ScreenToBufferPosition(const til::point position) const;
|
||||
til::point BufferToScreenPosition(const til::point position) const;
|
||||
|
||||
void Reset();
|
||||
void Reset() 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;
|
||||
|
||||
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;
|
||||
til::point _GetPreviousFromCursor() const noexcept;
|
||||
void _SetWrapOnCurrentRow() noexcept;
|
||||
void _AdjustWrapOnCurrentRow(const bool fSet) noexcept;
|
||||
til::point _GetPreviousFromCursor() const;
|
||||
void _SetWrapOnCurrentRow();
|
||||
void _AdjustWrapOnCurrentRow(const bool fSet);
|
||||
// Assist with maintaining proper buffer state for Double Byte character sequences
|
||||
bool _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute);
|
||||
bool _AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribute);
|
||||
ROW& _GetFirstRow() noexcept;
|
||||
void _ExpandTextRow(til::inclusive_rect& selectionRow) const;
|
||||
DelimiterClass _GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const noexcept;
|
||||
til::point _GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const noexcept;
|
||||
til::point _GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const noexcept;
|
||||
DelimiterClass _GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const;
|
||||
til::point _GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const;
|
||||
til::point _GetWordStartForSelection(const til::point target, const std::wstring_view wordDelimiters) const;
|
||||
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();
|
||||
|
||||
static void _AppendRTFText(std::ostringstream& contentBuilder, const std::wstring_view& text);
|
||||
@@ -249,13 +259,67 @@ private:
|
||||
std::unordered_map<size_t, std::wstring> _idsAndPatterns;
|
||||
size_t _currentPatternId = 0;
|
||||
|
||||
wil::unique_virtualalloc_ptr<std::byte> _charBuffer;
|
||||
std::vector<ROW> _storage;
|
||||
// This block describes the state of the underlying virtual memory buffer that holds all ROWs, text and attributes.
|
||||
// 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;
|
||||
til::CoordType _firstRow = 0; // indexes top row (not necessarily 0)
|
||||
|
||||
Cursor _cursor;
|
||||
Microsoft::Console::Types::Viewport _size;
|
||||
|
||||
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.
|
||||
// Arguments:
|
||||
// - 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)
|
||||
{
|
||||
@@ -315,7 +315,7 @@ void TextBufferCellIterator::_SetPos(const til::point newPos) noexcept
|
||||
// - pos - Position inside screen buffer bounds to retrieve row
|
||||
// Return Value:
|
||||
// - 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);
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@ public:
|
||||
til::point Pos() const noexcept;
|
||||
|
||||
protected:
|
||||
void _SetPos(const til::point newPos) noexcept;
|
||||
void _SetPos(const til::point newPos);
|
||||
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;
|
||||
OutputCellView _view;
|
||||
|
||||
@@ -180,7 +180,7 @@ public:
|
||||
ULONG GetCursorHeight() const noexcept override;
|
||||
ULONG GetCursorPixelWidth() 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 bool IsGridLineDrawingAllowed() noexcept override;
|
||||
const std::wstring GetHyperlinkUri(uint16_t id) const override;
|
||||
|
||||
@@ -70,7 +70,7 @@ CursorType Terminal::GetCursorStyle() const noexcept
|
||||
return _activeBuffer().GetCursor().GetType();
|
||||
}
|
||||
|
||||
bool Terminal::IsCursorDoubleWidth() const noexcept
|
||||
bool Terminal::IsCursorDoubleWidth() const
|
||||
{
|
||||
const auto& buffer = _activeBuffer();
|
||||
const auto position = buffer.GetCursor().GetPosition();
|
||||
|
||||
@@ -246,7 +246,7 @@ const std::vector<Microsoft::Console::Render::RenderOverlay> RenderData::GetOver
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true if the cursor should be drawn twice as wide as usual
|
||||
bool RenderData::IsCursorDoubleWidth() const noexcept
|
||||
bool RenderData::IsCursorDoubleWidth() const
|
||||
{
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
return gci.GetActiveOutputBuffer().CursorIsDoubleWidth();
|
||||
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
ULONG GetCursorHeight() const noexcept override;
|
||||
CursorType GetCursorStyle() 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;
|
||||
|
||||
|
||||
@@ -2609,7 +2609,7 @@ Viewport SCREEN_INFORMATION::GetVirtualViewport() const noexcept
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true if the character at the cursor's current position is wide
|
||||
bool SCREEN_INFORMATION::CursorIsDoubleWidth() const noexcept
|
||||
bool SCREEN_INFORMATION::CursorIsDoubleWidth() const
|
||||
{
|
||||
const auto& buffer = GetTextBuffer();
|
||||
const auto position = buffer.GetCursor().GetPosition();
|
||||
|
||||
@@ -157,7 +157,7 @@ public:
|
||||
InputBuffer* const GetActiveInputBuffer() const override;
|
||||
#pragma endregion
|
||||
|
||||
bool CursorIsDoubleWidth() const noexcept;
|
||||
bool CursorIsDoubleWidth() const;
|
||||
|
||||
DWORD OutputMode;
|
||||
WORD ResizingWindow; // > 0 if we should ignore WM_SIZE messages
|
||||
|
||||
@@ -188,7 +188,7 @@ void TextBufferTests::TestWrapFlag()
|
||||
{
|
||||
auto& textBuffer = GetTbi();
|
||||
|
||||
auto& Row = textBuffer._GetFirstRow();
|
||||
auto& Row = textBuffer.GetRowByOffset(0);
|
||||
|
||||
// no wrap by default
|
||||
VERIFY_IS_FALSE(Row.WasWrapForced());
|
||||
@@ -207,7 +207,7 @@ void TextBufferTests::TestWrapThroughWriteLine()
|
||||
auto& textBuffer = GetTbi();
|
||||
|
||||
auto VerifyWrap = [&](bool expected) {
|
||||
auto& Row = textBuffer._GetFirstRow();
|
||||
auto& Row = textBuffer.GetRowByOffset(0);
|
||||
|
||||
if (expected)
|
||||
{
|
||||
@@ -278,7 +278,7 @@ void TextBufferTests::TestDoubleBytePadFlag()
|
||||
{
|
||||
auto& textBuffer = GetTbi();
|
||||
|
||||
auto& Row = textBuffer._GetFirstRow();
|
||||
auto& Row = textBuffer.GetRowByOffset(0);
|
||||
|
||||
// no padding by default
|
||||
VERIFY_IS_FALSE(Row.WasDoubleBytePadded());
|
||||
@@ -300,7 +300,7 @@ void TextBufferTests::DoBoundaryTest(PCWCHAR const pwszInputString,
|
||||
{
|
||||
auto& textBuffer = GetTbi();
|
||||
|
||||
auto& row = textBuffer._GetFirstRow();
|
||||
auto& row = textBuffer.GetRowByOffset(0);
|
||||
|
||||
// copy string into buffer
|
||||
for (til::CoordType i = 0; i < cLength; ++i)
|
||||
@@ -622,7 +622,7 @@ void TextBufferTests::TestIncrementCircularBuffer()
|
||||
textBuffer._firstRow = iRowToTestIndex;
|
||||
|
||||
// fill first row with some stuff
|
||||
auto& FirstRow = textBuffer._GetFirstRow();
|
||||
auto& FirstRow = textBuffer.GetRowByOffset(0);
|
||||
FirstRow.ReplaceCharacters(0, 1, { L"A" });
|
||||
|
||||
// ensure it does say that it contains text
|
||||
@@ -633,7 +633,7 @@ void TextBufferTests::TestIncrementCircularBuffer()
|
||||
|
||||
// validate that first row has moved
|
||||
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
|
||||
VERIFY_IS_FALSE(FirstRow.ContainsText());
|
||||
@@ -1847,7 +1847,7 @@ void TextBufferTests::ResizeTraditionalRotationPreservesHighUnicode()
|
||||
// This is the negative squared latin capital letter B emoji: 🅱
|
||||
// It's encoded in UTF-16, as needed by the buffer.
|
||||
const auto bButton = L"\xD83C\xDD71";
|
||||
_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.
|
||||
const auto readBack = _buffer->GetTextDataAt(pos);
|
||||
@@ -1888,7 +1888,7 @@ void TextBufferTests::ScrollBufferRotationPreservesHighUnicode()
|
||||
// This is the fire emoji: 🔥
|
||||
// It's encoded in UTF-16, as needed by the buffer.
|
||||
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.
|
||||
const auto readBack = _buffer->GetTextDataAt(pos);
|
||||
@@ -1902,11 +1902,7 @@ void TextBufferTests::ScrollBufferRotationPreservesHighUnicode()
|
||||
// Scroll the row with our data by 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);
|
||||
|
||||
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())));
|
||||
}
|
||||
|
||||
@@ -1927,7 +1923,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeRowRemoval()
|
||||
// This is the eggplant emoji: 🍆
|
||||
// It's encoded in UTF-16, as needed by the buffer.
|
||||
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.
|
||||
const auto readBack = _buffer->GetTextDataAt(pos);
|
||||
@@ -1957,7 +1953,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeColumnRemoval()
|
||||
// This is the peach emoji: 🍑
|
||||
// It's encoded in UTF-16, as needed by the buffer.
|
||||
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.
|
||||
const auto readBack = _buffer->GetTextDataAt(pos);
|
||||
|
||||
@@ -325,7 +325,7 @@ public:
|
||||
return 12ul;
|
||||
}
|
||||
|
||||
bool IsCursorDoubleWidth() const noexcept override
|
||||
bool IsCursorDoubleWidth() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Microsoft::Console::Render
|
||||
virtual ULONG GetCursorHeight() const noexcept = 0;
|
||||
virtual CursorType GetCursorStyle() const noexcept = 0;
|
||||
virtual ULONG GetCursorPixelWidth() const noexcept = 0;
|
||||
virtual bool IsCursorDoubleWidth() const noexcept = 0;
|
||||
virtual bool IsCursorDoubleWidth() const = 0;
|
||||
virtual const std::vector<RenderOverlay> GetOverlays() const noexcept = 0;
|
||||
virtual const bool IsGridLineDrawingAllowed() noexcept = 0;
|
||||
virtual const std::wstring_view GetConsoleTitle() const noexcept = 0;
|
||||
|
||||
Reference in New Issue
Block a user