diff --git a/src/libs/utils/multitextcursor.cpp b/src/libs/utils/multitextcursor.cpp index 7225de9344a..a73c58bbb1d 100644 --- a/src/libs/utils/multitextcursor.cpp +++ b/src/libs/utils/multitextcursor.cpp @@ -294,7 +294,7 @@ static QTextLine currentTextLine(const QTextCursor &cursor) return layout->lineForTextPosition(relativePos); } -bool multiCursorAddEvent(QKeyEvent *e, QKeySequence::StandardKey matchKey) +bool MultiTextCursor::multiCursorAddEvent(QKeyEvent *e, QKeySequence::StandardKey matchKey) { uint searchkey = (e->modifiers() | e->key()) & ~(Qt::KeypadModifier diff --git a/src/libs/utils/multitextcursor.h b/src/libs/utils/multitextcursor.h index 90938848e8b..8fdf041faa0 100644 --- a/src/libs/utils/multitextcursor.h +++ b/src/libs/utils/multitextcursor.h @@ -27,6 +27,7 @@ #include "utils_global.h" +#include #include QT_BEGIN_NAMESPACE @@ -99,6 +100,8 @@ public: const_iterator constBegin() const { return m_cursors.constBegin(); } const_iterator constEnd() const { return m_cursors.constEnd(); } + static bool multiCursorAddEvent(QKeyEvent *e, QKeySequence::StandardKey matchKey); + private: QList m_cursors; }; diff --git a/src/plugins/texteditor/tabsettings.cpp b/src/plugins/texteditor/tabsettings.cpp index c61a0b3fb33..c363815572f 100644 --- a/src/plugins/texteditor/tabsettings.cpp +++ b/src/plugins/texteditor/tabsettings.cpp @@ -204,6 +204,11 @@ int TabSettings::columnAt(const QString &text, int position) const return column; } +int TabSettings::columnAtCursorPosition(const QTextCursor &cursor) const +{ + return columnAt(cursor.block().text(), cursor.positionInBlock()); +} + int TabSettings::positionAtColumn(const QString &text, int column, int *offset, bool allowOverstep) const { int col = 0; diff --git a/src/plugins/texteditor/tabsettings.h b/src/plugins/texteditor/tabsettings.h index e73d81b3651..c6446046a81 100644 --- a/src/plugins/texteditor/tabsettings.h +++ b/src/plugins/texteditor/tabsettings.h @@ -66,6 +66,7 @@ public: int lineIndentPosition(const QString &text) const; int columnAt(const QString &text, int position) const; + int columnAtCursorPosition(const QTextCursor &cursor) const; int positionAtColumn(const QString &text, int column, int *offset = nullptr, bool allowOverstep = false) const; int columnCountForText(const QString &text, int startColumn = 0) const; int indentedColumn(int column, bool doIndent = true) const; diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 7a769703895..499897522db 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -782,6 +782,18 @@ public: bool m_scrollBarUpdateScheduled = false; const MultiTextCursor m_cursors; + struct BlockSelection + { + int blockNumber = -1; + int column = -1; + int anchorBlockNumber = -1; + int anchorColumn = -1; + }; + QList m_blockSelections; + QList generateCursorsForBlockSelection(const BlockSelection &blockSelection); + void initBlockSelection(); + void clearBlockSelection(); + void handleMoveBlockSelection(QTextCursor::MoveOperation op); class UndoCursor { @@ -1348,6 +1360,81 @@ void TextEditorWidgetPrivate::updateAutoCompleteHighlight() q->setExtraSelections(TextEditorWidget::AutoCompleteSelection, extraSelections); } +QList TextEditorWidgetPrivate::generateCursorsForBlockSelection( + const BlockSelection &blockSelection) +{ + const TabSettings tabSettings = m_document->tabSettings(); + + QList result; + QTextBlock block = m_document->document()->findBlockByNumber(blockSelection.anchorBlockNumber); + QTextCursor cursor(block); + cursor.setPosition(block.position() + + tabSettings.positionAtColumn(block.text(), blockSelection.anchorColumn)); + + const bool forward = blockSelection.blockNumber > blockSelection.anchorBlockNumber + || (blockSelection.blockNumber == blockSelection.anchorBlockNumber + && blockSelection.column == blockSelection.anchorColumn); + + while (block.isValid()) { + const QString &blockText = block.text(); + cursor.setPosition(block.position() + + tabSettings.positionAtColumn(blockText, blockSelection.anchorColumn)); + cursor.setPosition(block.position() + + tabSettings.positionAtColumn(blockText, blockSelection.column), + QTextCursor::KeepAnchor); + result.append(cursor); + if (block.blockNumber() == blockSelection.blockNumber) + break; + block = forward ? block.next() : block.previous(); + } + return result; +} + +void TextEditorWidgetPrivate::initBlockSelection() +{ + const TabSettings tabSettings = m_document->tabSettings(); + for (const QTextCursor &cursor : m_cursors) { + const int column = tabSettings.columnAtCursorPosition(cursor); + QTextCursor anchor = cursor; + anchor.setPosition(anchor.anchor()); + const int anchorColumn = tabSettings.columnAtCursorPosition(anchor); + m_blockSelections.append({cursor.blockNumber(), column, anchor.blockNumber(), anchorColumn}); + } +} + +void TextEditorWidgetPrivate::clearBlockSelection() +{ + m_blockSelections.clear(); +} + +void TextEditorWidgetPrivate::handleMoveBlockSelection(QTextCursor::MoveOperation op) +{ + if (m_blockSelections.isEmpty()) + initBlockSelection(); + QList cursors; + for (BlockSelection &blockSelection : m_blockSelections) { + switch (op) { + case QTextCursor::Up: + blockSelection.blockNumber = qMax(0, blockSelection.blockNumber - 1); + break; + case QTextCursor::Down: + blockSelection.blockNumber = qMin(m_document->document()->blockCount() - 1, + blockSelection.blockNumber + 1); + break; + case QTextCursor::NextCharacter: + ++blockSelection.column; + break; + case QTextCursor::PreviousCharacter: + blockSelection.column = qMax(0, blockSelection.column - 1); + break; + default: + return; + } + cursors.append(generateCursorsForBlockSelection(blockSelection)); + } + q->setMultiTextCursor(MultiTextCursor(cursors)); +} + void TextEditorWidget::selectEncoding() { TextDocument *doc = d->m_document.data(); @@ -2181,6 +2268,8 @@ static inline bool isPrintableText(const QString &text) void TextEditorWidget::keyPressEvent(QKeyEvent *e) { + ExecuteOnDestruction eod([&]() { d->clearBlockSelection(); }); + if (!isModifier(e) && mouseHidingEnabled()) viewport()->setCursor(Qt::BlankCursor); ToolTip::hide(); @@ -2420,7 +2509,23 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e) } if (ro || !isPrintableText(eventText)) { - if (!d->cursorMoveKeyEvent(e)) { + QTextCursor::MoveOperation blockSelectionOperation = QTextCursor::NoMove; + if (e->modifiers() & Qt::AltModifier && !Utils::HostOsInfo::isMacHost()) { + if (MultiTextCursor::multiCursorAddEvent(e, QKeySequence::MoveToNextLine)) + blockSelectionOperation = QTextCursor::Down; + else if (MultiTextCursor::multiCursorAddEvent(e, QKeySequence::MoveToPreviousLine)) + blockSelectionOperation = QTextCursor::Up; + else if (MultiTextCursor::multiCursorAddEvent(e, QKeySequence::MoveToNextChar)) + blockSelectionOperation = QTextCursor::NextCharacter; + else if (MultiTextCursor::multiCursorAddEvent(e, QKeySequence::MoveToPreviousChar)) + blockSelectionOperation = QTextCursor::PreviousCharacter; + } + + if (blockSelectionOperation != QTextCursor::NoMove) { + auto doNothing = [](){}; + eod.reset(doNothing); + d->handleMoveBlockSelection(blockSelectionOperation); + } else if (!d->cursorMoveKeyEvent(e)) { QTextCursor cursor = textCursor(); bool cursorWithinSnippet = false; if (d->m_snippetOverlay->isVisible() @@ -5134,7 +5239,7 @@ void TextEditorWidget::mouseMoveEvent(QMouseEvent *e) cursor.addCursor(c); } cursor.mergeCursors(); - if (!cursor.isNull() && cursor != multiTextCursor()) + if (!cursor.isNull()) setMultiTextCursor(cursor); } else { if (startMouseMoveCursor.has_value())