File indexing completed on 2024-03-24 05:54:19
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 ¤tChar = _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 ¤tChar = _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 }