From cfaf997ebafdd6e4da390b6de11c7b5837eb20ea Mon Sep 17 00:00:00 2001 From: Matan Ziv-Av Date: Tue, 14 Jun 2022 23:31:02 +0300 Subject: [PATCH] Add semantic shell integration - Add `OSC 133 ; *` escape sequences (where * stands for one of A,B,C,D,L,P. For the shell to report text mode (one of prompt/input/output). - Store the mode of each character written to the screen. - Replace isRealCharacter field in Character with flags. - Store line length in _lineProperties array. --- src/Screen.cpp | 55 ++++++++++++++++++++++++------- src/Screen.h | 43 ++++++++++++++++++++++++ src/Vt102Emulation.cpp | 16 +++++++-- src/characters/Character.h | 27 +++++++++++---- src/decoders/PlainTextDecoder.cpp | 4 +-- 5 files changed, 124 insertions(+), 21 deletions(-) diff --git a/src/Screen.cpp b/src/Screen.cpp index f3ab5585..99e11e99 100644 --- a/src/Screen.cpp +++ b/src/Screen.cpp @@ -50,7 +50,7 @@ using namespace Konsole; #endif const Character Screen::DefaultChar = - Character(' ', CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR), CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR), DEFAULT_RENDITION, false); + Character(' ', CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR), CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR), DEFAULT_RENDITION, 0); Screen::Screen(int lines, int columns) : _currentTerminalDisplay(nullptr) @@ -73,6 +73,8 @@ Screen::Screen(int lines, int columns) , _currentRendition(DEFAULT_RENDITION) , _topMargin(0) , _bottomMargin(0) + , _replMode(REPL_None) + , _hasRepl(false) , _tabStops(QBitArray()) , _selBegin(0) , _selTopLeft(0) @@ -222,6 +224,7 @@ void Screen::reverseIndex() void Screen::nextLine() //=NEL { + _lineProperties[_cuY] = SetLineLength(_lineProperties[_cuY], _cuX); toStartOfLine(); index(); } @@ -243,7 +246,7 @@ void Screen::eraseBlock(int y, int x, int height, int width) width = qBound(0, width, _columns - x - 1); int endCol = x + width; height = qBound(0, height, _lines - y - 1); - Character chr(' ', CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR), CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR), RE_TRANSPARENT, false); + Character chr(' ', CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR), CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR), RE_TRANSPARENT, 0); for (int row = y; row < y + height; row++) { QVector &line = _screenLines[row]; if (line.size() < endCol + 1) { @@ -282,7 +285,7 @@ void Screen::deleteChars(int n) _screenLines[_cuY].remove(_cuX, n); // Append space(s) with current attributes - Character spaceWithCurrentAttrs(' ', _effectiveForeground, _effectiveBackground, _effectiveRendition, false); + Character spaceWithCurrentAttrs(' ', _effectiveForeground, _effectiveBackground, _effectiveRendition, 0); for (int i = 0; i < n; ++i) { _screenLines[_cuY].append(spaceWithCurrentAttrs); @@ -917,6 +920,7 @@ void Screen::initTabStops() void Screen::newLine() { if (getMode(MODE_NewLine)) { + _lineProperties[_cuY] = SetLineLength(_lineProperties[_cuY], _cuX); toStartOfLine(); } @@ -1047,7 +1051,7 @@ notcombine: currentChar.foregroundColor = _effectiveForeground; currentChar.backgroundColor = _effectiveBackground; currentChar.rendition = _effectiveRendition; - currentChar.isRealCharacter = true; + currentChar.flags = setRepl(EF_REAL, _replMode); _lastDrawnChar = c; @@ -1065,11 +1069,17 @@ notcombine: ch.foregroundColor = _effectiveForeground; ch.backgroundColor = _effectiveBackground; ch.rendition = _effectiveRendition; - ch.isRealCharacter = false; + ch.flags = setRepl(EF_UNREAL, _replMode); --w; } _cuX = newCursorX; + if (_replMode != REPL_None && std::make_pair(_cuY, _cuX) >= _replModeEnd) { + _replModeEnd = std::make_pair(_cuY, _cuX); + } + if (LineLength(_lineProperties[_cuY]) < _cuX) { + _lineProperties[_cuY] = SetLineLength(_lineProperties[_cuY], _cuX); + } if (_escapeSequenceUrlExtractor) { _escapeSequenceUrlExtractor->appendUrlText(QChar(c)); @@ -1215,18 +1225,27 @@ void Screen::clearImage(int loca, int loce, char c, bool resetLineRendition) const int topLine = loca / _columns; const int bottomLine = loce / _columns; - Character clearCh(uint(c), _currentForeground, _currentBackground, DEFAULT_RENDITION, false); + Character clearCh(uint(c), _currentForeground, _currentBackground, DEFAULT_RENDITION, 0); // 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] &= ~LINE_WRAPPED; + _lineProperties[y] = 0; const int endCol = (y == bottomLine) ? loce % _columns : _columns - 1; const int startCol = (y == topLine) ? loca % _columns : 0; + if (endCol < _columns - 1 || startCol > 0) { + _lineProperties[y] &= ~LINE_WRAPPED; + if (LineLength(_lineProperties[y]) < endCol && LineLength(_lineProperties[y]) > startCol) { + _lineProperties[y] = SetLineLength(_lineProperties[y], startCol); + } + } else { + _lineProperties[y] = LINE_DEFAULT; + } + QVector &line = _screenLines[y]; if (isDefaultCh && endCol == _columns - 1) { @@ -1512,7 +1531,7 @@ void Screen::setSelectionEnd(const int x, const int y, const bool trimTrailingWh _history->getCells(bottomRow, 0, histLineLen, histLine.data()); for (int j = bottomColumn; j < histLineLen; j++) { - if (histLine.at(j).isRealCharacter && (!trimTrailingWhitespace || !QChar(histLine.at(j).character).isSpace())) { + if ((histLine.at(j).flags & EF_REAL) != 0 && (!trimTrailingWhitespace || !QChar(histLine.at(j).character).isSpace())) { beyondLastColumn = false; } } @@ -1523,7 +1542,7 @@ void Screen::setSelectionEnd(const int x, const int y, const bool trimTrailingWh const int length = _screenLines.at(line).count(); for (int k = bottomColumn; k < lastColumn && k < length; k++) { - if (data[k].isRealCharacter && (!trimTrailingWhitespace || !QChar(data[k].character).isSpace())) { + if ((data[k].flags & EF_REAL) != 0 && (!trimTrailingWhitespace || !QChar(data[k].character).isSpace())) { beyondLastColumn = false; } } @@ -1681,7 +1700,7 @@ int Screen::copyLineToStream(int line, // Exclude trailing empty cells from count and don't bother processing them further. // See the comment on the similar case for screen lines for an explanation. - while (count > 0 && !characterBuffer[start + count - 1].isRealCharacter) { + while (count > 0 && (characterBuffer[start + count - 1].flags & EF_REAL) == 0) { count--; } @@ -1717,7 +1736,7 @@ int Screen::copyLineToStream(int line, // character when TrimTrailingWhitespace is true), so the returned // count from this function must not include empty cells beyond that // last character. - while (length > 0 && !data[length - 1].isRealCharacter) { + while (length > 0 && (data[length - 1].flags & EF_REAL) == 0) { length--; } @@ -1908,6 +1927,20 @@ void Screen::setLineProperty(LineProperty property, bool enable) _lineProperties[_cuY] = static_cast(_lineProperties.at(_cuY) & ~property); } } + +void Screen::setReplMode(int mode) +{ + if (_replMode != mode) { + _replMode = mode; + _replModeStart = std::make_pair(_cuY, _cuX); + _replModeEnd = std::make_pair(_cuY, _cuX); + } + if (mode != REPL_None) { + _hasRepl = true; + setLineProperty(LINE_PROMPT_START << (mode - REPL_PROMPT), true); + } +} + void Screen::fillWithDefaultChar(Character *dest, int count) { std::fill_n(dest, count, Screen::DefaultChar); diff --git a/src/Screen.h b/src/Screen.h index 31478c01..dbe59cb7 100644 --- a/src/Screen.h +++ b/src/Screen.h @@ -31,6 +31,11 @@ #define MODE_AppScreen 6 #define MODES_SCREEN 7 +#define REPL_None 0 +#define REPL_PROMPT 1 +#define REPL_INPUT 2 +#define REPL_OUTPUT 3 + struct TerminalGraphicsPlacement_t { QPixmap pixmap; qint64 id; @@ -89,6 +94,9 @@ public: PreserveLineBreaks = 0x2, TrimLeadingWhitespace = 0x4, TrimTrailingWhitespace = 0x8, + ExcludePrompt = 0x10, + ExcludeInput = 0x20, + ExcludeOutput = 0x40, }; Q_DECLARE_FLAGS(DecodingOptions, DecodingOption) @@ -536,6 +544,37 @@ public: */ void setLineProperty(LineProperty property, bool enable); + /** + * Set REPL mode (shell integration) + * + * @param mode is the REPL mode + * Possible modes are: + * REPL_None + * REPL_PROMPT + * REPL_INPUT + * REPL_OUTPUT + */ + void setReplMode(int mode); + /** Return true if semantic shell integration is in use. */ + bool hasRepl() const + { + return _hasRepl; + } + /** Return current REPL mode */ + int replMode() const + { + return _replMode; + } + /** Return location of current REPL mode start. */ + std::pair replModeStart() const + { + return _replModeStart; + } + std::pair replModeEnd() const + { + return _replModeEnd; + } + /** * Returns the number of lines that the image has been scrolled up or down by, * since the last call to resetScrolledLines(). @@ -763,6 +802,10 @@ private: // states ---------------- int _currentModes[MODES_SCREEN]; int _savedModes[MODES_SCREEN]; + int _replMode; + bool _hasRepl; + std::pair _replModeStart; + std::pair _replModeEnd; // ---------------------------- diff --git a/src/Vt102Emulation.cpp b/src/Vt102Emulation.cpp index a0a8a42f..b32a8926 100644 --- a/src/Vt102Emulation.cpp +++ b/src/Vt102Emulation.cpp @@ -1048,8 +1048,20 @@ void Vt102Emulation::processSessionAttributeRequest(const int tokenSize, const u } if (attribute == 133) { - if (value == QLatin1String("A")) { - _currentScreen->setLineProperty(LINE_PROMPT_START, true); + if (value == QLatin1String("A") || value == QLatin1String("N") || value == QLatin1String("P")) { + _currentScreen->setReplMode(REPL_PROMPT); + } + if (value == QLatin1String("L") && _currentScreen->getCursorX() > 0) { + _currentScreen->nextLine(); + } + if (value == QLatin1String("B")) { + _currentScreen->setReplMode(REPL_INPUT); + } + if (value == QLatin1String("C")) { + _currentScreen->setReplMode(REPL_OUTPUT); + } + if (value == QLatin1String("D")) { + _currentScreen->setReplMode(REPL_None); } } if (attribute == 4) { diff --git a/src/characters/Character.h b/src/characters/Character.h index e2105528..5a784538 100644 --- a/src/characters/Character.h +++ b/src/characters/Character.h @@ -20,10 +20,12 @@ namespace Konsole { -typedef unsigned char LineProperty; +typedef quint32 LineProperty; typedef quint16 RenditionFlags; +typedef quint16 ExtraFlags; + /* clang-format off */ const int LINE_DEFAULT = 0; const int LINE_WRAPPED = (1 << 0); @@ -31,6 +33,10 @@ const int LINE_DOUBLEWIDTH = (1 << 1); const int LINE_DOUBLEHEIGHT_TOP = (1 << 2); const int LINE_DOUBLEHEIGHT_BOTTOM = (1 << 3); const int LINE_PROMPT_START = (1 << 4); +const int LINE_INPUT_START = (1 << 5); +const int LINE_OUTPUT_START = (1 << 6); +const int LINE_LEN_POS = 8; +const int LINE_LEN_MASK = (0xfff << LINE_LEN_POS); const RenditionFlags DEFAULT_RENDITION = 0; const RenditionFlags RE_BOLD = (1 << 0); @@ -47,6 +53,16 @@ const RenditionFlags RE_OVERLINE = (1 << 10); const RenditionFlags RE_SELECTED = (1 << 11); const RenditionFlags RE_TRANSPARENT = (1 << 12); +const ExtraFlags EF_UNREAL = 0; +const ExtraFlags EF_REAL = (1 << 0); +const ExtraFlags EF_REPL = (3 << 1); +const ExtraFlags EF_REPL_PROMPT = (1 << 1); +const ExtraFlags EF_REPL_INPUT = (2 << 1); +const ExtraFlags EF_REPL_OUTPUT = (3 << 1); + +#define setRepl(f, m) (((f) & ~EF_REPL) | ((m) * EF_REPL_PROMPT)) +#define LineLength(f) static_cast(((f) & LINE_LEN_MASK) >> LINE_LEN_POS) +#define SetLineLength(f, l) (((f) & ~LINE_LEN_MASK) | ((l) << LINE_LEN_POS)) /* clang-format on */ /** @@ -65,19 +81,18 @@ public: * @param _b The color used to draw the character's background. * @param _r A set of rendition flags which specify how this character * is to be drawn. - * @param _real Indicate whether this character really exists, or exists - * simply as place holder. + * @param _flags Extra flags describing the character. not directly related to its rendition */ explicit constexpr Character(uint _c = ' ', CharacterColor _f = CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR), CharacterColor _b = CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR), RenditionFlags _r = DEFAULT_RENDITION, - bool _real = true) + ExtraFlags _flags = EF_REAL) : character(_c) , rendition(_r) , foregroundColor(_f) , backgroundColor(_b) - , isRealCharacter(_real) + , flags(_flags) { } @@ -107,7 +122,7 @@ public: * PlaceHolderCharacter: a character which exists as place holder * TabStopCharacter: a special place holder for HT("\t") */ - bool isRealCharacter; + ExtraFlags flags; /** * returns true if the format (color, rendition flag) of the compared characters is equal diff --git a/src/decoders/PlainTextDecoder.cpp b/src/decoders/PlainTextDecoder.cpp index 8c0ecce6..ea009a6a 100644 --- a/src/decoders/PlainTextDecoder.cpp +++ b/src/decoders/PlainTextDecoder.cpp @@ -97,7 +97,7 @@ void PlainTextDecoder::decodeLine(const Character *const characters, int count, // FIXME: the special case of '\n' here is really ugly // Maybe the '\n' should be added after calling this method in // Screen::copyLineToStream() - if (characters[i].isRealCharacter && characters[i].character != '\n') { + if ((characters[i].flags & EF_REAL) != 0 && characters[i].character != '\n') { realCharacterGuard = i; break; } @@ -129,7 +129,7 @@ void PlainTextDecoder::decodeLine(const Character *const characters, int count, // This feels tricky, but otherwise leading "whitespaces" may be // lost in some situation. One typical example is copying the result // of `dialog --infobox "qwe" 10 10` . - if (characters[i].isRealCharacter || i <= realCharacterGuard) { + if ((characters[i].flags & EF_REAL) != 0 || i <= realCharacterGuard) { characterBuffer.append(characters[i].character); i += qMax(1, Character::stringWidth(&characters[i].character, 1)); } else {