diff --git a/qmltermwidget/lib/Character.h b/qmltermwidget/lib/Character.h index 4a4ab77..9cdf70b 100644 --- a/qmltermwidget/lib/Character.h +++ b/qmltermwidget/lib/Character.h @@ -226,6 +226,7 @@ private: // Drops every sequence no screen shows any more, freeing its buffer. void pruneUnused(); static constexpr int kPruneThreshold = 4096; + int pruneAt = kPruneThreshold; // calculates the hash key of a sequence of unicode points of size 'length' uint extendedCharHash(uint* unicodePoints , ushort length) const; // tests whether the entry in the table specified by 'hash' matches the diff --git a/qmltermwidget/lib/Emulation.cpp b/qmltermwidget/lib/Emulation.cpp index 80678b4..7b4aa89 100644 --- a/qmltermwidget/lib/Emulation.cpp +++ b/qmltermwidget/lib/Emulation.cpp @@ -449,7 +449,7 @@ void ExtendedCharTable::pruneUnused() { if (w->screen()) { - usedExtendedChars += w->screen()->usedExtendedChars(); + w->screen()->collectUsedExtendedChars(usedExtendedChars); } } @@ -467,6 +467,10 @@ void ExtendedCharTable::pruneUnused() it = extendedCharTable.erase(it); } } + + // Still-used sequences stay; pruning again only once the table has doubled + // keeps a screen full of them from being rescanned on every new one. + pruneAt = qMax(kPruneThreshold, int(extendedCharTable.size()) * 2); } uint ExtendedCharTable::createExtendedChar(uint* unicodePoints , ushort length) @@ -482,7 +486,7 @@ uint ExtendedCharTable::createExtendedChar(uint* unicodePoints , ushort length) // The table only ever grew; a long session with many combining // sequences keeps every one until the screens are asked which are // still in use. - if (extendedCharTable.size() >= kPruneThreshold) + if (extendedCharTable.size() >= pruneAt) pruneUnused(); // check existing entry for match diff --git a/qmltermwidget/lib/Screen.cpp b/qmltermwidget/lib/Screen.cpp index ba8c437..de94747 100644 --- a/qmltermwidget/lib/Screen.cpp +++ b/qmltermwidget/lib/Screen.cpp @@ -1139,6 +1139,35 @@ void Screen::eraseAboveCursor() cuY = 0; } +void Screen::collectUsedExtendedChars(QSet &used) const +{ + // Lines grow only as far as the cursor has written, so most are shorter than columns. + for (int y = 0; y < lines; ++y) + { + for (const Character &ch : screenLines[y]) + { + if (ch.rendition & RE_EXTENDED_CHAR) + used << ch.character; + } + } + + // Scrollback still draws its sequences; freeing them would show another one in their place. + QVector cells; + for (int line = 0; line < history->getLines(); ++line) + { + const int length = history->getLineLen(line); + if (length <= 0) + continue; + cells.resize(length); + history->getCells(line, 0, length, cells.data()); + for (const Character &ch : std::as_const(cells)) + { + if (ch.rendition & RE_EXTENDED_CHAR) + used << ch.character; + } + } +} + int Screen::lastNonBlankLine() const { const Character blank; diff --git a/qmltermwidget/lib/Screen.h b/qmltermwidget/lib/Screen.h index 06e1e5c..e144c8a 100644 --- a/qmltermwidget/lib/Screen.h +++ b/qmltermwidget/lib/Screen.h @@ -575,22 +575,8 @@ public: */ static void fillWithDefaultChar(Character* dest, int count); - QSet usedExtendedChars() const - { - QSet result; - for (int i = 0; i < lines; ++i) - { - const ImageLine &il = screenLines[i]; - for (int j = 0; j < columns; ++j) - { - if (il[j].rendition & RE_EXTENDED_CHAR) - { - result << il[j].character; - } - } - } - return result; - } + /** Adds the extended characters on the screen and in its history to @p used. */ + void collectUsedExtendedChars(QSet &used) const; private: Screen(const Screen &) = delete; diff --git a/tests/vt102emulation_test.cpp b/tests/vt102emulation_test.cpp index 33aa28a..92b3843 100644 --- a/tests/vt102emulation_test.cpp +++ b/tests/vt102emulation_test.cpp @@ -53,6 +53,15 @@ public: return line.at(column); } + // A cell of the oldest line in the history. + Character historyCell(int column) + { + const Screen *screen = m_window->screen(); + QVector line(screen->getColumns()); + screen->getImage(line.data(), line.size(), 0, 0); + return line.at(column); + } + QString line(int length) { QString text; @@ -281,6 +290,36 @@ private slots: QCOMPARE(terminal.cell(2).rendition, terminal.cell(1).rendition); QVERIFY(terminal.cell(3).foregroundColor == CharacterColor(COLOR_SPACE_RGB, 0xff0000)); } + + void combiningSequencesInHistorySurviveAPrune() + { + Terminal terminal; + terminal.keepHistory(); + terminal.print(QStringLiteral("é\r\n").toUtf8()); + for (int i = 0; i < 60; ++i) + terminal.print("\r\n"); + QVERIFY(terminal.historyLines() > 0); + + // Thousands of distinct sequences written over one short line, each + // gone from the screen by the next, push the table past its pruning size. + for (int base = 0x100; base < 0x180; ++base) { + QString text = QStringLiteral("\r"); + for (int mark = 0x300; mark < 0x340; ++mark) { + text += QChar(base); + text += QChar(mark); + } + terminal.print(text.toUtf8()); + } + + const Character kept = terminal.historyCell(0); + QVERIFY(kept.rendition & RE_EXTENDED_CHAR); + ushort length = 0; + const uint *chars = ExtendedCharTable::instance.lookupExtendedChar(kept.character, length); + QVERIFY(chars); + QCOMPARE(length, ushort(2)); + QCOMPARE(chars[0], uint('e')); + QCOMPARE(chars[1], uint(0x301)); + } }; QTEST_MAIN(Vt102EmulationTest)