diff --git a/cmake/PedanticCompiler.cmake b/cmake/PedanticCompiler.cmake
index ecf2d63011..983db970dd 100644
--- a/cmake/PedanticCompiler.cmake
+++ b/cmake/PedanticCompiler.cmake
@@ -56,7 +56,11 @@ else()
endif()
if(${PEDANTIC_COMPILER_WERROR})
- try_add_compile_options(-Werror) # XXX Not yet, but hopefully soon.
+ try_add_compile_options(-Werror)
+
+ # Don't complain here. That's needed for bitpacking (codepoint_properties) in libunicode dependency.
+ try_add_compile_options(-Wno-error=c++20-extensions)
+ try_add_compile_options(-Wno-c++20-extensions)
# Not sure how to work around these.
try_add_compile_options(-Wno-error=class-memaccess)
diff --git a/metainfo.xml b/metainfo.xml
index aa1563156f..92a4b85a97 100644
--- a/metainfo.xml
+++ b/metainfo.xml
@@ -111,6 +111,7 @@
Removes `images.sixel_cursor_conformance` config option.
Adds VT sequence DECSCA, DECSEL, DECSED and DECSERA to support protected grid areas during erase operations (#29, #30, #31).
Improve Input Method (IME) handling, visualizing preedit-text.
+ Improve throughput performance of arbitrary complex Unicode.
Update Unicode data to version 15.0.0 (release). See Announcing The UnicodeĀ® Standard, Version 15.0.0.
Fixes cursor highlight in VI mode
diff --git a/scripts/install-deps.ps1 b/scripts/install-deps.ps1
index b7bdd5f032..11900ebf70 100755
--- a/scripts/install-deps.ps1
+++ b/scripts/install-deps.ps1
@@ -26,9 +26,9 @@ $ThirdParties =
Macro = ""
};
[ThirdParty]@{
- Folder = "libunicode-44969c6d80e44a4731b584d047ba108769926835";
- Archive = "libunicode-44969c6d80e44a4731b584d047ba108769926835.zip";
- URI = "https://github.com/contour-terminal/libunicode/archive/44969c6d80e44a4731b584d047ba108769926835.zip";
+ Folder = "libunicode-be83e5052f6e7590c244faad59be16af210db90b";
+ Archive = "libunicode-be83e5052f6e7590c244faad59be16af210db90b.zip";
+ URI = "https://github.com/contour-terminal/libunicode/archive/be83e5052f6e7590c244faad59be16af210db90b.zip";
Macro = "libunicode"
};
[ThirdParty]@{
diff --git a/scripts/install-deps.sh b/scripts/install-deps.sh
index 1db9848b73..4b15a8ca46 100755
--- a/scripts/install-deps.sh
+++ b/scripts/install-deps.sh
@@ -102,12 +102,20 @@ fetch_and_unpack_embeds()
https://github.com/contour-terminal/termbench-pro/archive/$termbench_pro_git_sha.tar.gz \
termbench_pro
- local libunicode_git_sha="44969c6d80e44a4731b584d047ba108769926835"
- fetch_and_unpack \
- libunicode-$libunicode_git_sha \
- libunicode-$libunicode_git_sha.tar.gz \
- https://github.com/contour-terminal/libunicode/archive/$libunicode_git_sha.tar.gz \
- libunicode
+ if test x$LIBUNICODE_SRC_DIR = x; then
+ local libunicode_git_sha="be83e5052f6e7590c244faad59be16af210db90b"
+ fetch_and_unpack \
+ libunicode-$libunicode_git_sha \
+ libunicode-$libunicode_git_sha.tar.gz \
+ https://github.com/contour-terminal/libunicode/archive/$libunicode_git_sha.tar.gz \
+ libunicode
+ else
+ echo "Hard linking external libunicode source directory to: $LIBUNICODE_SRC_DIR"
+ MACRO="libunicode"
+ echo "macro(ContourThirdParties_Embed_$MACRO)" >> $SYSDEPS_CMAKE_FILE
+ echo " add_subdirectory($LIBUNICODE_SRC_DIR libunicode EXCLUDE_FROM_ALL)" >> $SYSDEPS_CMAKE_FILE
+ echo "endmacro()" >> $SYSDEPS_CMAKE_FILE
+ fi
}
fetch_and_unpack_yaml_cpp()
diff --git a/src/terminal/Line.cpp b/src/terminal/Line.cpp
index f25cd1a04b..35326228b0 100644
--- a/src/terminal/Line.cpp
+++ b/src/terminal/Line.cpp
@@ -154,10 +154,10 @@ InflatedLineBuffer inflate(TrivialLineBuffer const& input)
auto columns = InflatedLineBuffer {};
columns.reserve(unbox(input.displayWidth));
- // fmt::print("Inflating {}/{}\n", input.text.size(), input.displayWidth);
auto lastChar = char32_t { 0 };
auto utf8DecoderState = unicode::utf8_decoder_state {};
+ auto gapPending = 0;
for (char const ch: input.text.view())
{
@@ -173,10 +173,16 @@ InflatedLineBuffer inflate(TrivialLineBuffer const& input)
if (!lastChar || isAsciiBreakable || unicode::grapheme_segmenter::breakable(lastChar, nextChar))
{
+ while (gapPending > 0)
+ {
+ columns.emplace_back(Cell { input.textAttributes, input.hyperlink });
+ --gapPending;
+ }
+ auto const charWidth = unicode::width(nextChar);
columns.emplace_back(Cell {});
columns.back().setHyperlink(input.hyperlink);
- columns.back().write(
- input.textAttributes, nextChar, static_cast(unicode::width(nextChar)));
+ columns.back().write(input.textAttributes, nextChar, static_cast(charWidth));
+ gapPending = charWidth - 1;
}
else
{
@@ -193,7 +199,15 @@ InflatedLineBuffer inflate(TrivialLineBuffer const& input)
}
}
}
+ lastChar = nextChar;
+ }
+
+ while (gapPending > 0)
+ {
+ columns.emplace_back(Cell { input.textAttributes, input.hyperlink });
+ --gapPending;
}
+
assert(columns.size() == unbox(input.usedColumns));
while (columns.size() < unbox(input.displayWidth))
diff --git a/src/terminal/Line_test.cpp b/src/terminal/Line_test.cpp
index f495ea4cdf..0070ffca16 100644
--- a/src/terminal/Line_test.cpp
+++ b/src/terminal/Line_test.cpp
@@ -66,3 +66,110 @@ TEST_CASE("Line.inflate", "[Line]")
CHECK(char(cell.codepoint(0)) == testText[i]);
}
}
+
+TEST_CASE("Line.inflate.Unicode", "[Line]")
+{
+ auto constexpr DisplayWidth = ColumnCount(10);
+ auto constexpr testTextUtf32 = U"0\u2705123456789ABCDEF"sv;
+ auto const testTextUtf8 = unicode::convert_to(testTextUtf32);
+
+ auto pool = BufferObjectPool(32);
+ auto bufferObject = pool.allocateBufferObject();
+ bufferObject->writeAtEnd(testTextUtf8);
+
+ // Buffer fragment containing 9 codepoints, with one of them using display width of 2.
+ auto const bufferFragment = bufferObject->ref(0, 11);
+
+ auto sgr = GraphicsAttributes {};
+ sgr.foregroundColor = RGBColor(0x123456);
+ sgr.backgroundColor = Color::Indexed(IndexedColor::Yellow);
+ sgr.underlineColor = Color::Indexed(IndexedColor::Red);
+ sgr.flags |= CellFlags::CurlyUnderlined;
+ auto const trivial =
+ TrivialLineBuffer { DisplayWidth, sgr, sgr, HyperlinkId {}, DisplayWidth, bufferFragment };
+
+ auto const inflated = inflate(trivial);
+
+ CHECK(inflated.size() == unbox(DisplayWidth));
+ for (size_t i = 0, k = 0; i < inflated.size();)
+ {
+ auto const& cell = inflated[i];
+ INFO(fmt::format("column {}, k {}, codepoint U+{:X}", i, k, (unsigned) cell.codepoint(0)));
+ REQUIRE(cell.codepointCount() == 1);
+ REQUIRE(cell.codepoint(0) == testTextUtf32[k]);
+ REQUIRE(cell.foregroundColor() == sgr.foregroundColor);
+ REQUIRE(cell.backgroundColor() == sgr.backgroundColor);
+ REQUIRE(cell.underlineColor() == sgr.underlineColor);
+ for (int n = 1; n < cell.width(); ++n)
+ {
+ INFO(fmt::format("column.sub: {}\n", n));
+ auto const& fillCell = inflated.at(i + static_cast(n));
+ REQUIRE(fillCell.codepointCount() == 0);
+ REQUIRE(fillCell.foregroundColor() == sgr.foregroundColor);
+ REQUIRE(fillCell.backgroundColor() == sgr.backgroundColor);
+ REQUIRE(fillCell.underlineColor() == sgr.underlineColor);
+ }
+ i += cell.width();
+ k++;
+ }
+}
+
+TEST_CASE("Line.inflate.Unicode.FamilyEmoji", "[Line]")
+{
+ // Ensure inflate() is also working for reaaally complex Unicode grapheme clusters.
+
+ auto constexpr DisplayWidth = ColumnCount(5);
+ auto constexpr UsedColumnCount = ColumnCount(4);
+ auto constexpr testTextUtf32 = U"A\U0001F468\u200D\U0001F468\u200D\U0001F467B"sv;
+ auto const testTextUtf8 = unicode::convert_to(testTextUtf32);
+ auto const familyEmojiUtf8 = unicode::convert_to(U"\U0001F468\u200D\U0001F468\u200D\U0001F467"sv);
+
+ auto pool = BufferObjectPool(32);
+ auto bufferObject = pool.allocateBufferObject();
+ bufferObject->writeAtEnd(testTextUtf8);
+
+ auto const bufferFragment = bufferObject->ref(0, testTextUtf8.size());
+
+ auto sgr = GraphicsAttributes {};
+ sgr.foregroundColor = RGBColor(0x123456);
+ sgr.backgroundColor = Color::Indexed(IndexedColor::Yellow);
+ sgr.underlineColor = Color::Indexed(IndexedColor::Red);
+ sgr.flags |= CellFlags::CurlyUnderlined;
+
+ auto fillSGR = GraphicsAttributes {};
+ fillSGR.foregroundColor = RGBColor(0x123456);
+ fillSGR.backgroundColor = Color::Indexed(IndexedColor::Yellow);
+ fillSGR.underlineColor = Color::Indexed(IndexedColor::Red);
+ fillSGR.flags |= CellFlags::CurlyUnderlined;
+
+ auto const trivial =
+ TrivialLineBuffer { DisplayWidth, sgr, fillSGR, HyperlinkId {}, UsedColumnCount, bufferFragment };
+
+ auto const inflated = inflate(trivial);
+
+ CHECK(inflated.size() == unbox(DisplayWidth));
+
+ // Check text in 0..3
+ // Check @4 is empty text.
+ // Check 0..3 has same SGR.
+ // Check @4 has fill-SGR.
+
+ REQUIRE(inflated[0].toUtf8() == "A");
+ REQUIRE(inflated[1].toUtf8() == familyEmojiUtf8);
+ REQUIRE(inflated[2].toUtf8() == "");
+ REQUIRE(inflated[3].toUtf8() == "B");
+ REQUIRE(inflated[4].toUtf8() == "");
+
+ for (auto const i: { 0u, 2u, 1u, 3u })
+ {
+ auto const& cell = inflated[i];
+ REQUIRE(cell.foregroundColor() == sgr.foregroundColor);
+ REQUIRE(cell.backgroundColor() == sgr.backgroundColor);
+ REQUIRE(cell.underlineColor() == sgr.underlineColor);
+ }
+
+ auto const& cell = inflated[4];
+ REQUIRE(cell.foregroundColor() == fillSGR.foregroundColor);
+ REQUIRE(cell.backgroundColor() == fillSGR.backgroundColor);
+ REQUIRE(cell.underlineColor() == fillSGR.underlineColor);
+}
diff --git a/src/terminal/Parser.cpp b/src/terminal/Parser.cpp
index 17ab982c56..795d070cf4 100644
--- a/src/terminal/Parser.cpp
+++ b/src/terminal/Parser.cpp
@@ -354,43 +354,49 @@ void Parser::parseFragment(std::string_view co
auto input = _data.data();
auto const end = _data.data() + _data.size();
- do
+ while (input != end)
{
- if (state_ == State::Ground)
+ if (state_ == State::Ground && eventListener_.acceptsBulkText())
{
auto const chunk = std::string_view(input, static_cast(std::distance(input, end)));
+ auto const [cellCount, next, subStart, subEnd] =
+ unicode::scan_for_text(scanState_, chunk, maxCharCount);
- if (auto const cellCount = unicode::scan_for_text_ascii(chunk, maxCharCount); cellCount > 0)
+ if (next != input)
{
- auto const next = input + cellCount;
- auto const byteCount = static_cast(std::distance(input, next));
- precedingGraphicCharacter = static_cast(input[cellCount - 1]);
- assert(byteCount <= chunk.size());
+ // We do not test on cellCount>0 because the scan could contain only a ZWJ (zero width
+ // joiner), and that would be misleading.
+
+ assert(subStart <= subEnd);
+ auto const byteCount = static_cast(std::distance(subStart, subEnd));
assert(cellCount <= maxCharCount);
+ assert(subEnd <= chunk.data() + chunk.size());
assert(next <= chunk.data() + chunk.size());
#if defined(LIBTERMINAL_LOG_TRACE)
if (VTTraceParserLog)
- VTTraceParserLog()(
- "[{}] Scanned text: cap {}; available cells {}; chars {}; bytes {}; \"{}\"",
- "US-ASCII",
- chunk.size(),
- maxCharCount,
- cellCount,
- byteCount,
- crispy::escape(std::string_view { input, byteCount }));
+ VTTraceParserLog()("[Unicode] Scanned text: {}/{} cells; \"{}\"",
+ cellCount,
+ maxCharCount,
+ crispy::escape(std::string_view { input, byteCount }));
#endif
- auto const text = std::string_view { input, byteCount };
- if (utf8DecoderState_.expectedLength == 0)
+ auto const text = std::string_view { subStart, byteCount };
+ if (scanState_.utf8.expectedLength == 0)
{
- maxCharCount = eventListener_.print(text, cellCount);
- precedingGraphicCharacter = static_cast(text.back());
+ if (!text.empty())
+ {
+ maxCharCount = eventListener_.print(text, cellCount);
+ }
}
else
{
- for (char const ch: text)
- printUtf8Byte(ch);
+ // fmt::print("Parser.text: incomplete UTF-8 sequence at end: {}/{}\n",
+ // scanState_.utf8.currentLength,
+ // scanState_.utf8.expectedLength);
+
+ // for (char const ch: text)
+ // printUtf8Byte(ch);
}
input = next;
@@ -429,13 +435,13 @@ void Parser::parseFragment(std::string_view co
state_,
ch,
static_cast(ch)));
- } while (input != end);
+ }
}
template
void Parser::printUtf8Byte(char ch)
{
- unicode::ConvertResult const r = unicode::from_utf8(utf8DecoderState_, (uint8_t) ch);
+ unicode::ConvertResult const r = unicode::from_utf8(scanState_.utf8, (uint8_t) ch);
if (std::holds_alternative(r))
return;
@@ -443,7 +449,7 @@ void Parser::printUtf8Byte(char ch)
auto const codepoint = std::holds_alternative(r) ? std::get(r).value
: ReplacementCharacter;
eventListener_.print(codepoint);
- precedingGraphicCharacter = codepoint;
+ scanState_.lastCodepointHint = codepoint;
}
template
@@ -466,7 +472,7 @@ void Parser::handle(ActionClass _actionClass,
switch (_action)
{
- case Action::GroundStart: precedingGraphicCharacter = 0; break;
+ case Action::GroundStart: scanState_.lastCodepointHint = 0; break;
case Action::Clear: eventListener_.clear(); break;
case Action::CollectLeader: eventListener_.collectLeader(ch); break;
case Action::Collect: eventListener_.collect(ch); break;
diff --git a/src/terminal/Parser.h b/src/terminal/Parser.h
index 5f1dd2739c..35822e7685 100644
--- a/src/terminal/Parser.h
+++ b/src/terminal/Parser.h
@@ -19,6 +19,7 @@
#include
#include
+#include
#include
#include
@@ -554,17 +555,18 @@ class Parser
[[nodiscard]] State state() const noexcept { return state_; }
- char32_t precedingGraphicCharacter = 0;
+ [[nodiscard]] char32_t precedingGraphicCharacter() const noexcept { return scanState_.lastCodepointHint; }
+
+ void printUtf8Byte(char ch);
private:
void handle(ActionClass _actionClass, Action _action, uint8_t _char);
- void printUtf8Byte(char ch);
// private properties
//
State state_ = State::Ground;
EventListener& eventListener_;
- unicode::utf8_decoder_state utf8DecoderState_ = {};
+ unicode::scan_state scanState_ {};
};
/// @returns parsed tuple with OSC code and offset to first data parameter byte.
diff --git a/src/terminal/ParserEvents.h b/src/terminal/ParserEvents.h
index 83f7520833..1d4e671f82 100644
--- a/src/terminal/ParserEvents.h
+++ b/src/terminal/ParserEvents.h
@@ -48,6 +48,14 @@ class ParserEvents
*/
virtual size_t print(std::string_view _chars, size_t cellCount) = 0;
+ /**
+ * Used to indicate whether or not the print() overload may be used to process bulk text.
+ *
+ * There may be situations where it would not be efficient to process bulk text. In such situations,
+ * simply calling print() per codepoint is sufficient (potentially being more performant).
+ */
+ [[nodiscard]] virtual bool acceptsBulkText() const noexcept = 0;
+
/**
* The C0 or C1 control function should be executed, which may have any one of a variety of
* effects, including changing the cursor position, suspending or resuming communications or
@@ -168,6 +176,7 @@ class NullParserEvents: public ParserEvents
void error(std::string_view const&) override {}
void print(char32_t) override {}
size_t print(std::string_view, size_t) override { return 0; }
+ [[nodiscard]] bool acceptsBulkText() const noexcept override { return true; }
void execute(char) override {}
void clear() override {}
void collect(char) override {}
diff --git a/src/terminal/Screen.cpp b/src/terminal/Screen.cpp
index c47c92baa1..4a6b83908d 100644
--- a/src/terminal/Screen.cpp
+++ b/src/terminal/Screen.cpp
@@ -292,6 +292,7 @@ string_view Screen::tryEmplaceChars(string_view _chars, size_t cellCount)
crlfIfWrapPending();
auto const columnsAvailable = pageSize().columns.value - _state.cursor.position.column.value;
+ assert(cellCount <= static_cast(columnsAvailable));
if (!_terminal.isModeEnabled(DECMode::AutoWrap) && cellCount > static_cast(columnsAvailable))
// With AutoWrap on, we can only emplace if it fits the line.
@@ -301,51 +302,36 @@ string_view Screen::tryEmplaceChars(string_view _chars, size_t cellCount)
{
if (currentLine().empty())
{
- _chars.remove_prefix(emplaceCharsIntoCurrentLine(_chars, cellCount));
- _chars = tryEmplaceContinuousChars(_chars, cellCount);
- _terminal.currentPtyBuffer()->advanceHotEndUntil(_chars.data());
- return _chars;
+ auto const numberOfBytesEmplaced = emplaceCharsIntoCurrentLine(_chars, cellCount);
+ _terminal.currentPtyBuffer()->advanceHotEndUntil(_chars.data() + numberOfBytesEmplaced);
+ _chars.remove_prefix(numberOfBytesEmplaced);
+ assert(_chars.empty());
}
return _chars;
}
- if (canResumeEmplace(_chars))
+ if (isContiguousToCurrentLine(_chars))
{
- auto const charsToWrite = static_cast(min(columnsAvailable, static_cast(_chars.size())));
+ // We can append the chars to a pre-existing non-empty line.
+ assert(static_cast(cellCount) <= columnsAvailable);
auto& lineBuffer = currentLine().trivialBuffer();
- lineBuffer.text.growBy(charsToWrite);
+ lineBuffer.text.growBy(_chars.size());
lineBuffer.usedColumns += ColumnCount::cast_from(cellCount);
- advanceCursorAfterWrite(ColumnCount::cast_from(charsToWrite));
- _chars.remove_prefix(charsToWrite);
- _chars = tryEmplaceContinuousChars(_chars, cellCount);
- _terminal.currentPtyBuffer()->advanceHotEndUntil(_chars.data());
+ advanceCursorAfterWrite(ColumnCount::cast_from(cellCount));
+ _terminal.currentPtyBuffer()->advanceHotEndUntil(_chars.data() + _chars.size());
+ _chars.remove_prefix(_chars.size());
return _chars;
}
return _chars;
}
-template
-string_view Screen::tryEmplaceContinuousChars(string_view _chars, size_t cellCount) noexcept
-{
- while (!_chars.empty())
- {
- crlf();
- if (!currentLine().empty())
- break;
-
- _chars.remove_prefix(emplaceCharsIntoCurrentLine(_chars, cellCount));
- }
-
- return _chars;
-}
-
template
size_t Screen::emplaceCharsIntoCurrentLine(string_view _chars, size_t cellCount) noexcept
{
- auto columnsAvailable = (_state.margin.horizontal.to.value + 1) - _state.cursor.position.column.value;
+ [[maybe_unused]] auto columnsAvailable =
+ (_state.margin.horizontal.to.value + 1) - _state.cursor.position.column.value;
assert(cellCount <= static_cast(columnsAvailable));
- auto const charsToWrite = static_cast(min(columnsAvailable, static_cast(_chars.size())));
Line& line = currentLine();
if (line.isTrivialBuffer() && line.empty())
@@ -357,29 +343,19 @@ size_t Screen::emplaceCharsIntoCurrentLine(string_view _chars, size_t cell
line.trivialBuffer().fillAttributes,
_state.cursor.hyperlink,
ColumnCount::cast_from(cellCount),
- crispy::BufferFragment {
- _terminal.currentPtyBuffer(),
- _chars.substr(0, charsToWrite),
- } });
+ crispy::BufferFragment { _terminal.currentPtyBuffer(), _chars } });
advanceCursorAfterWrite(ColumnCount::cast_from(cellCount));
}
else
{
// Transforming _chars input from UTF-8 to UTF-32 even though right now it should only
// be containing US-ASCII, but soon it'll be any arbitrary textual Unicode codepoints.
- auto utf8DecoderState = unicode::utf8_decoder_state {};
for (char const ch: _chars)
{
- auto const result = unicode::from_utf8(utf8DecoderState, static_cast(ch));
- if (holds_alternative(result))
- writeText(get(result).value);
- else if (holds_alternative(result))
- writeText(U'\uFFFE'); // U+FFFE (Not a Character)
+ _state.parser.printUtf8Byte(ch);
}
}
- // fmt::print("emplaceCharsIntoCurrentLine ({} cols, {} bytes): \"{}\"\n", cellCount, _chars.size(),
- // _chars);
- return charsToWrite;
+ return _chars.size();
}
template
@@ -397,34 +373,25 @@ void Screen::advanceCursorAfterWrite(ColumnCount n) noexcept
}
}
-template
-bool Screen::canResumeEmplace(std::string_view continuationChars) const noexcept
-{
- auto& line = currentLine();
- if (!line.isTrivialBuffer())
- return false;
- TrivialLineBuffer const& buffer = line.trivialBuffer();
- return buffer.text.view().end() == continuationChars.begin()
- && buffer.textAttributes == _state.cursor.graphicsRendition
- && buffer.hyperlink == _state.cursor.hyperlink
- && buffer.usedColumns == boxed_cast(cursor().position.column)
- && buffer.text.owner() == _terminal.currentPtyBuffer();
-}
-
template
void Screen::writeText(string_view _chars, size_t cellCount)
{
#if defined(LIBTERMINAL_LOG_TRACE)
if (VTTraceSequenceLog)
- VTTraceSequenceLog()("text({} bytes): \"{}\"", _chars.size(), _chars);
+ VTTraceSequenceLog()("text({} bytes, {} cells): \"{}\"", _chars.size(), cellCount, escape(_chars));
#endif
+ assert(cellCount <= static_cast(pageSize().columns.value - _state.cursor.position.column.value));
_chars = tryEmplaceChars(_chars, cellCount);
if (_chars.empty())
return;
+ // Making use of the optimized code path for the input characters did NOT work, so we need to first
+ // convert UTF-8 to UTF-32 codepoints (reusing the logic in VT parser) and pass these codepoints
+ // to the grapheme cluster processor.
+
for (char const ch: _chars)
- writeTextInternal(static_cast(ch));
+ _state.parser.printUtf8Byte(ch);
}
template
diff --git a/src/terminal/Screen.h b/src/terminal/Screen.h
index 3190d8aecc..b843c0b34c 100644
--- a/src/terminal/Screen.h
+++ b/src/terminal/Screen.h
@@ -605,7 +605,7 @@ class Screen final: public ScreenBase, public capabilities::StaticDatabase
}
[[nodiscard]] char32_t precedingGraphicCharacter() const noexcept
{
- return _state.parser.precedingGraphicCharacter;
+ return _state.parser.precedingGraphicCharacter();
}
void applyAndLog(FunctionDefinition const& function, Sequence const& seq);
@@ -615,10 +615,14 @@ class Screen final: public ScreenBase, public capabilities::StaticDatabase
private:
void writeTextInternal(char32_t _char);
+
+ /// Attempts to emplace the given character sequence into the current cursor position, assuming
+ /// that the current line is either empty or trivial and the input character sequence is contiguous.
+ ///
+ /// @returns the string view of the UTF-8 text that could not be emplaced.
std::string_view tryEmplaceChars(std::string_view chars, size_t cellCount) noexcept;
- std::string_view tryEmplaceContinuousChars(std::string_view chars, size_t cellCount) noexcept;
size_t emplaceCharsIntoCurrentLine(std::string_view chars, size_t cellCount) noexcept;
- [[nodiscard]] bool canResumeEmplace(std::string_view continuationChars) const noexcept;
+ [[nodiscard]] bool isContiguousToCurrentLine(std::string_view continuationChars) const noexcept;
void advanceCursorAfterWrite(ColumnCount n) noexcept;
void clearAllTabs();
@@ -660,4 +664,11 @@ inline void Screen::scrollUp(LineCount _n, Margin _margin)
scrollUp(_n, cursor().graphicsRendition, _margin);
}
+template
+inline bool Screen::isContiguousToCurrentLine(std::string_view continuationChars) const noexcept
+{
+ auto const& line = currentLine();
+ return line.isTrivialBuffer() && line.trivialBuffer().text.view().end() == continuationChars.begin();
+}
+
} // namespace terminal
diff --git a/src/terminal/Screen_test.cpp b/src/terminal/Screen_test.cpp
index dc6f1eb9e4..48e4918cc2 100644
--- a/src/terminal/Screen_test.cpp
+++ b/src/terminal/Screen_test.cpp
@@ -430,8 +430,8 @@ TEST_CASE("AppendChar.emoji_VS15_smiley", "[screen]")
mock.writeToScreen(U"\U0001F600");
REQUIRE(*screen.logicalCursorPosition().column == 2);
mock.writeToScreen(U"\uFE0E");
- REQUIRE(*screen.logicalCursorPosition().column
- == 2); // U+FE0E does *NOT* lower width to 1 (easier to implement)
+ REQUIRE(*screen.logicalCursorPosition().column == 2);
+ // ^^^ U+FE0E does *NOT* lower width to 1 (easier to implement)
mock.writeToScreen("X");
REQUIRE(*screen.logicalCursorPosition().column == 3);
logScreenText(screen);
diff --git a/src/terminal/Sequencer.cpp b/src/terminal/Sequencer.cpp
index 5b6eaba5b1..b82450a05c 100644
--- a/src/terminal/Sequencer.cpp
+++ b/src/terminal/Sequencer.cpp
@@ -150,6 +150,11 @@ void Sequencer::unhook()
}
}
+bool Sequencer::acceptsBulkText() const noexcept
+{
+ return terminal_.isPrimaryScreen() && terminal_.primaryScreen().currentLine().isTrivialBuffer();
+}
+
void Sequencer::handleSequence()
{
parameterBuilder_.fixiate();
diff --git a/src/terminal/Sequencer.h b/src/terminal/Sequencer.h
index f1c1eea707..1ec3fd1c81 100644
--- a/src/terminal/Sequencer.h
+++ b/src/terminal/Sequencer.h
@@ -168,6 +168,8 @@ class Sequencer
hookedParser_ = std::move(parserExtension);
}
+ [[nodiscard]] bool acceptsBulkText() const noexcept;
+
private:
void handleSequence();
| | | | | | | | | | | | | | | | |