mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-07 06:39:44 +00:00
Compare commits
1 Commits
release-1.
...
dev/lhecke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cfbae30b0 |
1
.github/actions/spelling/expect/expect.txt
vendored
1
.github/actions/spelling/expect/expect.txt
vendored
@@ -1116,7 +1116,6 @@ msrc
|
||||
MSVCRTD
|
||||
MTSM
|
||||
Munged
|
||||
munges
|
||||
murmurhash
|
||||
muxes
|
||||
myapplet
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "OutputCell.hpp"
|
||||
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
#include "../../types/inc/convert.hpp"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
|
||||
|
||||
@@ -8,27 +8,10 @@
|
||||
#include <til/unicode.h>
|
||||
|
||||
#include "../../types/inc/convert.hpp"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
|
||||
static constexpr TextAttribute InvalidTextAttribute{ INVALID_COLOR, INVALID_COLOR, INVALID_COLOR };
|
||||
|
||||
// Routine Description:
|
||||
// - This is a fill-mode iterator for one particular wchar. It will repeat forever if fillLimit is 0.
|
||||
// Arguments:
|
||||
// - wch - The character to use for filling
|
||||
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
|
||||
OutputCellIterator::OutputCellIterator(const wchar_t& wch, const size_t fillLimit) noexcept :
|
||||
_mode(Mode::Fill),
|
||||
_currentView(s_GenerateView(wch)),
|
||||
_run(),
|
||||
_attr(InvalidTextAttribute),
|
||||
_pos(0),
|
||||
_distance(0),
|
||||
_fillLimit(fillLimit)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is a fill-mode iterator for one particular color. It will repeat forever if fillLimit is 0.
|
||||
// Arguments:
|
||||
@@ -45,70 +28,6 @@ OutputCellIterator::OutputCellIterator(const TextAttribute& attr, const size_t f
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is a fill-mode iterator for one particular character and color. It will repeat forever if fillLimit is 0.
|
||||
// Arguments:
|
||||
// - wch - The character to use for filling
|
||||
// - attr - The color attribute to use for filling
|
||||
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
|
||||
OutputCellIterator::OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit) noexcept :
|
||||
_mode(Mode::Fill),
|
||||
_currentView(s_GenerateView(wch, attr)),
|
||||
_run(),
|
||||
_attr(InvalidTextAttribute),
|
||||
_pos(0),
|
||||
_distance(0),
|
||||
_fillLimit(fillLimit)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is a fill-mode iterator for one particular CHAR_INFO. It will repeat forever if fillLimit is 0.
|
||||
// Arguments:
|
||||
// - charInfo - The legacy character and color data to use for filling (uses Unicode portion of text data)
|
||||
// - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0.
|
||||
OutputCellIterator::OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit) noexcept :
|
||||
_mode(Mode::Fill),
|
||||
_currentView(s_GenerateView(charInfo)),
|
||||
_run(),
|
||||
_attr(InvalidTextAttribute),
|
||||
_pos(0),
|
||||
_distance(0),
|
||||
_fillLimit(fillLimit)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over a range of text only. No color data will be modified as the text is inserted.
|
||||
// Arguments:
|
||||
// - utf16Text - UTF-16 text range
|
||||
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) noexcept :
|
||||
_mode(Mode::LooseTextOnly),
|
||||
_currentView(s_GenerateView(utf16Text)),
|
||||
_run(utf16Text),
|
||||
_attr(InvalidTextAttribute),
|
||||
_pos(0),
|
||||
_distance(0),
|
||||
_fillLimit(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over a range text that will apply the same color to every position.
|
||||
// Arguments:
|
||||
// - utf16Text - UTF-16 text range
|
||||
// - attribute - Color to apply over the entire range
|
||||
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute& attribute, const size_t fillLimit) noexcept :
|
||||
_mode(Mode::Loose),
|
||||
_currentView(s_GenerateView(utf16Text, attribute)),
|
||||
_run(utf16Text),
|
||||
_attr(attribute),
|
||||
_distance(0),
|
||||
_pos(0),
|
||||
_fillLimit(fillLimit)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over legacy colors only. The text is not modified.
|
||||
// Arguments:
|
||||
@@ -124,21 +43,6 @@ OutputCellIterator::OutputCellIterator(const std::span<const WORD> legacyAttrs)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over legacy cell data. We will use the unicode text and the legacy color attribute.
|
||||
// Arguments:
|
||||
// - charInfos - Multiple cell with unicode text and legacy color data.
|
||||
OutputCellIterator::OutputCellIterator(const std::span<const CHAR_INFO> charInfos) noexcept :
|
||||
_mode(Mode::CharInfo),
|
||||
_currentView(s_GenerateView(til::at(charInfos, 0))),
|
||||
_run(charInfos),
|
||||
_attr(InvalidTextAttribute),
|
||||
_distance(0),
|
||||
_pos(0),
|
||||
_fillLimit(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over existing OutputCells with full text and color data.
|
||||
// Arguments:
|
||||
@@ -164,13 +68,6 @@ OutputCellIterator::operator bool() const noexcept
|
||||
{
|
||||
switch (_mode)
|
||||
{
|
||||
case Mode::Loose:
|
||||
case Mode::LooseTextOnly:
|
||||
{
|
||||
// In lieu of using start and end, this custom iterator type simply becomes bool false
|
||||
// when we run out of items to iterate over.
|
||||
return _pos < std::get<std::wstring_view>(_run).length();
|
||||
}
|
||||
case Mode::Fill:
|
||||
{
|
||||
if (_fillLimit > 0)
|
||||
@@ -183,10 +80,6 @@ OutputCellIterator::operator bool() const noexcept
|
||||
{
|
||||
return _pos < std::get<std::span<const OutputCell>>(_run).size();
|
||||
}
|
||||
case Mode::CharInfo:
|
||||
{
|
||||
return _pos < std::get<std::span<const CHAR_INFO>>(_run).size();
|
||||
}
|
||||
case Mode::LegacyAttr:
|
||||
{
|
||||
return _pos < std::get<std::span<const WORD>>(_run).size();
|
||||
@@ -214,34 +107,6 @@ OutputCellIterator& OutputCellIterator::operator++()
|
||||
|
||||
switch (_mode)
|
||||
{
|
||||
case Mode::Loose:
|
||||
{
|
||||
if (!_TryMoveTrailing())
|
||||
{
|
||||
// When walking through a text sequence, we need to move forward by the number of wchar_ts consumed in the previous view
|
||||
// in case we had a surrogate pair (or wider complex sequence) in the previous view.
|
||||
_pos += _currentView.Chars().size();
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateView(std::get<std::wstring_view>(_run).substr(_pos), _attr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::LooseTextOnly:
|
||||
{
|
||||
if (!_TryMoveTrailing())
|
||||
{
|
||||
// When walking through a text sequence, we need to move forward by the number of wchar_ts consumed in the previous view
|
||||
// in case we had a surrogate pair (or wider complex sequence) in the previous view.
|
||||
_pos += _currentView.Chars().size();
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateView(std::get<std::wstring_view>(_run).substr(_pos));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::Fill:
|
||||
{
|
||||
if (!_TryMoveTrailing())
|
||||
@@ -272,16 +137,6 @@ OutputCellIterator& OutputCellIterator::operator++()
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::CharInfo:
|
||||
{
|
||||
// Walk forward by one because charinfos are just the legacy version of cells and prealigned to columns
|
||||
_pos++;
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateView(til::at(std::get<std::span<const CHAR_INFO>>(_run), _pos));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::LegacyAttr:
|
||||
{
|
||||
// Walk forward by one because color attributes apply cell by cell (no complex text information)
|
||||
@@ -353,69 +208,6 @@ bool OutputCellIterator::_TryMoveTrailing() noexcept
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and specify that the attributes shouldn't be changed.
|
||||
// Arguments:
|
||||
// - view - View representing characters corresponding to a single glyph
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view) noexcept
|
||||
{
|
||||
return s_GenerateView(view, InvalidTextAttribute, TextAttributeBehavior::Current);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - view - View representing characters corresponding to a single glyph
|
||||
// - attr - Color attributes to apply to the text
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, const TextAttribute attr) noexcept
|
||||
{
|
||||
return s_GenerateView(view, attr, TextAttributeBehavior::Stored);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - view - View representing characters corresponding to a single glyph
|
||||
// - attr - Color attributes to apply to the text
|
||||
// - behavior - Behavior of the given text attribute (used when writing)
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, const TextAttribute attr, const TextAttributeBehavior behavior) noexcept
|
||||
{
|
||||
const auto glyph = til::utf16_next(view);
|
||||
const auto dbcsAttr = IsGlyphFullWidth(glyph) ? DbcsAttribute::Leading : DbcsAttribute::Single;
|
||||
return OutputCellView(glyph, dbcsAttr, attr, behavior);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - wch - View representing a single UTF-16 character (that can be represented without surrogates)
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch) noexcept
|
||||
{
|
||||
const auto glyph = std::wstring_view(&wch, 1);
|
||||
const auto dbcsAttr = IsGlyphFullWidth(wch) ? DbcsAttribute::Leading : DbcsAttribute::Single;
|
||||
return OutputCellView(glyph, dbcsAttr, InvalidTextAttribute, TextAttributeBehavior::Current);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
@@ -430,23 +222,6 @@ OutputCellView OutputCellIterator::s_GenerateView(const TextAttribute& attr) noe
|
||||
return OutputCellView({}, {}, attr, TextAttributeBehavior::StoredOnly);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - wch - View representing a single UTF-16 character (that can be represented without surrogates)
|
||||
// - attr - View representing a single color
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch, const TextAttribute& attr) noexcept
|
||||
{
|
||||
const auto glyph = std::wstring_view(&wch, 1);
|
||||
const auto dbcsAttr = IsGlyphFullWidth(wch) ? DbcsAttribute::Leading : DbcsAttribute::Single;
|
||||
return OutputCellView(glyph, dbcsAttr, attr, TextAttributeBehavior::Stored);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
@@ -465,35 +240,6 @@ OutputCellView OutputCellIterator::s_GenerateViewLegacyAttr(const WORD& legacyAt
|
||||
return s_GenerateView(attr);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - charInfo - character and attribute pair representing a single cell
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView OutputCellIterator::s_GenerateView(const CHAR_INFO& charInfo) noexcept
|
||||
{
|
||||
const auto glyph = std::wstring_view(&charInfo.Char.UnicodeChar, 1);
|
||||
|
||||
DbcsAttribute dbcsAttr = DbcsAttribute::Single;
|
||||
if (WI_IsFlagSet(charInfo.Attributes, COMMON_LVB_LEADING_BYTE))
|
||||
{
|
||||
dbcsAttr = DbcsAttribute::Leading;
|
||||
}
|
||||
else if (WI_IsFlagSet(charInfo.Attributes, COMMON_LVB_TRAILING_BYTE))
|
||||
{
|
||||
dbcsAttr = DbcsAttribute::Trailing;
|
||||
}
|
||||
|
||||
const TextAttribute textAttr(charInfo.Attributes);
|
||||
|
||||
const auto behavior = TextAttributeBehavior::Stored;
|
||||
return OutputCellView(glyph, dbcsAttr, textAttr, behavior);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
|
||||
@@ -34,14 +34,8 @@ public:
|
||||
using reference = OutputCellView&;
|
||||
|
||||
OutputCellIterator() = default;
|
||||
OutputCellIterator(const wchar_t& wch, const size_t fillLimit = 0) noexcept;
|
||||
OutputCellIterator(const TextAttribute& attr, const size_t fillLimit = 0) noexcept;
|
||||
OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit = 0) noexcept;
|
||||
OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit = 0) noexcept;
|
||||
OutputCellIterator(const std::wstring_view utf16Text) noexcept;
|
||||
OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute& attribute, const size_t fillLimit = 0) noexcept;
|
||||
OutputCellIterator(const std::span<const WORD> legacyAttributes) noexcept;
|
||||
OutputCellIterator(const std::span<const CHAR_INFO> charInfos) noexcept;
|
||||
OutputCellIterator(const std::span<const OutputCell> cells);
|
||||
~OutputCellIterator() = default;
|
||||
|
||||
@@ -63,14 +57,6 @@ public:
|
||||
private:
|
||||
enum class Mode
|
||||
{
|
||||
// Loose mode is where we're given text and attributes in a raw sort of form
|
||||
// like while data is being inserted from an API call.
|
||||
Loose,
|
||||
|
||||
// Loose mode with only text is where we're given just text and we want
|
||||
// to use the attribute already in the buffer when writing
|
||||
LooseTextOnly,
|
||||
|
||||
// Fill mode is where we were given one thing and we just need to keep giving
|
||||
// that back over and over for eternity.
|
||||
Fill,
|
||||
@@ -78,10 +64,6 @@ private:
|
||||
// Given a run of legacy attributes, convert each of them and insert only attribute data.
|
||||
LegacyAttr,
|
||||
|
||||
// CharInfo mode is where we've been given a pair of text and attribute for each
|
||||
// cell in the legacy format from an API call.
|
||||
CharInfo,
|
||||
|
||||
// Cell mode is where we have an already fully structured cell data usually
|
||||
// from accessing/copying data already put into the OutputBuffer.
|
||||
Cell,
|
||||
@@ -91,9 +73,7 @@ private:
|
||||
std::span<const WORD> _legacyAttrs;
|
||||
|
||||
std::variant<
|
||||
std::wstring_view,
|
||||
std::span<const WORD>,
|
||||
std::span<const CHAR_INFO>,
|
||||
std::span<const OutputCell>,
|
||||
std::monostate>
|
||||
_run;
|
||||
@@ -102,14 +82,10 @@ private:
|
||||
|
||||
bool _TryMoveTrailing() noexcept;
|
||||
|
||||
static OutputCellView s_GenerateView(const std::wstring_view view) noexcept;
|
||||
static OutputCellView s_GenerateView(const std::wstring_view view, const TextAttribute attr) noexcept;
|
||||
static OutputCellView s_GenerateView(const std::wstring_view view, const TextAttribute attr, const TextAttributeBehavior behavior) noexcept;
|
||||
static OutputCellView s_GenerateView(const wchar_t& wch) noexcept;
|
||||
static OutputCellView s_GenerateViewLegacyAttr(const WORD& legacyAttr) noexcept;
|
||||
static OutputCellView s_GenerateView(const TextAttribute& attr) noexcept;
|
||||
static OutputCellView s_GenerateView(const wchar_t& wch, const TextAttribute& attr) noexcept;
|
||||
static OutputCellView s_GenerateView(const CHAR_INFO& charInfo) noexcept;
|
||||
|
||||
static OutputCellView s_GenerateView(const OutputCell& cell);
|
||||
|
||||
|
||||
@@ -374,22 +374,30 @@ TextBufferCellIterator TextBuffer::GetCellDataAt(const til::point at, const View
|
||||
// Given the character offset `position` in the `chars` string, this function returns the starting position of the next grapheme.
|
||||
// For instance, given a `chars` of L"x\uD83D\uDE42y" and a `position` of 1 it'll return 3.
|
||||
// GraphemePrev would do the exact inverse of this operation.
|
||||
size_t TextBuffer::GraphemeNext(const std::wstring_view& chars, size_t position) noexcept
|
||||
size_t TextBuffer::GraphemeNext(const std::wstring_view& chars, size_t position, til::CoordType* width) noexcept
|
||||
{
|
||||
auto& cwd = CodepointWidthDetector::Singleton();
|
||||
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
|
||||
GraphemeState state{ .beg = chars.data() + position };
|
||||
cwd.GraphemeNext(state, chars);
|
||||
if (width)
|
||||
{
|
||||
*width = state.width;
|
||||
}
|
||||
return position + state.len;
|
||||
}
|
||||
|
||||
// It's the counterpart to GraphemeNext. See GraphemeNext.
|
||||
size_t TextBuffer::GraphemePrev(const std::wstring_view& chars, size_t position) noexcept
|
||||
size_t TextBuffer::GraphemePrev(const std::wstring_view& chars, size_t position, til::CoordType* width) noexcept
|
||||
{
|
||||
auto& cwd = CodepointWidthDetector::Singleton();
|
||||
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
|
||||
GraphemeState state{ .beg = chars.data() + position };
|
||||
cwd.GraphemePrev(state, chars);
|
||||
if (width)
|
||||
{
|
||||
*width = state.width;
|
||||
}
|
||||
return position - state.len;
|
||||
}
|
||||
|
||||
|
||||
@@ -99,8 +99,8 @@ public:
|
||||
|
||||
size_t GetCellDistance(const til::point from, const til::point to) const;
|
||||
|
||||
static size_t GraphemeNext(const std::wstring_view& chars, size_t position) noexcept;
|
||||
static size_t GraphemePrev(const std::wstring_view& chars, size_t position) noexcept;
|
||||
static size_t GraphemeNext(const std::wstring_view& chars, size_t position, til::CoordType* width = nullptr) noexcept;
|
||||
static size_t GraphemePrev(const std::wstring_view& chars, size_t position, til::CoordType* width = nullptr) noexcept;
|
||||
static size_t FitTextIntoColumns(const std::wstring_view& chars, til::CoordType columnLimit, til::CoordType& columns) noexcept;
|
||||
|
||||
til::point NavigateCursor(til::point position, til::CoordType distance) const;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include "../textBuffer.hpp"
|
||||
#include "../../renderer/inc/DummyRenderer.hpp"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
|
||||
#include <IDataSource.h>
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <unicode.hpp>
|
||||
#include <Utils.h>
|
||||
#include <LibraryResources.h>
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
#include "../../types/inc/Utils.hpp"
|
||||
#include "../../buffer/out/search.h"
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "../../terminal/parser/StateMachine.hpp"
|
||||
#include "../../terminal/input/terminalInput.hpp"
|
||||
#include "../../types/inc/Viewport.hpp"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
#include "../../cascadia/terminalcore/ITerminalInput.hpp"
|
||||
|
||||
#include <til/ticket_lock.h>
|
||||
|
||||
@@ -712,68 +712,3 @@ void VtIo::Writer::WriteAttributes(const TextAttribute& attributes) const
|
||||
{
|
||||
FormatAttributes(_io->_back, attributes);
|
||||
}
|
||||
|
||||
void VtIo::Writer::WriteInfos(til::point target, std::span<const CHAR_INFO> infos) const
|
||||
{
|
||||
const auto beg = infos.begin();
|
||||
const auto end = infos.end();
|
||||
const auto last = end - 1;
|
||||
WORD attributes = 0xffff;
|
||||
|
||||
WriteCUP(target);
|
||||
|
||||
for (auto it = beg; it != end; ++it)
|
||||
{
|
||||
const auto& ci = *it;
|
||||
auto ch = ci.Char.UnicodeChar;
|
||||
auto wide = WI_IsAnyFlagSet(ci.Attributes, COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
||||
|
||||
if (wide)
|
||||
{
|
||||
if (WI_IsAnyFlagSet(ci.Attributes, COMMON_LVB_LEADING_BYTE))
|
||||
{
|
||||
if (it == last)
|
||||
{
|
||||
// The leading half of a wide glyph won't fit into the last remaining column.
|
||||
// --> Replace it with a space.
|
||||
ch = L' ';
|
||||
wide = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (it == beg)
|
||||
{
|
||||
// The trailing half of a wide glyph won't fit into the first column. It's incomplete.
|
||||
// --> Replace it with a space.
|
||||
ch = L' ';
|
||||
wide = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Trailing halves of glyphs are ignored within the run. We only emit the leading half.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attributes != ci.Attributes)
|
||||
{
|
||||
attributes = ci.Attributes;
|
||||
WriteAttributes(TextAttribute{ attributes });
|
||||
}
|
||||
|
||||
int repeat = 1;
|
||||
if (wide && (til::is_surrogate(ch) || IsControlCharacter(ch)))
|
||||
{
|
||||
// Control characters, U+FFFD, etc. are narrow characters, so if the caller
|
||||
// asked for a wide glyph we need to repeat the replacement character twice.
|
||||
repeat++;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
WriteUCS2StripControlChars(ch);
|
||||
} while (--repeat);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
void WriteWindowVisibility(bool visible) const;
|
||||
void WriteWindowTitle(std::wstring_view title) const;
|
||||
void WriteAttributes(const TextAttribute& attributes) const;
|
||||
void WriteInfos(til::point target, std::span<const CHAR_INFO> infos) const;
|
||||
|
||||
private:
|
||||
VtIo* _io = nullptr;
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
#include "../types/inc/convert.hpp"
|
||||
#include "../types/inc/GlyphWidth.hpp"
|
||||
#include "../types/inc/Viewport.hpp"
|
||||
|
||||
#include <til/unicode.h>
|
||||
|
||||
using namespace Microsoft::Console::Types;
|
||||
using Microsoft::Console::Interactivity::ServiceLocator;
|
||||
|
||||
@@ -76,171 +77,166 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto& screenBuffer = screenInfo.GetActiveBuffer();
|
||||
auto& textBuffer = screenBuffer.GetTextBuffer();
|
||||
const auto bufferSize = screenBuffer.GetBufferSize();
|
||||
FillConsoleResult result;
|
||||
|
||||
if (!bufferSize.IsInBounds(startingCoordinate))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if (auto writer = gci.GetVtWriterForBuffer(&screenInfo))
|
||||
auto writer = gci.GetVtWriterForBuffer(&screenInfo);
|
||||
if (writer)
|
||||
{
|
||||
writer.BackupCursor();
|
||||
|
||||
const auto h = bufferSize.Height();
|
||||
const auto w = bufferSize.Width();
|
||||
auto y = startingCoordinate.y;
|
||||
auto input = static_cast<const uint16_t*>(data);
|
||||
size_t inputPos = 0;
|
||||
til::small_vector<CHAR_INFO, 1024> infoBuffer;
|
||||
Viewport unused;
|
||||
|
||||
infoBuffer.resize(gsl::narrow_cast<size_t>(w));
|
||||
|
||||
while (y < h && inputPos < lengthToWrite)
|
||||
{
|
||||
const auto beg = y == startingCoordinate.y ? startingCoordinate.x : 0;
|
||||
const auto columnsAvailable = w - beg;
|
||||
til::CoordType columns = 0;
|
||||
|
||||
const auto readViewport = Viewport::FromInclusive({ beg, y, w - 1, y });
|
||||
THROW_IF_FAILED(ReadConsoleOutputWImplHelper(screenInfo, infoBuffer, readViewport, unused));
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case FillConsoleMode::WriteAttribute:
|
||||
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++columns, ++inputPos)
|
||||
{
|
||||
infoBuffer[columns].Attributes = input[inputPos];
|
||||
}
|
||||
break;
|
||||
case FillConsoleMode::FillAttribute:
|
||||
for (const auto attr = input[0]; columns < columnsAvailable && inputPos < lengthToWrite; ++columns, ++inputPos)
|
||||
{
|
||||
infoBuffer[columns].Attributes = attr;
|
||||
}
|
||||
break;
|
||||
case FillConsoleMode::WriteCharacter:
|
||||
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos)
|
||||
{
|
||||
const auto ch = input[inputPos];
|
||||
if (ch >= 0x80 && IsGlyphFullWidth(ch))
|
||||
{
|
||||
// If the wide glyph doesn't fit into the last column, pad it with whitespace.
|
||||
if ((columns + 1) >= columnsAvailable)
|
||||
{
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = L' ';
|
||||
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
||||
break;
|
||||
}
|
||||
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = ch;
|
||||
lead.Attributes = lead.Attributes & ~COMMON_LVB_TRAILING_BYTE | COMMON_LVB_LEADING_BYTE;
|
||||
|
||||
auto& trail = infoBuffer[columns++];
|
||||
trail.Char.UnicodeChar = ch;
|
||||
trail.Attributes = trail.Attributes & ~COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = ch;
|
||||
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FillConsoleMode::FillCharacter:
|
||||
// Identical to WriteCharacter above, but with the if() and for() swapped.
|
||||
if (const auto ch = input[0]; ch >= 0x80 && IsGlyphFullWidth(ch))
|
||||
{
|
||||
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos)
|
||||
{
|
||||
// If the wide glyph doesn't fit into the last column, pad it with whitespace.
|
||||
if ((columns + 1) >= columnsAvailable)
|
||||
{
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = L' ';
|
||||
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
||||
break;
|
||||
}
|
||||
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = ch;
|
||||
lead.Attributes = lead.Attributes & ~COMMON_LVB_TRAILING_BYTE | COMMON_LVB_LEADING_BYTE;
|
||||
|
||||
auto& trail = infoBuffer[columns++];
|
||||
trail.Char.UnicodeChar = ch;
|
||||
trail.Attributes = trail.Attributes & ~COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos)
|
||||
{
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = ch;
|
||||
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const auto writeViewport = Viewport::FromInclusive({ beg, y, beg + columns - 1, y });
|
||||
THROW_IF_FAILED(WriteConsoleOutputWImplHelper(screenInfo, infoBuffer, w, writeViewport, unused));
|
||||
|
||||
y += 1;
|
||||
result.cellsModified += columns;
|
||||
}
|
||||
|
||||
result.lengthRead = inputPos;
|
||||
|
||||
writer.Submit();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Technically we could always pass `data` as `uint16_t*`, because `wchar_t` is guaranteed to be 16 bits large.
|
||||
// However, OutputCellIterator is terrifyingly unsafe code and so we don't do that.
|
||||
//
|
||||
// Constructing an OutputCellIterator with a `wchar_t` takes the `wchar_t` by reference, so that it can reference
|
||||
// it in a `wstring_view` forever. That's of course really bad because passing a `const uint16_t&` to a
|
||||
// `const wchar_t&` argument implicitly converts the types. To do so, the implicit conversion allocates a
|
||||
// `wchar_t` value on the stack. The lifetime of that copy DOES NOT get extended beyond the constructor call.
|
||||
// The result is that OutputCellIterator would read random data from the stack.
|
||||
//
|
||||
// Don't ever assume the lifetime of implicitly convertible types given by reference.
|
||||
// Ironically that's a bug that cannot happen with C pointers. To no ones surprise, C keeps on winning.
|
||||
auto attrs = static_cast<const uint16_t*>(data);
|
||||
auto chars = static_cast<const wchar_t*>(data);
|
||||
|
||||
OutputCellIterator it;
|
||||
FillConsoleResult result;
|
||||
const auto h = bufferSize.Height();
|
||||
const auto w = bufferSize.Width();
|
||||
auto y = startingCoordinate.y;
|
||||
auto input = static_cast<const uint16_t*>(data);
|
||||
const std::wstring_view inputText{ (const wchar_t*)input, lengthToWrite };
|
||||
size_t inputPos = 0;
|
||||
til::small_vector<CHAR_INFO, 1024> infoBuffer;
|
||||
Viewport unused;
|
||||
|
||||
infoBuffer.resize(gsl::narrow_cast<size_t>(w));
|
||||
|
||||
auto fillCharacter = UNICODE_REPLACEMENT;
|
||||
til::CoordType fillCharacterWidth = 1;
|
||||
if (mode == FillConsoleMode::FillCharacter)
|
||||
{
|
||||
wchar_t chars[3] = { L'a', inputText[0], L'a' };
|
||||
std::wstring_view foobar{ &chars[0], 3 };
|
||||
til::CoordType width = 1;
|
||||
const auto stop1 = textBuffer.GraphemeNext(foobar, 0, nullptr);
|
||||
const auto stop2 = textBuffer.GraphemeNext(foobar, stop1, &width);
|
||||
if (stop1 == 1 && stop2 == 2 && !til::is_surrogate(foobar[1]))
|
||||
{
|
||||
fillCharacter = foobar[1];
|
||||
fillCharacterWidth = width;
|
||||
}
|
||||
}
|
||||
|
||||
while (y < h && inputPos < lengthToWrite)
|
||||
{
|
||||
const auto beg = y == startingCoordinate.y ? startingCoordinate.x : 0;
|
||||
const auto columnsAvailable = w - beg;
|
||||
til::CoordType columns = 0;
|
||||
|
||||
const auto readViewport = Viewport::FromInclusive({ beg, y, w - 1, y });
|
||||
THROW_IF_FAILED(ReadConsoleOutputWImplHelper(screenInfo, infoBuffer, readViewport, unused));
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case FillConsoleMode::WriteAttribute:
|
||||
it = OutputCellIterator({ attrs, lengthToWrite });
|
||||
break;
|
||||
case FillConsoleMode::WriteCharacter:
|
||||
it = OutputCellIterator({ chars, lengthToWrite });
|
||||
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++columns, ++inputPos)
|
||||
{
|
||||
infoBuffer[columns].Attributes = input[inputPos];
|
||||
}
|
||||
break;
|
||||
case FillConsoleMode::FillAttribute:
|
||||
it = OutputCellIterator(TextAttribute(*attrs), lengthToWrite);
|
||||
for (const auto attr = input[0]; columns < columnsAvailable && inputPos < lengthToWrite; ++columns, ++inputPos)
|
||||
{
|
||||
infoBuffer[columns].Attributes = attr;
|
||||
}
|
||||
break;
|
||||
case FillConsoleMode::WriteCharacter:
|
||||
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos)
|
||||
{
|
||||
auto ch = input[inputPos];
|
||||
auto next = inputPos + 1;
|
||||
til::CoordType width = 1;
|
||||
|
||||
if (ch >= 0x80)
|
||||
{
|
||||
next = textBuffer.GraphemeNext(inputText, inputPos, &width);
|
||||
if (next - inputPos != 1 || til::is_surrogate(ch))
|
||||
{
|
||||
ch = UNICODE_REPLACEMENT;
|
||||
}
|
||||
}
|
||||
|
||||
if (width > 1)
|
||||
{
|
||||
const auto columnEnd = columns + width;
|
||||
|
||||
// If the wide glyph doesn't fit into the last column, pad it with whitespace.
|
||||
if (columnEnd > columnsAvailable)
|
||||
{
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = L' ';
|
||||
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
||||
break;
|
||||
}
|
||||
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = ch;
|
||||
lead.Attributes = lead.Attributes & ~COMMON_LVB_TRAILING_BYTE | COMMON_LVB_LEADING_BYTE;
|
||||
|
||||
auto& trail = infoBuffer[columns++];
|
||||
trail.Char.UnicodeChar = ch;
|
||||
trail.Attributes = trail.Attributes & ~COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = ch;
|
||||
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FillConsoleMode::FillCharacter:
|
||||
it = OutputCellIterator(*chars, lengthToWrite);
|
||||
{
|
||||
// Identical to WriteCharacter above, but with the if() and for() swapped.
|
||||
if (fillCharacterWidth > 1)
|
||||
{
|
||||
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos)
|
||||
{
|
||||
// If the wide glyph doesn't fit into the last column, pad it with whitespace.
|
||||
if ((columns + fillCharacterWidth) > columnsAvailable)
|
||||
{
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = L' ';
|
||||
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
||||
break;
|
||||
}
|
||||
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = fillCharacter;
|
||||
lead.Attributes = lead.Attributes & ~COMMON_LVB_TRAILING_BYTE | COMMON_LVB_LEADING_BYTE;
|
||||
|
||||
auto& trail = infoBuffer[columns++];
|
||||
trail.Char.UnicodeChar = fillCharacter;
|
||||
trail.Attributes = trail.Attributes & ~COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos)
|
||||
{
|
||||
auto& lead = infoBuffer[columns++];
|
||||
lead.Char.UnicodeChar = fillCharacter;
|
||||
lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
__assume(false);
|
||||
}
|
||||
}
|
||||
|
||||
const auto done = screenBuffer.Write(it, startingCoordinate, false);
|
||||
result.lengthRead = done.GetInputDistance(it);
|
||||
result.cellsModified = done.GetCellDistance(it);
|
||||
const auto writeViewport = Viewport::FromInclusive({ beg, y, beg + columns - 1, y });
|
||||
THROW_IF_FAILED(WriteConsoleOutputWImplHelper(screenInfo, infoBuffer, w, writeViewport, unused));
|
||||
|
||||
// If we've overwritten image content, it needs to be erased.
|
||||
ImageSlice::EraseCells(screenInfo.GetTextBuffer(), startingCoordinate, result.cellsModified);
|
||||
y += 1;
|
||||
result.cellsModified += columns;
|
||||
}
|
||||
|
||||
result.lengthRead = inputPos;
|
||||
|
||||
if (writer)
|
||||
{
|
||||
writer.Submit();
|
||||
}
|
||||
|
||||
if (screenBuffer.HasAccessibilityEventing())
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "VtIo.hpp"
|
||||
|
||||
#include "../types/inc/convert.hpp"
|
||||
#include "../types/inc/GlyphWidth.hpp"
|
||||
#include "../types/inc/Viewport.hpp"
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
@@ -349,24 +349,3 @@ MidiAudio& CONSOLE_INFORMATION::GetMidiAudio()
|
||||
{
|
||||
return _midiAudio;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Generates a CHAR_INFO for this output cell, using the TextAttribute
|
||||
// GetLegacyAttributes method to generate the legacy style attributes.
|
||||
// Arguments:
|
||||
// - cell: The cell to get the CHAR_INFO from
|
||||
// Return Value:
|
||||
// - a CHAR_INFO containing legacy information about the cell
|
||||
CHAR_INFO CONSOLE_INFORMATION::AsCharInfo(const OutputCellView& cell) const noexcept
|
||||
{
|
||||
CHAR_INFO ci{ 0 };
|
||||
ci.Char.UnicodeChar = Utf16ToUcs2(cell.Chars());
|
||||
|
||||
// If the current text attributes aren't legacy attributes, then
|
||||
// use gci to look up the correct legacy attributes to use
|
||||
// (for mapping RGB values to the nearest table value)
|
||||
const auto& attr = cell.TextAttr();
|
||||
ci.Attributes = attr.GetLegacyAttributes();
|
||||
ci.Attributes |= GeneratePublicApiAttributeFormat(cell.DbcsAttr());
|
||||
return ci;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "ApiRoutines.h"
|
||||
|
||||
#include "../types/inc/convert.hpp"
|
||||
#include "../types/inc/GlyphWidth.hpp"
|
||||
#include "../types/inc/viewport.hpp"
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
@@ -413,56 +412,6 @@ CATCH_RETURN();
|
||||
CATCH_RETURN();
|
||||
}
|
||||
|
||||
[[nodiscard]] static std::vector<CHAR_INFO> _ConvertCellsToMungedW(std::span<CHAR_INFO> buffer, const Viewport& rectangle)
|
||||
{
|
||||
std::vector<CHAR_INFO> result;
|
||||
result.reserve(buffer.size());
|
||||
|
||||
const auto size = rectangle.Dimensions();
|
||||
auto bufferIter = buffer.begin();
|
||||
|
||||
for (til::CoordType i = 0; i < size.height; i++)
|
||||
{
|
||||
for (til::CoordType j = 0; j < size.width; j++)
|
||||
{
|
||||
// Prepare a candidate charinfo on the output side copying the colors but not the lead/trail information.
|
||||
auto candidate = *bufferIter;
|
||||
WI_ClearAllFlags(candidate.Attributes, COMMON_LVB_SBCSDBCS);
|
||||
|
||||
// If the glyph we're given is full width, it needs to take two cells.
|
||||
if (IsGlyphFullWidth(candidate.Char.UnicodeChar))
|
||||
{
|
||||
// If we're not on the final cell of the row...
|
||||
if (j < size.width - 1)
|
||||
{
|
||||
// Mark that we're consuming two cells.
|
||||
j++;
|
||||
|
||||
// Fill one cell with a copy of the color and character marked leading
|
||||
WI_SetFlag(candidate.Attributes, COMMON_LVB_LEADING_BYTE);
|
||||
result.push_back(candidate);
|
||||
|
||||
// Fill a second cell with a copy of the color marked trailing and a padding character.
|
||||
WI_ClearFlag(candidate.Attributes, COMMON_LVB_LEADING_BYTE);
|
||||
WI_SetFlag(candidate.Attributes, COMMON_LVB_TRAILING_BYTE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're on the final cell, this won't fit. Replace with a space.
|
||||
candidate.Char.UnicodeChar = UNICODE_SPACE;
|
||||
}
|
||||
}
|
||||
|
||||
// Push our candidate in.
|
||||
result.push_back(candidate);
|
||||
|
||||
// Advance to read the next item.
|
||||
++bufferIter;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT ReadConsoleOutputWImplHelper(const SCREEN_INFORMATION& context,
|
||||
std::span<CHAR_INFO> targetBuffer,
|
||||
const Viewport& requestRectangle,
|
||||
@@ -470,8 +419,8 @@ CATCH_RETURN();
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto& storageBuffer = context.GetActiveBuffer();
|
||||
const auto& storageBuffer = context.GetActiveBuffer();
|
||||
const auto& textBuffer = storageBuffer.GetTextBuffer();
|
||||
const auto storageRectangle = storageBuffer.GetBufferSize();
|
||||
const auto clippedRectangle = storageRectangle.Clamp(requestRectangle);
|
||||
|
||||
@@ -482,7 +431,6 @@ CATCH_RETURN();
|
||||
}
|
||||
|
||||
const auto bufferStride = gsl::narrow_cast<size_t>(std::max(0, requestRectangle.Width()));
|
||||
const auto width = gsl::narrow_cast<size_t>(clippedRectangle.Width());
|
||||
const auto offsetY = clippedRectangle.Top() - requestRectangle.Top();
|
||||
const auto offsetX = clippedRectangle.Left() - requestRectangle.Left();
|
||||
// We always write the intersection between the valid `storageRectangle` and the given `requestRectangle`.
|
||||
@@ -498,12 +446,53 @@ CATCH_RETURN();
|
||||
|
||||
for (til::CoordType y = clippedRectangle.Top(); y <= clippedRectangle.BottomInclusive(); y++)
|
||||
{
|
||||
auto it = storageBuffer.GetCellDataAt({ clippedRectangle.Left(), y });
|
||||
const auto& row = textBuffer.GetRowByOffset(y);
|
||||
const auto& attributes = row.Attributes();
|
||||
const auto left = row.AdjustToGlyphStart(clippedRectangle.Left());
|
||||
const auto right = clippedRectangle.RightExclusive();
|
||||
|
||||
for (size_t i = 0; i < width; i++)
|
||||
{
|
||||
targetBuffer[totalOffset + i] = gci.AsCharInfo(*it);
|
||||
++it;
|
||||
const auto endOff = std::min<til::CoordType>(right, attributes.size());
|
||||
const auto begOff = std::min<til::CoordType>(left, endOff);
|
||||
const auto end = attributes.begin() + endOff;
|
||||
auto it = attributes.begin() + begOff;
|
||||
auto off = totalOffset;
|
||||
|
||||
for (; it != end; ++it, ++off)
|
||||
{
|
||||
targetBuffer[off].Attributes = it->GetLegacyAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto x = left;
|
||||
auto off = totalOffset;
|
||||
|
||||
while (x < right)
|
||||
{
|
||||
const auto next = row.NavigateToNext(x);
|
||||
const auto text = row.GetText(x, next);
|
||||
const auto wch = text.size() == 1 ? text.front() : UNICODE_REPLACEMENT;
|
||||
auto ci = &targetBuffer[off];
|
||||
|
||||
ci->Char.UnicodeChar = wch;
|
||||
off += 1;
|
||||
x += 1;
|
||||
|
||||
if (x < next)
|
||||
{
|
||||
ci->Attributes |= COMMON_LVB_LEADING_BYTE;
|
||||
|
||||
do
|
||||
{
|
||||
ci = &targetBuffer[off];
|
||||
ci->Char.UnicodeChar = wch;
|
||||
ci->Attributes |= COMMON_LVB_TRAILING_BYTE;
|
||||
off += 1;
|
||||
x += 1;
|
||||
} while (x < next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
totalOffset += bufferStride;
|
||||
@@ -575,6 +564,7 @@ CATCH_RETURN();
|
||||
}
|
||||
|
||||
auto& storageBuffer = context.GetActiveBuffer();
|
||||
auto& textBuffer = storageBuffer.GetTextBuffer();
|
||||
const auto storageRectangle = storageBuffer.GetBufferSize();
|
||||
const auto clippedRectangle = storageRectangle.Clamp(requestRectangle);
|
||||
|
||||
@@ -600,18 +590,83 @@ CATCH_RETURN();
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto writer = gci.GetVtWriterForBuffer(&context);
|
||||
std::wstring stringBuffer;
|
||||
|
||||
for (til::CoordType y = clippedRectangle.Top(); y <= clippedRectangle.BottomInclusive(); y++)
|
||||
{
|
||||
const auto charInfos = buffer.subspan(totalOffset, width);
|
||||
const til::point target{ clippedRectangle.Left(), y };
|
||||
const auto charInfos = til::safe_slice_len(buffer, totalOffset, width);
|
||||
if (charInfos.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make the iterator and write to the target position.
|
||||
storageBuffer.Write(OutputCellIterator(charInfos), target);
|
||||
auto left = clippedRectangle.Left();
|
||||
|
||||
if (writer)
|
||||
{
|
||||
writer.WriteInfos(target, charInfos);
|
||||
writer.WriteCUP({ left, y });
|
||||
}
|
||||
|
||||
for (size_t end = 0; end < charInfos.size();)
|
||||
{
|
||||
const auto attr = charInfos[end].Attributes;
|
||||
const auto beg = end;
|
||||
|
||||
// Find the next attribute change in the given CHAR_INFOs.
|
||||
while (++end < charInfos.size() && charInfos[end].Attributes == attr)
|
||||
{
|
||||
}
|
||||
|
||||
const auto infoRun = til::safe_slice_abs(charInfos, beg, end);
|
||||
THROW_HR_IF(E_UNEXPECTED, infoRun.empty());
|
||||
|
||||
// Accumulate this run of CHAR_INFOs with identical attributes into a single string.
|
||||
{
|
||||
stringBuffer.clear();
|
||||
stringBuffer.reserve(infoRun.size() * 2);
|
||||
|
||||
if (WI_IsAnyFlagSet(infoRun.front().Attributes, COMMON_LVB_TRAILING_BYTE))
|
||||
{
|
||||
stringBuffer.push_back(L' ');
|
||||
}
|
||||
|
||||
for (const auto& ci : infoRun)
|
||||
{
|
||||
if (WI_IsAnyFlagClear(ci.Attributes, COMMON_LVB_TRAILING_BYTE))
|
||||
{
|
||||
stringBuffer.push_back(Microsoft::Console::VirtualTerminal::VtIo::SanitizeUCS2(ci.Char.UnicodeChar));
|
||||
}
|
||||
}
|
||||
|
||||
if (WI_IsAnyFlagSet(infoRun.back().Attributes, COMMON_LVB_LEADING_BYTE))
|
||||
{
|
||||
stringBuffer.back() = L' ';
|
||||
}
|
||||
}
|
||||
|
||||
// Write the string into to the text buffer.
|
||||
// If the character widths assigned by the application via COMMON_LVB_LEADING_BYTE/COMMON_LVB_TRAILING_BYTE
|
||||
// don't match our idea of the character widths, we'll either pad it with whitespace or truncate it.
|
||||
stringBuffer.append(infoRun.size(), L' ');
|
||||
|
||||
const auto right = gsl::narrow<til::CoordType>(left + infoRun.size());
|
||||
const TextAttribute textAttr{ attr };
|
||||
std::wstring_view text{ stringBuffer };
|
||||
RowWriteState state{
|
||||
.text = text,
|
||||
.columnBegin = left,
|
||||
.columnLimit = right,
|
||||
};
|
||||
textBuffer.Replace(y, textAttr, state);
|
||||
|
||||
if (writer)
|
||||
{
|
||||
text.remove_suffix(state.text.size());
|
||||
writer.WriteAttributes(textAttr);
|
||||
writer.WriteUTF16(text);
|
||||
}
|
||||
|
||||
left = right;
|
||||
}
|
||||
|
||||
totalOffset += bufferStride;
|
||||
@@ -684,17 +739,7 @@ CATCH_RETURN();
|
||||
writer.BackupCursor();
|
||||
}
|
||||
|
||||
if (!context.GetActiveBuffer().GetCurrentFont().IsTrueTypeFont())
|
||||
{
|
||||
// For compatibility reasons, we must maintain the behavior that munges the data if we are writing while a raster font is enabled.
|
||||
// This can be removed when raster font support is removed.
|
||||
auto translated = _ConvertCellsToMungedW(buffer, requestRectangle);
|
||||
RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, translated, requestRectangle.Width(), requestRectangle, writtenRectangle));
|
||||
}
|
||||
else
|
||||
{
|
||||
RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, buffer, requestRectangle.Width(), requestRectangle, writtenRectangle));
|
||||
}
|
||||
RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, buffer, requestRectangle.Width(), requestRectangle, writtenRectangle));
|
||||
|
||||
if (writer)
|
||||
{
|
||||
@@ -718,10 +763,38 @@ CATCH_RETURN();
|
||||
|
||||
try
|
||||
{
|
||||
const auto attrs = ReadOutputAttributes(context.GetActiveBuffer(), origin, buffer.size());
|
||||
std::copy(attrs.cbegin(), attrs.cend(), buffer.begin());
|
||||
written = attrs.size();
|
||||
const auto width = context.GetBufferSize().Width();
|
||||
auto y = origin.y;
|
||||
size_t read = 0;
|
||||
|
||||
til::small_vector<CHAR_INFO, 1024> infoBuffer{ gsl::narrow_cast<size_t>(width) };
|
||||
|
||||
while (read < buffer.size())
|
||||
{
|
||||
const auto beg = y == origin.y ? origin.x : 0;
|
||||
const auto bufferRemaining = buffer.size() - read;
|
||||
const auto columnsRemaining = gsl::narrow_cast<size_t>(width - beg);
|
||||
const auto actualRemaining = gsl::narrow_cast<til::CoordType>(std::min(bufferRemaining, columnsRemaining));
|
||||
const auto end = beg + actualRemaining;
|
||||
|
||||
const auto wantViewport = Viewport::FromInclusive({ beg, y, end, y });
|
||||
Viewport actualViewport;
|
||||
RETURN_IF_FAILED(ReadConsoleOutputWImplHelper(context, infoBuffer, wantViewport, actualViewport));
|
||||
|
||||
if (wantViewport != actualViewport)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (til::CoordType i = 0; i < actualRemaining; ++i)
|
||||
{
|
||||
buffer[read++] = infoBuffer[i].Attributes;
|
||||
}
|
||||
|
||||
y += 1;
|
||||
}
|
||||
|
||||
written = read;
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
@@ -503,16 +503,15 @@ void ApiRoutines::SetConsoleActiveScreenBufferImpl(SCREEN_INFORMATION& newContex
|
||||
alt.SetViewportSize(&size);
|
||||
}
|
||||
|
||||
Viewport read;
|
||||
til::small_vector<CHAR_INFO, 1024> infos;
|
||||
infos.resize(area, CHAR_INFO{ L' ', FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED });
|
||||
|
||||
const auto dumpScreenInfo = [&](SCREEN_INFORMATION& screenInfo) {
|
||||
Viewport read;
|
||||
THROW_IF_FAILED(ReadConsoleOutputWImpl(screenInfo, infos, viewport, read));
|
||||
for (til::CoordType i = 0; i < size.height; i++)
|
||||
{
|
||||
writer.WriteInfos({ 0, i }, { infos.begin() + i * size.width, static_cast<size_t>(size.width) });
|
||||
}
|
||||
|
||||
Viewport write;
|
||||
THROW_IF_FAILED(WriteConsoleOutputWImpl(screenInfo, infos, read, write));
|
||||
|
||||
writer.WriteCUP(screenInfo.GetTextBuffer().GetCursor().GetPosition());
|
||||
writer.WriteAttributes(screenInfo.GetAttributes());
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <til/unicode.h>
|
||||
|
||||
#include "dbcs.h"
|
||||
#include "../types/inc/GlyphWidth.hpp"
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -119,63 +119,6 @@ static void _CopyRectangle(SCREEN_INFORMATION& screenInfo,
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine reads a sequence of attributes from the screen buffer.
|
||||
// Arguments:
|
||||
// - screenInfo - reference to screen buffer information.
|
||||
// - coordRead - Screen buffer coordinate to begin reading from.
|
||||
// - amountToRead - the number of elements to read
|
||||
// Return Value:
|
||||
// - vector of attribute data
|
||||
std::vector<WORD> ReadOutputAttributes(const SCREEN_INFORMATION& screenInfo,
|
||||
const til::point coordRead,
|
||||
const size_t amountToRead)
|
||||
{
|
||||
// Short circuit. If nothing to read, leave early.
|
||||
if (amountToRead == 0)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
// Short circuit, if reading out of bounds, leave early.
|
||||
if (!screenInfo.GetBufferSize().IsInBounds(coordRead))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
// Get iterator to the position we should start reading at.
|
||||
auto it = screenInfo.GetCellDataAt(coordRead);
|
||||
// Count up the number of cells we've attempted to read.
|
||||
ULONG amountRead = 0;
|
||||
// Prepare the return value string.
|
||||
std::vector<WORD> retVal;
|
||||
// Reserve the number of cells. If we have >U+FFFF, it will auto-grow later and that's OK.
|
||||
retVal.reserve(amountToRead);
|
||||
|
||||
// While we haven't read enough cells yet and the iterator is still valid (hasn't reached end of buffer)
|
||||
while (amountRead < amountToRead && it)
|
||||
{
|
||||
const auto legacyAttributes = it->TextAttr().GetLegacyAttributes();
|
||||
|
||||
// If the first thing we read is trailing, pad with a space.
|
||||
// OR If the last thing we read is leading, pad with a space.
|
||||
if ((amountRead == 0 && it->DbcsAttr() == DbcsAttribute::Trailing) ||
|
||||
(amountRead == (amountToRead - 1) && it->DbcsAttr() == DbcsAttribute::Leading))
|
||||
{
|
||||
retVal.push_back(legacyAttributes);
|
||||
}
|
||||
else
|
||||
{
|
||||
retVal.push_back(legacyAttributes | GeneratePublicApiAttributeFormat(it->DbcsAttr()));
|
||||
}
|
||||
|
||||
amountRead++;
|
||||
it++;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine reads a sequence of unicode characters from the screen buffer
|
||||
// Arguments:
|
||||
|
||||
@@ -26,10 +26,6 @@ void ScreenBufferSizeChange(const til::size coordNewSize);
|
||||
|
||||
[[nodiscard]] NTSTATUS DoCreateScreenBuffer();
|
||||
|
||||
std::vector<WORD> ReadOutputAttributes(const SCREEN_INFORMATION& screenInfo,
|
||||
const til::point coordRead,
|
||||
const size_t amountToRead);
|
||||
|
||||
std::wstring ReadOutputStringW(const SCREEN_INFORMATION& screenInfo,
|
||||
const til::point coordRead,
|
||||
const size_t amountToRead);
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "readDataRaw.hpp"
|
||||
#include "dbcs.h"
|
||||
#include "stream.h"
|
||||
#include "../types/inc/GlyphWidth.hpp"
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
|
||||
@@ -142,8 +142,6 @@ public:
|
||||
|
||||
MidiAudio& GetMidiAudio();
|
||||
|
||||
CHAR_INFO AsCharInfo(const OutputCellView& cell) const noexcept;
|
||||
|
||||
RenderData renderData;
|
||||
|
||||
private:
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include "ApiRoutines.h"
|
||||
|
||||
#include "../types/inc/GlyphWidth.hpp"
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
|
||||
|
||||
@@ -305,8 +305,6 @@ class TextBufferIteratorTests
|
||||
TEST_METHOD(DifferenceOperatorText);
|
||||
TEST_METHOD(DifferenceOperatorCell);
|
||||
|
||||
TEST_METHOD(AsCharInfoCell);
|
||||
|
||||
TEST_METHOD(DereferenceOperatorText);
|
||||
TEST_METHOD(DereferenceOperatorCell);
|
||||
|
||||
@@ -442,27 +440,6 @@ void TextBufferIteratorTests::DifferenceOperatorCell()
|
||||
DifferenceOperatorTestHelper<TextBufferCellIterator>();
|
||||
}
|
||||
|
||||
void TextBufferIteratorTests::AsCharInfoCell()
|
||||
{
|
||||
m_state->FillTextBuffer();
|
||||
const auto it = GetIterator<TextBufferCellIterator>();
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
|
||||
const auto& outputBuffer = gci.GetActiveOutputBuffer();
|
||||
|
||||
const auto& row = outputBuffer._textBuffer->GetRowByOffset(it._pos.y);
|
||||
|
||||
const auto wcharExpected = *row.GlyphAt(it._pos.x).begin();
|
||||
const auto attrExpected = row.GetAttrByColumn(it._pos.x);
|
||||
|
||||
const auto cellActual = gci.AsCharInfo(*it);
|
||||
const auto wcharActual = cellActual.Char.UnicodeChar;
|
||||
const auto attrActual = it->TextAttr();
|
||||
|
||||
VERIFY_ARE_EQUAL(wcharExpected, wcharActual);
|
||||
VERIFY_ARE_EQUAL(attrExpected, attrActual);
|
||||
}
|
||||
|
||||
void TextBufferIteratorTests::DereferenceOperatorText()
|
||||
{
|
||||
m_state->FillTextBuffer();
|
||||
|
||||
Reference in New Issue
Block a user