[plasma-mobile/qmlkonsole/release/26.08] lib: Adapt screen reflow logic from Konsole during resize
Devin Lin <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 572f6955a3a108b118aea04cd4674ec2999c5873 by Devin Lin.
Committed on 19/07/2026 at 17:41.
Pushed by devinlin into branch 'release/26.08'.
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/572f6955a3a108b118aea04cd4674ec2999c5873
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();
}