diff --git a/autotests/src/katedocument_test.cpp b/autotests/src/katedocument_test.cpp --- a/autotests/src/katedocument_test.cpp +++ b/autotests/src/katedocument_test.cpp @@ -358,6 +358,37 @@ doc.setText("foo \"bar"); typeText("\" haz"); QCOMPARE(doc.text(), testInput); + + // Let's check to add brackets to a selection... + view->setBlockSelection(false); + doc.setText("012xxx678"); + view->setSelection(Range(0, 3, 0, 6)); + typeText("["); + QCOMPARE(doc.text(), "012[xxx]678"); + QCOMPARE(view->selectionRange(), Range(0, 4, 0, 7)); + + // ...over multiple lines.. + doc.setText("012xxx678\n012xxx678"); + view->setSelection(Range(0, 3, 1, 6)); + typeText("["); + QCOMPARE(doc.text(), "012[xxx678\n012xxx]678"); + QCOMPARE(view->selectionRange(), Range(0, 4, 1, 6)); + + // ..once again in in block mode with increased complexity.. + view->setBlockSelection(true); + doc.setText("012xxx678\n012xxx678"); + view->setSelection(Range(0, 3, 1, 6)); + typeText("[("); + QCOMPARE(doc.text(), "012[(xxx)]678\n012[(xxx)]678"); + QCOMPARE(view->selectionRange(), Range(0, 5, 1, 8)); + + // ..and the same with right->left selection + view->setBlockSelection(true); + doc.setText("012xxx678\n012xxx678"); + view->setSelection(Range(0, 6, 1, 3)); + typeText("[("); + QCOMPARE(doc.text(), "012[(xxx)]678\n012[(xxx)]678"); + QCOMPARE(view->selectionRange(), Range(0, 8, 1, 5)); } void KateDocumentTest::testReplaceTabs() diff --git a/src/document/katedocument.cpp b/src/document/katedocument.cpp --- a/src/document/katedocument.cpp +++ b/src/document/katedocument.cpp @@ -3044,145 +3044,158 @@ } } + editStart(); + /** - * selection around => special handling if we want to add auto brackets + * special handling if we want to add auto brackets to a selection */ if (view->selection() && !closingBracket.isNull()) { - /** - * add bracket at start + end of the selection - */ - KTextEditor::Cursor oldCur = view->cursorPosition(); - insertText(view->selectionRange().start(), chars); - view->slotTextInserted(view, oldCur, chars); + std::unique_ptr selectionRange(newMovingRange(view->selectionRange())); + const int startLine = qMax(0, selectionRange->start().line()); + const int endLine = qMin(selectionRange->end().line(), lastLine()); + const bool blockMode = view->blockSelection() && (startLine != endLine); + if (blockMode) { + if (selectionRange->start().column() > selectionRange->end().column()) { + // Selection was done from right->left, requires special setting to ensure the new + // added brackets will not be part of the selection + selectionRange->setInsertBehaviors(MovingRange::ExpandLeft | MovingRange::ExpandRight); + } + // Add brackets to each line of the block + const int startColumn = qMin(selectionRange->start().column(), selectionRange->end().column()); + const int endColumn = qMax(selectionRange->start().column(), selectionRange->end().column()); + const KTextEditor::Range workingRange(startLine, startColumn, endLine, endColumn); + for (int line = startLine; line <= endLine; ++line) { + const KTextEditor::Range r(rangeOnLine(workingRange, line)); + insertText(r.end(), QString(closingBracket)); + view->slotTextInserted(view, r.end(), QString(closingBracket)); + insertText(r.start(), chars); + view->slotTextInserted(view, r.start(), chars); + } - view->setCursorPosition(view->selectionRange().end()); - oldCur = view->cursorPosition(); - insertText(view->selectionRange().end(), QString(closingBracket)); - view->slotTextInserted(view, oldCur, QString(closingBracket)); + } else { + // No block, just add to start & end of selection + insertText(selectionRange->end(), QString(closingBracket)); + view->slotTextInserted(view, selectionRange->end(), QString(closingBracket)); + insertText(selectionRange->start(), chars); + view->slotTextInserted(view, selectionRange->start(), chars); + } - /** - * expand selection - */ - view->setSelection(KTextEditor::Range(view->selectionRange().start() + Cursor{0, 1}, - view->cursorPosition() - Cursor{0, 1})); - view->setCursorPosition(view->selectionRange().start()); + // Refesh selection + view->setSelection(selectionRange->toRange()); + view->setCursorPosition(selectionRange->end()); + + editEnd(); + return true; } /** - * else normal handling + * normal handling */ - else { - editStart(); - - if (!view->config()->persistentSelection() && view->selection()) { - view->removeSelectedText(); - } + if (!view->config()->persistentSelection() && view->selection()) { + view->removeSelectedText(); + } - const KTextEditor::Cursor oldCur(view->cursorPosition()); - - const bool multiLineBlockMode = view->blockSelection() && view->selection(); - if (view->currentInputMode()->overwrite()) { - // blockmode multiline selection case: remove chars in every line - const KTextEditor::Range selectionRange = view->selectionRange(); - const int startLine = multiLineBlockMode ? qMax(0, selectionRange.start().line()) : view->cursorPosition().line(); - const int endLine = multiLineBlockMode ? qMin(selectionRange.end().line(), lastLine()) : startLine; - const int virtualColumn = toVirtualColumn(multiLineBlockMode ? selectionRange.end() : view->cursorPosition()); - - for (int line = endLine; line >= startLine; --line) { - Kate::TextLine textLine = m_buffer->plainLine(line); - Q_ASSERT(textLine); - const int column = fromVirtualColumn(line, virtualColumn); - KTextEditor::Range r = KTextEditor::Range(KTextEditor::Cursor(line, column), qMin(chars.length(), - textLine->length() - column)); - - // replace mode needs to know what was removed so it can be restored with backspace - if (oldCur.column() < lineLength(line)) { - QChar removed = characterAt(KTextEditor::Cursor(line, column)); - view->currentInputMode()->overwrittenChar(removed); - } + const KTextEditor::Cursor oldCur(view->cursorPosition()); - removeText(r); - } - } + const bool multiLineBlockMode = view->blockSelection() && view->selection(); + if (view->currentInputMode()->overwrite()) { + // blockmode multiline selection case: remove chars in every line + const KTextEditor::Range selectionRange = view->selectionRange(); + const int startLine = multiLineBlockMode ? qMax(0, selectionRange.start().line()) : view->cursorPosition().line(); + const int endLine = multiLineBlockMode ? qMin(selectionRange.end().line(), lastLine()) : startLine; + const int virtualColumn = toVirtualColumn(multiLineBlockMode ? selectionRange.end() : view->cursorPosition()); - if (multiLineBlockMode) { - KTextEditor::Range selectionRange = view->selectionRange(); - const int startLine = qMax(0, selectionRange.start().line()); - const int endLine = qMin(selectionRange.end().line(), lastLine()); - const int column = toVirtualColumn(selectionRange.end()); - for (int line = endLine; line >= startLine; --line) { - editInsertText(line, fromVirtualColumn(line, column), chars); + for (int line = endLine; line >= startLine; --line) { + Kate::TextLine textLine = m_buffer->plainLine(line); + Q_ASSERT(textLine); + const int column = fromVirtualColumn(line, virtualColumn); + KTextEditor::Range r = KTextEditor::Range(KTextEditor::Cursor(line, column), qMin(chars.length(), + textLine->length() - column)); + + // replace mode needs to know what was removed so it can be restored with backspace + if (oldCur.column() < lineLength(line)) { + QChar removed = characterAt(KTextEditor::Cursor(line, column)); + view->currentInputMode()->overwrittenChar(removed); } - int newSelectionColumn = toVirtualColumn(view->cursorPosition()); - selectionRange.setRange(KTextEditor::Cursor(selectionRange.start().line(), fromVirtualColumn(selectionRange.start().line(), newSelectionColumn)) - , KTextEditor::Cursor(selectionRange.end().line(), fromVirtualColumn(selectionRange.end().line(), newSelectionColumn))); - view->setSelection(selectionRange); - } else { - chars = eventuallyReplaceTabs(view->cursorPosition(), chars); - insertText(view->cursorPosition(), chars); - } - /** - * auto bracket handling for newly inserted text - * we inserted a bracket? - * => add the matching closing one to the view + input chars - * try to preserve the cursor position - */ - bool skipAutobrace = closingBracket == QLatin1Char('\''); - if (highlight() && skipAutobrace) { - // skip adding ' in spellchecked areas, because those are text - skipAutobrace = highlight()->spellCheckingRequiredForLocation(this, view->cursorPosition() - Cursor{0, 1}); + removeText(r); } + } - const auto cursorPos(view->cursorPosition()); - if (!skipAutobrace && (closingBracket == QLatin1Char('\''))) { - // skip auto quotes when these looks already balanced, bug 405089 - Kate::TextLine textLine = m_buffer->plainLine(cursorPos.line()); - // RegEx match quote, but not excaped quote, thanks to https://stackoverflow.com/a/11819111 - const int count = textLine->text().left(cursorPos.column()).count(QRegularExpression(QStringLiteral("(?plainLine(cursorPos.line()); - const int count = textLine->text().left(cursorPos.column()).count(QRegularExpression(QStringLiteral("(?selectionRange(); + const int startLine = qMax(0, selectionRange.start().line()); + const int endLine = qMin(selectionRange.end().line(), lastLine()); + const int column = toVirtualColumn(selectionRange.end()); + for (int line = endLine; line >= startLine; --line) { + editInsertText(line, fromVirtualColumn(line, column), chars); } + int newSelectionColumn = toVirtualColumn(view->cursorPosition()); + selectionRange.setRange(KTextEditor::Cursor(selectionRange.start().line(), fromVirtualColumn(selectionRange.start().line(), newSelectionColumn)) + , KTextEditor::Cursor(selectionRange.end().line(), fromVirtualColumn(selectionRange.end().line(), newSelectionColumn))); + view->setSelection(selectionRange); + } else { + chars = eventuallyReplaceTabs(view->cursorPosition(), chars); + insertText(view->cursorPosition(), chars); + } - if (!closingBracket.isNull() && !skipAutobrace) { - // add bracket to the view - const auto nextChar = view->document()->text({cursorPos, cursorPos + Cursor{0, 1}}).trimmed(); - if (nextChar.isEmpty() || !nextChar.at(0).isLetterOrNumber()) { - insertText(view->cursorPosition(), QString(closingBracket)); - const auto insertedAt(view->cursorPosition()); - view->setCursorPosition(cursorPos); - m_currentAutobraceRange.reset(newMovingRange({cursorPos - Cursor{0, 1}, insertedAt}, - KTextEditor::MovingRange::DoNotExpand)); - connect(view, &View::cursorPositionChanged, - this, &DocumentPrivate::checkCursorForAutobrace, Qt::UniqueConnection); - - // add bracket to chars inserted! needed for correct signals + indent - chars.append(closingBracket); - } - m_currentAutobraceClosingChar = closingBracket; + /** + * auto bracket handling for newly inserted text + * we inserted a bracket? + * => add the matching closing one to the view + input chars + * try to preserve the cursor position + */ + bool skipAutobrace = closingBracket == QLatin1Char('\''); + if (highlight() && skipAutobrace) { + // skip adding ' in spellchecked areas, because those are text + skipAutobrace = highlight()->spellCheckingRequiredForLocation(this, view->cursorPosition() - Cursor{0, 1}); + } + + const auto cursorPos(view->cursorPosition()); + if (!skipAutobrace && (closingBracket == QLatin1Char('\''))) { + // skip auto quotes when these looks already balanced, bug 405089 + Kate::TextLine textLine = m_buffer->plainLine(cursorPos.line()); + // RegEx match quote, but not excaped quote, thanks to https://stackoverflow.com/a/11819111 + const int count = textLine->text().left(cursorPos.column()).count(QRegularExpression(QStringLiteral("(?plainLine(cursorPos.line()); + const int count = textLine->text().left(cursorPos.column()).count(QRegularExpression(QStringLiteral("(?document()->text({cursorPos, cursorPos + Cursor{0, 1}}).trimmed(); + if (nextChar.isEmpty() || !nextChar.at(0).isLetterOrNumber()) { + insertText(view->cursorPosition(), QString(closingBracket)); + const auto insertedAt(view->cursorPosition()); + view->setCursorPosition(cursorPos); + m_currentAutobraceRange.reset(newMovingRange({cursorPos - Cursor{0, 1}, insertedAt}, + KTextEditor::MovingRange::DoNotExpand)); + connect(view, &View::cursorPositionChanged, + this, &DocumentPrivate::checkCursorForAutobrace, Qt::UniqueConnection); + + // add bracket to chars inserted! needed for correct signals + indent + chars.append(closingBracket); } + m_currentAutobraceClosingChar = closingBracket; + } - // end edit session here, to have updated HL in userTypedChar! - editEnd(); - - // trigger indentation - KTextEditor::Cursor b(view->cursorPosition()); - m_indenter->userTypedChar(view, b, chars.isEmpty() ? QChar() : chars.at(chars.length() - 1)); + // end edit session here, to have updated HL in userTypedChar! + editEnd(); - /** - * inform the view about the original inserted chars - */ - view->slotTextInserted(view, oldCur, chars); - } + // trigger indentation + KTextEditor::Cursor b(view->cursorPosition()); + m_indenter->userTypedChar(view, b, chars.isEmpty() ? QChar() : chars.at(chars.length() - 1)); /** - * be done + * inform the view about the original inserted chars */ + view->slotTextInserted(view, oldCur, chars); + return true; }