From 9890608d191fd3d6110241004ed5dfa4f347cac5 Mon Sep 17 00:00:00 2001 From: reionwong Date: Sun, 4 Oct 2026 20:13:12 +0800 Subject: [PATCH] perf(qmltermwidget): skip the tokenizer for plain text and move screen lines on scroll --- qmltermwidget/lib/Emulation.cpp | 25 +++++++++++++++----- qmltermwidget/lib/History.cpp | 7 +++++- qmltermwidget/lib/Screen.cpp | 33 +++++++++++++++++---------- qmltermwidget/lib/Screen.h | 13 +++++++++-- qmltermwidget/lib/Vt102Emulation.cpp | 13 +++++++---- qmltermwidget/lib/Vt102Emulation.h | 2 +- qmltermwidget/lib/konsole_wcwidth.cpp | 4 ++++ 7 files changed, 70 insertions(+), 27 deletions(-) diff --git a/qmltermwidget/lib/Emulation.cpp b/qmltermwidget/lib/Emulation.cpp index a8c496e..80678b4 100644 --- a/qmltermwidget/lib/Emulation.cpp +++ b/qmltermwidget/lib/Emulation.cpp @@ -253,13 +253,26 @@ void Emulation::receiveData(const char* text, int length) * U+10FFFF * https://unicodebook.readthedocs.io/unicode_encodings.html#surrogates */ - QByteArray ba(text, length); - QString str = _toUtf16(ba); - std::wstring unicodeText = str.toStdWString(); + const QString str = _toUtf16(QByteArrayView(text, length)); - //send characters to terminal emulator - for (size_t i=0;iisLowSurrogate()) + receiveChar(wchar_t(QChar::surrogateToUcs4(current, *unit++))); + else + receiveChar(wchar_t(QChar::ReplacementCharacter)); + } + else + { + receiveChar(wchar_t(current.unicode())); + } + } //look for z-modem indicator //-- someone who understands more about z-modems that I do may be able to move diff --git a/qmltermwidget/lib/History.cpp b/qmltermwidget/lib/History.cpp index a72caf1..60a0654 100644 --- a/qmltermwidget/lib/History.cpp +++ b/qmltermwidget/lib/History.cpp @@ -309,7 +309,12 @@ void HistoryScrollBuffer::addCellsVector(const QVector& cells) _head = 0; } - _historyBuffer[bufferIndex(_usedLines-1)] = cells; + // Screen lines keep a whole row of capacity to write into; a history line + // keeps only the cells it holds. + if (cells.capacity() > cells.size()) + _historyBuffer[bufferIndex(_usedLines-1)] = QVector(cells.cbegin(), cells.cend()); + else + _historyBuffer[bufferIndex(_usedLines-1)] = cells; _wrappedLine[bufferIndex(_usedLines-1)] = false; } void HistoryScrollBuffer::addCells(const Character a[], int count) diff --git a/qmltermwidget/lib/Screen.cpp b/qmltermwidget/lib/Screen.cpp index 73846f9..ba8c437 100644 --- a/qmltermwidget/lib/Screen.cpp +++ b/qmltermwidget/lib/Screen.cpp @@ -315,11 +315,6 @@ void Screen::restoreMode(int m) currentModes[m] = savedModes[m]; } -bool Screen::getMode(int m) const -{ - return currentModes[m]; -} - void Screen::saveCursor() { savedState.cursorColumn = cuX; @@ -658,10 +653,8 @@ void Screen::newLine() index(); } -void Screen::checkSelection(int from, int to) +void Screen::clearOverlappingSelection(int from, int to) { - if (selBegin == -1) - return; int scr_TL = loc(0, history->getLines()); //Clear entire selection if it overlaps region [from, to] if ( (selBottomRight >= (from+scr_TL)) && (selTopLeft <= (to+scr_TL)) ) @@ -816,6 +809,10 @@ notcombine: int size = screenLines[cuY].size(); if (size < cuX+w) { + // A line fills up one character at a time; allocate it once. The + // history keeps its own copy of what the line holds. + if (screenLines[cuY].capacity() < columns) + screenLines[cuY].reserve(columns); screenLines[cuY].resize(cuX+w); } @@ -1031,20 +1028,32 @@ void Screen::moveImage(int dest, int sourceBegin, int sourceEnd) //so it matters that we do the copy in the right order - //forwards if dest < sourceBegin or backwards otherwise. //(search the web for 'memmove implementation' for details) + const int destLine = dest / columns; + const int sourceLine = sourceBegin / columns; + const int shift = qAbs(destLine - sourceLine); + LineProperty* properties = lineProperties.data(); + //a source line a later step overwrites is moved, which spares sharing a buffer + //per line per scroll; a line nothing overwrites keeps its contents if (dest < sourceBegin) { for (int i=0;i<=lines;i++) { - screenLines[ (dest/columns)+i ] = screenLines[ (sourceBegin/columns)+i ]; - lineProperties[(dest/columns)+i]=lineProperties[(sourceBegin/columns)+i]; + if (i <= lines - shift) + screenLines[destLine + i] = std::move(screenLines[sourceLine + i]); + else + screenLines[destLine + i] = screenLines[sourceLine + i]; + properties[destLine + i] = properties[sourceLine + i]; } } else { for (int i=lines;i>=0;i--) { - screenLines[ (dest/columns)+i ] = screenLines[ (sourceBegin/columns)+i ]; - lineProperties[(dest/columns)+i]=lineProperties[(sourceBegin/columns)+i]; + if (shift > 0 && i >= shift) + screenLines[destLine + i] = std::move(screenLines[sourceLine + i]); + else + screenLines[destLine + i] = screenLines[sourceLine + i]; + properties[destLine + i] = properties[sourceLine + i]; } } diff --git a/qmltermwidget/lib/Screen.h b/qmltermwidget/lib/Screen.h index 50dbe1f..06e1e5c 100644 --- a/qmltermwidget/lib/Screen.h +++ b/qmltermwidget/lib/Screen.h @@ -242,7 +242,10 @@ public: /** Restores the state of a screen @p mode saved by calling saveMode() */ void restoreMode(int mode); /** Returns whether the specified screen @p mode is enabled or not .*/ - bool getMode(int mode) const; + bool getMode(int mode) const + { + return currentModes[mode]; + } /** * Saves the current position and appearance (text color and style) of the cursor. @@ -500,7 +503,12 @@ public: * @param from The start of the area to check. * @param to The end of the area to check */ - void checkSelection(int from, int to); + void checkSelection(int from, int to) + { + // Every printed character checks; there is rarely a selection. + if (selBegin != -1) + clearOverlappingSelection(from, to); + } /** * Sets or clears an attribute of the current line. @@ -633,6 +641,7 @@ private: void reverseRendition(Character& p) const; bool isSelectionValid() const; + void clearOverlappingSelection(int from, int to); // copies text from 'startIndex' to 'endIndex' to a stream // startIndex and endIndex are positions generated using the loc(x,y) macro void writeToStream(TerminalCharacterDecoder* decoder, int startIndex, diff --git a/qmltermwidget/lib/Vt102Emulation.cpp b/qmltermwidget/lib/Vt102Emulation.cpp index e098622..f60084a 100644 --- a/qmltermwidget/lib/Vt102Emulation.cpp +++ b/qmltermwidget/lib/Vt102Emulation.cpp @@ -287,6 +287,14 @@ void Vt102Emulation::receiveChar(wchar_t cc) if (cc == DEL) return; //VT100: ignore. + // Most output is printable text between sequences: what lun() below does, with + // the tokenizer already reset whenever its buffer is empty. + if (tokenBufferPos == 0 && cc >= 32 && cc != ESC+128 && getMode(MODE_Ansi)) + { + _currentScreen->displayCharacter(applyCharset(cc)); + return; + } + if (ces(CTL)) { // ignore control characters in the text part of Xpe (aka OSC) "ESC]" @@ -1705,11 +1713,6 @@ void Vt102Emulation::restoreMode(int m) resetMode(m); } -bool Vt102Emulation::getMode(int m) -{ - return _currentModes.mode[m]; -} - char Vt102Emulation::eraseChar() const { KeyboardTranslator::Entry entry = _keyTranslator->findEntry( diff --git a/qmltermwidget/lib/Vt102Emulation.h b/qmltermwidget/lib/Vt102Emulation.h index b1794e6..fc4e268 100644 --- a/qmltermwidget/lib/Vt102Emulation.h +++ b/qmltermwidget/lib/Vt102Emulation.h @@ -127,7 +127,7 @@ private: void setDefaultMargins(); // returns true if 'mode' is set or false otherwise - bool getMode (int mode); + bool getMode (int mode) { return _currentModes.mode[mode]; } // saves the current boolean value of 'mode' void saveMode (int mode); // restores the boolean value of 'mode' diff --git a/qmltermwidget/lib/konsole_wcwidth.cpp b/qmltermwidget/lib/konsole_wcwidth.cpp index cfb6c07..3f360c9 100644 --- a/qmltermwidget/lib/konsole_wcwidth.cpp +++ b/qmltermwidget/lib/konsole_wcwidth.cpp @@ -19,6 +19,10 @@ int konsole_wcwidth(wchar_t ucs) { + // Printable ASCII is one column in every locale, and most output is ASCII. + if (ucs >= 0x20 && ucs < 0x7f) + return 1; + #ifdef HAVE_UTF8PROC utf8proc_category_t cat = utf8proc_category( ucs ); if (cat == UTF8PROC_CATEGORY_CO) {