[plasma-mobile/qmlkonsole] lib: Adapt screen reflow logic from Konsole during resize

Devin Lin <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 6d75a85904e7ae060fc9d0f3faf7ddccf865c0ce by Devin Lin.
Committed on 19/07/2026 at 17:39.
Pushed by devinlin into branch 'master'.

Adapt screen reflow logic from Konsole during resize

We currently don't reflow the terminal content when the terminal is resized, adapt this logic from Konsole.

M  +102  -18   lib/History.cpp
M  +3    -0    lib/History.h
M  +75   -16   lib/Screen.cpp
M  +3    -3    lib/Screen.h
M  +1    -0    lib/TerminalDisplay.cpp

https://invent.kde.org/plasma-mobile/qmlkonsole/-/commit/6d75a85904e7ae060fc9d0f3faf7ddccf865c0ce

diff --git a/lib/History.cpp b/lib/History.cpp
index ae1722a..e2ab128 100644
--- a/lib/History.cpp
+++ b/lib/History.cpp
@@ -293,7 +293,7 @@ HistoryScrollBuffer::HistoryScrollBuffer(unsigned int maxLineCount)
     , _historyBuffer()
     , _maxLineCount(0)
     , _usedLines(0)
-    , _head(0)
+    , _head(-1)
 {
     setMaxNbLines(maxLineCount);
 }
