diff options
author | Nikolai Kosjar <[email protected]> | 2017-07-17 12:57:23 +0200 |
---|---|---|
committer | Nikolai Kosjar <[email protected]> | 2017-07-21 11:42:46 +0000 |
commit | a6aa287720112c70c1363bcb46d55d438fe57eac (patch) | |
tree | ebcc2c8005a92977fed1c391d07ac296a7cc2b34 /src/libs/cplusplus | |
parent | 2c9feb1f4b301e9a9228739a89c438bb7ff043f0 (diff) |
C++: Fine-tune auto insertion of '}'
Do not insert for these cases:
* <Cursor>{
* namespace X <Cursor>
* if the next block is indented, like e.g.:
if (e) <Cursor>
g();
* on empty line if text before looks like a finished statement or
scope opening/end
Change-Id: Id9decc1e964a775724a929c2a3e79b5283105560
Reviewed-by: David Schulz <[email protected]>
Diffstat (limited to 'src/libs/cplusplus')
-rw-r--r-- | src/libs/cplusplus/MatchingText.cpp | 225 | ||||
-rw-r--r-- | src/libs/cplusplus/MatchingText.h | 8 |
2 files changed, 211 insertions, 22 deletions
diff --git a/src/libs/cplusplus/MatchingText.cpp b/src/libs/cplusplus/MatchingText.cpp index 3fdb573b918..e2f04e4f0f2 100644 --- a/src/libs/cplusplus/MatchingText.cpp +++ b/src/libs/cplusplus/MatchingText.cpp @@ -34,6 +34,8 @@ #include <QChar> #include <QDebug> +#include <utils/algorithm.h> + using namespace CPlusPlus; enum { MAX_NUM_LINES = 20 }; @@ -135,16 +137,215 @@ static const Token tokenAtPosition(const Tokens &tokens, const unsigned pos) return Token(); } +static int tokenIndexBeforePosition(const Tokens &tokens, unsigned pos) +{ + for (int i = tokens.size() - 1; i >= 0; --i) { + if (tokens[i].utf16charsBegin() < pos) + return i; + } + return -1; +} + +static bool isCursorAtEndOfLineButMaybeBeforeComment(const Tokens &tokens, int pos) +{ + int index = tokenIndexBeforePosition(tokens, uint(pos)); + if (index == -1 || index >= tokens.size()) + return false; + + do { + ++index; + } while (index < tokens.size() && tokens[index].isComment()); + + return index >= tokens.size(); +} + +// 10.6.1 Attribute syntax and semantics +// This does not handle alignas() since it is not needed for the namespace case. +static int skipAttributeSpecifierSequence(const Tokens &tokens, int index) +{ + // [[ attribute-using-prefixopt attribute-list ]] + if (index >= 1 && tokens[index].is(T_RBRACKET) && tokens[index - 1].is(T_RBRACKET)) { + // Skip everything within [[ ]] + for (int i = index - 2; i >= 0; --i) { + if (i >= 1 && tokens[i].is(T_LBRACKET) && tokens[i - 1].is(T_LBRACKET)) + return i - 2; + } + + return -1; + } + + return index; +} + +static int skipNamespaceName(const Tokens &tokens, int index) +{ + if (index >= tokens.size()) + return -1; + + if (!tokens[index].is(T_IDENTIFIER)) + return index; + + // Accept + // SomeName + // Some::Nested::Name + bool expectIdentifier = false; + for (int i = index - 1; i >= 0; --i) { + if (expectIdentifier) { + if (tokens[i].is(T_IDENTIFIER)) + expectIdentifier = false; + else + return -1; + } else if (tokens[i].is(T_COLON_COLON)) { + expectIdentifier = true; + } else { + return i; + } + } + + return index; +} + +// 10.3.1 Namespace definition +static bool isAfterNamespaceDefinition(const Tokens &tokens, int position) +{ + int index = tokenIndexBeforePosition(tokens, uint(position)); + if (index == -1) + return false; + + // Handle optional name + index = skipNamespaceName(tokens, index); + if (index == -1) + return false; + + // Handle optional attribute specifier sequence + index = skipAttributeSpecifierSequence(tokens, index); + if (index == -1) + return false; + + return index >= 0 && tokens[index].is(T_NAMESPACE); +} + +static int isEmptyOrWhitespace(const QString &text) +{ + return Utils::allOf(text, [](const QChar &c) {return c.isSpace(); }); +} + +static QTextBlock previousNonEmptyBlock(const QTextBlock ¤tBlock) +{ + QTextBlock block = currentBlock.previous(); + forever { + if (!block.isValid() || !isEmptyOrWhitespace(block.text())) + return block; + block = block.previous(); + } +} + +static QTextBlock nextNonEmptyBlock(const QTextBlock ¤tBlock) +{ + QTextBlock block = currentBlock.next(); + forever { + if (!block.isValid() || !isEmptyOrWhitespace(block.text())) + return block; + block = block.next(); + } +} + +static bool allowAutoClosingBraceAtEmptyLine( + const QTextBlock &block, + MatchingText::IsNextBlockDeeperIndented isNextDeeperIndented) +{ + QTextBlock previousBlock = previousNonEmptyBlock(block); + if (!previousBlock.isValid()) + return false; // Nothing before + + QTextBlock nextBlock = nextNonEmptyBlock(block); + if (!nextBlock.isValid()) + return true; // Nothing behind + + if (isNextDeeperIndented && isNextDeeperIndented(previousBlock)) + return false; // Before indented + + const QString trimmedText = previousBlock.text().trimmed(); + return !trimmedText.endsWith(';') + && !trimmedText.endsWith('{') + && !trimmedText.endsWith('}'); +} + +static Tokens getTokens(const QTextCursor &cursor, int &prevState) +{ + LanguageFeatures features; + features.qtEnabled = false; + features.qtKeywordsEnabled = false; + features.qtMocRunEnabled = false; + features.cxx11Enabled = true; + features.cxxEnabled = true; + features.c99Enabled = true; + features.objCEnabled = true; + + SimpleLexer tokenize; + tokenize.setLanguageFeatures(features); + + prevState = BackwardsScanner::previousBlockState(cursor.block()) & 0xFF; + return tokenize(cursor.block().text(), prevState); +} + +static QChar firstNonSpace(const QTextCursor &cursor) +{ + int position = cursor.position(); + QChar ch = cursor.document()->characterAt(position); + while (ch.isSpace()) + ch = cursor.document()->characterAt(++position); + + return ch; +} + +static bool allowAutoClosingBraceByLookahead(const QTextCursor &cursor) +{ + const QChar lookAhead = firstNonSpace(cursor); + if (lookAhead.isNull()) + return true; + + switch (lookAhead.unicode()) { + case ';': case ',': + case ')': case '}': case ']': + return true; + } + + return false; +} + +static bool allowAutoClosingBrace(const QTextCursor &cursor, + MatchingText::IsNextBlockDeeperIndented isNextIndented) +{ + if (MatchingText::isInCommentHelper(cursor)) + return false; + + const QTextBlock block = cursor.block(); + if (isEmptyOrWhitespace(block.text())) + return allowAutoClosingBraceAtEmptyLine(cursor.block(), isNextIndented); + + int prevState; + const Tokens tokens = getTokens(cursor, prevState); + if (isAfterNamespaceDefinition(tokens, cursor.positionInBlock())) + return false; + + if (isCursorAtEndOfLineButMaybeBeforeComment(tokens, cursor.positionInBlock())) + return !(isNextIndented && isNextIndented(block)); + + return allowAutoClosingBraceByLookahead(cursor); +} + bool MatchingText::contextAllowsAutoParentheses(const QTextCursor &cursor, - const QString &textToInsert) + const QString &textToInsert, + IsNextBlockDeeperIndented isNextIndented) { QChar ch; if (!textToInsert.isEmpty()) ch = textToInsert.at(0); - if (ch == QLatin1Char('{') && cursor.block().text().trimmed().isEmpty()) - return false; // User just might want to wrap up some lines. + if (ch == QLatin1Char('{')) + return allowAutoClosingBrace(cursor, isNextIndented); if (!shouldInsertMatchingText(cursor) && ch != QLatin1Char('\'') && ch != QLatin1Char('"')) return false; @@ -198,24 +399,6 @@ bool MatchingText::shouldInsertMatchingText(QChar lookAhead) } // switch } -static Tokens getTokens(const QTextCursor &cursor, int &prevState) -{ - LanguageFeatures features; - features.qtEnabled = false; - features.qtKeywordsEnabled = false; - features.qtMocRunEnabled = false; - features.cxx11Enabled = true; - features.cxxEnabled = true; - features.c99Enabled = true; - features.objCEnabled = true; - - SimpleLexer tokenize; - tokenize.setLanguageFeatures(features); - - prevState = BackwardsScanner::previousBlockState(cursor.block()) & 0xFF; - return tokenize(cursor.block().text(), prevState); -} - bool MatchingText::isInCommentHelper(const QTextCursor &cursor, Token *retToken) { int prevState = 0; diff --git a/src/libs/cplusplus/MatchingText.h b/src/libs/cplusplus/MatchingText.h index 59463917c41..b394e342af3 100644 --- a/src/libs/cplusplus/MatchingText.h +++ b/src/libs/cplusplus/MatchingText.h @@ -30,7 +30,10 @@ #include <cplusplus/Token.h> #include <cplusplus/CPlusPlusForwardDeclarations.h> +#include <functional> + QT_FORWARD_DECLARE_CLASS(QTextCursor) +QT_FORWARD_DECLARE_CLASS(QTextBlock) QT_FORWARD_DECLARE_CLASS(QChar) namespace CPlusPlus { @@ -38,8 +41,11 @@ namespace CPlusPlus { class CPLUSPLUS_EXPORT MatchingText { public: + using IsNextBlockDeeperIndented = std::function<bool(const QTextBlock &textBlock)>; static bool contextAllowsAutoParentheses(const QTextCursor &cursor, - const QString &textToInsert); + const QString &textToInsert, + IsNextBlockDeeperIndented isNextIndented + = IsNextBlockDeeperIndented()); static bool contextAllowsAutoQuotes(const QTextCursor &cursor, const QString &textToInsert); static bool contextAllowsElectricCharacters(const QTextCursor &cursor); |