diff --git a/src/Screen.cpp b/src/Screen.cpp index 0b4bf269..1bfabbbf 100644 --- a/src/Screen.cpp +++ b/src/Screen.cpp @@ -1,1512 +1,1512 @@ /* This file is part of Konsole, an X terminal. Copyright 2007-2008 by Robert Knight Copyright 1997,1998 by Lars Doelle This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ // Own #include "Screen.h" // Qt #include // Konsole #include "TerminalCharacterDecoder.h" #include "History.h" #include "ExtendedCharTable.h" using namespace Konsole; //Macro to convert x,y position on screen to position within an image. // //Originally the image was stored as one large contiguous block of //memory, so a position within the image could be represented as an //offset from the beginning of the block. For efficiency reasons this //is no longer the case. //Many internal parts of this class still use this representation for parameters and so on, //notably moveImage() and clearImage(). //This macro converts from an X,Y position into an image offset. #ifndef loc #define loc(X,Y) ((Y)*_columns+(X)) #endif const Character Screen::DefaultChar = Character(' ', CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR), CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR), DEFAULT_RENDITION, false); Screen::Screen(int lines, int columns): _currentTerminalDisplay(nullptr), _lines(lines), _columns(columns), _screenLines(new ImageLine[_lines + 1]), _screenLinesSize(_lines), _scrolledLines(0), _lastScrolledRegion(QRect()), _droppedLines(0), _lineProperties(QVarLengthArray()), _history(new HistoryScrollNone()), _cuX(0), _cuY(0), _currentForeground(CharacterColor()), _currentBackground(CharacterColor()), _currentRendition(DEFAULT_RENDITION), _topMargin(0), _bottomMargin(0), _tabStops(QBitArray()), _selBegin(0), _selTopLeft(0), _selBottomRight(0), _blockSelectionMode(false), _effectiveForeground(CharacterColor()), _effectiveBackground(CharacterColor()), _effectiveRendition(DEFAULT_RENDITION), _lastPos(-1), _lastDrawnChar(0) { _lineProperties.resize(_lines + 1); for (int i = 0; i < _lines + 1; i++) { _lineProperties[i] = LINE_DEFAULT; } initTabStops(); clearSelection(); reset(); } Screen::~Screen() { delete[] _screenLines; delete _history; } void Screen::cursorUp(int n) //=CUU { if (n == 0) { n = 1; // Default } const int stop = _cuY < _topMargin ? 0 : _topMargin; _cuX = qMin(_columns - 1, _cuX); // nowrap! _cuY = qMax(stop, _cuY - n); } void Screen::cursorDown(int n) //=CUD { if (n == 0) { n = 1; // Default } const int stop = _cuY > _bottomMargin ? _lines - 1 : _bottomMargin; _cuX = qMin(_columns - 1, _cuX); // nowrap! _cuY = qMin(stop, _cuY + n); } void Screen::cursorLeft(int n) //=CUB { if (n == 0) { n = 1; // Default } _cuX = qMin(_columns - 1, _cuX); // nowrap! _cuX = qMax(0, _cuX - n); } void Screen::cursorRight(int n) //=CUF { if (n == 0) { n = 1; // Default } _cuX = qMin(_columns - 1, _cuX + n); } void Screen::setMargins(int top, int bot) //=STBM { if (top == 0) { top = 1; // Default } if (bot == 0) { bot = _lines; // Default } top = top - 1; // Adjust to internal lineno bot = bot - 1; // Adjust to internal lineno if (!(0 <= top && top < bot && bot < _lines)) { //Debug()<<" setRegion("< 0) { _cuY -= 1; } } void Screen::nextLine() //=NEL { toStartOfLine(); index(); } void Screen::eraseChars(int n) { if (n == 0) { n = 1; // Default } const int p = qMax(0, qMin(_cuX + n - 1, _columns - 1)); clearImage(loc(_cuX, _cuY), loc(p, _cuY), ' '); } void Screen::deleteChars(int n) { Q_ASSERT(n >= 0); // always delete at least one char if (n == 0) { n = 1; } // if cursor is beyond the end of the line there is nothing to do if (_cuX >= _screenLines[_cuY].count()) { return; } if (_cuX + n > _screenLines[_cuY].count()) { n = _screenLines[_cuY].count() - _cuX; } Q_ASSERT(n >= 0); Q_ASSERT(_cuX + n <= _screenLines[_cuY].count()); _screenLines[_cuY].remove(_cuX, n); // Append space(s) with current attributes Character spaceWithCurrentAttrs(' ', _effectiveForeground, _effectiveBackground, _effectiveRendition, false); for (int i = 0; i < n; i++) { _screenLines[_cuY].append(spaceWithCurrentAttrs); } } void Screen::insertChars(int n) { if (n == 0) { n = 1; // Default } if (_screenLines[_cuY].size() < _cuX) { _screenLines[_cuY].resize(_cuX); } _screenLines[_cuY].insert(_cuX, n, Character(' ')); if (_screenLines[_cuY].count() > _columns) { _screenLines[_cuY].resize(_columns); } } void Screen::repeatChars(int n) { if (n == 0) { n = 1; // Default } // From ECMA-48 version 5, section 8.3.103: // "If the character preceding REP is a control function or part of a // control function, the effect of REP is not defined by this Standard." // // So, a "normal" program should always use REP immediately after a visible // character (those other than escape sequences). So, _lastDrawnChar can be // safely used. for (int i = 0; i < n; i++) { displayCharacter(_lastDrawnChar); } } void Screen::deleteLines(int n) { if (n == 0) { n = 1; // Default } scrollUp(_cuY, n); } void Screen::insertLines(int n) { if (n == 0) { n = 1; // Default } scrollDown(_cuY, n); } void Screen::setMode(int m) { _currentModes[m] = 1; switch (m) { case MODE_Origin : _cuX = 0; _cuY = _topMargin; break; //FIXME: home } } void Screen::resetMode(int m) { _currentModes[m] = 0; switch (m) { case MODE_Origin : _cuX = 0; _cuY = 0; break; //FIXME: home } } void Screen::saveMode(int m) { _savedModes[m] = _currentModes[m]; } void Screen::restoreMode(int m) { _currentModes[m] = _savedModes[m]; } bool Screen::getMode(int m) const { return _currentModes[m] != 0; } void Screen::saveCursor() { _savedState.cursorColumn = _cuX; _savedState.cursorLine = _cuY; _savedState.rendition = _currentRendition; _savedState.foreground = _currentForeground; _savedState.background = _currentBackground; } void Screen::restoreCursor() { _cuX = qMin(_savedState.cursorColumn, _columns - 1); _cuY = qMin(_savedState.cursorLine, _lines - 1); _currentRendition = _savedState.rendition; _currentForeground = _savedState.foreground; _currentBackground = _savedState.background; updateEffectiveRendition(); } void Screen::resizeImage(int new_lines, int new_columns) { if ((new_lines == _lines) && (new_columns == _columns)) { return; } if (_cuY > new_lines - 1) { // attempt to preserve focus and _lines _bottomMargin = _lines - 1; //FIXME: margin lost for (int i = 0; i < _cuY - (new_lines - 1); i++) { addHistLine(); scrollUp(0, 1); } } // create new screen _lines and copy from old to new auto newScreenLines = new ImageLine[new_lines + 1]; for (int i = 0; i < qMin(_lines, new_lines + 1) ; i++) { newScreenLines[i] = _screenLines[i]; } _lineProperties.resize(new_lines + 1); for (int i = _lines; (i > 0) && (i < new_lines + 1); i++) { _lineProperties[i] = LINE_DEFAULT; } clearSelection(); delete[] _screenLines; _screenLines = newScreenLines; _screenLinesSize = new_lines; _lines = new_lines; _columns = new_columns; _cuX = qMin(_cuX, _columns - 1); _cuY = qMin(_cuY, _lines - 1); // FIXME: try to keep values, evtl. _topMargin = 0; _bottomMargin = _lines - 1; initTabStops(); clearSelection(); } void Screen::setDefaultMargins() { _topMargin = 0; _bottomMargin = _lines - 1; } /* Clarifying rendition here and in the display. The rendition attributes are attribute -------------- RE_UNDERLINE RE_BLINK RE_BOLD RE_REVERSE RE_TRANSPARENT RE_FAINT RE_STRIKEOUT RE_CONCEAL RE_OVERLINE Depending on settings, bold may be rendered as a heavier font in addition to a different color. */ void Screen::reverseRendition(Character& p) const { CharacterColor f = p.foregroundColor; CharacterColor b = p.backgroundColor; p.foregroundColor = b; p.backgroundColor = f; //p->r &= ~RE_TRANSPARENT; } void Screen::updateEffectiveRendition() { _effectiveRendition = _currentRendition; if ((_currentRendition & RE_REVERSE) != 0) { _effectiveForeground = _currentBackground; _effectiveBackground = _currentForeground; } else { _effectiveForeground = _currentForeground; _effectiveBackground = _currentBackground; } if ((_currentRendition & RE_BOLD) != 0) { if ((_currentRendition & RE_FAINT) == 0) { _effectiveForeground.setIntensive(); } } else { if ((_currentRendition & RE_FAINT) != 0) { _effectiveForeground.setFaint(); } } } void Screen::copyFromHistory(Character* dest, int startLine, int count) const { Q_ASSERT(startLine >= 0 && count > 0 && startLine + count <= _history->getLines()); for (int line = startLine; line < startLine + count; line++) { const int length = qMin(_columns, _history->getLineLen(line)); const int destLineOffset = (line - startLine) * _columns; _history->getCells(line, 0, length, dest + destLineOffset); for (int column = length; column < _columns; column++) { dest[destLineOffset + column] = Screen::DefaultChar; } // invert selected text if (_selBegin != -1) { for (int column = 0; column < _columns; column++) { if (isSelected(column, line)) { reverseRendition(dest[destLineOffset + column]); } } } } } void Screen::copyFromScreen(Character* dest , int startLine , int count) const { Q_ASSERT(startLine >= 0 && count > 0 && startLine + count <= _lines); for (int line = startLine; line < (startLine + count) ; line++) { int srcLineStartIndex = line * _columns; int destLineStartIndex = (line - startLine) * _columns; for (int column = 0; column < _columns; column++) { int srcIndex = srcLineStartIndex + column; int destIndex = destLineStartIndex + column; dest[destIndex] = _screenLines[srcIndex / _columns].value(srcIndex % _columns, Screen::DefaultChar); // invert selected text if (_selBegin != -1 && isSelected(column, line + _history->getLines())) { reverseRendition(dest[destIndex]); } } } } void Screen::getImage(Character* dest, int size, int startLine, int endLine) const { Q_ASSERT(startLine >= 0); Q_ASSERT(endLine >= startLine && endLine < _history->getLines() + _lines); const int mergedLines = endLine - startLine + 1; Q_ASSERT(size >= mergedLines * _columns); Q_UNUSED(size); const int linesInHistoryBuffer = qBound(0, _history->getLines() - startLine, mergedLines); const int linesInScreenBuffer = mergedLines - linesInHistoryBuffer; // copy _lines from history buffer if (linesInHistoryBuffer > 0) { copyFromHistory(dest, startLine, linesInHistoryBuffer); } // copy _lines from screen buffer if (linesInScreenBuffer > 0) { copyFromScreen(dest + linesInHistoryBuffer * _columns, startLine + linesInHistoryBuffer - _history->getLines(), linesInScreenBuffer); } // invert display when in screen mode if (getMode(MODE_Screen)) { for (int i = 0; i < mergedLines * _columns; i++) { reverseRendition(dest[i]); // for reverse display } } int visX = qMin(_cuX, _columns - 1); // mark the character at the current cursor position int cursorIndex = loc(visX, _cuY + linesInHistoryBuffer); if (getMode(MODE_Cursor) && cursorIndex < _columns * mergedLines) { dest[cursorIndex].rendition |= RE_CURSOR; } } QVector Screen::getLineProperties(int startLine , int endLine) const { Q_ASSERT(startLine >= 0); Q_ASSERT(endLine >= startLine && endLine < _history->getLines() + _lines); const int mergedLines = endLine - startLine + 1; const int linesInHistory = qBound(0, _history->getLines() - startLine, mergedLines); const int linesInScreen = mergedLines - linesInHistory; QVector result(mergedLines); int index = 0; // copy properties for _lines in history for (int line = startLine; line < startLine + linesInHistory; line++) { //TODO Support for line properties other than wrapped _lines if (_history->isWrappedLine(line)) { result[index] = static_cast(result[index] | LINE_WRAPPED); } index++; } // copy properties for _lines in screen buffer const int firstScreenLine = startLine + linesInHistory - _history->getLines(); for (int line = firstScreenLine; line < firstScreenLine + linesInScreen; line++) { result[index] = _lineProperties[line]; index++; } return result; } void Screen::reset() { // Clear screen, but preserve the current line scrollUp(0, _cuY); _cuY = 0; _currentModes[MODE_Origin] = 0; _savedModes[MODE_Origin] = 0; setMode(MODE_Wrap); saveMode(MODE_Wrap); // wrap at end of margin resetMode(MODE_Insert); saveMode(MODE_Insert); // overstroke setMode(MODE_Cursor); // cursor visible resetMode(MODE_Screen); // screen not inverse resetMode(MODE_NewLine); _topMargin = 0; _bottomMargin = _lines - 1; // Other terminal emulators reset the entire scroll history during a reset // setScroll(getScroll(), false); setDefaultRendition(); saveCursor(); } void Screen::backspace() { _cuX = qMin(_columns - 1, _cuX); // nowrap! _cuX = qMax(0, _cuX - 1); if (_screenLines[_cuY].size() < _cuX + 1) { _screenLines[_cuY].resize(_cuX + 1); } } void Screen::tab(int n) { // note that TAB is a format effector (does not write ' '); if (n == 0) { n = 1; } while ((n > 0) && (_cuX < _columns - 1)) { cursorRight(1); while ((_cuX < _columns - 1) && !_tabStops[_cuX]) { cursorRight(1); } n--; } } void Screen::backtab(int n) { // note that TAB is a format effector (does not write ' '); if (n == 0) { n = 1; } while ((n > 0) && (_cuX > 0)) { cursorLeft(1); while ((_cuX > 0) && !_tabStops[_cuX]) { cursorLeft(1); } n--; } } void Screen::clearTabStops() { for (int i = 0; i < _columns; i++) { _tabStops[i] = false; } } void Screen::changeTabStop(bool set) { if (_cuX >= _columns) { return; } _tabStops[_cuX] = set; } void Screen::initTabStops() { _tabStops.resize(_columns); // The 1st tabstop has to be one longer than the other. // i.e. the kids start counting from 0 instead of 1. // Other programs might behave correctly. Be aware. for (int i = 0; i < _columns; i++) { _tabStops[i] = (i % 8 == 0 && i != 0); } } void Screen::newLine() { if (getMode(MODE_NewLine)) { toStartOfLine(); } index(); } void Screen::checkSelection(int from, int to) { if (_selBegin == -1) { return; } const int scr_TL = loc(0, _history->getLines()); //Clear entire selection if it overlaps region [from, to] if ((_selBottomRight >= (from + scr_TL)) && (_selTopLeft <= (to + scr_TL))) { clearSelection(); } } void Screen::displayCharacter(uint c) { // Note that VT100 does wrapping BEFORE putting the character. // This has impact on the assumption of valid cursor positions. // We indicate the fact that a newline has to be triggered by // putting the cursor one right to the last column of the screen. int w = Character::width(c); if (w < 0) { // Non-printable character return; } else if (w == 0) { const QChar::Category category = QChar::category(c); if (category != QChar::Mark_NonSpacing && category != QChar::Letter_Other) { return; } // Find previous "real character" to try to combine with int charToCombineWithX = qMin(_cuX, _screenLines[_cuY].length()); int charToCombineWithY = _cuY; do { if (charToCombineWithX > 0) { charToCombineWithX--; } else if (charToCombineWithY > 0) { // Try previous line charToCombineWithY--; charToCombineWithX = _screenLines[charToCombineWithY].length() - 1; } else { // Give up return; } // Failsafe if (charToCombineWithX < 0) { return; } } while(!_screenLines[charToCombineWithY][charToCombineWithX].isRealCharacter); Character& currentChar = _screenLines[charToCombineWithY][charToCombineWithX]; if ((currentChar.rendition & RE_EXTENDED_CHAR) == 0) { const uint chars[2] = { currentChar.character, c }; currentChar.rendition |= RE_EXTENDED_CHAR; currentChar.character = ExtendedCharTable::instance.createExtendedChar(chars, 2); } else { ushort extendedCharLength; const uint* oldChars = ExtendedCharTable::instance.lookupExtendedChar(currentChar.character, extendedCharLength); Q_ASSERT(oldChars); if (((oldChars) != nullptr) && extendedCharLength < 3) { Q_ASSERT(extendedCharLength > 1); Q_ASSERT(extendedCharLength < 65535); auto chars = new uint[extendedCharLength + 1]; memcpy(chars, oldChars, sizeof(uint) * extendedCharLength); chars[extendedCharLength] = c; currentChar.character = ExtendedCharTable::instance.createExtendedChar(chars, extendedCharLength + 1); delete[] chars; } } return; } if (_cuX + w > _columns) { if (getMode(MODE_Wrap)) { _lineProperties[_cuY] = static_cast(_lineProperties[_cuY] | LINE_WRAPPED); nextLine(); } else { _cuX = qMax(_columns - w, 0); } } // ensure current line vector has enough elements if (_screenLines[_cuY].size() < _cuX + w) { _screenLines[_cuY].resize(_cuX + w); } if (getMode(MODE_Insert)) { insertChars(w); } _lastPos = loc(_cuX, _cuY); // check if selection is still valid. checkSelection(_lastPos, _lastPos); Character& currentChar = _screenLines[_cuY][_cuX]; currentChar.character = c; currentChar.foregroundColor = _effectiveForeground; currentChar.backgroundColor = _effectiveBackground; currentChar.rendition = _effectiveRendition; currentChar.isRealCharacter = true; _lastDrawnChar = c; int i = 0; const int newCursorX = _cuX + w--; while (w != 0) { i++; if (_screenLines[_cuY].size() < _cuX + i + 1) { _screenLines[_cuY].resize(_cuX + i + 1); } Character& ch = _screenLines[_cuY][_cuX + i]; ch.character = 0; ch.foregroundColor = _effectiveForeground; ch.backgroundColor = _effectiveBackground; ch.rendition = _effectiveRendition; ch.isRealCharacter = false; w--; } _cuX = newCursorX; } int Screen::scrolledLines() const { return _scrolledLines; } int Screen::droppedLines() const { return _droppedLines; } void Screen::resetDroppedLines() { _droppedLines = 0; } void Screen::resetScrolledLines() { _scrolledLines = 0; } void Screen::scrollUp(int n) { if (n == 0) { n = 1; // Default } if (_topMargin == 0) { addHistLine(); // history.history } scrollUp(_topMargin, n); } QRect Screen::lastScrolledRegion() const { return _lastScrolledRegion; } void Screen::scrollUp(int from, int n) { if (n <= 0) { return; } if (from > _bottomMargin) { return; } if (from + n > _bottomMargin) { n = _bottomMargin + 1 - from; } _scrolledLines -= n; _lastScrolledRegion = QRect(0, _topMargin, _columns - 1, (_bottomMargin - _topMargin)); //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds. moveImage(loc(0, from), loc(0, from + n), loc(_columns, _bottomMargin)); clearImage(loc(0, _bottomMargin - n + 1), loc(_columns - 1, _bottomMargin), ' '); } void Screen::scrollDown(int n) { if (n == 0) { n = 1; // Default } scrollDown(_topMargin, n); } void Screen::scrollDown(int from, int n) { _scrolledLines += n; //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds. if (n <= 0) { return; } if (from > _bottomMargin) { return; } if (from + n > _bottomMargin) { n = _bottomMargin - from; } moveImage(loc(0, from + n), loc(0, from), loc(_columns - 1, _bottomMargin - n)); clearImage(loc(0, from), loc(_columns - 1, from + n - 1), ' '); } void Screen::setCursorYX(int y, int x) { setCursorY(y); setCursorX(x); } void Screen::setCursorX(int x) { if (x == 0) { x = 1; // Default } x -= 1; // Adjust _cuX = qMax(0, qMin(_columns - 1, x)); } void Screen::setCursorY(int y) { if (y == 0) { y = 1; // Default } y -= 1; // Adjust _cuY = qMax(0, qMin(_lines - 1, y + (getMode(MODE_Origin) ? _topMargin : 0))); } void Screen::toStartOfLine() { _cuX = 0; } int Screen::getCursorX() const { return qMin(_cuX, _columns - 1); } int Screen::getCursorY() const { return _cuY; } void Screen::clearImage(int loca, int loce, char c) { const int scr_TL = loc(0, _history->getLines()); //FIXME: check positions //Clear entire selection if it overlaps region to be moved... if ((_selBottomRight > (loca + scr_TL)) && (_selTopLeft < (loce + scr_TL))) { clearSelection(); } const int topLine = loca / _columns; const int bottomLine = loce / _columns; Character clearCh(uint(c), _currentForeground, _currentBackground, DEFAULT_RENDITION, false); //if the character being used to clear the area is the same as the //default character, the affected _lines can simply be shrunk. const bool isDefaultCh = (clearCh == Screen::DefaultChar); for (int y = topLine; y <= bottomLine; y++) { _lineProperties[y] = 0; const int endCol = (y == bottomLine) ? loce % _columns : _columns - 1; const int startCol = (y == topLine) ? loca % _columns : 0; QVector& line = _screenLines[y]; if (isDefaultCh && endCol == _columns - 1) { line.resize(startCol); } else { if (line.size() < endCol + 1) { line.resize(endCol + 1); } Character* data = line.data(); for (int i = startCol; i <= endCol; i++) { data[i] = clearCh; } } } } void Screen::moveImage(int dest, int sourceBegin, int sourceEnd) { Q_ASSERT(sourceBegin <= sourceEnd); const int lines = (sourceEnd - sourceBegin) / _columns; //move screen image and line properties: //the source and destination areas of the image may overlap, //so it matters that we do the copy in the right order - //forwards if dest < sourceBegin or backwards otherwise. //(search the web for 'memmove implementation' for details) if (dest < sourceBegin) { for (int i = 0; i <= lines; i++) { _screenLines[(dest / _columns) + i ] = _screenLines[(sourceBegin / _columns) + i ]; _lineProperties[(dest / _columns) + i] = _lineProperties[(sourceBegin / _columns) + i]; } } else { for (int i = lines; i >= 0; i--) { _screenLines[(dest / _columns) + i ] = _screenLines[(sourceBegin / _columns) + i ]; _lineProperties[(dest / _columns) + i] = _lineProperties[(sourceBegin / _columns) + i]; } } if (_lastPos != -1) { const int diff = dest - sourceBegin; // Scroll by this amount _lastPos += diff; if ((_lastPos < 0) || (_lastPos >= (lines * _columns))) { _lastPos = -1; } } // Adjust selection to follow scroll. if (_selBegin != -1) { const bool beginIsTL = (_selBegin == _selTopLeft); const int diff = dest - sourceBegin; // Scroll by this amount const int scr_TL = loc(0, _history->getLines()); const int srca = sourceBegin + scr_TL; // Translate index from screen to global const int srce = sourceEnd + scr_TL; // Translate index from screen to global const int desta = srca + diff; const int deste = srce + diff; if ((_selTopLeft >= srca) && (_selTopLeft <= srce)) { _selTopLeft += diff; } else if ((_selTopLeft >= desta) && (_selTopLeft <= deste)) { _selBottomRight = -1; // Clear selection (see below) } if ((_selBottomRight >= srca) && (_selBottomRight <= srce)) { _selBottomRight += diff; } else if ((_selBottomRight >= desta) && (_selBottomRight <= deste)) { _selBottomRight = -1; // Clear selection (see below) } if (_selBottomRight < 0) { clearSelection(); } else { if (_selTopLeft < 0) { _selTopLeft = 0; } } if (beginIsTL) { _selBegin = _selTopLeft; } else { _selBegin = _selBottomRight; } } } void Screen::clearToEndOfScreen() { clearImage(loc(_cuX, _cuY), loc(_columns - 1, _lines - 1), ' '); } void Screen::clearToBeginOfScreen() { clearImage(loc(0, 0), loc(_cuX, _cuY), ' '); } void Screen::clearEntireScreen() { clearImage(loc(0, 0), loc(_columns - 1, _lines - 1), ' '); } /*! fill screen with 'E' This is to aid screen alignment */ void Screen::helpAlign() { clearImage(loc(0, 0), loc(_columns - 1, _lines - 1), 'E'); } void Screen::clearToEndOfLine() { clearImage(loc(_cuX, _cuY), loc(_columns - 1, _cuY), ' '); } void Screen::clearToBeginOfLine() { clearImage(loc(0, _cuY), loc(_cuX, _cuY), ' '); } void Screen::clearEntireLine() { clearImage(loc(0, _cuY), loc(_columns - 1, _cuY), ' '); } void Screen::setRendition(RenditionFlags rendition) { _currentRendition |= rendition; updateEffectiveRendition(); } void Screen::resetRendition(RenditionFlags rendition) { _currentRendition &= ~rendition; updateEffectiveRendition(); } void Screen::setDefaultRendition() { setForeColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR); setBackColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR); _currentRendition = DEFAULT_RENDITION; updateEffectiveRendition(); } void Screen::setForeColor(int space, int color) { _currentForeground = CharacterColor(quint8(space), color); if (_currentForeground.isValid()) { updateEffectiveRendition(); } else { setForeColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR); } } void Screen::setBackColor(int space, int color) { _currentBackground = CharacterColor(quint8(space), color); if (_currentBackground.isValid()) { updateEffectiveRendition(); } else { setBackColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR); } } void Screen::clearSelection() { _selBottomRight = -1; _selTopLeft = -1; _selBegin = -1; } void Screen::getSelectionStart(int& column , int& line) const { if (_selTopLeft != -1) { column = _selTopLeft % _columns; line = _selTopLeft / _columns; } else { column = _cuX + getHistLines(); line = _cuY + getHistLines(); } } void Screen::getSelectionEnd(int& column , int& line) const { if (_selBottomRight != -1) { column = _selBottomRight % _columns; line = _selBottomRight / _columns; } else { column = _cuX + getHistLines(); line = _cuY + getHistLines(); } } void Screen::setSelectionStart(const int x, const int y, const bool blockSelectionMode) { _selBegin = loc(x, y); /* FIXME, HACK to correct for x too far to the right... */ if (x == _columns) { _selBegin--; } _selBottomRight = _selBegin; _selTopLeft = _selBegin; _blockSelectionMode = blockSelectionMode; } void Screen::setSelectionEnd(const int x, const int y) { if (_selBegin == -1) { return; } int endPos = loc(x, y); if (endPos < _selBegin) { _selTopLeft = endPos; _selBottomRight = _selBegin; } else { /* FIXME, HACK to correct for x too far to the right... */ if (x == _columns) { endPos--; } _selTopLeft = _selBegin; _selBottomRight = endPos; } // Normalize the selection in column mode if (_blockSelectionMode) { const int topRow = _selTopLeft / _columns; const int topColumn = _selTopLeft % _columns; const int bottomRow = _selBottomRight / _columns; const int bottomColumn = _selBottomRight % _columns; _selTopLeft = loc(qMin(topColumn, bottomColumn), topRow); _selBottomRight = loc(qMax(topColumn, bottomColumn), bottomRow); } } bool Screen::isSelected(const int x, const int y) const { bool columnInSelection = true; if (_blockSelectionMode) { columnInSelection = x >= (_selTopLeft % _columns) && x <= (_selBottomRight % _columns); } const int pos = loc(x, y); return pos >= _selTopLeft && pos <= _selBottomRight && columnInSelection; } QString Screen::selectedText(const DecodingOptions options) const { if (!isSelectionValid()) { return QString(); } return text(_selTopLeft, _selBottomRight, options); } QString Screen::text(int startIndex, int endIndex, const DecodingOptions options) const { QString result; QTextStream stream(&result, QIODevice::ReadWrite); HTMLDecoder htmlDecoder; PlainTextDecoder plainTextDecoder; TerminalCharacterDecoder *decoder; if((options & ConvertToHtml) != 0u) { decoder = &htmlDecoder; } else { decoder = &plainTextDecoder; } decoder->begin(&stream); writeToStream(decoder, startIndex, endIndex, options); decoder->end(); return result; } bool Screen::isSelectionValid() const { return _selTopLeft >= 0 && _selBottomRight >= 0; } void Screen::writeSelectionToStream(TerminalCharacterDecoder* decoder , const DecodingOptions options) const { if (!isSelectionValid()) { return; } writeToStream(decoder, _selTopLeft, _selBottomRight, options); } void Screen::writeToStream(TerminalCharacterDecoder* decoder, int startIndex, int endIndex, const DecodingOptions options) const { const int top = startIndex / _columns; const int left = startIndex % _columns; const int bottom = endIndex / _columns; const int right = endIndex % _columns; Q_ASSERT(top >= 0 && left >= 0 && bottom >= 0 && right >= 0); for (int y = top; y <= bottom; y++) { int start = 0; if (y == top || _blockSelectionMode) { start = left; } int count = -1; if (y == bottom || _blockSelectionMode) { count = right - start + 1; } const bool appendNewLine = (y != bottom); int copied = copyLineToStream(y, start, count, decoder, appendNewLine, options); // if the selection goes beyond the end of the last line then // append a new line character. // // this makes it possible to 'select' a trailing new line character after // the text on a line. if (y == bottom && copied < count && !options.testFlag(TrimTrailingWhitespace)) { Character newLineChar('\n'); decoder->decodeLine(&newLineChar, 1, 0); } } } int Screen::copyLineToStream(int line , int start, int count, TerminalCharacterDecoder* decoder, bool appendNewLine, const DecodingOptions options) const { //buffer to hold characters for decoding //the buffer is static to avoid initializing every //element on each call to copyLineToStream //(which is unnecessary since all elements will be overwritten anyway) static const int MAX_CHARS = 1024; static Character characterBuffer[MAX_CHARS]; Q_ASSERT(count < MAX_CHARS); LineProperty currentLineProperties = 0; //determine if the line is in the history buffer or the screen image if (line < _history->getLines()) { const int lineLength = _history->getLineLen(line); // ensure that start position is before end of line start = qMin(start, qMax(0, lineLength - 1)); // retrieve line from history buffer. It is assumed // that the history buffer does not store trailing white space // at the end of the line, so it does not need to be trimmed here if (count == -1) { count = lineLength - start; } else { count = qMin(start + count, lineLength) - start; } // safety checks Q_ASSERT(start >= 0); Q_ASSERT(count >= 0); Q_ASSERT((start + count) <= _history->getLineLen(line)); _history->getCells(line, start, count, characterBuffer); if (_history->isWrappedLine(line)) { currentLineProperties |= LINE_WRAPPED; } } else { if (count == -1) { count = _columns - start; } Q_ASSERT(count >= 0); int screenLine = line - _history->getLines(); Q_ASSERT(screenLine <= _screenLinesSize); screenLine = qMin(screenLine, _screenLinesSize); Character* data = _screenLines[screenLine].data(); int length = _screenLines[screenLine].count(); // Don't remove end spaces in lines that wrap if (options.testFlag(TrimTrailingWhitespace) && ((_lineProperties[screenLine] & LINE_WRAPPED) == 0)) { // ignore trailing white space at the end of the line for (int i = length-1; i >= 0; i--) { if (QChar(data[i].character).isSpace()) { length--; } else { break; } } } //retrieve line from screen image for (int i = start; i < qMin(start + count, length); i++) { characterBuffer[i - start] = data[i]; } // count cannot be any greater than length count = qBound(0, count, length - start); Q_ASSERT(screenLine < _lineProperties.count()); currentLineProperties |= _lineProperties[screenLine]; } if (appendNewLine && (count + 1 < MAX_CHARS)) { if ((currentLineProperties & LINE_WRAPPED) != 0) { // do nothing extra when this line is wrapped. } else { // When users ask not to preserve the linebreaks, they usually mean: // `treat LINEBREAK as SPACE, thus joining multiple _lines into // single line in the same way as 'J' does in VIM.` characterBuffer[count] = options.testFlag(PreserveLineBreaks) ? Character('\n') : Character(' '); count++; } } if ((options & TrimLeadingWhitespace) != 0u) { int spacesCount = 0; for (spacesCount = 0; spacesCount < count; spacesCount++) { - if (!QChar(characterBuffer[spacesCount].character).isSpace()) { + if (QChar::category(characterBuffer[spacesCount].character) != QChar::Category::Separator_Space) { break; } } if (spacesCount >= count) { return 0; } for (int i=0; i < count - spacesCount; i++) { characterBuffer[i] = characterBuffer[i + spacesCount]; } count -= spacesCount; } //decode line and write to text stream decoder->decodeLine(characterBuffer, count, currentLineProperties); return count; } void Screen::writeLinesToStream(TerminalCharacterDecoder* decoder, int fromLine, int toLine) const { writeToStream(decoder, loc(0, fromLine), loc(_columns - 1, toLine), PreserveLineBreaks); } void Screen::addHistLine() { // add line to history buffer // we have to take care about scrolling, too... if (hasScroll()) { const int oldHistLines = _history->getLines(); _history->addCellsVector(_screenLines[0]); _history->addLine((_lineProperties[0] & LINE_WRAPPED) != 0); const int newHistLines = _history->getLines(); const bool beginIsTL = (_selBegin == _selTopLeft); // If the history is full, increment the count // of dropped _lines if (newHistLines == oldHistLines) { _droppedLines++; } // Adjust selection for the new point of reference if (newHistLines > oldHistLines) { if (_selBegin != -1) { _selTopLeft += _columns; _selBottomRight += _columns; } } if (_selBegin != -1) { // Scroll selection in history up const int top_BR = loc(0, 1 + newHistLines); if (_selTopLeft < top_BR) { _selTopLeft -= _columns; } if (_selBottomRight < top_BR) { _selBottomRight -= _columns; } if (_selBottomRight < 0) { clearSelection(); } else { if (_selTopLeft < 0) { _selTopLeft = 0; } } if (beginIsTL) { _selBegin = _selTopLeft; } else { _selBegin = _selBottomRight; } } } } int Screen::getHistLines() const { return _history->getLines(); } void Screen::setScroll(const HistoryType& t , bool copyPreviousScroll) { clearSelection(); if (copyPreviousScroll) { _history = t.scroll(_history); } else { HistoryScroll* oldScroll = _history; _history = t.scroll(nullptr); delete oldScroll; } } bool Screen::hasScroll() const { return _history->hasScroll(); } const HistoryType& Screen::getScroll() const { return _history->getType(); } void Screen::setLineProperty(LineProperty property , bool enable) { if (enable) { _lineProperties[_cuY] = static_cast(_lineProperties[_cuY] | property); } else { _lineProperties[_cuY] = static_cast(_lineProperties[_cuY] & ~property); } } void Screen::fillWithDefaultChar(Character* dest, int count) { for (int i = 0; i < count; i++) { dest[i] = Screen::DefaultChar; } }