From 9333d6713abc606084bad8fa199e396fc85ef9d9 Mon Sep 17 00:00:00 2001 From: Matan Ziv-Av Date: Mon, 4 Jul 2022 23:49:18 +0300 Subject: [PATCH] Add semantic integration copy actions - Selecting Copy (or pressing ctrl-shift-c) when there is no selection selects current output if current mode is output, current input if current mode is input and input is non-empty, and last output if current input is empty. - Ctrl + mouse triple click selects the pointed input/output/prompt. - There is also a fix that was missed in previous patch for keeping track of current semantic mode through scrolls. --- src/Screen.cpp | 125 ++++++++++++++++++++++++ src/Screen.h | 9 ++ src/characters/Character.h | 6 ++ src/session/SessionController.cpp | 7 +- src/terminalDisplay/TerminalDisplay.cpp | 7 +- 5 files changed, 150 insertions(+), 4 deletions(-) diff --git a/src/Screen.cpp b/src/Screen.cpp index 33f41515..2c8b9e50 100644 --- a/src/Screen.cpp +++ b/src/Screen.cpp @@ -75,6 +75,7 @@ Screen::Screen(int lines, int columns) , _bottomMargin(0) , _replMode(REPL_None) , _hasRepl(false) + , _replLastOutputStart(std::pair(-1, -1)) , _tabStops(QBitArray()) , _selBegin(0) , _selTopLeft(0) @@ -1142,6 +1143,14 @@ void Screen::scrollUp(int from, int n) if (_hasGraphics) { scrollPlacements(n); } + if (_replMode != REPL_None) { + _replModeStart = std::make_pair(_replModeStart.first - 1, _replModeStart.second); + _replModeEnd = std::make_pair(_replModeEnd.first - 1, _replModeEnd.second); + if (_replLastOutputStart.first > -1) { + _replLastOutputStart = std::make_pair(_replLastOutputStart.first - 1, _replLastOutputStart.second); + _replLastOutputEnd = std::make_pair(_replLastOutputEnd.first - 1, _replLastOutputEnd.second); + } + } } void Screen::scrollDown(int n) @@ -1569,9 +1578,120 @@ bool Screen::isSelected(const int x, const int y) const return pos >= _selTopLeft && pos <= _selBottomRight && columnInSelection; } +Character Screen::getCharacter(int col, int row) const +{ + Character ch; + if (row >= _history->getLines()) { + ch = _screenLines[row - _history->getLines()].value(col); + } else { + if (col < _history->getLineLen(row)) { + _history->getCells(row, col, 1, &ch); + } else { + ch = Character(); + } + } + return ch; +} + +void Screen::selectReplContigious(const int x, const int y) +{ + // Avoid searching if in current input + if (_replMode == REPL_INPUT && _replModeStart <= std::pair(y, x) && std::pair(y, x) <= _replModeEnd) { + setSelectionStart(_replModeStart.second, _replModeStart.first, false); + setSelectionEnd(_replModeEnd.second, _replModeEnd.first, true); + Q_EMIT _currentTerminalDisplay->screenWindow()->selectionChanged(); + return; + } + int col = x; + int row = y; + if (row < _history->getLines()) { + col = std::min(col, _history->getLineLen(row) - 1); + } else { + col = std::min(col, _screenLines[row - _history->getLines()].size() - 1); + } + while (col > 0 && (getCharacter(col, row).flags & EF_REPL) == EF_REPL_NONE) { + col--; + } + if ((getCharacter(col, row).flags & EF_REPL) == EF_REPL_NONE) { + return; + } + int mode = getCharacter(col, row).flags & EF_REPL; + int startX = x; + int startY = y; + int lastX = x; + int lastY = y; + bool stop = false; + while (true) { + while (startX >= 0) { + // mode or NONE continue search, but ignore last run of NONEs + if (getCharacter(startX, startY).repl() == mode) { + lastX = startX; + lastY = startY; + } + if (getCharacter(startX, startY).repl() != mode && getCharacter(startX, startY).repl() != EF_REPL_NONE) { + stop = true; + startX = lastX; + startY = lastY; + break; + } + startX--; + } + if (stop) { + break; + } + startY--; + if (startY < 0) { + startY = 0; + startX = 0; + break; + } + startX = getLineLength(startY) - 1; + } + int endX = x; + int endY = y; + stop = false; + while (endY < _lines + _history->getLines()) { + while (endX < getLineLength(endY)) { + if (getCharacter(endX, endY).repl() != mode && getCharacter(endX, endY).repl() != EF_REPL_NONE) { + stop = true; + break; + } + endX++; + } + if (stop) { + break; + } + endX = 0; + endY++; + } + if (endX == 0) { + endY--; + endX = getLineLength(endY) - 1; + } else { + endX--; + } + setSelectionStart(startX, startY, false); + setSelectionEnd(endX, endY, true); + Q_EMIT _currentTerminalDisplay->screenWindow()->selectionChanged(); +} + QString Screen::selectedText(const DecodingOptions options) const { if (!isSelectionValid()) { + if (!_hasRepl) { + return QString(); + } + int currentStart = (_history->getLines() + _replModeStart.first) * _columns + _replModeStart.second; + int currentEnd = (_history->getLines() + _replModeEnd.first) * _columns + _replModeEnd.second - 1; + + if (_replMode == REPL_INPUT && currentStart > currentEnd && _replLastOutputStart.first > -1) { + // If no input yet, copy last output + currentStart = (_history->getLines() + _replLastOutputStart.first) * _columns + _replLastOutputStart.second; + currentEnd = (_history->getLines() + _replLastOutputEnd.first) * _columns + _replLastOutputEnd.second - 1; + } + if (currentEnd >= currentStart) { + return text(currentStart, currentEnd, options); + } return QString(); } @@ -1960,12 +2080,17 @@ void Screen::setLineProperty(LineProperty property, bool enable) void Screen::setReplMode(int mode) { if (_replMode != mode) { + if (_replMode == REPL_OUTPUT) { + _replLastOutputStart = _replModeStart; + _replLastOutputEnd = _replModeEnd; + } _replMode = mode; _replModeStart = std::make_pair(_cuY, _cuX); _replModeEnd = std::make_pair(_cuY, _cuX); } if (mode != REPL_None) { _hasRepl = true; + Q_EMIT _currentTerminalDisplay->screenWindow()->selectionChanged(); // Enable copy action setLineProperty(LINE_PROMPT_START << (mode - REPL_PROMPT), true); } } diff --git a/src/Screen.h b/src/Screen.h index dbe59cb7..db70aa0a 100644 --- a/src/Screen.h +++ b/src/Screen.h @@ -464,6 +464,11 @@ public: */ void setSelectionEnd(const int x, const int y, const bool trimTrailingWhitespace); + /** + * Selects a range of characters with the same REPL mode as the character at (@p x, @p y) + */ + void selectReplContigious(const int x, const int y); + /** * Retrieves the start of the selection or the cursor position if there * is no selection. @@ -743,6 +748,8 @@ private: // starting from 'startLine', where 0 is the first line in the history void copyFromHistory(Character *dest, int startLine, int count) const; + Character getCharacter(int col, int row) const; + // returns a buffer that can hold at most 'count' characters, // where the number of reallocations and object reinitializations // should be as minimal as possible @@ -806,6 +813,8 @@ private: bool _hasRepl; std::pair _replModeStart; std::pair _replModeEnd; + std::pair _replLastOutputStart; + std::pair _replLastOutputEnd; // ---------------------------- diff --git a/src/characters/Character.h b/src/characters/Character.h index 039b397a..b123772e 100644 --- a/src/characters/Character.h +++ b/src/characters/Character.h @@ -56,6 +56,7 @@ 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_NONE = (0 << 1); const ExtraFlags EF_REPL_PROMPT = (1 << 1); const ExtraFlags EF_REPL_INPUT = (2 << 1); const ExtraFlags EF_REPL_OUTPUT = (3 << 1); @@ -155,6 +156,11 @@ public: return width(character); } + int repl() const + { + return flags & EF_REPL; + } + static int width(uint ucs4) { // ASCII diff --git a/src/session/SessionController.cpp b/src/session/SessionController.cpp index 9c82f333..f6e2a32a 100644 --- a/src/session/SessionController.cpp +++ b/src/session/SessionController.cpp @@ -455,10 +455,11 @@ void SessionController::updateCopyAction(const bool selectionEmpty) QAction *copyAction = actionCollection()->action(QStringLiteral("edit_copy")); QAction *copyContextMenu = actionCollection()->action(QStringLiteral("edit_copy_contextmenu")); // copy action is meaningful only when some text is selected. - copyAction->setEnabled(!selectionEmpty); - copyContextMenu->setVisible(!selectionEmpty); - QAction *Action = actionCollection()->action(QStringLiteral("edit_copy_contextmenu_in")); + // Or when semantic integration is used. bool hasRepl = view() && view()->screenWindow() && view()->screenWindow()->screen() && view()->screenWindow()->screen()->hasRepl(); + copyAction->setEnabled(!selectionEmpty || hasRepl); + copyContextMenu->setVisible(!selectionEmpty || hasRepl); + QAction *Action = actionCollection()->action(QStringLiteral("edit_copy_contextmenu_in")); Action->setVisible(!selectionEmpty && hasRepl); Action = actionCollection()->action(QStringLiteral("edit_copy_contextmenu_out")); Action->setVisible(!selectionEmpty && hasRepl); diff --git a/src/terminalDisplay/TerminalDisplay.cpp b/src/terminalDisplay/TerminalDisplay.cpp index 42303fac..02e25f3f 100644 --- a/src/terminalDisplay/TerminalDisplay.cpp +++ b/src/terminalDisplay/TerminalDisplay.cpp @@ -1916,7 +1916,12 @@ void TerminalDisplay::mouseTripleClickEvent(QMouseEvent *ev) } auto [charLine, charColumn] = getCharacterPosition(ev->pos(), true); - selectLine(QPoint(charColumn, charLine), _tripleClickMode == Enum::SelectWholeLine); + if (_screenWindow->screen()->hasRepl() && ev->modifiers() & Qt::ControlModifier) { + _screenWindow->screen()->selectReplContigious(charColumn, charLine + _screenWindow->currentLine()); + copyToX11Selection(); + } else { + selectLine(QPoint(charColumn, charLine), _tripleClickMode == Enum::SelectWholeLine); + } } void TerminalDisplay::selectLine(QPoint pos, bool entireLine)