@@ -305,16 +305,22 @@ HistoryScrollBuffer::~HistoryScrollBuffer()
 
 void HistoryScrollBuffer::addCellsVector(const QVector<Character> &cells)
 {
-    _head++;
-    if (_usedLines < _maxLineCount)
-        _usedLines++;
+    if (_maxLineCount == 0) {
+        return;
+    }
 
-    if (_head >= _maxLineCount) {
-        _head = 0;
+    int index;
+    if (_usedLines < _maxLineCount) {
+        index = _usedLines;
+        _usedLines++;
+        _head = index;
+    } else {
+        _head = (_head + 1) % _maxLineCount;
+        index = _head;
     }
 
-    _historyBuffer[bufferIndex(_usedLines - 1)] = cells;
-    _wrappedLine[bufferIndex(_usedLines - 1)] = false;
+    _historyBuffer[index] = cells;
+    _wrappedLine[index] = false;
 }
 void HistoryScrollBuffer::addCells(std::span<const Character> a, int count)
 {
@@ -326,6 +332,9 @@ void HistoryScrollBuffer::addCells(std::span<const Character> a, int count)
 
 void HistoryScrollBuffer::addLine(bool previousWrapped)
 {
+    if (_usedLines == 0) {
+        return;
+    }
     _wrappedLine[bufferIndex(_usedLines - 1)] = previousWrapped;
 }
 
@@ -379,23 +388,98 @@ void HistoryScrollBuffer::getCells(int lineNumber, int startColumn, int count, s
     memcpy(buffer.data(), line.constData() + startColumn, count * sizeof(Character));
 }
 
-void HistoryScrollBuffer::setMaxNbLines(unsigned int lineCount)
+void HistoryScrollBuffer::removeCells()
 {
-    HistoryLine *oldBuffer = _historyBuffer;
-    HistoryLine *newBuffer = new HistoryLine[lineCount];
+    if (_usedLines == 0) {
+        return;
+    }
 
-    for (int i = 0; i < qMin(_usedLines, (int)lineCount); i++) {
-        newBuffer[i] = oldBuffer[bufferIndex(i)];
+    if (_usedLines < _maxLineCount) {
+        _historyBuffer[_usedLines - 1].clear();
+        _wrappedLine[_usedLines - 1] = false;
+        --_usedLines;
+        _head = _usedLines - 1;
+        return;
     }
 
-    _usedLines = qMin(_usedLines, (int)lineCount);
-    _maxLineCount = lineCount;
-    _head = (_usedLines == _maxLineCount) ? 0 : _usedLines - 1;
+    auto *newBuffer = new HistoryLine[_maxLineCount];
+    QBitArray newWrappedLine(_maxLineCount);
+    for (int line = 0; line < _usedLines - 1; ++line) {
+        const int oldIndex = bufferIndex(line);
+        newBuffer[line] = std::move(_historyBuffer[oldIndex]);
+        newWrappedLine[line] = _wrappedLine[oldIndex];
+    }
 
+    delete[] _historyBuffer;
     _historyBuffer = newBuffer;
-    delete[] oldBuffer;
+    _wrappedLine = std::move(newWrappedLine);
+    --_usedLines;
+    _head = _usedLines - 1;
+}
+
+int HistoryScrollBuffer::reflowLines(int columns)
+{
+    QVector<HistoryLine> reflowedLines;
+    QBitArray reflowedWrappedLines;
+
+    int currentLine = 0;
+    while (currentLine < _usedLines) {
+        HistoryLine logicalLine = _historyBuffer[bufferIndex(currentLine)];
+        while (currentLine < _usedLines - 1 && isWrappedLine(currentLine)) {
+            ++currentLine;
+            logicalLine.append(_historyBuffer[bufferIndex(currentLine)]);
+        }
 
-    _wrappedLine.resize(lineCount);
+        for (int offset = 0; offset < logicalLine.size(); offset += columns) {
+            const int count = qMin(columns, logicalLine.size() - offset);
+            reflowedLines.append(logicalLine.mid(offset, count));
+            reflowedWrappedLines.resize(reflowedLines.size());
+            reflowedWrappedLines[reflowedLines.size() - 1] = offset + count < logicalLine.size();
+        }
+
+        if (logicalLine.isEmpty()) {
+            reflowedLines.append(HistoryLine{});
+            reflowedWrappedLines.resize(reflowedLines.size());
+            reflowedWrappedLines[reflowedLines.size() - 1] = false;
+        }
+        ++currentLine;
+    }
+
+    const int removedLines = qMax(0, reflowedLines.size() - _maxLineCount);
+    auto *newBuffer = new HistoryLine[_maxLineCount];
+    QBitArray newWrappedLine(_maxLineCount);
+    const int firstLine = removedLines;
+    _usedLines = qMin(reflowedLines.size(), _maxLineCount);
+    for (int line = 0; line < _usedLines; ++line) {
+        newBuffer[line] = std::move(reflowedLines[firstLine + line]);
+        newWrappedLine[line] = reflowedWrappedLines[firstLine + line];
+    }
+
+    delete[] _historyBuffer;
+    _historyBuffer = newBuffer;
+    _wrappedLine = std::move(newWrappedLine);
+    _head = _usedLines - 1;
+    return removedLines;
+}
+
+void HistoryScrollBuffer::setMaxNbLines(unsigned int lineCount)
+{
+    const int keptLines = qMin(_usedLines, static_cast<int>(lineCount));
+    const int firstKeptLine = _usedLines - keptLines;
+    HistoryLine *newBuffer = new HistoryLine[lineCount];
+    QBitArray newWrappedLine(lineCount);
+    for (int line = 0; line < keptLines; ++line) {
+        const int oldIndex = bufferIndex(firstKeptLine + line);
+        newBuffer[line] = std::move(_historyBuffer[oldIndex]);
+        newWrappedLine[line] = _wrappedLine[oldIndex];
+    }
+
+    delete[] _historyBuffer;
+    _historyBuffer = newBuffer;
+    _wrappedLine = std::move(newWrappedLine);
+    _maxLineCount = lineCount;
+    _usedLines = keptLines;
+    _head = _usedLines - 1;
     dynamic_cast<HistoryTypeBuffer *>(m_histType)->m_nbLines = lineCount;
 }
 
diff --git a/lib/History.h b/lib/History.h
index 6eba57b..cd5e3e0 100644
--- a/lib/History.h
+++ b/lib/History.h
@@ -183,6 +183,9 @@ public:
     void addCellsVector(const QVector<Character> &cells) override;
     void addLine(bool previousWrapped = false) override;
 
+    void removeCells();
+    int reflowLines(int columns);
+
     void setMaxNbLines(unsigned int nbLines);
     unsigned int maxNbLines() const
     {
diff --git a/lib/Screen.cpp b/lib/Screen.cpp
index 2d4bd14..f7c11f7 100644
--- a/lib/Screen.cpp
+++ b/lib/Screen.cpp
@@ -326,35 +326,94 @@ void Screen::resizeImage(int new_lines, int new_columns)
     if ((new_lines == lines) && (new_columns == columns))
         return;
 
-    if (cuY > new_lines - 1) { // attempt to preserve focus and lines
-        _bottomMargin = lines - 1; // FIXME: margin lost
-        for (int i = 0; i < cuY - (new_lines - 1); i++) {
+    auto *reflowHistory = dynamic_cast<HistoryScrollBuffer *>(history.get());
+    int cursorLine = cuY;
+    const int oldCursorLine = (cursorLine == lines - 1 || cursorLine >= new_lines) ? new_lines - 1 : cursorLine;
+
+    if (reflowHistory && new_columns != columns && history->getLines()) {
+        while (history->getLines() && history->isWrappedLine(history->getLines() - 1)) {
             addHistLine();
             scrollUp(0, 1);
+            --cursorLine;
         }
+        _droppedLines += reflowHistory->reflowLines(new_columns);
     }
 
-    // create new screen lines and copy from old to new
+    if (reflowHistory && new_columns != columns) {
+        int currentLine = 0;
+        while (currentLine < cursorLine && currentLine < screenLines.size() - 1) {
+            if (lineProperties.at(currentLine) & LINE_WRAPPED) {
+                screenLines[currentLine].append(screenLines.at(currentLine + 1));
+                screenLines.removeAt(currentLine + 1);
+                lineProperties.remove(currentLine);
+                --cursorLine;
+                continue;
+            }
 
-    auto newScreenLines = QVector<ImageLine>(new_lines + 1);
-    for (int i = 0; i < qMin(lines, new_lines + 1); i++)
-        newScreenLines[i] = screenLines[i];
-    for (int i = lines; (i > 0) && (i < new_lines + 1); i++)
-        newScreenLines[i].resize(new_columns);
+            int lineSize = screenLines.at(currentLine).size();
+            while (lineSize > 0 && screenLines.at(currentLine).at(lineSize - 1).character.isSpace()) {
+                --lineSize;
+            }
 
-    lineProperties.resize(new_lines + 1);
-    for (int i = lines; (i > 0) && (i < new_lines + 1); i++)
-        lineProperties[i] = LINE_DEFAULT;
+            if (lineSize > new_columns && !(lineProperties.at(currentLine) & (LINE_DOUBLEWIDTH | LINE_DOUBLEHEIGHT))) {
+                auto overflow = screenLines.at(currentLine).mid(new_columns);
+                screenLines[currentLine].resize(new_columns);
+                const LineProperty properties = lineProperties.at(currentLine);
+                lineProperties.insert(currentLine + 1, properties);
+                screenLines.insert(currentLine + 1, std::move(overflow));
+                lineProperties[currentLine] = static_cast<LineProperty>(lineProperties.at(currentLine) | LINE_WRAPPED);
+                ++cursorLine;
+            }
+            ++currentLine;
+        }
+    }
 
-    clearSelection();
+    while (cursorLine > new_lines - 1) {
+        addHistLine();
+        scrollUp(0, 1);
+        --cursorLine;
+    }
+
+    if (reflowHistory) {
+        while (cursorLine < oldCursorLine && history->getLines()) {
+            const int historyLine = history->getLines() - 1;
+            const int lineLength = history->getLineLen(historyLine);
+            ImageLine characters(lineLength);
+            history->getCells(historyLine, 0, lineLength, characters);
+            const LineProperty properties = history->isWrappedLine(historyLine) ? LINE_WRAPPED : LINE_DEFAULT;
+            screenLines.insert(0, std::move(characters));
+            lineProperties.insert(qsizetype(0), properties);
+            reflowHistory->removeCells();
+            ++cursorLine;
+        }
+    }
 
-    screenLines.clear();
-    screenLines = std::move(newScreenLines);
+    if (reflowHistory) {
+        const int previousScreenLineCount = screenLines.size();
+        lineProperties.resize(new_lines + 1);
+        if (lineProperties.size() > previousScreenLineCount) {
+            std::fill(lineProperties.begin() + previousScreenLineCount, lineProperties.end(), LINE_DEFAULT);
+        }
+        screenLines.resize(new_lines + 1);
+    } else {
+        // create new screen lines and copy from old to new
+        auto newScreenLines = QVector<ImageLine>(new_lines + 1);
+        for (int i = 0; i < qMin(lines, new_lines + 1); i++)
+            newScreenLines[i] = screenLines[i];
+        for (int i = lines; (i > 0) && (i < new_lines + 1); i++)
+            newScreenLines[i].resize(new_columns);
+
+        lineProperties.resize(new_lines + 1);
+        for (int i = lines; (i > 0) && (i < new_lines + 1); i++)
+            lineProperties[i] = LINE_DEFAULT;
+
+        screenLines = std::move(newScreenLines);
+    }
 
     lines = new_lines;
     columns = new_columns;
     cuX = qMin(cuX, columns - 1);
-    cuY = qMin(cuY, lines - 1);
+    cuY = reflowHistory ? qBound(0, cursorLine, lines - 1) : qMin(cuY, lines - 1);
 
     // FIXME: try to keep values, evtl.
     _topMargin = 0;
diff --git a/lib/Screen.h b/lib/Screen.h
index 3383385..17aa19f 100644
--- a/lib/Screen.h
+++ b/lib/Screen.h
@@ -351,9 +351,9 @@ public:
 
     /**
      * Resizes the image to a new fixed size of @p new_lines by @p new_columns.
-     * In the case that @p new_columns is smaller than the current number of columns,
-     * existing lines are not truncated.  This prevents characters from being lost
-     * if the terminal display is resized smaller and then larger again.
+     * Wrapped lines in a finite scrollback-backed screen are reflowed to fit
+     * @p new_columns. Other screens preserve oversized lines so characters are
+     * not lost when the terminal display is resized smaller and then larger again.
      *
      * The top and bottom margins are reset to the top and bottom of the new
      * screen size.  Tab stops are also reset and the current selection is
diff --git a/lib/TerminalDisplay.cpp b/lib/TerminalDisplay.cpp
index 50c9f9f..bc5f260 100644
--- a/lib/TerminalDisplay.cpp
+++ b/lib/TerminalDisplay.cpp
@@ -1536,6 +1536,7 @@ void TerminalDisplay::blinkCursorEvent()
 void TerminalDisplay::resizeEvent(QResizeEvent *)
 {
     updateImageSize();
+    updateImage();
     processFilters();
 }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.