You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1560 lines
41 KiB

/*
This file is part of Konsole, an X terminal.
Copyright 2007-2008 by Robert Knight <robert.knight@gmail.com>
Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
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 <QTextStream>
// 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<LineProperty, 64>()),
_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::cursorNextLine(int n)
//=CNL
{
if (n == 0) {
n = 1; // Default
}
_cuX = 0;
while (n > 0) {
if (_cuY < _lines - 1) {
_cuY += 1;
}
n--;
}
}
void Screen::cursorPreviousLine(int n)
//=CPL
{
if (n == 0) {
n = 1; // Default
}
_cuX = 0;
while (n > 0) {
if (_cuY > 0) {
_cuY -= 1;
}
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("<<top<<","<<bot<<") : bad range.";
return; // Default error action: ignore
}
_topMargin = top;
_bottomMargin = bot;
_cuX = 0;
_cuY = getMode(MODE_Origin) ? top : 0;
}
int Screen::topMargin() const
{
return _topMargin;
}
int Screen::bottomMargin() const
{
return _bottomMargin;
}
void Screen::index()
//=IND
{
if (_cuY == _bottomMargin) {
scrollUp(1);
} else if (_cuY < _lines - 1) {
_cuY += 1;
}
}
void Screen::reverseIndex()
//=RI
{
if (_cuY == _topMargin) {
scrollDown(_topMargin, 1);
} else if (_cuY > 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<LineProperty> 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<LineProperty> 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<LineProperty>(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); // redundant due to above check
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<LineProperty>(_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<Character>& 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;
}
bool Screen::hasSelection() const
{
return _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::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::getLineLength(const int line) const
{
// determine if the line is in the history buffer or the screen image
const bool isInHistoryBuffer = line < _history->getLines();
if (isInHistoryBuffer) {
return _history->getLineLen(line);
}
return _columns;
}
Character *Screen::getCharacterBuffer(const int size)
{
// 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 QVector<Character> characterBuffer(MAX_CHARS);
if (characterBuffer.count() < size) {
characterBuffer.resize(size);
}
return characterBuffer.data();
}
int Screen::copyLineToStream(int line ,
int start,
int count,
TerminalCharacterDecoder* decoder,
bool appendNewLine,
const DecodingOptions options) const
{
const int lineLength = getLineLength(line);
// ensure that this method, can append space or 'eol' character to
// the selection
Character *characterBuffer = getCharacterBuffer((count > -1 ? count : lineLength - start) + 1);
LineProperty currentLineProperties = 0;
// determine if the line is in the history buffer or the screen image
if (line < _history->getLines()) {
// 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 = lineLength - 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) {
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::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<LineProperty>(_lineProperties[_cuY] | property);
} else {
_lineProperties[_cuY] = static_cast<LineProperty>(_lineProperties[_cuY] & ~property);
}
}
void Screen::fillWithDefaultChar(Character* dest, int count)
{
for (int i = 0; i < count; i++) {
dest[i] = Screen::DefaultChar;
}
}