File indexing completed on 2024-05-19 05:28:18

0001 /*
0002    This file is part of Konsole, an X terminal.
0003 
0004    SPDX-FileCopyrightText: 2007-2008 Robert Knight <robert.knight@gmail.com>
0005    SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
0006 
0007    SPDX-License-Identifier: GPL-2.0-or-later
0008 
0009    This program is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012    GNU General Public License for more details.
0013 
0014    You should have received a copy of the GNU General Public License
0015    along with this program; if not, write to the Free Software
0016    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0017    02110-1301  USA.
0018    */
0019 
0020 // Own
0021 #include "Screen.h"
0022 
0023 // Standard
0024 #include <cctype>
0025 #include <cstdio>
0026 #include <cstdlib>
0027 #include <cstring>
0028 #include <unistd.h>
0029 
0030 // Qt
0031 #include <QDate>
0032 #include <QTextStream>
0033 
0034 // KDE
0035 // #include <kdebug.h>
0036 
0037 // Konsole
0038 #include "TerminalCharacterDecoder.h"
0039 #include "konsole_wcwidth.h"
0040 
0041 using namespace Konsole;
0042 
0043 // FIXME: this is emulation specific. Use false for xterm, true for ANSI.
0044 // FIXME: see if we can get this from terminfo.
0045 #define BS_CLEARS false
0046 
0047 // Macro to convert x,y position on screen to position within an image.
0048 //
0049 // Originally the image was stored as one large contiguous block of
0050 // memory, so a position within the image could be represented as an
0051 // offset from the beginning of the block.  For efficiency reasons this
0052 // is no longer the case.
0053 // Many internal parts of this class still use this representation for parameters and so on,
0054 // notably moveImage() and clearImage().
0055 // This macro converts from an X,Y position into an image offset.
0056 #ifndef loc
0057 #define loc(X, Y) ((Y)*columns + (X))
0058 #endif
0059 
0060 Character Screen::defaultChar =
0061     Character(' ', CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR), CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR), DEFAULT_RENDITION);
0062 
0063 // #define REVERSE_WRAPPED_LINES  // for wrapped line debug
0064 
0065 Screen::Screen(int l, int c)
0066     : lines(l)
0067     , columns(c)
0068     , screenLines(lines + 1)
0069     , _scrolledLines(0)
0070     , _droppedLines(0)
0071     , history(std::make_unique<HistoryScrollNone>())
0072     , cuX(0)
0073     , cuY(0)
0074     , currentRendition(0)
0075     , _topMargin(0)
0076     , _bottomMargin(0)
0077     , selBegin(0)
0078     , selTopLeft(0)
0079     , selBottomRight(0)
0080     , blockSelectionMode(false)
0081     , effectiveForeground(CharacterColor())
0082     , effectiveBackground(CharacterColor())
0083     , effectiveRendition(0)
0084     , lastPos(-1)
0085 {
0086     lineProperties.resize(lines + 1);
0087     for (int i = 0; i < lines + 1; i++)
0088         lineProperties[i] = LINE_DEFAULT;
0089 
0090     initTabStops();
0091     clearSelection();
0092     reset();
0093 }
0094 
0095 /*! Destructor
0096  */
0097 
0098 Screen::~Screen()
0099 {
0100 }
0101 
0102 void Screen::cursorUp(int n)
0103 //=CUU
0104 {
0105     if (n == 0)
0106         n = 1; // Default
0107     int stop = cuY < _topMargin ? 0 : _topMargin;
0108     cuX = qMin(columns - 1, cuX); // nowrap!
0109     cuY = qMax(stop, cuY - n);
0110 }
0111 
0112 void Screen::cursorDown(int n)
0113 //=CUD
0114 {
0115     if (n == 0)
0116         n = 1; // Default
0117     int stop = cuY > _bottomMargin ? lines - 1 : _bottomMargin;
0118     cuX = qMin(columns - 1, cuX); // nowrap!
0119     cuY = qMin(stop, cuY + n);
0120 }
0121 
0122 void Screen::cursorLeft(int n)
0123 //=CUB
0124 {
0125     if (n == 0)
0126         n = 1; // Default
0127     cuX = qMin(columns - 1, cuX); // nowrap!
0128     cuX = qMax(0, cuX - n);
0129 }
0130 
0131 void Screen::cursorRight(int n)
0132 //=CUF
0133 {
0134     if (n == 0)
0135         n = 1; // Default
0136     cuX = qMin(columns - 1, cuX + n);
0137 }
0138 
0139 void Screen::setMargins(int top, int bot)
0140 //=STBM
0141 {
0142     if (top == 0)
0143         top = 1; // Default
0144     if (bot == 0)
0145         bot = lines; // Default
0146     top = top - 1; // Adjust to internal lineno
0147     bot = bot - 1; // Adjust to internal lineno
0148     if (!(0 <= top && top < bot && bot < lines)) { // Debug()<<" setRegion("<<top<<","<<bot<<") : bad range.";
0149         return; // Default error action: ignore
0150     }
0151     _topMargin = top;
0152     _bottomMargin = bot;
0153     cuX = 0;
0154     cuY = getMode(MODE_Origin) ? top : 0;
0155 }
0156 
0157 int Screen::topMargin() const
0158 {
0159     return _topMargin;
0160 }
0161 int Screen::bottomMargin() const
0162 {
0163     return _bottomMargin;
0164 }
0165 
0166 void Screen::index()
0167 //=IND
0168 {
0169     if (cuY == _bottomMargin)
0170         scrollUp(1);
0171     else if (cuY < lines - 1)
0172         cuY += 1;
0173 }
0174 
0175 void Screen::reverseIndex()
0176 //=RI
0177 {
0178     if (cuY == _topMargin)
0179         scrollDown(_topMargin, 1);
0180     else if (cuY > 0)
0181         cuY -= 1;
0182 }
0183 
0184 void Screen::nextLine()
0185 //=NEL
0186 {
0187     toStartOfLine();
0188     index();
0189 }
0190 
0191 void Screen::eraseChars(int n)
0192 {
0193     if (n == 0)
0194         n = 1; // Default
0195     int p = qMax(0, qMin(cuX + n - 1, columns - 1));
0196     clearImage(loc(cuX, cuY), loc(p, cuY), ' ');
0197 }
0198 
0199 void Screen::deleteChars(int n)
0200 {
0201     Q_ASSERT(n >= 0);
0202 
0203     // always delete at least one char
0204     if (n == 0)
0205         n = 1;
0206 
0207     // if cursor is beyond the end of the line there is nothing to do
0208     if (cuX >= screenLines[cuY].size())
0209         return;
0210 
0211     if (cuX + n > screenLines[cuY].size())
0212         n = screenLines[cuY].size() - cuX;
0213 
0214     Q_ASSERT(n >= 0);
0215     Q_ASSERT(cuX + n <= screenLines[cuY].size());
0216 
0217     screenLines[cuY].remove(cuX, n);
0218 }
0219 
0220 void Screen::insertChars(int n)
0221 {
0222     if (n == 0)
0223         n = 1; // Default
0224 
0225     if (screenLines[cuY].size() < cuX)
0226         screenLines[cuY].resize(cuX);
0227 
0228     screenLines[cuY].insert(cuX, n, ' ');
0229 
0230     if (screenLines[cuY].size() > columns)
0231         screenLines[cuY].resize(columns);
0232 }
0233 
0234 void Screen::repeatChars(int count)
0235 //=REP
0236 {
0237     if (count == 0) {
0238         count = 1;
0239     }
0240     /**
0241      * From ECMA-48 version 5, section 8.3.103
0242      * If the character preceding REP is a control function or part of a
0243      * control function, the effect of REP is not defined by this Standard.
0244      *
0245      * So, a "normal" program should always use REP immediately after a visible
0246      * character (those other than escape sequences). So, lastDrawnChar can be
0247      * safely used.
0248      */
0249     for (int i = 0; i < count; i++) {
0250         displayCharacter(lastDrawnChar);
0251     }
0252 }
0253 
0254 void Screen::deleteLines(int n)
0255 {
0256     if (n == 0)
0257         n = 1; // Default
0258     scrollUp(cuY, n);
0259 }
0260 
0261 void Screen::insertLines(int n)
0262 {
0263     if (n == 0)
0264         n = 1; // Default
0265     scrollDown(cuY, n);
0266 }
0267 
0268 void Screen::setMode(int m)
0269 {
0270     currentModes[m] = true;
0271     switch (m) {
0272     case MODE_Origin:
0273         cuX = 0;
0274         cuY = _topMargin;
0275         break; // FIXME: home
0276     }
0277 }
0278 
0279 void Screen::resetMode(int m)
0280 {
0281     currentModes[m] = false;
0282     switch (m) {
0283     case MODE_Origin:
0284         cuX = 0;
0285         cuY = 0;
0286         break; // FIXME: home
0287     }
0288 }
0289 
0290 void Screen::saveMode(int m)
0291 {
0292     savedModes[m] = currentModes[m];
0293 }
0294 
0295 void Screen::restoreMode(int m)
0296 {
0297     currentModes[m] = savedModes[m];
0298 }
0299 
0300 bool Screen::getMode(int m) const
0301 {
0302     return currentModes[m];
0303 }
0304 
0305 void Screen::saveCursor()
0306 {
0307     savedState.cursorColumn = cuX;
0308     savedState.cursorLine = cuY;
0309     savedState.rendition = currentRendition;
0310     savedState.foreground = currentForeground;
0311     savedState.background = currentBackground;
0312 }
0313 
0314 void Screen::restoreCursor()
0315 {
0316     cuX = qMin(savedState.cursorColumn, columns - 1);
0317     cuY = qMin(savedState.cursorLine, lines - 1);
0318     currentRendition = savedState.rendition;
0319     currentForeground = savedState.foreground;
0320     currentBackground = savedState.background;
0321     updateEffectiveRendition();
0322 }
0323 
0324 void Screen::resizeImage(int new_lines, int new_columns)
0325 {
0326     if ((new_lines == lines) && (new_columns == columns))
0327         return;
0328 
0329     if (cuY > new_lines - 1) { // attempt to preserve focus and lines
0330         _bottomMargin = lines - 1; // FIXME: margin lost
0331         for (int i = 0; i < cuY - (new_lines - 1); i++) {
0332             addHistLine();
0333             scrollUp(0, 1);
0334         }
0335     }
0336 
0337     // create new screen lines and copy from old to new
0338 
0339     auto newScreenLines = QVector<ImageLine>(new_lines + 1);
0340     for (int i = 0; i < qMin(lines, new_lines + 1); i++)
0341         newScreenLines[i] = screenLines[i];
0342     for (int i = lines; (i > 0) && (i < new_lines + 1); i++)
0343         newScreenLines[i].resize(new_columns);
0344 
0345     lineProperties.resize(new_lines + 1);
0346     for (int i = lines; (i > 0) && (i < new_lines + 1); i++)
0347         lineProperties[i] = LINE_DEFAULT;
0348 
0349     clearSelection();
0350 
0351     screenLines.clear();
0352     screenLines = std::move(newScreenLines);
0353 
0354     lines = new_lines;
0355     columns = new_columns;
0356     cuX = qMin(cuX, columns - 1);
0357     cuY = qMin(cuY, lines - 1);
0358 
0359     // FIXME: try to keep values, evtl.
0360     _topMargin = 0;
0361     _bottomMargin = lines - 1;
0362     initTabStops();
0363     clearSelection();
0364 }
0365 
0366 void Screen::setDefaultMargins()
0367 {
0368     _topMargin = 0;
0369     _bottomMargin = lines - 1;
0370 }
0371 
0372 /*
0373    Clarifying rendition here and in the display.
0374 
0375    currently, the display's color table is
0376    0       1       2 .. 9    10 .. 17
0377    dft_fg, dft_bg, dim 0..7, intensive 0..7
0378 
0379    currentForeground, currentBackground contain values 0..8;
0380    - 0    = default color
0381    - 1..8 = ansi specified color
0382 
0383    re_fg, re_bg contain values 0..17
0384    due to the TerminalDisplay's color table
0385 
0386    rendition attributes are
0387 
0388    attr           widget screen
0389    -------------- ------ ------
0390    RE_UNDERLINE     XX     XX    affects foreground only
0391    RE_BLINK         XX     XX    affects foreground only
0392    RE_BOLD          XX     XX    affects foreground only
0393    RE_REVERSE       --     XX
0394    RE_TRANSPARENT   XX     --    affects background only
0395    RE_INTENSIVE     XX     --    affects foreground only
0396 
0397    Note that RE_BOLD is used in both widget
0398    and screen rendition. Since xterm/vt102
0399    is to poor to distinguish between bold
0400    (which is a font attribute) and intensive
0401    (which is a color attribute), we translate
0402    this and RE_BOLD in falls eventually appart
0403    into RE_BOLD and RE_INTENSIVE.
0404    */
0405 
0406 void Screen::reverseRendition(Character &p) const
0407 {
0408     CharacterColor f = p.foregroundColor;
0409     CharacterColor b = p.backgroundColor;
0410 
0411     p.foregroundColor = b;
0412     p.backgroundColor = f; // p->r &= ~RE_TRANSPARENT;
0413 }
0414 
0415 void Screen::updateEffectiveRendition()
0416 {
0417     effectiveRendition = currentRendition;
0418     if (currentRendition & RE_REVERSE) {
0419         effectiveForeground = currentBackground;
0420         effectiveBackground = currentForeground;
0421     } else {
0422         effectiveForeground = currentForeground;
0423         effectiveBackground = currentBackground;
0424     }
0425 
0426     if (currentRendition & RE_BOLD)
0427         effectiveForeground.setIntensive();
0428 }
0429 
0430 void Screen::copyFromHistory(std::span<Character> dest, int startLine, int count) const
0431 {
0432     Q_ASSERT(startLine >= 0 && count > 0 && startLine + count <= history->getLines());
0433 
0434     for (int line = startLine; line < startLine + count; line++) {
0435         const int length = qMin(columns, history->getLineLen(line));
0436         const int destLineOffset = (line - startLine) * columns;
0437 
0438         history->getCells(line, 0, length, dest.subspan(destLineOffset));
0439 
0440         for (int column = length; column < columns; column++)
0441             dest[destLineOffset + column] = defaultChar;
0442 
0443         // invert selected text
0444         if (selBegin != -1) {
0445             for (int column = 0; column < columns; column++) {
0446                 if (isSelected(column, line)) {
0447                     reverseRendition(dest[destLineOffset + column]);
0448                 }
0449             }
0450         }
0451     }
0452 }
0453 
0454 void Screen::copyFromScreen(std::span<Character> dest, int startLine, int count) const
0455 {
0456     Q_ASSERT(startLine >= 0 && count > 0 && startLine + count <= lines);
0457 
0458     for (int line = startLine; line < (startLine + count); line++) {
0459         int srcLineStartIndex = line * columns;
0460         int destLineStartIndex = (line - startLine) * columns;
0461 
0462         for (int column = 0; column < columns; column++) {
0463             int srcIndex = srcLineStartIndex + column;
0464             int destIndex = destLineStartIndex + column;
0465 
0466             dest[destIndex] = screenLines[srcIndex / columns].value(srcIndex % columns, defaultChar);
0467 
0468             // invert selected text
0469             if (selBegin != -1 && isSelected(column, line + history->getLines()))
0470                 reverseRendition(dest[destIndex]);
0471         }
0472     }
0473 }
0474 
0475 void Screen::getImage(std::span<Character> dest, int size, int startLine, int endLine) const
0476 {
0477     Q_ASSERT(startLine >= 0);
0478     Q_ASSERT(endLine >= startLine && endLine < history->getLines() + lines);
0479 
0480     const int mergedLines = endLine - startLine + 1;
0481 
0482     Q_ASSERT(size >= mergedLines * columns);
0483     Q_UNUSED(size);
0484 
0485     const int linesInHistoryBuffer = qBound(0, history->getLines() - startLine, mergedLines);
0486     const int linesInScreenBuffer = mergedLines - linesInHistoryBuffer;
0487 
0488     // copy lines from history buffer
0489     if (linesInHistoryBuffer > 0)
0490         copyFromHistory(dest, startLine, linesInHistoryBuffer);
0491 
0492     // copy lines from screen buffer
0493     if (linesInScreenBuffer > 0)
0494         copyFromScreen(dest.subspan(linesInHistoryBuffer * columns), startLine + linesInHistoryBuffer - history->getLines(), linesInScreenBuffer);
0495 
0496     // invert display when in screen mode
0497     if (getMode(MODE_Screen)) {
0498         for (int i = 0; i < mergedLines * columns; i++)
0499             reverseRendition(dest[i]); // for reverse display
0500     }
0501 
0502     // mark the character at the current cursor position
0503     int cursorIndex = loc(cuX, cuY + linesInHistoryBuffer);
0504     if (getMode(MODE_Cursor) && cursorIndex < columns * mergedLines)
0505         dest[cursorIndex].rendition |= RE_CURSOR;
0506 }
0507 
0508 QVector<LineProperty> Screen::getLineProperties(int startLine, int endLine) const
0509 {
0510     Q_ASSERT(startLine >= 0);
0511     Q_ASSERT(endLine >= startLine && endLine < history->getLines() + lines);
0512 
0513     const int mergedLines = endLine - startLine + 1;
0514     const int linesInHistory = qBound(0, history->getLines() - startLine, mergedLines);
0515     const int linesInScreen = mergedLines - linesInHistory;
0516 
0517     QVector<LineProperty> result(mergedLines);
0518     int index = 0;
0519 
0520     // copy properties for lines in history
0521     for (int line = startLine; line < startLine + linesInHistory; line++) {
0522         // TODO Support for line properties other than wrapped lines
0523         if (history->isWrappedLine(line)) {
0524             result[index] = (LineProperty)(result[index] | LINE_WRAPPED);
0525         }
0526         index++;
0527     }
0528 
0529     // copy properties for lines in screen buffer
0530     const int firstScreenLine = startLine + linesInHistory - history->getLines();
0531     for (int line = firstScreenLine; line < firstScreenLine + linesInScreen; line++) {
0532         result[index] = lineProperties[line];
0533         index++;
0534     }
0535 
0536     return result;
0537 }
0538 
0539 void Screen::reset(bool clearScreen)
0540 {
0541     setMode(MODE_Wrap);
0542     saveMode(MODE_Wrap); // wrap at end of margin
0543     resetMode(MODE_Origin);
0544     saveMode(MODE_Origin); // position refere to [1,1]
0545     resetMode(MODE_Insert);
0546     saveMode(MODE_Insert); // overstroke
0547     setMode(MODE_Cursor); // cursor visible
0548     resetMode(MODE_Screen); // screen not inverse
0549     resetMode(MODE_NewLine);
0550 
0551     _topMargin = 0;
0552     _bottomMargin = lines - 1;
0553 
0554     setDefaultRendition();
0555     saveCursor();
0556 
0557     if (clearScreen)
0558         clear();
0559 }
0560 
0561 void Screen::clear()
0562 {
0563     clearEntireScreen();
0564     home();
0565 }
0566 
0567 void Screen::backspace()
0568 {
0569     cuX = qMin(columns - 1, cuX); // nowrap!
0570     cuX = qMax(0, cuX - 1);
0571 
0572     if (screenLines[cuY].size() < cuX + 1)
0573         screenLines[cuY].resize(cuX + 1);
0574 
0575     if (BS_CLEARS)
0576         screenLines[cuY][cuX].character = u' ';
0577 }
0578 
0579 void Screen::tab(int n)
0580 {
0581     // note that TAB is a format effector (does not write ' ');
0582     if (n == 0)
0583         n = 1;
0584     while ((n > 0) && (cuX < columns - 1)) {
0585         cursorRight(1);
0586         while ((cuX < columns - 1) && !tabStops[cuX])
0587             cursorRight(1);
0588         n--;
0589     }
0590 }
0591 
0592 void Screen::backtab(int n)
0593 {
0594     // note that TAB is a format effector (does not write ' ');
0595     if (n == 0)
0596         n = 1;
0597     while ((n > 0) && (cuX > 0)) {
0598         cursorLeft(1);
0599         while ((cuX > 0) && !tabStops[cuX])
0600             cursorLeft(1);
0601         n--;
0602     }
0603 }
0604 
0605 void Screen::clearTabStops()
0606 {
0607     for (int i = 0; i < columns; i++)
0608         tabStops[i] = false;
0609 }
0610 
0611 void Screen::changeTabStop(bool set)
0612 {
0613     if (cuX >= columns)
0614         return;
0615     tabStops[cuX] = set;
0616 }
0617 
0618 void Screen::initTabStops()
0619 {
0620     tabStops.resize(columns);
0621 
0622     // Arrg! The 1st tabstop has to be one longer than the other.
0623     // i.e. the kids start counting from 0 instead of 1.
0624     // Other programs might behave correctly. Be aware.
0625     for (int i = 0; i < columns; i++)
0626         tabStops[i] = (i % 8 == 0 && i != 0);
0627 }
0628 
0629 void Screen::newLine()
0630 {
0631     if (getMode(MODE_NewLine))
0632         toStartOfLine();
0633     index();
0634 }
0635 
0636 void Screen::checkSelection(int from, int to)
0637 {
0638     if (selBegin == -1)
0639         return;
0640     int scr_TL = loc(0, history->getLines());
0641     // Clear entire selection if it overlaps region [from, to]
0642     if ((selBottomRight >= (from + scr_TL)) && (selTopLeft <= (to + scr_TL)))
0643         clearSelection();
0644 }
0645 
0646 void Screen::displayCharacter(QChar c)
0647 {
0648     // Note that VT100 does wrapping BEFORE putting the character.
0649     // This has impact on the assumption of valid cursor positions.
0650     // We indicate the fact that a newline has to be triggered by
0651     // putting the cursor one right to the last column of the screen.
0652 
0653     int w = konsole_wcwidth(c);
0654     if (w <= 0)
0655         return;
0656 
0657     if (cuX + w > columns) {
0658         if (getMode(MODE_Wrap)) {
0659             lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | LINE_WRAPPED);
0660             nextLine();
0661         } else
0662             cuX = columns - w;
0663     }
0664 
0665     // ensure current line vector has enough elements
0666     int size = screenLines[cuY].size();
0667     if (size < cuX + w) {
0668         screenLines[cuY].resize(cuX + w);
0669     }
0670 
0671     if (getMode(MODE_Insert))
0672         insertChars(w);
0673 
0674     lastPos = loc(cuX, cuY);
0675 
0676     // check if selection is still valid.
0677     checkSelection(lastPos, lastPos);
0678 
0679     Character &currentChar = screenLines[cuY][cuX];
0680 
0681     currentChar.character = c;
0682     currentChar.foregroundColor = effectiveForeground;
0683     currentChar.backgroundColor = effectiveBackground;
0684     currentChar.rendition = effectiveRendition;
0685 
0686     lastDrawnChar = c;
0687 
0688     int i = 0;
0689     int newCursorX = cuX + w--;
0690     while (w) {
0691         i++;
0692 
0693         if (screenLines[cuY].size() < cuX + i + 1)
0694             screenLines[cuY].resize(cuX + i + 1);
0695 
0696         Character &ch = screenLines[cuY][cuX + i];
0697         ch.character = QChar(0);
0698         ch.foregroundColor = effectiveForeground;
0699         ch.backgroundColor = effectiveBackground;
0700         ch.rendition = effectiveRendition;
0701 
0702         w--;
0703     }
0704     cuX = newCursorX;
0705 }
0706 
0707 void Screen::compose(const QString & /*compose*/)
0708 {
0709     Q_ASSERT(0 /*Not implemented yet*/);
0710 
0711     /*  if (lastPos == -1)
0712         return;
0713 
0714         QChar c(image[lastPos].character);
0715         compose.prepend(c);
0716     //compose.compose(); ### FIXME!
0717     image[lastPos].character = compose[0].unicode();*/
0718 }
0719 
0720 int Screen::scrolledLines() const
0721 {
0722     return _scrolledLines;
0723 }
0724 int Screen::droppedLines() const
0725 {
0726     return _droppedLines;
0727 }
0728 void Screen::resetDroppedLines()
0729 {
0730     _droppedLines = 0;
0731 }
0732 void Screen::resetScrolledLines()
0733 {
0734     _scrolledLines = 0;
0735 }
0736 
0737 void Screen::scrollUp(int n)
0738 {
0739     if (n == 0)
0740         n = 1; // Default
0741     if (_topMargin == 0)
0742         addHistLine(); // history.history
0743     scrollUp(_topMargin, n);
0744 }
0745 
0746 QRect Screen::lastScrolledRegion() const
0747 {
0748     return _lastScrolledRegion;
0749 }
0750 
0751 void Screen::scrollUp(int from, int n)
0752 {
0753     if (n <= 0)
0754         return;
0755     if (from > _bottomMargin)
0756         return;
0757     if (from + n > _bottomMargin)
0758         n = _bottomMargin + 1 - from;
0759 
0760     _scrolledLines -= n;
0761     _lastScrolledRegion = QRect(0, _topMargin, columns - 1, (_bottomMargin - _topMargin));
0762 
0763     // FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds.
0764     moveImage(loc(0, from), loc(0, from + n), loc(columns, _bottomMargin));
0765     clearImage(loc(0, _bottomMargin - n + 1), loc(columns - 1, _bottomMargin), ' ');
0766 }
0767 
0768 void Screen::scrollDown(int n)
0769 {
0770     if (n == 0)
0771         n = 1; // Default
0772     scrollDown(_topMargin, n);
0773 }
0774 
0775 void Screen::scrollDown(int from, int n)
0776 {
0777     _scrolledLines += n;
0778 
0779     // FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds.
0780     if (n <= 0)
0781         return;
0782     if (from > _bottomMargin)
0783         return;
0784     if (from + n > _bottomMargin)
0785         n = _bottomMargin - from;
0786     moveImage(loc(0, from + n), loc(0, from), loc(columns - 1, _bottomMargin - n));
0787     clearImage(loc(0, from), loc(columns - 1, from + n - 1), ' ');
0788 }
0789 
0790 void Screen::setCursorYX(int y, int x)
0791 {
0792     setCursorY(y);
0793     setCursorX(x);
0794 }
0795 
0796 void Screen::setCursorX(int x)
0797 {
0798     if (x == 0)
0799         x = 1; // Default
0800     x -= 1; // Adjust
0801     cuX = qMax(0, qMin(columns - 1, x));
0802 }
0803 
0804 void Screen::setCursorY(int y)
0805 {
0806     if (y == 0)
0807         y = 1; // Default
0808     y -= 1; // Adjust
0809     cuY = qMax(0, qMin(lines - 1, y + (getMode(MODE_Origin) ? _topMargin : 0)));
0810 }
0811 
0812 void Screen::home()
0813 {
0814     cuX = 0;
0815     cuY = 0;
0816 }
0817 
0818 void Screen::toStartOfLine()
0819 {
0820     cuX = 0;
0821 }
0822 
0823 int Screen::getCursorX() const
0824 {
0825     return cuX;
0826 }
0827 
0828 int Screen::getCursorY() const
0829 {
0830     return cuY;
0831 }
0832 
0833 void Screen::clearImage(int loca, int loce, char c)
0834 {
0835     int scr_TL = loc(0, history->getLines());
0836     // FIXME: check positions
0837 
0838     // Clear entire selection if it overlaps region to be moved...
0839     if ((selBottomRight > (loca + scr_TL)) && (selTopLeft < (loce + scr_TL))) {
0840         clearSelection();
0841     }
0842 
0843     int topLine = loca / columns;
0844     int bottomLine = loce / columns;
0845 
0846     Character clearCh(c, currentForeground, currentBackground, DEFAULT_RENDITION);
0847 
0848     // if the character being used to clear the area is the same as the
0849     // default character, the affected lines can simply be shrunk.
0850     bool isDefaultCh = (clearCh == Character());
0851 
0852     for (int y = topLine; y <= bottomLine; y++) {
0853         lineProperties[y] = 0;
0854 
0855         int endCol = (y == bottomLine) ? loce % columns : columns - 1;
0856         int startCol = (y == topLine) ? loca % columns : 0;
0857 
0858         QVector<Character> &line = screenLines[y];
0859 
0860         if (isDefaultCh && endCol == columns - 1) {
0861             line.resize(startCol);
0862         } else {
0863             if (line.size() < endCol + 1)
0864                 line.resize(endCol + 1);
0865 
0866             Character *data = line.data();
0867             for (int i = startCol; i <= endCol; i++)
0868                 data[i] = clearCh;
0869         }
0870     }
0871 }
0872 
0873 void Screen::moveImage(int dest, int sourceBegin, int sourceEnd)
0874 {
0875     Q_ASSERT(sourceBegin <= sourceEnd);
0876 
0877     int lines = (sourceEnd - sourceBegin) / columns;
0878 
0879     // move screen image and line properties:
0880     // the source and destination areas of the image may overlap,
0881     // so it matters that we do the copy in the right order -
0882     // forwards if dest < sourceBegin or backwards otherwise.
0883     //(search the web for 'memmove implementation' for details)
0884     if (dest < sourceBegin) {
0885         for (int i = 0; i <= lines; i++) {
0886             screenLines[(dest / columns) + i] = screenLines[(sourceBegin / columns) + i];
0887             lineProperties[(dest / columns) + i] = lineProperties[(sourceBegin / columns) + i];
0888         }
0889     } else {
0890         for (int i = lines; i >= 0; i--) {
0891             screenLines[(dest / columns) + i] = screenLines[(sourceBegin / columns) + i];
0892             lineProperties[(dest / columns) + i] = lineProperties[(sourceBegin / columns) + i];
0893         }
0894     }
0895 
0896     if (lastPos != -1) {
0897         int diff = dest - sourceBegin; // Scroll by this amount
0898         lastPos += diff;
0899         if ((lastPos < 0) || (lastPos >= (lines * columns)))
0900             lastPos = -1;
0901     }
0902 
0903     // Adjust selection to follow scroll.
0904     if (selBegin != -1) {
0905         bool beginIsTL = (selBegin == selTopLeft);
0906         int diff = dest - sourceBegin; // Scroll by this amount
0907         int scr_TL = loc(0, history->getLines());
0908         int srca = sourceBegin + scr_TL; // Translate index from screen to global
0909         int srce = sourceEnd + scr_TL; // Translate index from screen to global
0910         int desta = srca + diff;
0911         int deste = srce + diff;
0912 
0913         if ((selTopLeft >= srca) && (selTopLeft <= srce))
0914             selTopLeft += diff;
0915         else if ((selTopLeft >= desta) && (selTopLeft <= deste))
0916             selBottomRight = -1; // Clear selection (see below)
0917 
0918         if ((selBottomRight >= srca) && (selBottomRight <= srce))
0919             selBottomRight += diff;
0920         else if ((selBottomRight >= desta) && (selBottomRight <= deste))
0921             selBottomRight = -1; // Clear selection (see below)
0922 
0923         if (selBottomRight < 0) {
0924             clearSelection();
0925         } else {
0926             if (selTopLeft < 0)
0927                 selTopLeft = 0;
0928         }
0929 
0930         if (beginIsTL)
0931             selBegin = selTopLeft;
0932         else
0933             selBegin = selBottomRight;
0934     }
0935 }
0936 
0937 void Screen::clearToEndOfScreen()
0938 {
0939     clearImage(loc(cuX, cuY), loc(columns - 1, lines - 1), ' ');
0940 }
0941 
0942 void Screen::clearToBeginOfScreen()
0943 {
0944     clearImage(loc(0, 0), loc(cuX, cuY), ' ');
0945 }
0946 
0947 void Screen::clearEntireScreen()
0948 {
0949     // Add entire screen to history
0950     for (int i = 0; i < (lines - 1); i++) {
0951         addHistLine();
0952         scrollUp(0, 1);
0953     }
0954 
0955     clearImage(loc(0, 0), loc(columns - 1, lines - 1), ' ');
0956 }
0957 
0958 /*! fill screen with 'E'
0959   This is to aid screen alignment
0960   */
0961 
0962 void Screen::helpAlign()
0963 {
0964     clearImage(loc(0, 0), loc(columns - 1, lines - 1), 'E');
0965 }
0966 
0967 void Screen::clearToEndOfLine()
0968 {
0969     clearImage(loc(cuX, cuY), loc(columns - 1, cuY), ' ');
0970 }
0971 
0972 void Screen::clearToBeginOfLine()
0973 {
0974     clearImage(loc(0, cuY), loc(cuX, cuY), ' ');
0975 }
0976 
0977 void Screen::clearEntireLine()
0978 {
0979     clearImage(loc(0, cuY), loc(columns - 1, cuY), ' ');
0980 }
0981 
0982 void Screen::setRendition(int re)
0983 {
0984     currentRendition |= re;
0985     updateEffectiveRendition();
0986 }
0987 
0988 void Screen::resetRendition(int re)
0989 {
0990     currentRendition &= ~re;
0991     updateEffectiveRendition();
0992 }
0993 
0994 void Screen::setDefaultRendition()
0995 {
0996     setForeColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR);
0997     setBackColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR);
0998     currentRendition = DEFAULT_RENDITION;
0999     updateEffectiveRendition();
1000 }
1001 
1002 void Screen::setForeColor(int space, int color)
1003 {
1004     currentForeground = CharacterColor(space, color);
1005 
1006     if (currentForeground.isValid())
1007         updateEffectiveRendition();
1008     else
1009         setForeColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR);
1010 }
1011 
1012 void Screen::setBackColor(int space, int color)
1013 {
1014     currentBackground = CharacterColor(space, color);
1015 
1016     if (currentBackground.isValid())
1017         updateEffectiveRendition();
1018     else
1019         setBackColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR);
1020 }
1021 
1022 void Screen::clearSelection()
1023 {
1024     selBottomRight = -1;
1025     selTopLeft = -1;
1026     selBegin = -1;
1027 }
1028 
1029 void Screen::getSelectionStart(int &column, int &line) const
1030 {
1031     if (selTopLeft != -1) {
1032         column = selTopLeft % columns;
1033         line = selTopLeft / columns;
1034     } else {
1035         column = cuX + getHistLines();
1036         line = cuY + getHistLines();
1037     }
1038 }
1039 void Screen::getSelectionEnd(int &column, int &line) const
1040 {
1041     if (selBottomRight != -1) {
1042         column = selBottomRight % columns;
1043         line = selBottomRight / columns;
1044     } else {
1045         column = cuX + getHistLines();
1046         line = cuY + getHistLines();
1047     }
1048 }
1049 void Screen::setSelectionStart(const int x, const int y, const bool mode)
1050 {
1051     selBegin = loc(x, y);
1052     /* FIXME, HACK to correct for x too far to the right... */
1053     if (x == columns)
1054         selBegin--;
1055 
1056     selBottomRight = selBegin;
1057     selTopLeft = selBegin;
1058     blockSelectionMode = mode;
1059 }
1060 
1061 void Screen::setSelectionEnd(const int x, const int y)
1062 {
1063     if (selBegin == -1)
1064         return;
1065 
1066     int endPos = loc(x, y);
1067 
1068     if (endPos < selBegin) {
1069         selTopLeft = endPos;
1070         selBottomRight = selBegin;
1071     } else {
1072         /* FIXME, HACK to correct for x too far to the right... */
1073         if (x == columns)
1074             endPos--;
1075 
1076         selTopLeft = selBegin;
1077         selBottomRight = endPos;
1078     }
1079 
1080     // Normalize the selection in column mode
1081     if (blockSelectionMode) {
1082         int topRow = selTopLeft / columns;
1083         int topColumn = selTopLeft % columns;
1084         int bottomRow = selBottomRight / columns;
1085         int bottomColumn = selBottomRight % columns;
1086 
1087         selTopLeft = loc(qMin(topColumn, bottomColumn), topRow);
1088         selBottomRight = loc(qMax(topColumn, bottomColumn), bottomRow);
1089     }
1090 }
1091 
1092 bool Screen::isSelected(const int x, const int y) const
1093 {
1094     bool columnInSelection = true;
1095     if (blockSelectionMode) {
1096         columnInSelection = x >= (selTopLeft % columns) && x <= (selBottomRight % columns);
1097     }
1098 
1099     int pos = loc(x, y);
1100     return pos >= selTopLeft && pos <= selBottomRight && columnInSelection;
1101 }
1102 
1103 QString Screen::selectedText(bool preserveLineBreaks) const
1104 {
1105     QString result;
1106     QTextStream stream(&result, QIODevice::ReadWrite);
1107 
1108     PlainTextDecoder decoder;
1109     decoder.begin(&stream);
1110     writeSelectionToStream(&decoder, preserveLineBreaks);
1111     decoder.end();
1112 
1113     return result;
1114 }
1115 
1116 bool Screen::isSelectionValid() const
1117 {
1118     return selTopLeft >= 0 && selBottomRight >= 0;
1119 }
1120 
1121 void Screen::writeSelectionToStream(TerminalCharacterDecoder *decoder, bool preserveLineBreaks) const
1122 {
1123     if (!isSelectionValid())
1124         return;
1125     writeToStream(decoder, selTopLeft, selBottomRight, preserveLineBreaks);
1126 }
1127 
1128 void Screen::writeToStream(TerminalCharacterDecoder *decoder, int startIndex, int endIndex, bool preserveLineBreaks) const
1129 {
1130     int top = startIndex / columns;
1131     int left = startIndex % columns;
1132 
1133     int bottom = endIndex / columns;
1134     int right = endIndex % columns;
1135 
1136     Q_ASSERT(top >= 0 && left >= 0 && bottom >= 0 && right >= 0);
1137 
1138     for (int y = top; y <= bottom; y++) {
1139         int start = 0;
1140         if (y == top || blockSelectionMode)
1141             start = left;
1142 
1143         int count = -1;
1144         if (y == bottom || blockSelectionMode)
1145             count = right - start + 1;
1146 
1147         const bool appendNewLine = (y != bottom);
1148         int copied = copyLineToStream(y, start, count, decoder, appendNewLine, preserveLineBreaks);
1149 
1150         // if the selection goes beyond the end of the last line then
1151         // append a new line character.
1152         //
1153         // this makes it possible to 'select' a trailing new line character after
1154         // the text on a line.
1155         if (y == bottom && copied < count) {
1156             Character newLineChar('\n');
1157             decoder->decodeLine(std::span(&newLineChar, 1), 0);
1158         }
1159     }
1160 }
1161 
1162 int Screen::copyLineToStream(int line, int start, int count, TerminalCharacterDecoder *decoder, bool appendNewLine, bool preserveLineBreaks) const
1163 {
1164     // buffer to hold characters for decoding
1165     // the buffer is static to avoid initialising every
1166     // element on each call to copyLineToStream
1167     //(which is unnecessary since all elements will be overwritten anyway)
1168     static const int MAX_CHARS = 1024;
1169     static std::array<Character, MAX_CHARS> characterBuffer;
1170 
1171     Q_ASSERT(count < MAX_CHARS);
1172 
1173     LineProperty currentLineProperties = 0;
1174 
1175     // determine if the line is in the history buffer or the screen image
1176     if (line < history->getLines()) {
1177         const int lineLength = history->getLineLen(line);
1178 
1179         // ensure that start position is before end of line
1180         start = qMin(start, qMax(0, lineLength - 1));
1181 
1182         // retrieve line from history buffer.  It is assumed
1183         // that the history buffer does not store trailing white space
1184         // at the end of the line, so it does not need to be trimmed here
1185         if (count == -1) {
1186             count = lineLength - start;
1187         } else {
1188             count = qMin(start + count, lineLength) - start;
1189         }
1190 
1191         // safety checks
1192         Q_ASSERT(start >= 0);
1193         Q_ASSERT(count >= 0);
1194         Q_ASSERT((start + count) <= history->getLineLen(line));
1195 
1196         history->getCells(line, start, count, characterBuffer);
1197 
1198         if (history->isWrappedLine(line))
1199             currentLineProperties |= LINE_WRAPPED;
1200     } else {
1201         if (count == -1)
1202             count = columns - start;
1203 
1204         Q_ASSERT(count >= 0);
1205 
1206         const int screenLine = line - history->getLines();
1207 
1208         auto data = screenLines[screenLine].data();
1209         int length = screenLines[screenLine].size();
1210 
1211         // retrieve line from screen image
1212         for (int i = start; i < qMin(start + count, length); i++) {
1213             characterBuffer[i - start] = data[i];
1214         }
1215 
1216         // count cannot be any greater than length
1217         count = qBound(0, length - start, count);
1218 
1219         Q_ASSERT(screenLine < lineProperties.size());
1220         currentLineProperties |= lineProperties[screenLine];
1221     }
1222 
1223     // add new line character at end
1224     const bool omitLineBreak = (currentLineProperties & LINE_WRAPPED) || !preserveLineBreaks;
1225 
1226     if (!omitLineBreak && appendNewLine && (count + 1 < MAX_CHARS)) {
1227         characterBuffer[count] = '\n';
1228         count++;
1229     }
1230 
1231     // decode line and write to text stream
1232     decoder->decodeLine(std::span(characterBuffer).subspan(0, count), currentLineProperties);
1233 
1234     return count;
1235 }
1236 
1237 void Screen::writeLinesToStream(TerminalCharacterDecoder *decoder, int fromLine, int toLine) const
1238 {
1239     writeToStream(decoder, loc(0, fromLine), loc(columns - 1, toLine));
1240 }
1241 
1242 void Screen::addHistLine()
1243 {
1244     // add line to history buffer
1245     // we have to take care about scrolling, too...
1246 
1247     if (hasScroll()) {
1248         int oldHistLines = history->getLines();
1249 
1250         history->addCellsVector(screenLines[0]);
1251         history->addLine(lineProperties[0] & LINE_WRAPPED);
1252 
1253         int newHistLines = history->getLines();
1254 
1255         bool beginIsTL = (selBegin == selTopLeft);
1256 
1257         // If the history is full, increment the count
1258         // of dropped lines
1259         if (newHistLines == oldHistLines)
1260             _droppedLines++;
1261 
1262         // Adjust selection for the new point of reference
1263         if (newHistLines > oldHistLines) {
1264             if (selBegin != -1) {
1265                 selTopLeft += columns;
1266                 selBottomRight += columns;
1267             }
1268         }
1269 
1270         if (selBegin != -1) {
1271             // Scroll selection in history up
1272             int top_BR = loc(0, 1 + newHistLines);
1273 
1274             if (selTopLeft < top_BR)
1275                 selTopLeft -= columns;
1276 
1277             if (selBottomRight < top_BR)
1278                 selBottomRight -= columns;
1279 
1280             if (selBottomRight < 0)
1281                 clearSelection();
1282             else {
1283                 if (selTopLeft < 0)
1284                     selTopLeft = 0;
1285             }
1286 
1287             if (beginIsTL)
1288                 selBegin = selTopLeft;
1289             else
1290                 selBegin = selBottomRight;
1291         }
1292     }
1293 }
1294 
1295 int Screen::getHistLines() const
1296 {
1297     return history->getLines();
1298 }
1299 
1300 void Screen::setScroll(const HistoryType &t, bool copyPreviousScroll)
1301 {
1302     clearSelection();
1303 
1304     if (copyPreviousScroll)
1305         history = t.scroll(std::move(history));
1306     else {
1307         auto oldScroll = std::move(history);
1308         history = t.scroll(nullptr);
1309     }
1310 }
1311 
1312 bool Screen::hasScroll() const
1313 {
1314     return history->hasScroll();
1315 }
1316 
1317 const HistoryType &Screen::getScroll() const
1318 {
1319     return history->getType();
1320 }
1321 
1322 void Screen::setLineProperty(LineProperty property, bool enable)
1323 {
1324     if (enable)
1325         lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | property);
1326     else
1327         lineProperties[cuY] = (LineProperty)(lineProperties[cuY] & ~property);
1328 }
1329 void Screen::fillWithDefaultChar(std::span<Character> dest, int count)
1330 {
1331     for (int i = 0; i < count; i++)
1332         dest[i] = defaultChar;
1333 }