File indexing completed on 2024-04-28 09:46:51

0001 /*
0002    SPDX-FileCopyrightText: 2007-2008 Robert Knight <robert.knight@gmail.com>
0003    SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
0004 
0005    SPDX-License-Identifier: GPL-2.0-or-later
0006    */
0007 
0008 // Own
0009 #include "Screen.h"
0010 
0011 #include "config-konsole.h"
0012 
0013 // Qt
0014 #include <QFile>
0015 #include <QTextStream>
0016 
0017 // Konsole decoders
0018 #include <HTMLDecoder.h>
0019 #include <PlainTextDecoder.h>
0020 
0021 #include "session/Session.h"
0022 #include "session/SessionController.h"
0023 #include "terminalDisplay/TerminalDisplay.h"
0024 #include "terminalDisplay/TerminalFonts.h"
0025 
0026 #include "EscapeSequenceUrlExtractor.h"
0027 #include "characters/ExtendedCharTable.h"
0028 #include "history/HistoryScrollNone.h"
0029 #include "history/HistoryType.h"
0030 
0031 #if HAVE_MALLOC_TRIM
0032 // For malloc_trim, which is a GNU extension
0033 extern "C" {
0034 #include <malloc.h>
0035 }
0036 #endif
0037 
0038 using namespace Konsole;
0039 
0040 // Macro to convert x,y position on screen to position within an image.
0041 //
0042 // Originally the image was stored as one large contiguous block of
0043 // memory, so a position within the image could be represented as an
0044 // offset from the beginning of the block.  For efficiency reasons this
0045 // is no longer the case.
0046 // Many internal parts of this class still use this representation for parameters and so on,
0047 // notably moveImage() and clearImage().
0048 // This macro converts from an X,Y position into an image offset.
0049 #ifndef loc
0050 #define loc(X, Y) ((Y)*_columns + (X))
0051 #endif
0052 
0053 const Character Screen::DefaultChar =
0054     Character(' ', CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR), CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR), DEFAULT_RENDITION, 0);
0055 
0056 Screen::Screen(int lines, int columns)
0057     : _currentTerminalDisplay(nullptr)
0058     , _lines(lines)
0059     , _columns(columns)
0060     , _screenLines(_lines + 1)
0061     , _screenLinesSize(_lines)
0062     , _scrolledLines(0)
0063     , _lastScrolledRegion(QRect())
0064     , _droppedLines(0)
0065     , _oldTotalLines(0)
0066     , _isResize(false)
0067     , _enableReflowLines(false)
0068     , _lineProperties(_lines + 1)
0069     , _history(std::make_unique<HistoryScrollNone>())
0070     , _cuX(0)
0071     , _cuY(0)
0072     , _currentForeground(CharacterColor())
0073     , _currentBackground(CharacterColor())
0074     , _currentRendition({DEFAULT_RENDITION})
0075     , _ulColorQueueStart(0)
0076     , _ulColorQueueEnd(0)
0077     , _currentULColor(0)
0078     , _topMargin(0)
0079     , _bottomMargin(0)
0080     , _replMode(REPL_None)
0081     , _hasRepl(false)
0082     , _replHadOutput(false)
0083     , _replLastOutputStart(std::pair(-1, -1))
0084     , _tabStops(QBitArray())
0085     , _selBegin(0)
0086     , _selTopLeft(0)
0087     , _selBottomRight(0)
0088     , _blockSelectionMode(false)
0089     , _effectiveForeground(CharacterColor())
0090     , _effectiveBackground(CharacterColor())
0091     , _effectiveRendition({DEFAULT_RENDITION})
0092     , _lastPos(-1)
0093     , _lastDrawnChar(0)
0094     , _escapeSequenceUrlExtractor(nullptr)
0095     , _ignoreWcWidth(false)
0096 {
0097     std::fill(_lineProperties.begin(), _lineProperties.end(), LineProperty());
0098 
0099     _graphicsPlacements = std::vector<std::unique_ptr<TerminalGraphicsPlacement_t>>();
0100     _hasGraphics = false;
0101 
0102     initTabStops();
0103     clearSelection();
0104     reset();
0105 }
0106 
0107 Screen::~Screen() = default;
0108 
0109 void Screen::cursorUp(int n)
0110 //=CUU
0111 {
0112     if (n < 1) {
0113         n = 1; // Default
0114     }
0115     const int stop = _cuY < _topMargin ? 0 : _topMargin;
0116     _cuX = qMin(getScreenLineColumns(_cuY) - 1, _cuX); // nowrap!
0117     _cuY = qMax(stop, _cuY - n);
0118 }
0119 
0120 void Screen::cursorDown(int n)
0121 //=CUD
0122 {
0123     if (n < 1) {
0124         n = 1; // Default
0125     }
0126     if (n > MAX_SCREEN_ARGUMENT) {
0127         n = MAX_SCREEN_ARGUMENT;
0128     }
0129     const int stop = _cuY > _bottomMargin ? _lines - 1 : _bottomMargin;
0130     _cuX = qMin(getScreenLineColumns(_cuY) - 1, _cuX); // nowrap!
0131     _cuY = qMin(stop, _cuY + n);
0132 }
0133 
0134 void Screen::cursorLeft(int n)
0135 //=CUB
0136 {
0137     if (n < 1) {
0138         n = 1; // Default
0139     }
0140     _cuX = qMin(getScreenLineColumns(_cuY) - 1, _cuX); // nowrap!
0141     _cuX = qMax(0, _cuX - n);
0142 }
0143 
0144 void Screen::cursorNextLine(int n)
0145 //=CNL
0146 {
0147     if (n < 1) {
0148         n = 1; // Default
0149     }
0150     if (n > MAX_SCREEN_ARGUMENT) {
0151         n = MAX_SCREEN_ARGUMENT;
0152     }
0153     _cuX = 0;
0154     const int stop = _cuY > _bottomMargin ? _lines - 1 : _bottomMargin;
0155     _cuY = qMin(stop, _cuY + n);
0156 }
0157 
0158 void Screen::cursorPreviousLine(int n)
0159 //=CPL
0160 {
0161     if (n < 1) {
0162         n = 1; // Default
0163     }
0164     _cuX = 0;
0165     const int stop = _cuY < _topMargin ? 0 : _topMargin;
0166     _cuY = qMax(stop, _cuY - n);
0167 }
0168 
0169 void Screen::cursorRight(int n)
0170 //=CUF
0171 {
0172     if (n < 1) {
0173         n = 1; // Default
0174     }
0175     if (n > MAX_SCREEN_ARGUMENT) {
0176         n = MAX_SCREEN_ARGUMENT;
0177     }
0178     _cuX = qMin(getScreenLineColumns(_cuY) - 1, _cuX + n);
0179 }
0180 
0181 void Screen::initSelCursor()
0182 {
0183     _selCuX = _cuX;
0184     _selCuY = _cuY;
0185 }
0186 
0187 int Screen::selCursorUp(int n)
0188 {
0189     if (n == 0) {
0190         // Half page
0191         n = _lines / 2;
0192     } else if (n == -1) {
0193         // Full page
0194         n = _lines;
0195     } else if (n == -2) {
0196         // First line
0197         n = _selCuY + _history->getLines();
0198     }
0199     _selCuY = qMax(-_history->getLines(), _selCuY - n);
0200     return _selCuY;
0201 }
0202 
0203 int Screen::selCursorDown(int n)
0204 {
0205     if (n == 0) {
0206         // Half page
0207         n = _lines / 2;
0208     } else if (n == -1) {
0209         // Full page
0210         n = _lines;
0211     } else if (n == -2) {
0212         // Last line
0213         n = _lines - 1 - _selCuY;
0214     }
0215     _selCuY = qMin(_lines - 1, _selCuY + n);
0216     return _selCuY;
0217 }
0218 
0219 int Screen::selCursorLeft(int n)
0220 {
0221     if (n == 0) {
0222         // Home
0223         n = _selCuX;
0224     }
0225     if (_selCuX >= n) {
0226         _selCuX -= n;
0227     } else {
0228         if (_selCuY > -_history->getLines()) {
0229             _selCuY -= 1;
0230             _selCuX = qMax(_columns - n + _selCuX, 0);
0231         } else {
0232             _selCuX = 0;
0233         }
0234     }
0235     return _selCuY;
0236 }
0237 
0238 int Screen::selCursorRight(int n)
0239 {
0240     if (n == 0) {
0241         // End
0242         n = _columns - _selCuX - 1;
0243     }
0244     if (_selCuX + n < _columns) {
0245         _selCuX += n;
0246     } else {
0247         if (_selCuY < _lines - 1) {
0248             _selCuY += 1;
0249             _selCuX = qMin(n + _selCuX - _columns, _columns - 1);
0250         } else {
0251             _selCuX = _columns - 1;
0252         }
0253     }
0254     return _selCuY;
0255 }
0256 
0257 int Screen::selSetSelectionStart(int mode)
0258 {
0259     // mode: 0 = character selection
0260     //       1 = line selection
0261     int x = _selCuX;
0262     if (mode == 1) {
0263         x = 0;
0264     }
0265     setSelectionStart(x, _selCuY + _history->getLines(), false);
0266     return 0;
0267 }
0268 
0269 int Screen::selSetSelectionEnd(int mode)
0270 {
0271     int y = _selCuY + _history->getLines();
0272     int x = _selCuX;
0273     if (mode == 1) {
0274         int l = _selBegin / _columns;
0275         if (y < l) {
0276             if (_selBegin % _columns == 0) {
0277                 setSelectionStart(_columns - 1, l, false);
0278             }
0279             x = 0;
0280         } else {
0281             x = _columns - 1;
0282             if (_selBegin % _columns != 0) {
0283                 setSelectionStart(0, l, false);
0284             }
0285         }
0286     }
0287     setSelectionEnd(x, y, false);
0288     Q_EMIT _currentTerminalDisplay->screenWindow()->selectionChanged();
0289     return 0;
0290 }
0291 
0292 void Screen::setMargins(int top, int bot)
0293 //=STBM
0294 {
0295     if (top < 1) {
0296         top = 1; // Default
0297     }
0298     if (bot < 1) {
0299         bot = _lines; // Default
0300     }
0301     top = top - 1; // Adjust to internal lineno
0302     bot = bot - 1; // Adjust to internal lineno
0303     if (!(0 <= top && top < bot && bot < _lines)) {
0304         // Debug()<<" setRegion("<<top<<","<<bot<<") : bad range.";
0305         return; // Default error action: ignore
0306     }
0307     _topMargin = top;
0308     _bottomMargin = bot;
0309     _cuX = 0;
0310     _cuY = getMode(MODE_Origin) ? top : 0;
0311 }
0312 
0313 int Screen::topMargin() const
0314 {
0315     return _topMargin;
0316 }
0317 int Screen::bottomMargin() const
0318 {
0319     return _bottomMargin;
0320 }
0321 
0322 void Screen::index()
0323 //=IND
0324 {
0325     if (_cuY == _bottomMargin) {
0326         scrollUp(1);
0327     } else if (_cuY < _lines - 1) {
0328         _cuY += 1;
0329     }
0330 }
0331 
0332 void Screen::reverseIndex()
0333 //=RI
0334 {
0335     if (_cuY == _topMargin) {
0336         scrollDown(_topMargin, 1);
0337     } else if (_cuY > 0) {
0338         _cuY -= 1;
0339     }
0340 }
0341 
0342 void Screen::nextLine()
0343 //=NEL
0344 {
0345     _lineProperties[_cuY].length = _cuX;
0346     toStartOfLine();
0347     index();
0348 }
0349 
0350 void Screen::eraseChars(int n)
0351 {
0352     if (n < 1) {
0353         n = 1; // Default
0354     }
0355     if (n > MAX_SCREEN_ARGUMENT) {
0356         n = MAX_SCREEN_ARGUMENT;
0357     }
0358     const int p = qBound(0, _cuX + n - 1, _columns - 1);
0359     clearImage(loc(_cuX, _cuY), loc(p, _cuY), ' ', false);
0360 }
0361 
0362 void Screen::eraseBlock(int y, int x, int height, int width)
0363 {
0364     width = qBound(0, width, _columns - x - 1);
0365     int endCol = x + width;
0366     height = qBound(0, height, _lines - y - 1);
0367     Character chr(' ', CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR), CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR), RE_TRANSPARENT, 0);
0368     for (int row = y; row < y + height; row++) {
0369         QVector<Character> &line = _screenLines[row];
0370         if (line.size() < endCol + 1) {
0371             line.resize(endCol + 1);
0372         }
0373         if (endCol == _columns - 1) {
0374             line.resize(endCol + 1);
0375         }
0376         if (x <= endCol) {
0377             std::fill(line.begin() + x, line.begin() + (endCol + 1), chr);
0378         }
0379     }
0380 }
0381 
0382 void Screen::deleteChars(int n)
0383 {
0384     Q_ASSERT(n >= 0);
0385 
0386     // always delete at least one char
0387     if (n < 1) {
0388         n = 1;
0389     }
0390 
0391     // if cursor is beyond the end of the line there is nothing to do
0392     if (_cuX >= _screenLines.at(_cuY).count()) {
0393         return;
0394     }
0395 
0396     if (_cuX + n > _screenLines.at(_cuY).count()) {
0397         n = _screenLines.at(_cuY).count() - _cuX;
0398     }
0399 
0400     Q_ASSERT(n >= 0);
0401     Q_ASSERT(_cuX + n <= _screenLines.at(_cuY).count());
0402 
0403     _screenLines[_cuY].remove(_cuX, n);
0404 
0405     // Append space(s) with current attributes
0406     Character spaceWithCurrentAttrs(' ', _effectiveForeground, _effectiveBackground, _effectiveRendition.all, 0);
0407 
0408     for (int i = 0; i < n; ++i) {
0409         _screenLines[_cuY].append(spaceWithCurrentAttrs);
0410     }
0411 }
0412 
0413 void Screen::insertChars(int n)
0414 {
0415     if (n < 1) {
0416         n = 1; // Default
0417     }
0418 
0419     if (_screenLines.at(_cuY).size() < _cuX) {
0420         _screenLines[_cuY].resize(_cuX);
0421     }
0422 
0423     _screenLines[_cuY].insert(_cuX, n, Character(' '));
0424 
0425     if (_screenLines.at(_cuY).count() > getScreenLineColumns(_cuY)) {
0426         _screenLines[_cuY].resize(getScreenLineColumns(_cuY));
0427     }
0428 }
0429 
0430 void Screen::repeatChars(int n)
0431 {
0432     if (n < 1) {
0433         n = 1; // Default
0434     }
0435 
0436     // From ECMA-48 version 5, section 8.3.103:
0437     // "If the character preceding REP is a control function or part of a
0438     // control function, the effect of REP is not defined by this Standard."
0439     //
0440     // So, a "normal" program should always use REP immediately after a visible
0441     // character (those other than escape sequences). So, _lastDrawnChar can be
0442     // safely used.
0443     while (n > 0) {
0444         displayCharacter(_lastDrawnChar);
0445         --n;
0446     }
0447 }
0448 
0449 void Screen::deleteLines(int n)
0450 {
0451     if (_cuY < _topMargin) {
0452         return;
0453     }
0454     if (n < 1) {
0455         n = 1; // Default
0456     }
0457     scrollUp(_cuY, n);
0458 }
0459 
0460 void Screen::insertLines(int n)
0461 {
0462     if (_cuY < _topMargin) {
0463         return;
0464     }
0465     if (n < 1) {
0466         n = 1; // Default
0467     }
0468     scrollDown(_cuY, n);
0469 }
0470 
0471 void Screen::setMode(int m)
0472 {
0473     _currentModes[m] = 1;
0474     switch (m) {
0475     case MODE_Origin:
0476         _cuX = 0;
0477         _cuY = _topMargin;
0478         break; // FIXME: home
0479     }
0480 }
0481 
0482 void Screen::resetMode(int m)
0483 {
0484     _currentModes[m] = 0;
0485     switch (m) {
0486     case MODE_Origin:
0487         _cuX = 0;
0488         _cuY = 0;
0489         break; // FIXME: home
0490     }
0491 }
0492 
0493 void Screen::saveMode(int m)
0494 {
0495     _savedModes[m] = _currentModes[m];
0496 }
0497 
0498 void Screen::restoreMode(int m)
0499 {
0500     _currentModes[m] = _savedModes[m];
0501 }
0502 
0503 bool Screen::getMode(int m) const
0504 {
0505     return _currentModes[m] != 0;
0506 }
0507 
0508 void Screen::saveCursor()
0509 {
0510     _savedState.cursorColumn = _cuX;
0511     _savedState.cursorLine = _cuY;
0512     _savedState.rendition = _currentRendition;
0513     _savedState.foreground = _currentForeground;
0514     _savedState.background = _currentBackground;
0515     _savedState.originMode = _currentModes[MODE_Origin];
0516 }
0517 
0518 void Screen::restoreCursor()
0519 {
0520     _cuY = qMin(_savedState.cursorLine, _lines - 1);
0521     _cuX = qMin(_savedState.cursorColumn, getScreenLineColumns(_cuY) - 1);
0522     _currentRendition = _savedState.rendition;
0523     _currentForeground = _savedState.foreground;
0524     _currentBackground = _savedState.background;
0525     updateEffectiveRendition();
0526     _currentModes[MODE_Origin] = _savedState.originMode;
0527     /* XXX: DEC STD-070 states that DECRC should make sure the cursor lies
0528      * inside the scrolling region, but that behavior doesn't seem to be
0529      * widespread (neither VT1xx, VT240, mlterm, vte do it, and xterm
0530      * only limits the bottom margin).
0531     if (getMode(MODE_Origin)) {
0532         _cuY = qBound(_topMargin, _cuY, _bottomMargin);
0533     }
0534     */
0535 }
0536 
0537 int Screen::getOldTotalLines()
0538 {
0539     return _oldTotalLines;
0540 }
0541 
0542 bool Screen::isResize()
0543 {
0544     if (_isResize) {
0545         _isResize = false;
0546         return true;
0547     }
0548     return _isResize;
0549 }
0550 
0551 void Screen::setReflowLines(bool enable)
0552 {
0553     _enableReflowLines = enable;
0554 }
0555 
0556 void Screen::setIgnoreWcWidth(bool ignore)
0557 {
0558     _ignoreWcWidth = ignore;
0559 }
0560 
0561 /* Note that if you use these debugging functions, it will
0562    fail to compile on gcc 8.3.1 as of Feb 2021 due to for_each_n().
0563    See BKO: 432639
0564 
0565 // Debugging auxiliary functions to show what is written in screen or history
0566 void toDebug(const Character *s, int count, bool wrapped = false)
0567 {
0568     QString out;
0569     std::for_each_n(s, count, [&out](const Character &i) { out += i.character; });
0570     if (wrapped) {
0571         qDebug() << out << "*wrapped*";
0572     } else {
0573         qDebug() << out;
0574     }
0575 }
0576 void toDebug(const QVector<Character> &s, bool wrapped = false)
0577 {
0578     toDebug(s.data(), s.size(), wrapped);
0579 }
0580 */
0581 
0582 int Screen::getCursorLine()
0583 {
0584     if (isAppMode()) {
0585         return _savedState.cursorLine;
0586     }
0587     return _cuY;
0588 }
0589 
0590 void Screen::setCursorLine(int newLine)
0591 {
0592     if (isAppMode()) {
0593         _savedState.cursorLine = newLine;
0594         _cuY = qBound(0, _cuY, _lines - 1);
0595     } else {
0596         _cuY = newLine;
0597     }
0598 }
0599 
0600 void Screen::resizeImage(int new_lines, int new_columns)
0601 {
0602     if ((new_lines == _lines) && (new_columns == _columns)) {
0603         return;
0604     }
0605     // Adjust scroll position, and fix glitches
0606     _oldTotalLines = getLines() + getHistLines();
0607     _isResize = true;
0608 
0609     int cursorLine = getCursorLine();
0610     const int oldCursorLine = (cursorLine == _lines - 1 || cursorLine > new_lines - 1) ? new_lines - 1 : cursorLine;
0611 
0612     // Check if _history need to change
0613     if (_enableReflowLines && new_columns != _columns && _history->getLines() && _history->getMaxLines()) {
0614         // Join next line from _screenLine to _history
0615         while (!_screenLines.empty() && _history->isWrappedLine(_history->getLines() - 1)) {
0616             fastAddHistLine();
0617             --cursorLine;
0618             scrollPlacements(1);
0619         }
0620         std::map<int, int> deltas = {};
0621         auto removedLines = _history->reflowLines(new_columns, &deltas);
0622 
0623         // If _history size > max history size it will drop a line from _history.
0624         // We need to verify if we need to remove a URL.
0625         if (removedLines && _escapeSequenceUrlExtractor) {
0626             _escapeSequenceUrlExtractor->historyLinesRemoved(removedLines);
0627         }
0628 
0629         for (const auto &[pos, delta] : deltas) {
0630             scrollPlacements(delta, INT64_MIN, pos);
0631         }
0632     }
0633 
0634     if (_enableReflowLines && new_columns != _columns) {
0635         int cursorLineCorrection = 0;
0636         if (_currentTerminalDisplay) {
0637             // The 'zsh' works different from other shells when writing the command line.
0638             // It needs to identify the 'zsh' and calculate the new command line.
0639             auto sessionController = _currentTerminalDisplay->sessionController();
0640             auto terminal = sessionController->session()->foregroundProcessName();
0641             if (terminal == QLatin1String("zsh")) {
0642                 while (cursorLine + cursorLineCorrection > 0 && (linePropertiesAt(cursorLine + cursorLineCorrection).flags.f.prompt_start) == 0) {
0643                     --cursorLineCorrection;
0644                 }
0645                 if (cursorLine + cursorLineCorrection > 0 && (linePropertiesAt(cursorLine + cursorLineCorrection).flags.f.prompt_start) != 0) {
0646                     _lineProperties[cursorLine + cursorLineCorrection - 1].flags.f.wrapped = 0;
0647                 } else {
0648                     cursorLineCorrection = 0;
0649                     while (cursorLine + cursorLineCorrection > 0 && (linePropertiesAt(cursorLine + cursorLineCorrection - 1).flags.f.wrapped) != 0) {
0650                         --cursorLineCorrection;
0651                     }
0652                 }
0653             }
0654         }
0655 
0656         // Analyze the lines and move the data to lines below.
0657         int currentPos = 0;
0658         while (currentPos < (cursorLine + cursorLineCorrection) && currentPos < (int)_screenLines.size() - 1) {
0659             // Join wrapped line in current position
0660             if ((_lineProperties.at(currentPos).flags.f.wrapped) != 0) {
0661                 auto starts = _lineProperties.at(currentPos).getStarts();
0662                 _screenLines[currentPos].append(_screenLines.at(currentPos + 1));
0663                 _screenLines.erase(_screenLines.begin() + currentPos + 1);
0664                 _lineProperties.erase(_lineProperties.begin() + currentPos);
0665                 _lineProperties.at(currentPos).setStarts(starts);
0666                 --cursorLine;
0667                 scrollPlacements(1, currentPos);
0668                 continue;
0669             }
0670 
0671             // Ignore whitespaces at the end of the line
0672             int lineSize = _screenLines.at(currentPos).size();
0673             while (lineSize > 0 && QChar(_screenLines.at(currentPos).at(lineSize - 1).character).isSpace()) {
0674                 --lineSize;
0675             }
0676 
0677             // If need to move to line below, copy from the current line, to the next one.
0678             if (lineSize > new_columns
0679                 && !(_lineProperties.at(currentPos).flags.f.doubleheight_bottom | _lineProperties.at(currentPos).flags.f.doubleheight_top)) {
0680                 auto values = _screenLines.at(currentPos).mid(new_columns);
0681                 _screenLines[currentPos].resize(new_columns);
0682                 LineProperty newLineProperty = _lineProperties.at(currentPos);
0683                 newLineProperty.resetStarts();
0684                 _lineProperties.insert(_lineProperties.begin() + currentPos + 1, newLineProperty);
0685                 _screenLines.insert(_screenLines.begin() + currentPos + 1, std::move(values));
0686                 _lineProperties[currentPos].flags.f.wrapped = 1;
0687                 ++cursorLine;
0688                 scrollPlacements(-1, currentPos);
0689             }
0690             currentPos += 1;
0691         }
0692     }
0693 
0694     // Check if it need to move from _screenLine to _history
0695     while (cursorLine > new_lines - 1) {
0696         fastAddHistLine();
0697         --cursorLine;
0698         scrollPlacements(1);
0699     }
0700 
0701     if (_enableReflowLines) {
0702         // Check cursor position and send from _history to _screenLines
0703         ImageLine histLine;
0704         while (cursorLine < oldCursorLine && _history->getLines()) {
0705             int histPos = _history->getLines() - 1;
0706             int histLineLen = _history->getLineLen(histPos);
0707             LineProperty lineProperty = _history->getLineProperty(histPos);
0708             histLine.resize(histLineLen);
0709             _history->getCells(histPos, 0, histLineLen, histLine.data());
0710             _screenLines.insert(_screenLines.begin(), std::move(histLine));
0711             _lineProperties.insert(_lineProperties.begin(), lineProperty);
0712             _history->removeCells();
0713             ++cursorLine;
0714             scrollPlacements(-1);
0715         }
0716     }
0717 
0718     _lineProperties.resize(new_lines + 1);
0719     if (_lineProperties.size() > _screenLines.size()) {
0720         std::fill(_lineProperties.begin() + _screenLines.size(), _lineProperties.end(), LineProperty());
0721     }
0722     _screenLines.resize(new_lines + 1);
0723 
0724     _screenLinesSize = new_lines;
0725     _lines = new_lines;
0726     _columns = new_columns;
0727     _cuX = qMin(_cuX, _columns - 1);
0728     cursorLine = qBound(0, cursorLine, _lines - 1);
0729     setCursorLine(cursorLine);
0730 
0731     // FIXME: try to keep values, evtl.
0732     setDefaultMargins();
0733     initTabStops();
0734     clearSelection();
0735 }
0736 
0737 void Screen::setDefaultMargins()
0738 {
0739     _topMargin = 0;
0740     _bottomMargin = _lines - 1;
0741 }
0742 
0743 /*
0744    Clarifying rendition here and in the display.
0745 
0746    The rendition attributes are
0747 
0748    attribute
0749    --------------
0750    RE_UNDERLINE
0751    RE_BLINK
0752    RE_BOLD
0753    RE_REVERSE
0754    RE_TRANSPARENT
0755    RE_FAINT
0756    RE_STRIKEOUT
0757    RE_CONCEAL
0758    RE_OVERLINE
0759 
0760    Depending on settings, bold may be rendered as a heavier font
0761    in addition to a different color.
0762    */
0763 
0764 void Screen::reverseRendition(Character &p) const
0765 {
0766     CharacterColor f = p.foregroundColor;
0767     CharacterColor b = p.backgroundColor;
0768 
0769     p.foregroundColor = b;
0770     p.backgroundColor = f; // p->r &= ~RE_TRANSPARENT;
0771 }
0772 
0773 void Screen::updateEffectiveRendition()
0774 {
0775     _effectiveRendition = _currentRendition;
0776     if ((_currentRendition.f.reverse) != 0) {
0777         _effectiveForeground = _currentBackground;
0778         _effectiveBackground = _currentForeground;
0779     } else {
0780         _effectiveForeground = _currentForeground;
0781         _effectiveBackground = _currentBackground;
0782     }
0783 
0784     if ((_currentRendition.f.bold) != 0) {
0785         if ((_currentRendition.f.faint) == 0) {
0786             _effectiveForeground.setIntensive();
0787         }
0788     } else {
0789         if ((_currentRendition.f.faint) != 0) {
0790             _effectiveForeground.setFaint();
0791         }
0792     }
0793 }
0794 
0795 void Screen::copyFromHistory(Character *dest, int startLine, int count) const
0796 {
0797     const int columns = _columns;
0798 
0799     Q_ASSERT(startLine >= 0 && count > 0 && startLine + count <= _history->getLines());
0800 
0801     for (int line = startLine; line < startLine + count; ++line) {
0802         const int length = qMin(columns, _history->getLineLen(line));
0803         const int destLineOffset = (line - startLine) * columns;
0804         const int lastColumn = (_history->getLineProperty(line).flags.f.doublewidth) ? columns / 2 : columns;
0805 
0806         _history->getCells(line, 0, length, dest + destLineOffset);
0807 
0808         if (length < columns) {
0809             const int begin = destLineOffset + length;
0810             const int end = destLineOffset + columns;
0811             std::fill(dest + begin, dest + end, Screen::DefaultChar);
0812         }
0813 
0814         // invert selected text
0815         if (_selBegin != -1) {
0816             bool prevSelected = false;
0817             for (int column = 0; column < lastColumn; ++column) {
0818                 const bool selected = isSelected(column, line);
0819                 if (selected) {
0820                     // Make sure to not mark as selected the right half of a CJK character if the left half isn't selected
0821                     if (column == 0 || prevSelected || !dest[destLineOffset + column].isRightHalfOfDoubleWide())
0822                         dest[destLineOffset + column].rendition.f.selected = 1;
0823                     // Make sure to mark as selected the right half of a CJK character if the left half is selected
0824                     if (column + 1 < lastColumn && dest[destLineOffset + column + 1].isRightHalfOfDoubleWide())
0825                         dest[destLineOffset + column + 1].rendition.f.selected = 1;
0826                 }
0827                 prevSelected = selected;
0828             }
0829         }
0830     }
0831 }
0832 
0833 void Screen::copyFromScreen(Character *dest, int startLine, int count) const
0834 {
0835     const int endLine = startLine + count;
0836     const int columns = _columns;
0837     const int historyLines = _history->getLines();
0838 
0839     Q_ASSERT(startLine >= 0 && count > 0 && endLine <= _lines);
0840 
0841     for (int line = startLine; line < endLine; ++line) {
0842         const int destLineOffset = (line - startLine) * columns;
0843         const int lastColumn = (line < (int)_lineProperties.size() && _lineProperties[line].flags.f.doublewidth) ? columns / 2 : columns;
0844         const ImageLine srcLine = _screenLines.at(line);
0845         const int length = qMin(columns, srcLine.size());
0846 
0847         std::copy(srcLine.cbegin(), srcLine.cbegin() + length, dest + destLineOffset);
0848 
0849         if (length < columns) {
0850             const int begin = destLineOffset + length;
0851             const int end = destLineOffset + columns;
0852             std::fill(dest + begin, dest + end, Screen::DefaultChar);
0853         }
0854 
0855         if (_selBegin != -1) {
0856             bool prevSelected = false;
0857             for (int column = 0; column < lastColumn; ++column) {
0858                 const bool selected = isSelected(column, line + historyLines);
0859                 if (selected) {
0860                     // Make sure to not mark as selected the right half of a CJK character if the left half isn't selected
0861                     if (column == 0 || prevSelected || !dest[destLineOffset + column].isRightHalfOfDoubleWide())
0862                         dest[destLineOffset + column].rendition.f.selected = 1;
0863                     // Make sure to mark as selected the right half of a CJK character if the left half is selected
0864                     if (column + 1 < lastColumn && dest[destLineOffset + column + 1].isRightHalfOfDoubleWide())
0865                         dest[destLineOffset + column + 1].rendition.f.selected = 1;
0866                 }
0867                 prevSelected = selected;
0868             }
0869         }
0870     }
0871 }
0872 
0873 void Screen::getImage(Character *dest, int size, int startLine, int endLine) const
0874 {
0875     Q_ASSERT(startLine >= 0);
0876     Q_ASSERT(endLine >= startLine && endLine < _history->getLines() + _lines);
0877 
0878     const int mergedLines = endLine - startLine + 1;
0879 
0880     Q_ASSERT(size >= mergedLines * _columns);
0881     Q_UNUSED(size)
0882 
0883     const int linesInHistoryBuffer = qBound(0, _history->getLines() - startLine, mergedLines);
0884     const int linesInScreenBuffer = mergedLines - linesInHistoryBuffer;
0885 
0886     // copy _lines from history buffer
0887     if (linesInHistoryBuffer > 0) {
0888         copyFromHistory(dest, startLine, linesInHistoryBuffer);
0889     }
0890 
0891     // copy _lines from screen buffer
0892     if (linesInScreenBuffer > 0) {
0893         copyFromScreen(dest + linesInHistoryBuffer * _columns, startLine + linesInHistoryBuffer - _history->getLines(), linesInScreenBuffer);
0894     }
0895 
0896     // invert display when in screen mode
0897     if (getMode(MODE_Screen)) {
0898         for (int i = 0; i < mergedLines * _columns; ++i) {
0899             reverseRendition(dest[i]); // for reverse display
0900         }
0901     }
0902 
0903     int visX = qMin(_cuX, getScreenLineColumns(_cuY) - 1);
0904     // mark the character at the current cursor position
0905     int cursorIndex = loc(visX, _cuY + linesInHistoryBuffer);
0906     if (getMode(MODE_Cursor) && cursorIndex < _columns * mergedLines) {
0907         dest[cursorIndex].rendition.f.cursor = 1;
0908     }
0909     cursorIndex = loc(_selCuX, _selCuY - startLine + _history->getLines());
0910 
0911     if (getMode(MODE_SelectCursor) && cursorIndex >= 0 && cursorIndex < _columns * mergedLines) {
0912         dest[cursorIndex].rendition.f.cursor = 1;
0913     }
0914 }
0915 
0916 QVector<LineProperty> Screen::getLineProperties(int startLine, int endLine) const
0917 {
0918     Q_ASSERT(startLine >= 0);
0919     Q_ASSERT(endLine >= startLine && endLine < _history->getLines() + _lines);
0920 
0921     const int mergedLines = endLine - startLine + 1;
0922     const int linesInHistory = qBound(0, _history->getLines() - startLine, mergedLines);
0923     const int linesInScreen = mergedLines - linesInHistory;
0924 
0925     QVector<LineProperty> result(mergedLines);
0926     int index = 0;
0927 
0928     // copy properties for _lines in history
0929     for (int line = startLine; line < startLine + linesInHistory; ++line) {
0930         result[index] = _history->getLineProperty(line);
0931         ++index;
0932     }
0933 
0934     // copy properties for _lines in screen buffer
0935     const int firstScreenLine = startLine + linesInHistory - _history->getLines();
0936     for (int line = firstScreenLine; line < firstScreenLine + linesInScreen; ++line) {
0937         result[index] = _lineProperties.at(line);
0938         ++index;
0939     }
0940 
0941     return result;
0942 }
0943 
0944 int Screen::getScreenLineColumns(const int line) const
0945 {
0946     if (line < (int)_lineProperties.size() && _lineProperties.at(line).flags.f.doublewidth) {
0947         return _columns / 2;
0948     }
0949 
0950     return _columns;
0951 }
0952 
0953 void Screen::reset(bool softReset, bool preservePrompt)
0954 {
0955     setDefaultRendition();
0956 
0957     if (!softReset) {
0958         if (preservePrompt) {
0959             // Clear screen, but preserve the current line and X position
0960             scrollUp(0, _cuY);
0961             _cuY = 0;
0962             if (_hasGraphics) {
0963                 delPlacements();
0964                 _currentTerminalDisplay->update();
0965             }
0966         } else {
0967             clearEntireScreen();
0968             _cuY = 0;
0969             _cuX = 0;
0970         }
0971 
0972         resetMode(MODE_Screen); // screen not inverse
0973         resetMode(MODE_NewLine);
0974 
0975         initTabStops();
0976     }
0977 
0978     _currentModes[MODE_Origin] = 0;
0979     _savedModes[MODE_Origin] = 0;
0980 
0981     setMode(MODE_Wrap);
0982     saveMode(MODE_Wrap); // wrap at end of margin
0983 
0984     resetMode(MODE_Insert);
0985     saveMode(MODE_Insert); // overstroke
0986 
0987     setMode(MODE_Cursor); // cursor visible
0988     resetMode(MODE_SelectCursor);
0989 
0990     _topMargin = 0;
0991     _bottomMargin = _lines - 1;
0992 
0993     // Other terminal emulators reset the entire scroll history during a reset
0994     //    setScroll(getScroll(), false);
0995 
0996     saveCursor();
0997 
0998     // DECSTR homes the saved cursor even though it doesn't home the current cursor
0999     _savedState.cursorColumn = 0;
1000     _savedState.cursorLine = 0;
1001 }
1002 
1003 void Screen::backspace()
1004 {
1005     _cuX = qMin(getScreenLineColumns(_cuY) - 1, _cuX); // nowrap!
1006     _cuX = qMax(0, _cuX - 1);
1007 
1008     if (_screenLines.at(_cuY).size() < _cuX + 1) {
1009         _screenLines[_cuY].resize(_cuX + 1);
1010     }
1011 }
1012 
1013 void Screen::tab(int n)
1014 {
1015     // note that TAB is a format effector (does not write ' ');
1016     if (n < 1) {
1017         n = 1;
1018     }
1019     while ((n > 0) && (_cuX < getScreenLineColumns(_cuY) - 1)) {
1020         cursorRight(1);
1021         while ((_cuX < getScreenLineColumns(_cuY) - 1) && !_tabStops.at(_cuX)) {
1022             cursorRight(1);
1023         }
1024         --n;
1025     }
1026 }
1027 
1028 void Screen::backtab(int n)
1029 {
1030     // note that TAB is a format effector (does not write ' ');
1031     if (n < 1) {
1032         n = 1;
1033     }
1034     while ((n > 0) && (_cuX > 0)) {
1035         cursorLeft(1);
1036         while ((_cuX > 0) && !_tabStops.at(_cuX)) {
1037             cursorLeft(1);
1038         }
1039         --n;
1040     }
1041 }
1042 
1043 void Screen::clearTabStops()
1044 {
1045     _tabStops.fill(false);
1046 }
1047 
1048 void Screen::changeTabStop(bool set)
1049 {
1050     if (_cuX >= _columns) {
1051         return;
1052     }
1053 
1054     _tabStops[_cuX] = set;
1055 }
1056 
1057 void Screen::initTabStops()
1058 {
1059     _tabStops.resize(_columns);
1060 
1061     // The 1st tabstop has to be one longer than the other.
1062     // i.e. the kids start counting from 0 instead of 1.
1063     // Other programs might behave correctly. Be aware.
1064     for (int i = 0; i < _columns; ++i) {
1065         _tabStops[i] = (i % 8 == 0 && i != 0);
1066     }
1067 }
1068 
1069 void Screen::newLine()
1070 {
1071     if (getMode(MODE_NewLine)) {
1072         _lineProperties[_cuY].length = _cuX;
1073         toStartOfLine();
1074     }
1075 
1076     index();
1077     _lineProperties[_cuY].counter = commandCounter;
1078 }
1079 
1080 void Screen::checkSelection(int from, int to)
1081 {
1082     if (_selBegin == -1) {
1083         return;
1084     }
1085     const int scr_TL = loc(0, _history->getLines());
1086     // Clear entire selection if it overlaps region [from, to]
1087     if ((_selBottomRight >= (from + scr_TL)) && (_selTopLeft <= (to + scr_TL))) {
1088         clearSelection();
1089     }
1090 }
1091 
1092 void Screen::displayCharacter(uint c)
1093 {
1094     // Note that VT100 does wrapping BEFORE putting the character.
1095     // This has impact on the assumption of valid cursor positions.
1096     // We indicate the fact that a newline has to be triggered by
1097     // putting the cursor one right to the last column of the screen.
1098 
1099     int w = Character::width(c);
1100     const QChar::Category category = QChar::category(c);
1101     if (w < 0) {
1102         // Non-printable character
1103         return;
1104     } else if (category == QChar::Mark_SpacingCombining || w == 0 || Character::emoji(c) || c == 0x20E3 || (_ignoreWcWidth && c == 0x00AD)) {
1105         bool emoji = Character::emoji(c);
1106         if (category != QChar::Mark_SpacingCombining && category != QChar::Mark_NonSpacing && category != QChar::Letter_Other && category != QChar::Other_Format
1107             && !emoji && c != 0x20E3 && c != 0x00AD) {
1108             return;
1109         }
1110         // Find previous "real character" to try to combine with
1111         int charToCombineWithX = qMin(_cuX, _screenLines.at(_cuY).length());
1112         int charToCombineWithY = _cuY;
1113         bool previousChar = true;
1114         do {
1115             if (charToCombineWithX > 0) {
1116                 --charToCombineWithX;
1117             } else if (charToCombineWithY > 0 && _lineProperties.at(charToCombineWithY - 1).flags.f.wrapped) { // Try previous line
1118                 --charToCombineWithY;
1119                 charToCombineWithX = _screenLines.at(charToCombineWithY).length() - 1;
1120             } else {
1121                 // Give up
1122                 previousChar = false;
1123                 break;
1124             }
1125 
1126             // Failsafe
1127             if (charToCombineWithX < 0) {
1128                 previousChar = false;
1129                 break;
1130             }
1131         } while (_screenLines.at(charToCombineWithY).at(charToCombineWithX).isRightHalfOfDoubleWide());
1132 
1133         if (!previousChar) {
1134             if (emoji) {
1135                 goto notcombine;
1136             }
1137             if (!Hangul::isHangul(c)) {
1138                 return;
1139             } else {
1140                 w = 2;
1141                 goto notcombine;
1142             }
1143         }
1144 
1145         Character &currentChar = _screenLines[charToCombineWithY][charToCombineWithX];
1146 
1147         if (c == 0x20E3) {
1148             // Combining Enclosing Keycap - only combines with presentation mode #,*,0-9
1149             if ((currentChar.character != 0x23 && currentChar.character != 0x2A && (currentChar.character < '0' || currentChar.character > '9'))
1150                 || (currentChar.flags & EF_EMOJI_REPRESENTATION) == 0) {
1151                 // Is this the right thing TODO?
1152                 return;
1153             }
1154         }
1155         if (c == 0xFE0F) {
1156             currentChar.flags |= EF_EMOJI_REPRESENTATION;
1157             if (charToCombineWithX == _cuX - 1) {
1158                 // If width was 1, change to two.
1159                 if (_screenLines[_cuY].size() < _cuX + 1) {
1160                     _screenLines[_cuY].resize(_cuX + 1);
1161                 }
1162                 Character &ch = _screenLines[_cuY][_cuX];
1163                 ch.setRightHalfOfDoubleWide();
1164                 ch.foregroundColor = _effectiveForeground;
1165                 ch.backgroundColor = _effectiveBackground;
1166                 ch.rendition = _effectiveRendition;
1167                 ch.flags = setRepl(EF_UNREAL, _replMode);
1168                 _cuX += 1;
1169             }
1170             // Emoji presentation should not be included
1171             // (maybe a bug in Qt? including this code point in sequences breaks emoji-zwj-sequences.txt)
1172             return;
1173         }
1174         if (c == 0x200D) {
1175             // Zero width joiner
1176             currentChar.flags |= EF_EMOJI_REPRESENTATION;
1177         }
1178         if (c >= 0xE0020 && c <= 0xE007F) {
1179             // Tags - used for some flags
1180             currentChar.flags |= EF_EMOJI_REPRESENTATION;
1181         }
1182 
1183         if (c >= 0x1f3fb && c <= 0x1f3ff) {
1184             // Emoji modifier Fitzpatrick - changes skin color
1185             char32_t currentUcs4 = currentChar.character;
1186             if (currentChar.rendition.f.extended == 1) {
1187                 ushort extendedCharLength;
1188                 const char32_t *oldChars = ExtendedCharTable::instance.lookupExtendedChar(currentChar.character, extendedCharLength);
1189                 currentUcs4 = oldChars[extendedCharLength - 1];
1190             }
1191             if (currentUcs4 < 0x261d || (currentUcs4 > 0x270d && currentUcs4 < 0x1efff) || currentUcs4 > 0x1faff) {
1192                 goto notcombine;
1193             }
1194             currentChar.flags |= EF_EMOJI_REPRESENTATION;
1195         } else if (c >= 0x1f1e6 && c <= 0x1f1ff) {
1196             // Regional indicators - flag components
1197             if (currentChar.rendition.f.extended == 1 || currentChar.character < 0x1f1e6 || currentChar.character > 0x1f1ff) {
1198                 goto notcombine;
1199             }
1200             currentChar.flags |= EF_EMOJI_REPRESENTATION;
1201         } else if (emoji) {
1202             if (currentChar.rendition.f.extended == 0) {
1203                 goto notcombine;
1204             }
1205             ushort extendedCharLength;
1206             const char32_t *oldChars = ExtendedCharTable::instance.lookupExtendedChar(currentChar.character, extendedCharLength);
1207             if (oldChars[extendedCharLength - 1] != 0x200d) {
1208                 goto notcombine;
1209             }
1210         }
1211 
1212         if (Hangul::isHangul(c) && !Hangul::combinesWith(currentChar, c)) {
1213             w = 2;
1214             goto notcombine;
1215         }
1216 
1217         if (currentChar.rendition.f.extended == 0) {
1218             const char32_t chars[2] = {currentChar.character, c};
1219             currentChar.rendition.f.extended = 1;
1220             auto extChars = [this]() {
1221                 return usedExtendedChars();
1222             };
1223             currentChar.character = ExtendedCharTable::instance.createExtendedChar(chars, 2, extChars);
1224             if (category == QChar::Mark_SpacingCombining) {
1225                 // ensure current line vector has enough elements
1226                 if (_screenLines[_cuY].size() < _cuX + w) {
1227                     _screenLines[_cuY].resize(_cuX + w);
1228                 }
1229                 Character &ch = _screenLines[_cuY][_cuX];
1230                 ch.setRightHalfOfDoubleWide();
1231                 ch.foregroundColor = _effectiveForeground;
1232                 ch.backgroundColor = _effectiveBackground;
1233                 ch.rendition = _effectiveRendition;
1234                 ch.flags = setRepl(EF_UNREAL, _replMode);
1235                 _cuX += 1;
1236             }
1237         } else {
1238             ushort extendedCharLength;
1239             const char32_t *oldChars = ExtendedCharTable::instance.lookupExtendedChar(currentChar.character, extendedCharLength);
1240             Q_ASSERT(extendedCharLength > 1);
1241             Q_ASSERT(oldChars);
1242             if (((oldChars) != nullptr) && extendedCharLength < 10) {
1243                 Q_ASSERT(extendedCharLength < 65535); // redundant due to above check
1244                 auto chars = std::make_unique<char32_t[]>(extendedCharLength + 1);
1245                 std::copy_n(oldChars, extendedCharLength, chars.get());
1246                 chars[extendedCharLength] = c;
1247                 auto extChars = [this]() {
1248                     return usedExtendedChars();
1249                 };
1250                 currentChar.character = ExtendedCharTable::instance.createExtendedChar(chars.get(), extendedCharLength + 1, extChars);
1251             }
1252         }
1253         return;
1254     }
1255 
1256 notcombine:
1257     if (_cuX + w > getScreenLineColumns(_cuY)) {
1258         if (getMode(MODE_Wrap)) {
1259             _lineProperties[_cuY].flags.f.wrapped = 1;
1260             nextLine();
1261         } else {
1262             _cuX = qMax(getScreenLineColumns(_cuY) - w, 0);
1263         }
1264     }
1265 
1266     // ensure current line vector has enough elements
1267     if (_screenLines[_cuY].size() < _cuX + w) {
1268         _screenLines[_cuY].resize(_cuX + w);
1269     }
1270 
1271     if (getMode(MODE_Insert)) {
1272         insertChars(w);
1273     }
1274 
1275     _lastPos = loc(_cuX, _cuY);
1276 
1277     // check if selection is still valid.
1278     checkSelection(_lastPos, _lastPos);
1279 
1280     Character &currentChar = _screenLines[_cuY][_cuX];
1281 
1282     currentChar.character = c;
1283     currentChar.foregroundColor = _effectiveForeground;
1284     currentChar.backgroundColor = _effectiveBackground;
1285     currentChar.rendition = _effectiveRendition;
1286     currentChar.flags = setRepl(EF_REAL, _replMode) | SetULColor(0, _currentULColor);
1287     if (Character::emojiPresentation(c)) {
1288         currentChar.flags |= EF_EMOJI_REPRESENTATION;
1289     }
1290     if (c <= '~' && c > ' ') {
1291         currentChar.flags |= EF_ASCII_WORD;
1292     }
1293     if (c >= 0x900
1294         && (c <= 0x109f || (c >= 0x1700 && c <= 0x18af) || (c >= 0x1900 && c <= 0x1aaf) || (c >= 0x1b00 && c <= 0x1c4f) || (c >= 0xa800 && c <= 0xa82f)
1295             || (c >= 0xa840 && c <= 0xa95f) || (c >= 0xa980 && c <= 0xaaff) || (c >= 0xabc0 && c <= 0xabff) || (c >= 0x10a00 && c <= 0x10a5f)
1296             || (c >= 0x11000 && c <= 0x11fff))) {
1297         currentChar.flags |= EF_BRAHMIC_WORD;
1298     }
1299 
1300     _lastDrawnChar = c;
1301 
1302     int i = 0;
1303     const int newCursorX = _cuX + w--;
1304     while (w != 0) {
1305         ++i;
1306 
1307         if (_screenLines.at(_cuY).size() < _cuX + i + 1) {
1308             _screenLines[_cuY].resize(_cuX + i + 1);
1309         }
1310 
1311         Character &ch = _screenLines[_cuY][_cuX + i];
1312         ch.setRightHalfOfDoubleWide();
1313         ch.foregroundColor = _effectiveForeground;
1314         ch.backgroundColor = _effectiveBackground;
1315         ch.rendition = _effectiveRendition;
1316         ch.flags = setRepl(EF_UNREAL, _replMode);
1317 
1318         --w;
1319     }
1320     _cuX = newCursorX;
1321     if (_replMode != REPL_None && std::make_pair(_cuY, _cuX) >= _replModeEnd) {
1322         _replModeEnd = std::make_pair(_cuY, _cuX);
1323     }
1324     if (_lineProperties[_cuY].length < _cuX) {
1325         _lineProperties[_cuY].length = _cuX;
1326     }
1327 
1328     if (_escapeSequenceUrlExtractor) {
1329         _escapeSequenceUrlExtractor->appendUrlText(QChar(c));
1330     }
1331 }
1332 
1333 int Screen::scrolledLines() const
1334 {
1335     return _scrolledLines;
1336 }
1337 int Screen::droppedLines() const
1338 {
1339     return _droppedLines;
1340 }
1341 void Screen::resetDroppedLines()
1342 {
1343     _droppedLines = 0;
1344 }
1345 void Screen::resetScrolledLines()
1346 {
1347     _scrolledLines = 0;
1348 }
1349 
1350 void Screen::scrollUp(int n)
1351 {
1352     if (n < 1) {
1353         n = 1; // Default
1354     }
1355     for (int i = 0; i < n; i++) {
1356         if (_topMargin == 0) {
1357             addHistLine(); // history.history
1358         }
1359         scrollUp(_topMargin, 1);
1360     }
1361 }
1362 
1363 QRect Screen::lastScrolledRegion() const
1364 {
1365     return _lastScrolledRegion;
1366 }
1367 
1368 void Screen::scrollUp(int from, int n)
1369 {
1370     if (n <= 0) {
1371         return;
1372     }
1373     if (from > _bottomMargin) {
1374         return;
1375     }
1376     if (from + n > _bottomMargin) {
1377         n = _bottomMargin + 1 - from;
1378     }
1379 
1380     _scrolledLines -= n;
1381     _lastScrolledRegion = QRect(0, _topMargin, _columns - 1, (_bottomMargin - _topMargin));
1382 
1383     // FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds.
1384     moveImage(loc(0, from), loc(0, from + n), loc(_columns, _bottomMargin));
1385     clearImage(loc(0, _bottomMargin - n + 1), loc(_columns - 1, _bottomMargin), ' ');
1386     if (_hasGraphics) {
1387         scrollPlacements(n);
1388     }
1389     _selCuY = qMax(_selCuY - n, -_history->getLines());
1390     if (_replMode != REPL_None) {
1391         if (_replModeStart.first > 0) {
1392             _replModeStart = std::make_pair(_replModeStart.first - 1, _replModeStart.second);
1393             _replModeEnd = std::make_pair(_replModeEnd.first - 1, _replModeEnd.second);
1394         }
1395         if (_replLastOutputStart.first > -1) {
1396             _replLastOutputStart = std::make_pair(_replLastOutputStart.first - 1, _replLastOutputStart.second);
1397             _replLastOutputEnd = std::make_pair(_replLastOutputEnd.first - 1, _replLastOutputEnd.second);
1398         }
1399     }
1400 }
1401 
1402 void Screen::scrollDown(int n)
1403 {
1404     if (n < 1) {
1405         n = 1; // Default
1406     }
1407     scrollDown(_topMargin, n);
1408 }
1409 
1410 void Screen::scrollDown(int from, int n)
1411 {
1412     _scrolledLines += n;
1413 
1414     // FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds.
1415     if (n <= 0) {
1416         return;
1417     }
1418     if (from > _bottomMargin) {
1419         return;
1420     }
1421     if (n >= _bottomMargin + 1 - from) {
1422         n = _bottomMargin + 1 - from;
1423     } else {
1424         moveImage(loc(0, from + n), loc(0, from), loc(_columns - 1, _bottomMargin - n));
1425     }
1426     clearImage(loc(0, from), loc(_columns - 1, from + n - 1), ' ');
1427 }
1428 
1429 void Screen::setCursorYX(int y, int x)
1430 {
1431     setCursorY(y);
1432     setCursorX(x);
1433 }
1434 
1435 void Screen::setCursorX(int x)
1436 {
1437     if (x < 1) {
1438         x = 1; // Default
1439     }
1440     _cuX = qBound(0, x - 1, _columns - 1);
1441 }
1442 
1443 void Screen::setCursorY(int y)
1444 {
1445     if (y < 1) {
1446         y = 1; // Default
1447     }
1448     if (y > MAX_SCREEN_ARGUMENT) {
1449         y = MAX_SCREEN_ARGUMENT;
1450     }
1451     y += (getMode(MODE_Origin) ? _topMargin : 0);
1452     _cuY = qBound(0, y - 1, _lines - 1);
1453 }
1454 
1455 void Screen::toStartOfLine()
1456 {
1457     _cuX = 0;
1458 }
1459 
1460 int Screen::getCursorX() const
1461 {
1462     return qMin(_cuX, _columns - 1);
1463 }
1464 
1465 int Screen::getCursorY() const
1466 {
1467     return _cuY;
1468 }
1469 
1470 void Screen::clearImage(int loca, int loce, char c, bool resetLineRendition)
1471 {
1472     const int scr_TL = loc(0, _history->getLines());
1473     // FIXME: check positions
1474 
1475     // Clear entire selection if it overlaps region to be moved...
1476     if ((_selBottomRight > (loca + scr_TL)) && (_selTopLeft < (loce + scr_TL))) {
1477         clearSelection();
1478     }
1479 
1480     const int topLine = loca / _columns;
1481     const int bottomLine = loce / _columns;
1482 
1483     // When readline shortens text, it uses clearImage() to remove the extraneous text
1484     if (_replMode != REPL_None && std::make_pair(topLine, loca % _columns) <= _replModeEnd) {
1485         _replModeEnd = std::make_pair(topLine, loca % _columns);
1486     }
1487 
1488     Character clearCh(uint(c), _currentForeground, _currentBackground, DEFAULT_RENDITION, 0);
1489 
1490     // if the character being used to clear the area is the same as the
1491     // default character, the affected _lines can simply be shrunk.
1492     const bool isDefaultCh = (clearCh == Screen::DefaultChar);
1493 
1494     for (int y = topLine; y <= bottomLine; ++y) {
1495         const int endCol = (y == bottomLine) ? loce % _columns : _columns - 1;
1496         const int startCol = (y == topLine) ? loca % _columns : 0;
1497 
1498         if (endCol < _columns - 1 || startCol > 0) {
1499             _lineProperties[y].flags.f.wrapped = 0;
1500             if (_lineProperties[y].length < endCol && _lineProperties[y].length > startCol) {
1501                 _lineProperties[y].length = startCol;
1502             }
1503         } else {
1504             if (resetLineRendition) {
1505                 _lineProperties[y] = LineProperty();
1506             }
1507             {
1508                 _lineProperties[y].flags.all &= ~(LINE_WRAPPED | LINE_PROMPT_START | LINE_INPUT_START | LINE_OUTPUT_START);
1509             }
1510         }
1511 
1512         QVector<Character> &line = _screenLines[y];
1513 
1514         if (isDefaultCh && endCol == _columns - 1) {
1515             line.resize(startCol);
1516         } else {
1517             if (line.size() < endCol + 1) {
1518                 line.resize(endCol + 1);
1519             }
1520 
1521             if (endCol == _columns - 1) {
1522                 line.resize(endCol + 1);
1523             }
1524 
1525             if (startCol <= endCol) {
1526                 std::fill(line.begin() + startCol, line.begin() + (endCol + 1), clearCh);
1527             }
1528         }
1529     }
1530 }
1531 
1532 void Screen::moveImage(int dest, int sourceBegin, int sourceEnd)
1533 {
1534     Q_ASSERT(sourceBegin <= sourceEnd);
1535 
1536     const int lines = (sourceEnd - sourceBegin) / _columns;
1537 
1538     // move screen image and line properties:
1539     // the source and destination areas of the image may overlap,
1540     // so it matters that we do the copy in the right order -
1541     // forwards if dest < sourceBegin or backwards otherwise.
1542     //(search the web for 'memmove implementation' for details)
1543     const int destY = dest / _columns;
1544     const int srcY = sourceBegin / _columns;
1545     if (dest < sourceBegin) {
1546         /**
1547          * This is basically a left rotate.
1548          *
1549          * - "dest -> src" is the range of lines we want to move to the end
1550          * - "lines" is the range of lines that will be rotated
1551          *
1552          * we take lines [destY, srcY] and move them to the end of 'lines'.
1553          * Then we remove the now moved-from lines [destY, srcY].
1554          *
1555          * std::rotate can be used here but it is slower than this approach.
1556          */
1557         auto from = std::make_move_iterator(_screenLines.begin() + destY);
1558         auto to = std::make_move_iterator(_screenLines.begin() + srcY);
1559 
1560         _screenLines.insert(_screenLines.begin() + lines + srcY, from, to);
1561         _screenLines.erase(_screenLines.begin() + destY, _screenLines.begin() + srcY);
1562 
1563         std::rotate(_lineProperties.begin() + destY, _lineProperties.begin() + srcY, _lineProperties.begin() + srcY + lines);
1564     } else {
1565         for (int i = lines; i >= 0; --i) {
1566             _screenLines[destY + i] = std::move(_screenLines[srcY + i]);
1567             _lineProperties[destY + i] = _lineProperties.at(srcY + i);
1568         }
1569     }
1570 
1571     if (_lastPos != -1) {
1572         const int diff = dest - sourceBegin; // Scroll by this amount
1573         _lastPos += diff;
1574         if ((_lastPos < 0) || (_lastPos >= (lines * _columns))) {
1575             _lastPos = -1;
1576         }
1577     }
1578 
1579     // Adjust selection to follow scroll.
1580     if (_selBegin != -1) {
1581         const bool beginIsTL = (_selBegin == _selTopLeft);
1582         const int diff = dest - sourceBegin; // Scroll by this amount
1583         const int scr_TL = loc(0, _history->getLines());
1584         const int srca = sourceBegin + scr_TL; // Translate index from screen to global
1585         const int srce = sourceEnd + scr_TL; // Translate index from screen to global
1586         const int desta = srca + diff;
1587         const int deste = srce + diff;
1588 
1589         if ((_selTopLeft >= srca) && (_selTopLeft <= srce)) {
1590             _selTopLeft += diff;
1591         } else if ((_selTopLeft >= desta) && (_selTopLeft <= deste)) {
1592             _selBottomRight = -1; // Clear selection (see below)
1593         }
1594 
1595         if ((_selBottomRight >= srca) && (_selBottomRight <= srce)) {
1596             _selBottomRight += diff;
1597         } else if ((_selBottomRight >= desta) && (_selBottomRight <= deste)) {
1598             _selBottomRight = -1; // Clear selection (see below)
1599         }
1600 
1601         if (_selBottomRight < 0) {
1602             clearSelection();
1603         } else {
1604             if (_selTopLeft < 0) {
1605                 _selTopLeft = 0;
1606             }
1607         }
1608 
1609         if (beginIsTL) {
1610             _selBegin = _selTopLeft;
1611         } else {
1612             _selBegin = _selBottomRight;
1613         }
1614     }
1615 }
1616 
1617 void Screen::clearToEndOfScreen()
1618 {
1619     clearImage(loc(_cuX, _cuY), loc(_columns - 1, _lines - 1), ' ');
1620 }
1621 
1622 void Screen::clearToBeginOfScreen()
1623 {
1624     clearImage(loc(0, 0), loc(_cuX, _cuY), ' ');
1625 }
1626 
1627 void Screen::clearEntireScreen()
1628 {
1629     clearImage(loc(0, 0), loc(_columns - 1, _lines - 1), ' ');
1630     if (_hasGraphics) {
1631         delPlacements();
1632         _currentTerminalDisplay->update();
1633     }
1634 }
1635 
1636 /*! fill screen with 'E'
1637   This is to aid screen alignment
1638   */
1639 
1640 void Screen::helpAlign()
1641 {
1642     clearImage(loc(0, 0), loc(_columns - 1, _lines - 1), 'E');
1643     _cuY = 0;
1644     _cuX = 0;
1645 }
1646 
1647 void Screen::clearToEndOfLine()
1648 {
1649     clearImage(loc(_cuX, _cuY), loc(_columns - 1, _cuY), ' ', false);
1650 }
1651 
1652 void Screen::clearToBeginOfLine()
1653 {
1654     clearImage(loc(0, _cuY), loc(_cuX, _cuY), ' ', false);
1655 }
1656 
1657 void Screen::clearEntireLine()
1658 {
1659     clearImage(loc(0, _cuY), loc(_columns - 1, _cuY), ' ', false);
1660 }
1661 
1662 void Screen::setRendition(RenditionFlags rendition)
1663 {
1664     _currentRendition.all |= rendition;
1665     updateEffectiveRendition();
1666 }
1667 
1668 void Screen::setUnderlineType(int type)
1669 {
1670     _currentRendition.f.underline = type;
1671     updateEffectiveRendition();
1672 }
1673 
1674 void Screen::resetRendition(RenditionFlags rendition)
1675 {
1676     _currentRendition.all &= ~rendition;
1677     updateEffectiveRendition();
1678 }
1679 
1680 void Screen::setDefaultRendition()
1681 {
1682     setForeColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR);
1683     setBackColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR);
1684     _currentULColor = 0;
1685     _currentRendition = {DEFAULT_RENDITION};
1686     updateEffectiveRendition();
1687 }
1688 
1689 void Screen::setForeColor(int space, int color)
1690 {
1691     _currentForeground = CharacterColor(quint8(space), color);
1692 
1693     if (_currentForeground.isValid()) {
1694         updateEffectiveRendition();
1695     } else {
1696         setForeColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR);
1697     }
1698 }
1699 
1700 void Screen::setBackColor(int space, int color)
1701 {
1702     _currentBackground = CharacterColor(quint8(space), color);
1703 
1704     if (_currentBackground.isValid()) {
1705         updateEffectiveRendition();
1706     } else {
1707         setBackColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR);
1708     }
1709 }
1710 
1711 void Screen::setULColor(int space, int color)
1712 {
1713     CharacterColor col(quint8(space), color);
1714     if (col.isValid()) {
1715         int end = _ulColorQueueEnd;
1716         if (end < _ulColorQueueStart) {
1717             end += 15;
1718         }
1719         for (int i = _ulColorQueueStart; i < end; i++) {
1720             if (col == _ulColors[i % 15]) {
1721                 _currentULColor = i % 15 + 1;
1722                 return;
1723             }
1724         }
1725         _ulColors[_ulColorQueueEnd] = col;
1726         _currentULColor = _ulColorQueueEnd + 1;
1727         _ulColorQueueEnd = (_ulColorQueueEnd + 1) % 15;
1728         if (_ulColorQueueEnd == _ulColorQueueStart) {
1729             _ulColorQueueStart = (_ulColorQueueStart + 1) % 15;
1730         }
1731     } else {
1732         _currentULColor = 0;
1733     }
1734 }
1735 
1736 void Screen::clearSelection()
1737 {
1738     _selBottomRight = -1;
1739     _selTopLeft = -1;
1740     _selBegin = -1;
1741 }
1742 
1743 bool Screen::hasSelection() const
1744 {
1745     return _selBegin != -1;
1746 }
1747 
1748 void Screen::getSelectionStart(int &column, int &line) const
1749 {
1750     if (_selTopLeft != -1) {
1751         column = _selTopLeft % _columns;
1752         line = _selTopLeft / _columns;
1753     } else {
1754         column = _cuX + getHistLines();
1755         line = _cuY + getHistLines();
1756     }
1757 }
1758 void Screen::getSelectionEnd(int &column, int &line) const
1759 {
1760     if (_selBottomRight != -1) {
1761         column = _selBottomRight % _columns;
1762         line = _selBottomRight / _columns;
1763     } else {
1764         column = _cuX + getHistLines();
1765         line = _cuY + getHistLines();
1766     }
1767 }
1768 void Screen::setSelectionStart(const int x, const int y, const bool blockSelectionMode)
1769 {
1770     _selBegin = loc(x, y);
1771     /* FIXME, HACK to correct for x too far to the right... */
1772     if (x == _columns) {
1773         --_selBegin;
1774     }
1775 
1776     _selBottomRight = _selBegin;
1777     _selTopLeft = _selBegin;
1778     _blockSelectionMode = blockSelectionMode;
1779 }
1780 
1781 void Screen::setSelectionEnd(const int x, const int y, const bool trimTrailingWhitespace)
1782 {
1783     if (_selBegin == -1) {
1784         return;
1785     }
1786 
1787     int endPos = loc(x, y);
1788 
1789     if (endPos < _selBegin) {
1790         _selTopLeft = endPos;
1791         _selBottomRight = _selBegin;
1792     } else {
1793         /* FIXME, HACK to correct for x too far to the right... */
1794         if (x == _columns) {
1795             --endPos;
1796         }
1797 
1798         _selTopLeft = _selBegin;
1799         _selBottomRight = endPos;
1800     }
1801 
1802     if (_blockSelectionMode) {
1803         // Normalize the selection in column mode
1804         const int topRow = _selTopLeft / _columns;
1805         const int topColumn = _selTopLeft % _columns;
1806         const int bottomRow = _selBottomRight / _columns;
1807         const int bottomColumn = _selBottomRight % _columns;
1808 
1809         _selTopLeft = loc(qMin(topColumn, bottomColumn), topRow);
1810         _selBottomRight = loc(qMax(topColumn, bottomColumn), bottomRow);
1811         return;
1812     }
1813 
1814     // Extend the selection to the rightmost column if beyond the last character in the line
1815     const int bottomRow = _selBottomRight / _columns;
1816     const int bottomColumn = _selBottomRight % _columns;
1817 
1818     bool beyondLastColumn = true;
1819     if (bottomRow < _history->getLines()) {
1820         ImageLine histLine;
1821         const int histLineLen = _history->getLineLen(bottomRow);
1822         histLine.resize(histLineLen);
1823 
1824         _history->getCells(bottomRow, 0, histLineLen, histLine.data());
1825 
1826         for (int j = bottomColumn; j < histLineLen; j++) {
1827             if ((histLine.at(j).flags & EF_REAL) != 0 && (!trimTrailingWhitespace || !QChar(histLine.at(j).character).isSpace())) {
1828                 beyondLastColumn = false;
1829             }
1830         }
1831     } else {
1832         size_t line = bottomRow - _history->getLines();
1833         const int lastColumn = (line < _lineProperties.size() && _lineProperties[line].flags.f.doublewidth) ? _columns / 2 : _columns;
1834         const auto *data = _screenLines[line].data();
1835 
1836         // This should never happen, but it's happening. this is just to gather some information
1837         // about the crash.
1838         // Do not let this code go to a release.
1839         if (_screenLines.size() < line) {
1840             QFile konsoleInfo(QStringLiteral("~/konsole_info_crash_array_out_of_bounds.txt"));
1841             konsoleInfo.open(QIODevice::WriteOnly);
1842             QTextStream messages(&konsoleInfo);
1843 
1844             messages << "_selBegin" << _selBegin << "\n";
1845             messages << "endPos" << endPos << "\n";
1846             messages << "_selBottomRight" << _selBottomRight << "\n";
1847             messages << "bottomRow Calculation: (_selBottomRight / _columns) = " << _selBottomRight << "/" << _columns << "\n";
1848             messages << "line Calculation: (bottomRow - _history->getLines()) = " << bottomRow << "-" << _history->getLines() << "\n";
1849             messages << "_screenLines.count()" << _screenLines.size() << "\n";
1850             messages << "line" << line << "\n";
1851         }
1852 
1853         // HACK: do not crash.
1854         if (_screenLines.size() < line) {
1855             line = _screenLines.size() - 1;
1856         }
1857         const int length = _screenLines.at(line).count();
1858 
1859         for (int k = bottomColumn; k < lastColumn && k < length; k++) {
1860             if ((data[k].flags & EF_REAL) != 0 && (!trimTrailingWhitespace || !QChar(data[k].character).isSpace())) {
1861                 beyondLastColumn = false;
1862             }
1863         }
1864     }
1865 
1866     if (beyondLastColumn) {
1867         _selBottomRight = loc(_columns - 1, bottomRow);
1868     }
1869 }
1870 
1871 bool Screen::isSelected(const int x, const int y) const
1872 {
1873     bool columnInSelection = true;
1874     if (_blockSelectionMode) {
1875         columnInSelection = x >= (_selTopLeft % _columns) && x <= (_selBottomRight % _columns);
1876     }
1877 
1878     const int pos = loc(x, y);
1879     return pos >= _selTopLeft && pos <= _selBottomRight && columnInSelection;
1880 }
1881 
1882 Character Screen::getCharacter(int col, int row) const
1883 {
1884     Character ch;
1885     if (row >= _history->getLines()) {
1886         ch = _screenLines[row - _history->getLines()].value(col);
1887     } else {
1888         if (col < _history->getLineLen(row)) {
1889             _history->getCells(row, col, 1, &ch);
1890         } else {
1891             ch = Character();
1892         }
1893     }
1894     return ch;
1895 }
1896 
1897 void Screen::selectReplContigious(const int x, const int y)
1898 {
1899     // Avoid searching if in current input
1900     if (_replMode == REPL_INPUT && _replModeStart <= std::pair(y, x) && std::pair(y, x) <= _replModeEnd) {
1901         setSelectionStart(_replModeStart.second, _replModeStart.first, false);
1902         setSelectionEnd(_replModeEnd.second, _replModeEnd.first, true);
1903         Q_EMIT _currentTerminalDisplay->screenWindow()->selectionChanged();
1904         return;
1905     }
1906     int col = x;
1907     int row = y;
1908     if (row < _history->getLines()) {
1909         col = std::min(col, _history->getLineLen(row) - 1);
1910     } else {
1911         col = std::min(col, int(_screenLines[row - _history->getLines()].size()) - 1);
1912     }
1913     while (col > 0 && (getCharacter(col, row).flags & EF_REPL) == EF_REPL_NONE) {
1914         col--;
1915     }
1916     if ((getCharacter(col, row).flags & EF_REPL) == EF_REPL_NONE) {
1917         return;
1918     }
1919     int mode = getCharacter(col, row).flags & EF_REPL;
1920     int startX = x;
1921     int startY = y;
1922     int lastX = x;
1923     int lastY = y;
1924     bool stop = false;
1925     while (true) {
1926         while (startX >= 0) {
1927             // mode or NONE continue search, but ignore last run of NONEs
1928             if (getCharacter(startX, startY).repl() == mode) {
1929                 lastX = startX;
1930                 lastY = startY;
1931             }
1932             if (getCharacter(startX, startY).repl() != mode && getCharacter(startX, startY).repl() != EF_REPL_NONE) {
1933                 stop = true;
1934                 startX = lastX;
1935                 startY = lastY;
1936                 break;
1937             }
1938             startX--;
1939         }
1940         if (stop) {
1941             break;
1942         }
1943         startY--;
1944         if (startY < 0) {
1945             startY = 0;
1946             startX = 0;
1947             break;
1948         }
1949         startX = getLineLength(startY) - 1;
1950     }
1951     int endX = x;
1952     int endY = y;
1953     stop = false;
1954     while (endY < _lines + _history->getLines()) {
1955         while (endX < getLineLength(endY)) {
1956             if (getCharacter(endX, endY).repl() != mode && getCharacter(endX, endY).repl() != EF_REPL_NONE) {
1957                 stop = true;
1958                 break;
1959             }
1960             endX++;
1961         }
1962         if (stop) {
1963             break;
1964         }
1965         endX = 0;
1966         endY++;
1967     }
1968     if (endX == 0) {
1969         endY--;
1970         endX = getLineLength(endY) - 1;
1971     } else {
1972         endX--;
1973     }
1974     setSelectionStart(startX, startY, false);
1975     setSelectionEnd(endX, endY, true);
1976     Q_EMIT _currentTerminalDisplay->screenWindow()->selectionChanged();
1977 }
1978 
1979 QString Screen::selectedText(const DecodingOptions options) const
1980 {
1981     if (!isSelectionValid()) {
1982         if (!_hasRepl) {
1983             return QString();
1984         }
1985         int currentStart = (_history->getLines() + _replModeStart.first) * _columns + _replModeStart.second;
1986         int currentEnd = (_history->getLines() + _replModeEnd.first) * _columns + _replModeEnd.second - 1;
1987 
1988         if (_replMode == REPL_INPUT && currentStart > currentEnd && _replLastOutputStart.first > -1) {
1989             // If no input yet, copy last output
1990             currentStart = (_history->getLines() + _replLastOutputStart.first) * _columns + _replLastOutputStart.second;
1991             currentEnd = (_history->getLines() + _replLastOutputEnd.first) * _columns + _replLastOutputEnd.second - 1;
1992         }
1993         if (currentEnd >= currentStart) {
1994             return text(currentStart, currentEnd, options);
1995         }
1996         return QString();
1997     }
1998 
1999     return text(_selTopLeft, _selBottomRight, options);
2000 }
2001 
2002 QString Screen::text(int startIndex, int endIndex, const DecodingOptions options) const
2003 {
2004     QString result;
2005     QTextStream stream(&result, QIODevice::ReadWrite);
2006 
2007     HTMLDecoder htmlDecoder(ColorScheme::defaultTable);
2008     PlainTextDecoder plainTextDecoder;
2009 
2010     TerminalCharacterDecoder *decoder;
2011     if ((options & ConvertToHtml) != 0U) {
2012         decoder = &htmlDecoder;
2013     } else {
2014         decoder = &plainTextDecoder;
2015     }
2016 
2017     decoder->begin(&stream);
2018     writeToStream(decoder, startIndex, endIndex, options);
2019     decoder->end();
2020 
2021     return result;
2022 }
2023 
2024 bool Screen::isSelectionValid() const
2025 {
2026     return _selTopLeft >= 0 && _selBottomRight >= 0;
2027 }
2028 
2029 void Screen::writeToStream(TerminalCharacterDecoder *decoder, int startIndex, int endIndex, const DecodingOptions options) const
2030 {
2031     const int top = startIndex / _columns;
2032     const int left = startIndex % _columns;
2033 
2034     const int bottom = endIndex / _columns;
2035     const int right = endIndex % _columns;
2036 
2037     Q_ASSERT(top >= 0 && left >= 0 && bottom >= 0 && right >= 0);
2038 
2039     for (int y = top; y <= bottom; ++y) {
2040         int start = 0;
2041         if (y == top || _blockSelectionMode) {
2042             start = left;
2043         }
2044 
2045         int count = -1;
2046         if (y == bottom || _blockSelectionMode) {
2047             count = right - start + 1;
2048         }
2049 
2050         const bool appendNewLine = (y != bottom);
2051         int copied = copyLineToStream(y, start, count, decoder, appendNewLine, _blockSelectionMode, options);
2052 
2053         // if the selection goes beyond the end of the last line then
2054         // append a new line character.
2055         //
2056         // this makes it possible to 'select' a trailing new line character after
2057         // the text on a line.
2058         if (y == bottom && copied < count && !options.testFlag(TrimTrailingWhitespace)) {
2059             Character newLineChar('\n');
2060             decoder->decodeLine(&newLineChar, 1, LineProperty());
2061         }
2062     }
2063 }
2064 
2065 int Screen::getLineLength(const int line) const
2066 {
2067     // determine if the line is in the history buffer or the screen image
2068     const bool isInHistoryBuffer = line < _history->getLines();
2069 
2070     if (isInHistoryBuffer) {
2071         return _history->getLineLen(line);
2072     }
2073 
2074     return _columns;
2075 }
2076 
2077 Character *Screen::getCharacterBuffer(const int size)
2078 {
2079     // buffer to hold characters for decoding
2080     // the buffer is static to avoid initializing every
2081     // element on each call to copyLineToStream
2082     // (which is unnecessary since all elements will be overwritten anyway)
2083     static const int MAX_CHARS = 1024;
2084     static QVector<Character> characterBuffer(MAX_CHARS);
2085 
2086     if (characterBuffer.count() < size) {
2087         characterBuffer.resize(size);
2088     }
2089 
2090     return characterBuffer.data();
2091 }
2092 
2093 int Screen::copyLineToStream(int line,
2094                              int start,
2095                              int count,
2096                              TerminalCharacterDecoder *decoder,
2097                              bool appendNewLine,
2098                              bool isBlockSelectionMode,
2099                              const DecodingOptions options) const
2100 {
2101     const int lineLength = getLineLength(line);
2102     // ensure that this method, can append space or 'eol' character to
2103     // the selection
2104     Character *characterBuffer = getCharacterBuffer((count > -1 ? count : lineLength - start) + 1);
2105     LineProperty currentLineProperties = LineProperty();
2106 
2107     // determine if the line is in the history buffer or the screen image
2108     if (line < _history->getLines()) {
2109         // ensure that start position is before end of line
2110         // lineLength can be 0 as well
2111         start = lineLength <= 0 ? 0 : qBound(0, start, lineLength - 1);
2112 
2113         // retrieve line from history buffer
2114         if (count == -1) {
2115             count = lineLength - start;
2116         } else {
2117             count = qMin(start + count, lineLength) - start;
2118         }
2119 
2120         // safety checks
2121         Q_ASSERT(start >= 0);
2122         Q_ASSERT(count >= 0);
2123         Q_ASSERT((start + count) <= _history->getLineLen(line));
2124 
2125         _history->getCells(line, start, count, characterBuffer);
2126 
2127         // Exclude trailing empty cells from count and don't bother processing them further.
2128         // See the comment on the similar case for screen lines for an explanation.
2129         while (count > 0 && (characterBuffer[count - 1].flags & EF_REAL) == 0) {
2130             count--;
2131         }
2132 
2133         if (_history->isWrappedLine(line)) {
2134             currentLineProperties.flags.f.wrapped = 1;
2135         } else {
2136             if (options.testFlag(TrimTrailingWhitespace)) {
2137                 // ignore trailing white space at the end of the line
2138                 while (count > 0 && QChar(characterBuffer[count - 1].character).isSpace()) {
2139                     count--;
2140                 }
2141             }
2142         }
2143     } else {
2144         if (count == -1) {
2145             count = lineLength - start;
2146         }
2147 
2148         Q_ASSERT(count >= 0);
2149 
2150         int screenLine = line - _history->getLines();
2151 
2152         Q_ASSERT(screenLine <= _screenLinesSize);
2153 
2154         screenLine = qMin(screenLine, _screenLinesSize);
2155 
2156         auto *data = _screenLines[screenLine].data();
2157         int length = _screenLines.at(screenLine).count();
2158 
2159         // Exclude trailing empty cells from count and don't bother processing them further.
2160         // This is necessary because a newline gets added to the last line when
2161         // the selection extends beyond the last character (last non-whitespace
2162         // character when TrimTrailingWhitespace is true), so the returned
2163         // count from this function must not include empty cells beyond that
2164         // last character.
2165         while (length > 0 && (data[length - 1].flags & EF_REAL) == 0) {
2166             length--;
2167         }
2168 
2169         // Don't remove end spaces in lines that wrap
2170         if (options.testFlag(TrimTrailingWhitespace) && ((_lineProperties.at(screenLine).flags.f.wrapped) == 0)) {
2171             // ignore trailing white space at the end of the line
2172             while (length > 0 && QChar(data[length - 1].character).isSpace()) {
2173                 length--;
2174             }
2175         }
2176 
2177         // retrieve line from screen image
2178         auto end = qMin(start + count, length);
2179         if (start < end) {
2180             std::copy(data + start, data + end, characterBuffer);
2181         }
2182 
2183         // count cannot be any greater than length
2184         // and if start is after length we have nothing to copy
2185         count = start >= length ? 0 : qBound(0, count, length - start);
2186 
2187         Q_ASSERT((size_t)screenLine < _lineProperties.size());
2188         currentLineProperties = _lineProperties[screenLine];
2189     }
2190 
2191     // If the last character is wide, account for it
2192     if (Character::width(characterBuffer[count - 1].character) == 2)
2193         count++;
2194 
2195     if (appendNewLine) {
2196         // When users ask not to preserve the linebreaks, they usually mean:
2197         // `treat LINEBREAK as SPACE, thus joining multiple _lines into
2198         // single line in the same way as 'J' does in VIM.`
2199         const bool isLineWrapped = (currentLineProperties.flags.f.wrapped) != 0;
2200         if (isBlockSelectionMode || !isLineWrapped) {
2201             characterBuffer[count] = options.testFlag(PreserveLineBreaks) ? Character('\n') : Character(' ');
2202             ++count;
2203         }
2204     }
2205 
2206     int spacesCount = 0;
2207     if ((options & TrimLeadingWhitespace) != 0U) {
2208         for (spacesCount = 0; spacesCount < count; ++spacesCount) {
2209             if (QChar::category(characterBuffer[spacesCount].character) != QChar::Category::Separator_Space) {
2210                 break;
2211             }
2212         }
2213 
2214         if (spacesCount >= count) {
2215             return 0;
2216         }
2217 
2218         count -= spacesCount;
2219     }
2220     // Filter CharacterBuffer
2221     Character *newBuffer;
2222     bool todel = false;
2223     if ((options & (ExcludePrompt | ExcludeInput | ExcludeOutput)) != 0) {
2224         newBuffer = new Character[count];
2225         todel = true;
2226         int p = 0;
2227         for (int i = 0; i < count; i++) {
2228             Character c = characterBuffer[spacesCount + i];
2229             if (((options & ExcludePrompt) != 0 && (c.flags & EF_REPL) == EF_REPL_PROMPT)
2230                 || ((options & ExcludeInput) != 0 && (c.flags & EF_REPL) == EF_REPL_INPUT)
2231                 || ((options & ExcludeOutput) != 0 && (c.flags & EF_REPL) == EF_REPL_OUTPUT)) {
2232                 continue;
2233             }
2234             newBuffer[p++] = c;
2235         }
2236         count = p;
2237     } else {
2238         newBuffer = characterBuffer + spacesCount;
2239     }
2240 
2241     // decode line and write to text stream
2242     decoder->decodeLine(newBuffer, count, currentLineProperties);
2243 
2244     if (todel) {
2245         delete[] newBuffer;
2246     }
2247 
2248     return count;
2249 }
2250 
2251 void Screen::writeLinesToStream(TerminalCharacterDecoder *decoder, int fromLine, int toLine) const
2252 {
2253     writeToStream(decoder, loc(0, fromLine), loc(_columns - 1, toLine), PreserveLineBreaks);
2254 }
2255 
2256 void Screen::fastAddHistLine()
2257 {
2258     const bool removeLine = _history->getLines() == _history->getMaxLines();
2259     _history->addCellsVector(_screenLines.at(0));
2260     _history->addLine(linePropertiesAt(0));
2261 
2262     // If _history size > max history size it will drop a line from _history.
2263     // We need to verify if we need to remove a URL.
2264     if (removeLine && _escapeSequenceUrlExtractor) {
2265         _escapeSequenceUrlExtractor->historyLinesRemoved(1);
2266     }
2267     // Rotate left + clear the last line
2268     std::rotate(_screenLines.begin(), _screenLines.begin() + 1, _screenLines.end());
2269     auto last = _screenLines.back();
2270     Character clearCh(uint(' '), _currentForeground, _currentBackground, DEFAULT_RENDITION, false);
2271     std::fill(last.begin(), last.end(), clearCh);
2272 
2273     _lineProperties.erase(_lineProperties.begin());
2274 }
2275 
2276 void Screen::addHistLine()
2277 {
2278     // add line to history buffer
2279     // we have to take care about scrolling, too...
2280     const int oldHistLines = _history->getLines();
2281     int newHistLines = _history->getLines();
2282 
2283     if (hasScroll()) {
2284         _history->addCellsVector(_screenLines.at(0));
2285         _history->addLine(_lineProperties.at(0));
2286 
2287         newHistLines = _history->getLines();
2288 
2289         // If the history is full, increment the count
2290         // of dropped _lines
2291         if (newHistLines <= oldHistLines) {
2292             _droppedLines += oldHistLines - newHistLines + 1;
2293 
2294             _currentTerminalDisplay->removeLines(oldHistLines - newHistLines + 1);
2295             // We removed some lines, we need to verify if we need to remove a URL.
2296             if (_escapeSequenceUrlExtractor) {
2297                 _escapeSequenceUrlExtractor->historyLinesRemoved(oldHistLines - newHistLines + 1);
2298             }
2299         }
2300     }
2301 
2302     bool beginIsTL = (_selBegin == _selTopLeft);
2303 
2304     // Adjust selection for the new point of reference
2305     if (newHistLines != oldHistLines) {
2306         if (_selBegin != -1) {
2307             _selTopLeft += _columns * (newHistLines - oldHistLines);
2308             _selBottomRight += _columns * (newHistLines - oldHistLines);
2309         }
2310     }
2311 
2312     if (_selBegin != -1) {
2313         // Scroll selection in history up
2314         const int top_BR = loc(0, 1 + newHistLines);
2315 
2316         if (_selTopLeft < top_BR) {
2317             _selTopLeft -= _columns;
2318         }
2319 
2320         if (_selBottomRight < top_BR) {
2321             _selBottomRight -= _columns;
2322         }
2323 
2324         if (_selBottomRight < 0) {
2325             clearSelection();
2326         } else {
2327             if (_selTopLeft < 0) {
2328                 _selTopLeft = 0;
2329             }
2330         }
2331 
2332         if (beginIsTL) {
2333             _selBegin = _selTopLeft;
2334         } else {
2335             _selBegin = _selBottomRight;
2336         }
2337     }
2338 }
2339 
2340 int Screen::getHistLines() const
2341 {
2342     return _history->getLines();
2343 }
2344 
2345 void Screen::setScroll(const HistoryType &t, bool copyPreviousScroll)
2346 {
2347     clearSelection();
2348 
2349     if (copyPreviousScroll) {
2350         t.scroll(_history);
2351     } else {
2352         // As 't' can be '_history' pointer, move it to a temporary smart pointer
2353         // making _history = nullptr
2354         auto oldHistory = std::move(_history);
2355         _currentTerminalDisplay->removeLines(oldHistory->getLines());
2356         t.scroll(_history);
2357     }
2358     _graphicsPlacements.clear();
2359 #if HAVE_MALLOC_TRIM
2360 
2361 #ifdef Q_OS_LINUX
2362     // We might have been using gigabytes of memory, so make sure it is actually released
2363     malloc_trim(0);
2364 #endif
2365 #endif
2366 }
2367 
2368 bool Screen::hasScroll() const
2369 {
2370     return _history->hasScroll();
2371 }
2372 
2373 const HistoryType &Screen::getScroll() const
2374 {
2375     return _history->getType();
2376 }
2377 
2378 void Screen::setLineProperty(quint16 property, bool enable)
2379 {
2380     if (enable) {
2381         _lineProperties[_cuY].flags.all |= property;
2382     } else {
2383         _lineProperties[_cuY].flags.all &= ~property;
2384     }
2385 }
2386 
2387 LineProperty Screen::linePropertiesAt(unsigned int line)
2388 {
2389     if (line < _lineProperties.size()) {
2390         return _lineProperties.at(line);
2391     }
2392     return LineProperty();
2393 }
2394 
2395 void Screen::setReplMode(int mode)
2396 {
2397     if (_replMode != mode) {
2398         if (_replMode == REPL_OUTPUT) {
2399             _replLastOutputStart = _replModeStart;
2400             _replLastOutputEnd = _replModeEnd;
2401         } else if (_replMode == REPL_PROMPT) {
2402             _lineProperties[_cuY].counter = ++commandCounter;
2403         }
2404         if (mode == REPL_PROMPT) {
2405             if (_replHadOutput) {
2406                 _currentTerminalDisplay->sessionController()->notifyPrompt();
2407                 _replHadOutput = false;
2408             }
2409         }
2410         if (mode == REPL_OUTPUT) {
2411             _replHadOutput = true;
2412         }
2413         _replMode = mode;
2414         _replModeStart = std::make_pair(_cuY, _cuX);
2415         _replModeEnd = std::make_pair(_cuY, _cuX);
2416     }
2417     if (mode != REPL_None) {
2418         if (!_hasRepl) {
2419             _hasRepl = true;
2420             _currentTerminalDisplay->sessionController()->setVisible(QStringLiteral("monitor-prompt"), true);
2421         }
2422         Q_EMIT _currentTerminalDisplay->screenWindow()->selectionChanged(); // Enable copy action
2423         setLineProperty(LINE_PROMPT_START << (mode - REPL_PROMPT), true);
2424     }
2425 }
2426 
2427 void Screen::setExitCode(int /*exitCode*/)
2428 {
2429     int y = _cuY - 1;
2430     while (y >= 0) {
2431         _lineProperties[y].flags.f.error = 1;
2432         if (_lineProperties[y].flags.f.prompt_start) {
2433             return;
2434         }
2435         y--;
2436     }
2437     while (y > -_history->getLines()) {
2438         LineProperty prop = _history->getLineProperty(y + _history->getLines());
2439         prop.flags.f.error = 1;
2440         _history->setLineProperty(y + _history->getLines(), prop);
2441         if (prop.flags.f.prompt_start) {
2442             return;
2443         }
2444         y--;
2445     }
2446 }
2447 void Screen::fillWithDefaultChar(Character *dest, int count)
2448 {
2449     std::fill_n(dest, count, Screen::DefaultChar);
2450 }
2451 
2452 void Konsole::Screen::setEnableUrlExtractor(const bool enable)
2453 {
2454     if (enable) {
2455         if (_escapeSequenceUrlExtractor) {
2456             return;
2457         }
2458         _escapeSequenceUrlExtractor = std::make_unique<EscapeSequenceUrlExtractor>();
2459         _escapeSequenceUrlExtractor->setScreen(this);
2460     } else {
2461         if (!_escapeSequenceUrlExtractor) {
2462             return;
2463         }
2464         _escapeSequenceUrlExtractor.reset();
2465     }
2466 }
2467 
2468 Konsole::EscapeSequenceUrlExtractor *Konsole::Screen::urlExtractor() const
2469 {
2470     return _escapeSequenceUrlExtractor.get();
2471 }
2472 
2473 void Screen::addPlacement(QPixmap pixmap,
2474                           int &rows,
2475                           int &cols,
2476                           int row,
2477                           int col,
2478                           bool scrolling,
2479                           int moveCursor,
2480                           bool leaveText,
2481                           int z,
2482                           int id,
2483                           int pid,
2484                           qreal opacity,
2485                           int X,
2486                           int Y)
2487 {
2488     if (pixmap.isNull()) {
2489         return;
2490     }
2491 
2492     std::unique_ptr<TerminalGraphicsPlacement_t> p(new TerminalGraphicsPlacement_t);
2493 
2494     if (row == -1) {
2495         row = _cuY;
2496     }
2497     if (col == -1) {
2498         col = _cuX;
2499     }
2500     if (rows == -1) {
2501         rows = (pixmap.height() - 1) / _currentTerminalDisplay->terminalFont()->fontHeight() + 1;
2502     }
2503     if (cols == -1) {
2504         cols = (pixmap.width() - 1) / _currentTerminalDisplay->terminalFont()->fontWidth() + 1;
2505     }
2506 
2507     p->pixmap = pixmap;
2508     p->z = z;
2509     p->row = row;
2510     p->col = col;
2511     p->rows = rows;
2512     p->cols = cols;
2513     p->id = id;
2514     p->pid = pid;
2515     p->opacity = opacity;
2516     p->scrolling = scrolling;
2517     p->X = X;
2518     p->Y = Y;
2519 
2520     if (!leaveText) {
2521         eraseBlock(row, col, rows, cols);
2522     }
2523     addPlacement(p);
2524     int needScroll = qBound(0, row + rows - _lines, rows);
2525     if (moveCursor && scrolling && needScroll > 0) {
2526         scrollUp(needScroll);
2527     }
2528     if (moveCursor) {
2529         if (rows - needScroll - 1 > 0) {
2530             cursorDown(rows - needScroll - 1);
2531         }
2532         if (moveCursor == 2 || _cuX + cols >= _columns) {
2533             toStartOfLine();
2534             newLine();
2535         } else {
2536             cursorRight(cols);
2537         }
2538     }
2539 }
2540 
2541 void Screen::addPlacement(std::unique_ptr<TerminalGraphicsPlacement_t> &placement)
2542 {
2543     std::vector<std::unique_ptr<TerminalGraphicsPlacement_t>>::iterator i;
2544     // remove placement with the same id and pid, if pid is non zero
2545     if (placement->pid >= 0 && placement->id >= 0) {
2546         i = _graphicsPlacements.begin();
2547         while (i != _graphicsPlacements.end()) {
2548             TerminalGraphicsPlacement_t *p = i->get();
2549             if (p->id == placement->id && p->pid == placement->pid) {
2550                 _graphicsPlacements.erase(i);
2551                 break;
2552             }
2553             i++;
2554         }
2555     }
2556 
2557     for (i = _graphicsPlacements.begin(); i != _graphicsPlacements.end() && placement->z >= i->get()->z; i++)
2558         ;
2559     _graphicsPlacements.insert(i, std::move(placement));
2560     _hasGraphics = true;
2561     // Placements with pid<0 cannot be deleted by the application, so remove those fully covered
2562     // by others.
2563     QRegion covered = QRegion();
2564     std::vector<std::unique_ptr<TerminalGraphicsPlacement_t>>::reverse_iterator rit;
2565     rit = _graphicsPlacements.rbegin();
2566     while (rit != _graphicsPlacements.rend()) {
2567         TerminalGraphicsPlacement_t *p = rit->get();
2568         if (p->pid < 0) {
2569             QRect rect(p->col, p->row, p->cols, p->rows);
2570             if (covered.intersected(rect) == QRegion(rect)) {
2571                 std::advance(rit, 1);
2572                 _graphicsPlacements.erase(rit.base());
2573             } else {
2574                 covered += rect;
2575                 std::advance(rit, 1);
2576             }
2577         } else {
2578             std::advance(rit, 1);
2579         }
2580     }
2581 }
2582 
2583 TerminalGraphicsPlacement_t *Screen::getGraphicsPlacement(unsigned int i)
2584 {
2585     if (i >= _graphicsPlacements.size()) {
2586         return nullptr;
2587     }
2588     return _graphicsPlacements[i].get();
2589 }
2590 
2591 void Screen::scrollPlacements(int n, qint64 below, qint64 above)
2592 {
2593     std::vector<std::unique_ptr<TerminalGraphicsPlacement_t>>::iterator i;
2594     int histMaxLines = _history->getMaxLines();
2595     i = _graphicsPlacements.begin();
2596     while (i != _graphicsPlacements.end()) {
2597         TerminalGraphicsPlacement_t *placement = i->get();
2598         if ((placement->scrolling && below == INT64_MAX) || (placement->row > below && placement->row < above)) {
2599             placement->row -= n;
2600             if (placement->row + placement->rows < -histMaxLines) {
2601                 i = _graphicsPlacements.erase(i);
2602             } else {
2603                 i++;
2604             }
2605         } else {
2606             i++;
2607         }
2608     }
2609 }
2610 
2611 void Screen::delPlacements(int del, qint64 id, qint64 pid, int x, int y, int z)
2612 {
2613     auto i = _graphicsPlacements.begin();
2614     while (i != _graphicsPlacements.end()) {
2615         TerminalGraphicsPlacement_t *placement = i->get();
2616         bool remove = false;
2617         switch (del) {
2618         case 1:
2619             remove = true;
2620             break;
2621         case 'z':
2622             if (placement->z == z) {
2623                 remove = true;
2624             }
2625             break;
2626         case 'x':
2627             if (placement->col <= x && x < placement->col + placement->cols) {
2628                 remove = true;
2629             }
2630             break;
2631         case 'y':
2632             if (placement->row <= y && y < placement->row + placement->rows) {
2633                 remove = true;
2634             }
2635             break;
2636         case 'p':
2637             if (placement->col <= x && x < placement->col + placement->cols && placement->row <= y && y < placement->row + placement->rows) {
2638                 remove = true;
2639             }
2640             break;
2641         case 'q':
2642             if (placement->col <= x && x < placement->col + placement->cols && placement->row <= y && y < placement->row + placement->rows
2643                 && placement->z == z) {
2644                 remove = true;
2645             }
2646             break;
2647         case 'a':
2648             if (placement->row + placement->rows > 0) {
2649                 remove = true;
2650             }
2651             break;
2652         case 'i':
2653             if ((id < 0 || placement->id == id) && (pid < 0 || placement->pid == pid)) {
2654                 remove = true;
2655             }
2656             break;
2657         }
2658         if (remove) {
2659             i = _graphicsPlacements.erase(i);
2660         } else {
2661             i++;
2662         }
2663     }
2664 }