mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-19 18:11:39 -05:00
Use new row primitives for ResizeTraditional (#15105)
This will allow us to share the same fundamental text insertion logic for both `ResizeTraditional` and `Reflow`, because both can be implemented with `ROW::CopyRangeFrom`. It also replaces the `BufferAllocator` struct with a `_allocateBuffer` function which will help us allocate scratch buffer rows in the future. Closes #14696 ## PR Checklist * Disable reflow resize in conhost * Print "zhwik8.txt" - a enwik8.txt equivalent of Chinese Wikipedia * Run `color 80` in cmd * Resize windows from 120 to 119 columns * Wide glyphs disappear and are replaced with whitespace ✅ * Resizing the window to >120 columns adds gray whitespace ✅
This commit is contained in:
@@ -88,19 +88,6 @@ ROW::ROW(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void swap(ROW& lhs, ROW& rhs) noexcept
|
|
||||||
{
|
|
||||||
std::swap(lhs._charsBuffer, rhs._charsBuffer);
|
|
||||||
std::swap(lhs._charsHeap, rhs._charsHeap);
|
|
||||||
std::swap(lhs._chars, rhs._chars);
|
|
||||||
std::swap(lhs._charOffsets, rhs._charOffsets);
|
|
||||||
std::swap(lhs._attr, rhs._attr);
|
|
||||||
std::swap(lhs._columnCount, rhs._columnCount);
|
|
||||||
std::swap(lhs._lineRendition, rhs._lineRendition);
|
|
||||||
std::swap(lhs._wrapForced, rhs._wrapForced);
|
|
||||||
std::swap(lhs._doubleBytePadded, rhs._doubleBytePadded);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ROW::SetWrapForced(const bool wrap) noexcept
|
void ROW::SetWrapForced(const bool wrap) noexcept
|
||||||
{
|
{
|
||||||
_wrapForced = wrap;
|
_wrapForced = wrap;
|
||||||
@@ -154,78 +141,6 @@ void ROW::_init() noexcept
|
|||||||
std::iota(_charOffsets.begin(), _charOffsets.end(), uint16_t{ 0 });
|
std::iota(_charOffsets.begin(), _charOffsets.end(), uint16_t{ 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
|
||||||
// - resizes ROW to new width
|
|
||||||
// Arguments:
|
|
||||||
// - charsBuffer - a new backing buffer to use for _charsBuffer
|
|
||||||
// - charOffsetsBuffer - a new backing buffer to use for _charOffsets
|
|
||||||
// - rowWidth - the new width, in cells
|
|
||||||
// - fillAttribute - the attribute to use for any newly added, trailing cells
|
|
||||||
void ROW::Resize(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute)
|
|
||||||
{
|
|
||||||
// A default-constructed ROW has no cols/chars to copy.
|
|
||||||
// It can be detected by the lack of a _charsBuffer (among others).
|
|
||||||
//
|
|
||||||
// Otherwise, this block figures out how much we can copy into the new `rowWidth`.
|
|
||||||
uint16_t colsToCopy = 0;
|
|
||||||
uint16_t charsToCopy = 0;
|
|
||||||
if (_charsBuffer)
|
|
||||||
{
|
|
||||||
colsToCopy = std::min(rowWidth, _columnCount);
|
|
||||||
// Safety: colsToCopy is [0, _columnCount].
|
|
||||||
charsToCopy = _uncheckedCharOffset(colsToCopy);
|
|
||||||
// Safety: colsToCopy is [0, _columnCount] due to colsToCopy != 0.
|
|
||||||
for (; colsToCopy != 0 && _uncheckedIsTrailer(colsToCopy); --colsToCopy)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we grow the row width, we have to append a bunch of whitespace.
|
|
||||||
// `trailingWhitespace` stores that amount.
|
|
||||||
// Safety: The preceding block left colsToCopy in the range [0, rowWidth].
|
|
||||||
const uint16_t trailingWhitespace = rowWidth - colsToCopy;
|
|
||||||
|
|
||||||
// Allocate memory for the new `_chars` array.
|
|
||||||
// Use the provided charsBuffer if possible, otherwise allocate a `_charsHeap`.
|
|
||||||
std::unique_ptr<wchar_t[]> charsHeap;
|
|
||||||
std::span chars{ charsBuffer, rowWidth };
|
|
||||||
const std::span charOffsets{ charOffsetsBuffer, ::base::strict_cast<size_t>(rowWidth) + 1u };
|
|
||||||
if (const uint16_t charsCapacity = charsToCopy + trailingWhitespace; charsCapacity > rowWidth)
|
|
||||||
{
|
|
||||||
charsHeap = std::make_unique_for_overwrite<wchar_t[]>(charsCapacity);
|
|
||||||
chars = { charsHeap.get(), charsCapacity };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy chars and charOffsets over.
|
|
||||||
{
|
|
||||||
const auto it = std::copy_n(_chars.begin(), charsToCopy, chars.begin());
|
|
||||||
std::fill_n(it, trailingWhitespace, L' ');
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const auto it = std::copy_n(_charOffsets.begin(), colsToCopy, charOffsets.begin());
|
|
||||||
// The _charOffsets array is 1 wider than newWidth indicates.
|
|
||||||
// This is because the extra column contains the past-the-end index into _chars.
|
|
||||||
iota_n(it, trailingWhitespace + 1u, charsToCopy);
|
|
||||||
}
|
|
||||||
|
|
||||||
_charsBuffer = charsBuffer;
|
|
||||||
_charsHeap = std::move(charsHeap);
|
|
||||||
_chars = chars;
|
|
||||||
_charOffsets = charOffsets;
|
|
||||||
_columnCount = rowWidth;
|
|
||||||
|
|
||||||
// .resize_trailing_extent() doesn't work if the vector is empty,
|
|
||||||
// since there's no trailing item that could be extended.
|
|
||||||
if (_attr.empty())
|
|
||||||
{
|
|
||||||
_attr = { rowWidth, fillAttribute };
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_attr.resize_trailing_extent(rowWidth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ROW::TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& attr, til::CoordType newWidth)
|
void ROW::TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& attr, til::CoordType newWidth)
|
||||||
{
|
{
|
||||||
_attr = attr;
|
_attr = attr;
|
||||||
|
|||||||
@@ -66,11 +66,9 @@ public:
|
|||||||
ROW(const ROW& other) = delete;
|
ROW(const ROW& other) = delete;
|
||||||
ROW& operator=(const ROW& other) = delete;
|
ROW& operator=(const ROW& other) = delete;
|
||||||
|
|
||||||
explicit ROW(ROW&& other) = default;
|
ROW(ROW&& other) = default;
|
||||||
ROW& operator=(ROW&& other) = default;
|
ROW& operator=(ROW&& other) = default;
|
||||||
|
|
||||||
friend void swap(ROW& lhs, ROW& rhs) noexcept;
|
|
||||||
|
|
||||||
void SetWrapForced(const bool wrap) noexcept;
|
void SetWrapForced(const bool wrap) noexcept;
|
||||||
bool WasWrapForced() const noexcept;
|
bool WasWrapForced() const noexcept;
|
||||||
void SetDoubleBytePadded(const bool doubleBytePadded) noexcept;
|
void SetDoubleBytePadded(const bool doubleBytePadded) noexcept;
|
||||||
@@ -79,7 +77,6 @@ public:
|
|||||||
LineRendition GetLineRendition() const noexcept;
|
LineRendition GetLineRendition() const noexcept;
|
||||||
|
|
||||||
void Reset(const TextAttribute& attr);
|
void Reset(const TextAttribute& attr);
|
||||||
void Resize(wchar_t* charsBuffer, uint16_t* charOffsetsBuffer, uint16_t rowWidth, const TextAttribute& fillAttribute);
|
|
||||||
void TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& attr, til::CoordType newWidth);
|
void TransferAttributes(const til::small_rle<TextAttribute, uint16_t, 1>& attr, til::CoordType newWidth);
|
||||||
|
|
||||||
til::CoordType NavigateToPrevious(til::CoordType column) const noexcept;
|
til::CoordType NavigateToPrevious(til::CoordType column) const noexcept;
|
||||||
|
|||||||
@@ -13,75 +13,6 @@
|
|||||||
#include "../types/inc/convert.hpp"
|
#include "../types/inc/convert.hpp"
|
||||||
#include "../../types/inc/GlyphWidth.hpp"
|
#include "../../types/inc/GlyphWidth.hpp"
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
struct BufferAllocator
|
|
||||||
{
|
|
||||||
BufferAllocator(til::size sz)
|
|
||||||
{
|
|
||||||
const auto w = gsl::narrow<uint16_t>(sz.width);
|
|
||||||
const auto h = gsl::narrow<uint16_t>(sz.height);
|
|
||||||
|
|
||||||
const auto charsBytes = w * sizeof(wchar_t);
|
|
||||||
// The ROW::_indices array stores 1 more item than the buffer is wide.
|
|
||||||
// That extra column stores the past-the-end _chars pointer.
|
|
||||||
const auto indicesBytes = w * sizeof(uint16_t) + sizeof(uint16_t);
|
|
||||||
const auto rowStride = charsBytes + indicesBytes;
|
|
||||||
// 65535*65535 cells would result in a charsAreaSize of 8GiB.
|
|
||||||
// --> Use uint64_t so that we can safely do our calculations even on x86.
|
|
||||||
const auto allocSize = gsl::narrow<size_t>(::base::strict_cast<uint64_t>(rowStride) * ::base::strict_cast<uint64_t>(h));
|
|
||||||
|
|
||||||
_buffer = wil::unique_virtualalloc_ptr<std::byte>{ static_cast<std::byte*>(VirtualAlloc(nullptr, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) };
|
|
||||||
THROW_IF_NULL_ALLOC(_buffer);
|
|
||||||
|
|
||||||
_data = std::span{ _buffer.get(), allocSize }.begin();
|
|
||||||
_rowStride = rowStride;
|
|
||||||
_indicesOffset = charsBytes;
|
|
||||||
_width = w;
|
|
||||||
_height = h;
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferAllocator& operator++() noexcept
|
|
||||||
{
|
|
||||||
_data += _rowStride;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
wchar_t* chars() const noexcept
|
|
||||||
{
|
|
||||||
return til::bit_cast<wchar_t*>(&*_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t* indices() const noexcept
|
|
||||||
{
|
|
||||||
return til::bit_cast<uint16_t*>(&*(_data + _indicesOffset));
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t width() const noexcept
|
|
||||||
{
|
|
||||||
return _width;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t height() const noexcept
|
|
||||||
{
|
|
||||||
return _height;
|
|
||||||
}
|
|
||||||
|
|
||||||
wil::unique_virtualalloc_ptr<std::byte>&& take() noexcept
|
|
||||||
{
|
|
||||||
return std::move(_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
wil::unique_virtualalloc_ptr<std::byte> _buffer;
|
|
||||||
std::span<std::byte>::iterator _data;
|
|
||||||
size_t _rowStride;
|
|
||||||
size_t _indicesOffset;
|
|
||||||
uint16_t _width;
|
|
||||||
uint16_t _height;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
using namespace Microsoft::Console;
|
using namespace Microsoft::Console;
|
||||||
using namespace Microsoft::Console::Types;
|
using namespace Microsoft::Console::Types;
|
||||||
|
|
||||||
@@ -111,16 +42,7 @@ 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);
|
||||||
BufferAllocator allocator{ screenBufferSize };
|
|
||||||
|
|
||||||
_storage.reserve(allocator.height());
|
|
||||||
for (til::CoordType i = 0; i < screenBufferSize.height; ++i, ++allocator)
|
|
||||||
{
|
|
||||||
_storage.emplace_back(allocator.chars(), allocator.indices(), allocator.width(), _currentAttributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
_charBuffer = allocator.take();
|
|
||||||
_UpdateSize();
|
_UpdateSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -775,6 +697,37 @@ const Viewport TextBuffer::GetSize() const noexcept
|
|||||||
return _size;
|
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()
|
void TextBuffer::_UpdateSize()
|
||||||
{
|
{
|
||||||
_size = Viewport::FromDimensions({ _storage.at(0).size(), gsl::narrow<til::CoordType>(_storage.size()) });
|
_size = Viewport::FromDimensions({ _storage.at(0).size(), gsl::narrow<til::CoordType>(_storage.size()) });
|
||||||
@@ -1001,37 +954,55 @@ void TextBuffer::Reset()
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
BufferAllocator allocator{ newSize };
|
|
||||||
|
|
||||||
const auto currentSize = GetSize().Dimensions();
|
|
||||||
const auto attributes = GetCurrentAttributes();
|
|
||||||
|
|
||||||
til::CoordType TopRow = 0; // new top row of the screen buffer
|
til::CoordType TopRow = 0; // new top row of the screen buffer
|
||||||
if (newSize.height <= GetCursor().GetPosition().y)
|
if (newSize.height <= GetCursor().GetPosition().y)
|
||||||
{
|
{
|
||||||
TopRow = GetCursor().GetPosition().y - newSize.height + 1;
|
TopRow = GetCursor().GetPosition().y - newSize.height + 1;
|
||||||
}
|
}
|
||||||
const auto TopRowIndex = (GetFirstRowIndex() + TopRow) % currentSize.height;
|
const auto TopRowIndex = gsl::narrow_cast<size_t>(_firstRow + TopRow) % _storage.size();
|
||||||
|
|
||||||
// rotate rows until the top row is at index 0
|
std::vector<ROW> newStorage;
|
||||||
std::rotate(_storage.begin(), _storage.begin() + TopRowIndex, _storage.end());
|
auto newBuffer = _allocateBuffer(newSize, _currentAttributes, newStorage);
|
||||||
_SetFirstRowIndex(0);
|
|
||||||
|
|
||||||
// realloc in the Y direction
|
// This basically imitates a std::rotate_copy(first, mid, last), but uses ROW::CopyRangeFrom() to do the copying.
|
||||||
// remove rows if we're shrinking
|
|
||||||
_storage.resize(allocator.height());
|
|
||||||
|
|
||||||
// realloc in the X direction
|
|
||||||
for (auto& it : _storage)
|
|
||||||
{
|
{
|
||||||
it.Resize(allocator.chars(), allocator.indices(), allocator.width(), attributes);
|
const auto first = _storage.begin();
|
||||||
++allocator;
|
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 (const auto& oldRow : sourceRange)
|
||||||
|
{
|
||||||
|
til::CoordType begin = 0;
|
||||||
|
dest->CopyRangeFrom(0, til::CoordTypeMax, oldRow, begin, til::CoordTypeMax);
|
||||||
|
dest->TransferAttributes(oldRow.Attributes(), newSize.width);
|
||||||
|
++dest;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the cached size value
|
_charBuffer = std::move(newBuffer);
|
||||||
_UpdateSize();
|
_storage = std::move(newStorage);
|
||||||
|
|
||||||
_charBuffer = allocator.take();
|
_SetFirstRowIndex(0);
|
||||||
|
_UpdateSize();
|
||||||
}
|
}
|
||||||
CATCH_RETURN();
|
CATCH_RETURN();
|
||||||
|
|
||||||
|
|||||||
@@ -219,6 +219,8 @@ 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 _UpdateSize();
|
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 noexcept;
|
||||||
|
|||||||
Reference in New Issue
Block a user