forked from qt-creator/qt-creator
C++: Check previous lines for auto insertion of closing brace
If '{' is typed, we check whether '}' should be auto-inserted. Previously, we did this by only looking at the tokens of the current line. By using the BackwardsScanner we can easily look also at the previous lines, which fixes e.g. namespace N <CURSOR> // type '{' - no '}' should be inserted. Change-Id: Ib2c2989c33f87464431d45facf986bcbb2eff2f8 Reviewed-by: Ivan Donchevskii <ivan.donchevskii@qt.io>
This commit is contained in:
@@ -35,6 +35,7 @@
|
||||
#include <QDebug>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/optional.h>
|
||||
|
||||
using namespace CPlusPlus;
|
||||
|
||||
@@ -137,50 +138,73 @@ static const Token tokenAtPosition(const Tokens &tokens, const unsigned pos)
|
||||
return Token();
|
||||
}
|
||||
|
||||
static int tokenIndexBeforePosition(const Tokens &tokens, unsigned pos)
|
||||
static LanguageFeatures languageFeatures()
|
||||
{
|
||||
for (int i = tokens.size() - 1; i >= 0; --i) {
|
||||
if (tokens[i].utf16charsBegin() < pos)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
LanguageFeatures features;
|
||||
features.qtEnabled = false;
|
||||
features.qtKeywordsEnabled = false;
|
||||
features.qtMocRunEnabled = false;
|
||||
features.cxx11Enabled = true;
|
||||
features.cxxEnabled = true;
|
||||
features.c99Enabled = true;
|
||||
features.objCEnabled = true;
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
static bool isCursorAtEndOfLineButMaybeBeforeComment(const Tokens &tokens, int pos)
|
||||
static Tokens getTokens(const QTextCursor &cursor, int &prevState)
|
||||
{
|
||||
int index = tokenIndexBeforePosition(tokens, uint(pos));
|
||||
if (index == -1 || index >= tokens.size())
|
||||
return false;
|
||||
SimpleLexer tokenize;
|
||||
tokenize.setLanguageFeatures(languageFeatures());
|
||||
|
||||
do {
|
||||
++index;
|
||||
} while (index < tokens.size() && tokens[index].isComment());
|
||||
|
||||
return index >= tokens.size();
|
||||
prevState = BackwardsScanner::previousBlockState(cursor.block()) & 0xFF;
|
||||
return tokenize(cursor.block().text(), prevState);
|
||||
}
|
||||
|
||||
static int isEmptyOrWhitespace(const QString &text)
|
||||
{
|
||||
return Utils::allOf(text, [](const QChar &c) {return c.isSpace(); });
|
||||
}
|
||||
|
||||
static bool isCursorAtEndOfLineButMaybeBeforeComment(const QTextCursor &cursor)
|
||||
{
|
||||
const QString textAfterCursor = cursor.block().text().mid(cursor.positionInBlock());
|
||||
if (isEmptyOrWhitespace(textAfterCursor))
|
||||
return true;
|
||||
|
||||
SimpleLexer tokenize;
|
||||
tokenize.setLanguageFeatures(languageFeatures());
|
||||
const Tokens tokens = tokenize(textAfterCursor);
|
||||
return Utils::allOf(tokens, [](const Token &token) { return token.isComment(); });
|
||||
}
|
||||
|
||||
using TokenIndexResult = Utils::optional<int>;
|
||||
|
||||
// 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)
|
||||
static TokenIndexResult skipAttributeSpecifierSequence(const BackwardsScanner &tokens, int index)
|
||||
{
|
||||
if (tokens[index].is(T_EOF_SYMBOL))
|
||||
return {};
|
||||
|
||||
// [[ attribute-using-prefixopt attribute-list ]]
|
||||
if (index >= 1 && tokens[index].is(T_RBRACKET) && tokens[index - 1].is(T_RBRACKET)) {
|
||||
if (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))
|
||||
for (int i = index - 2; !tokens[i].is(T_EOF_SYMBOL); --i) {
|
||||
if (tokens[i].is(T_LBRACKET) && tokens[i - 1].is(T_LBRACKET))
|
||||
return i - 2;
|
||||
}
|
||||
|
||||
return -1;
|
||||
return {};
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
static int skipNamespaceName(const Tokens &tokens, int index)
|
||||
static TokenIndexResult skipNamespaceName(const BackwardsScanner &tokens, int index)
|
||||
{
|
||||
if (index >= tokens.size())
|
||||
return -1;
|
||||
if (tokens[index].is(T_EOF_SYMBOL))
|
||||
return {};
|
||||
|
||||
if (!tokens[index].is(T_IDENTIFIER))
|
||||
return index;
|
||||
@@ -189,12 +213,12 @@ static int skipNamespaceName(const Tokens &tokens, int index)
|
||||
// SomeName
|
||||
// Some::Nested::Name
|
||||
bool expectIdentifier = false;
|
||||
for (int i = index - 1; i >= 0; --i) {
|
||||
for (int i = index - 1; !tokens[i].is(T_EOF_SYMBOL); --i) {
|
||||
if (expectIdentifier) {
|
||||
if (tokens[i].is(T_IDENTIFIER))
|
||||
expectIdentifier = false;
|
||||
else
|
||||
return -1;
|
||||
return {};
|
||||
} else if (tokens[i].is(T_COLON_COLON)) {
|
||||
expectIdentifier = true;
|
||||
} else {
|
||||
@@ -206,28 +230,22 @@ static int skipNamespaceName(const Tokens &tokens, int index)
|
||||
}
|
||||
|
||||
// 10.3.1 Namespace definition
|
||||
static bool isAfterNamespaceDefinition(const Tokens &tokens, int position)
|
||||
static bool isAfterNamespaceDefinition(const BackwardsScanner &tokens, int index)
|
||||
{
|
||||
int index = tokenIndexBeforePosition(tokens, uint(position));
|
||||
if (index == -1)
|
||||
if (tokens[index].is(T_EOF_SYMBOL))
|
||||
return false;
|
||||
|
||||
// Handle optional name
|
||||
index = skipNamespaceName(tokens, index);
|
||||
if (index == -1)
|
||||
TokenIndexResult newIndex = skipNamespaceName(tokens, index);
|
||||
if (!newIndex)
|
||||
return false;
|
||||
|
||||
// Handle optional attribute specifier sequence
|
||||
index = skipAttributeSpecifierSequence(tokens, index);
|
||||
if (index == -1)
|
||||
newIndex = skipAttributeSpecifierSequence(tokens, newIndex.value());
|
||||
if (!newIndex)
|
||||
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(); });
|
||||
return tokens[newIndex.value()].is(T_NAMESPACE);
|
||||
}
|
||||
|
||||
static QTextBlock previousNonEmptyBlock(const QTextBlock ¤tBlock)
|
||||
@@ -271,24 +289,6 @@ static bool allowAutoClosingBraceAtEmptyLine(
|
||||
&& !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();
|
||||
@@ -320,21 +320,20 @@ static bool allowAutoClosingBrace(const QTextCursor &cursor,
|
||||
if (MatchingText::isInCommentHelper(cursor))
|
||||
return false;
|
||||
|
||||
BackwardsScanner tokens(cursor, languageFeatures(), /*maxBlockCount=*/ 5);
|
||||
const int index = tokens.startToken() - 1;
|
||||
|
||||
if (tokens[index].isStringLiteral())
|
||||
return false;
|
||||
|
||||
if (isAfterNamespaceDefinition(tokens, index))
|
||||
return false;
|
||||
|
||||
const QTextBlock block = cursor.block();
|
||||
if (isEmptyOrWhitespace(block.text()))
|
||||
return allowAutoClosingBraceAtEmptyLine(cursor.block(), isNextIndented);
|
||||
|
||||
int prevState;
|
||||
const Tokens tokens = getTokens(cursor, prevState);
|
||||
|
||||
const Token token = tokenAtPosition(tokens, cursor.positionInBlock());
|
||||
if (token.isStringLiteral())
|
||||
return false;
|
||||
|
||||
if (isAfterNamespaceDefinition(tokens, cursor.positionInBlock()))
|
||||
return false;
|
||||
|
||||
if (isCursorAtEndOfLineButMaybeBeforeComment(tokens, cursor.positionInBlock()))
|
||||
if (isCursorAtEndOfLineButMaybeBeforeComment(cursor))
|
||||
return !(isNextIndented && isNextIndented(block));
|
||||
|
||||
return allowAutoClosingBraceByLookahead(cursor);
|
||||
|
Reference in New Issue
Block a user