Index: autotests/src/katedocument_test.h =================================================================== --- autotests/src/katedocument_test.h +++ autotests/src/katedocument_test.h @@ -37,25 +37,18 @@ void testWordWrap(); void testReplaceQStringList(); void testMovingInterfaceSignals(); - void testSetTextPerformance(); void testRemoveTextPerformance(); - void testForgivingApiUsage(); - void testRemoveMultipleLines(); void testInsertNewline(); void testInsertAfterEOF(); - void testReplaceTabs(); - + void testAutoBrackets(); void testDigest(); void testModelines(); - void testDefStyleNum(); - void testTypeCharsWithSurrogateAndNewLine(); - void testRemoveComposedCharacters(); }; Index: autotests/src/katedocument_test.cpp =================================================================== --- autotests/src/katedocument_test.cpp +++ autotests/src/katedocument_test.cpp @@ -280,6 +280,66 @@ QVERIFY(doc.removeText(Range(0, 0, 0, 1000))); } +void KateDocumentTest::testAutoBrackets() +{ + KTextEditor::DocumentPrivate doc; + auto view = static_cast(doc.createView(nullptr)); + + auto reset = [&]() { + doc.setText(""); + view->setCursorPosition(Cursor(0, 0)); + }; + + auto typeText = [&](const QString &text) { + for (int i = 0; i < text.size(); ++i) { + doc.typeChars(view, text.at(i)); + } + }; + + doc.setHighlightingMode ("Normal"); // Just to be sure + view->config()->setAutoBrackets(true); + + QString testInput; + + testInput = ("("); + typeText(testInput); + QCOMPARE(doc.text(), "()"); + + reset(); + testInput = ("\""); + typeText(testInput); + QCOMPARE(doc.text(), "\"\""); + + reset(); + testInput = ("'"); + typeText(testInput); + QCOMPARE(doc.text(), "'"); // In Normal mode there is only one quote to expect + + // + // Switch over to some other mode + // + doc.setHighlightingMode ("C++"); + + reset(); + typeText(testInput); + QCOMPARE(doc.text(), "''"); // Now it must be two + + reset(); + testInput = "('')"; + typeText(testInput); + // Known bad behaviour in case of nested brackets + QCOMPARE(doc.text(), "(''))"); // FIXME/TODO should be "('')" like testInput + + reset(); + testInput = ("foo \"bar\" haz"); + typeText(testInput); + QCOMPARE(doc.text(), testInput); + // Simulate afterwards to add quotes, bug 405089 + doc.setText("foo \"bar"); + typeText("\" haz"); + QCOMPARE(doc.text(), testInput); +} + void KateDocumentTest::testReplaceTabs() { KTextEditor::DocumentPrivate doc; Index: src/document/katedocument.cpp =================================================================== --- src/document/katedocument.cpp +++ src/document/katedocument.cpp @@ -114,19 +114,17 @@ return indexOf(list, entry) >= 0; } -static inline QChar matchingStartBracket(QChar c, bool withQuotes) +static inline QChar matchingStartBracket(const QChar c) { switch (c.toLatin1()) { case '}': return QLatin1Char('{'); case ']': return QLatin1Char('['); case ')': return QLatin1Char('('); - case '\'': return withQuotes ? QLatin1Char('\'') : QChar(); - case '"': return withQuotes ? QLatin1Char('"') : QChar(); } return QChar(); } -static inline QChar matchingEndBracket(QChar c, bool withQuotes) +static inline QChar matchingEndBracket(const QChar c, bool withQuotes = true) { switch (c.toLatin1()) { case '{': return QLatin1Char('}'); @@ -138,26 +136,26 @@ return QChar(); } -static inline QChar matchingBracket(QChar c, bool withQuotes) +static inline QChar matchingBracket(const QChar c) { - QChar bracket = matchingStartBracket(c, withQuotes); + QChar bracket = matchingStartBracket(c); if (bracket.isNull()) { - bracket = matchingEndBracket(c, false); + bracket = matchingEndBracket(c, /*withQuotes=*/false); } return bracket; } -static inline bool isStartBracket(const QChar &c) +static inline bool isStartBracket(const QChar c) { - return ! matchingEndBracket(c, false).isNull(); + return ! matchingEndBracket(c, /*withQuotes=*/false).isNull(); } -static inline bool isEndBracket(const QChar &c) +static inline bool isEndBracket(const QChar c) { - return ! matchingStartBracket(c, false).isNull(); + return ! matchingStartBracket(c).isNull(); } -static inline bool isBracket(const QChar &c) +static inline bool isBracket(const QChar c) { return isStartBracket(c) || isEndBracket(c); } @@ -2957,16 +2955,17 @@ */ QChar closingBracket; if (view->config()->autoBrackets() && chars.size() == 1) { + const QChar typedChar = chars.at(0); /** * we inserted a bracket? * => remember the matching closing one */ - closingBracket = matchingEndBracket(chars[0], true); + closingBracket = matchingEndBracket(typedChar); /** * closing bracket for the autobracket we inserted earlier? */ - if ( m_currentAutobraceClosingChar == chars[0] && m_currentAutobraceRange ) { + if (m_currentAutobraceClosingChar == typedChar && m_currentAutobraceRange) { // do nothing m_currentAutobraceRange.reset(nullptr); view->cursorRight(); @@ -3059,15 +3058,30 @@ * try to preserve the cursor position */ bool skipAutobrace = closingBracket == QLatin1Char('\''); - if ( highlight() && skipAutobrace ) { + if (highlight() && skipAutobrace) { // skip adding ' in spellchecked areas, because those are text skipAutobrace = highlight()->spellCheckingRequiredForLocation(this, view->cursorPosition() - Cursor{0, 1}); } - if (!closingBracket.isNull() && !skipAutobrace ) { + + 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("(?cursorPosition()); const auto nextChar = view->document()->text({cursorPos, cursorPos + Cursor{0, 1}}).trimmed(); - if ( nextChar.isEmpty() || ! nextChar.at(0).isLetterOrNumber() ) { + if (nextChar.isEmpty() || !nextChar.at(0).isLetterOrNumber()) { insertText(view->cursorPosition(), QString(closingBracket)); const auto insertedAt(view->cursorPosition()); view->setCursorPosition(cursorPos); @@ -4065,7 +4079,7 @@ return KTextEditor::Range::invalid(); } - const QChar opposite = matchingBracket(bracket, false); + const QChar opposite = matchingBracket(bracket); if (opposite.isNull()) { return KTextEditor::Range::invalid(); }