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 <QDebug>
|
||||||
|
|
||||||
#include <utils/algorithm.h>
|
#include <utils/algorithm.h>
|
||||||
|
#include <utils/optional.h>
|
||||||
|
|
||||||
using namespace CPlusPlus;
|
using namespace CPlusPlus;
|
||||||
|
|
||||||
@@ -137,50 +138,73 @@ static const Token tokenAtPosition(const Tokens &tokens, const unsigned pos)
|
|||||||
return Token();
|
return Token();
|
||||||
}
|
}
|
||||||
|
|
||||||
static int tokenIndexBeforePosition(const Tokens &tokens, unsigned pos)
|
static LanguageFeatures languageFeatures()
|
||||||
{
|
{
|
||||||
for (int i = tokens.size() - 1; i >= 0; --i) {
|
LanguageFeatures features;
|
||||||
if (tokens[i].utf16charsBegin() < pos)
|
features.qtEnabled = false;
|
||||||
return i;
|
features.qtKeywordsEnabled = false;
|
||||||
}
|
features.qtMocRunEnabled = false;
|
||||||
return -1;
|
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));
|
SimpleLexer tokenize;
|
||||||
if (index == -1 || index >= tokens.size())
|
tokenize.setLanguageFeatures(languageFeatures());
|
||||||
return false;
|
|
||||||
|
|
||||||
do {
|
prevState = BackwardsScanner::previousBlockState(cursor.block()) & 0xFF;
|
||||||
++index;
|
return tokenize(cursor.block().text(), prevState);
|
||||||
} while (index < tokens.size() && tokens[index].isComment());
|
|
||||||
|
|
||||||
return index >= tokens.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// 10.6.1 Attribute syntax and semantics
|
||||||
// This does not handle alignas() since it is not needed for the namespace case.
|
// 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 ]]
|
// [[ 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 [[ ]]
|
// Skip everything within [[ ]]
|
||||||
for (int i = index - 2; i >= 0; --i) {
|
for (int i = index - 2; !tokens[i].is(T_EOF_SYMBOL); --i) {
|
||||||
if (i >= 1 && tokens[i].is(T_LBRACKET) && tokens[i - 1].is(T_LBRACKET))
|
if (tokens[i].is(T_LBRACKET) && tokens[i - 1].is(T_LBRACKET))
|
||||||
return i - 2;
|
return i - 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int skipNamespaceName(const Tokens &tokens, int index)
|
static TokenIndexResult skipNamespaceName(const BackwardsScanner &tokens, int index)
|
||||||
{
|
{
|
||||||
if (index >= tokens.size())
|
if (tokens[index].is(T_EOF_SYMBOL))
|
||||||
return -1;
|
return {};
|
||||||
|
|
||||||
if (!tokens[index].is(T_IDENTIFIER))
|
if (!tokens[index].is(T_IDENTIFIER))
|
||||||
return index;
|
return index;
|
||||||
@@ -189,12 +213,12 @@ static int skipNamespaceName(const Tokens &tokens, int index)
|
|||||||
// SomeName
|
// SomeName
|
||||||
// Some::Nested::Name
|
// Some::Nested::Name
|
||||||
bool expectIdentifier = false;
|
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 (expectIdentifier) {
|
||||||
if (tokens[i].is(T_IDENTIFIER))
|
if (tokens[i].is(T_IDENTIFIER))
|
||||||
expectIdentifier = false;
|
expectIdentifier = false;
|
||||||
else
|
else
|
||||||
return -1;
|
return {};
|
||||||
} else if (tokens[i].is(T_COLON_COLON)) {
|
} else if (tokens[i].is(T_COLON_COLON)) {
|
||||||
expectIdentifier = true;
|
expectIdentifier = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -206,28 +230,22 @@ static int skipNamespaceName(const Tokens &tokens, int index)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 10.3.1 Namespace definition
|
// 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 (tokens[index].is(T_EOF_SYMBOL))
|
||||||
if (index == -1)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Handle optional name
|
// Handle optional name
|
||||||
index = skipNamespaceName(tokens, index);
|
TokenIndexResult newIndex = skipNamespaceName(tokens, index);
|
||||||
if (index == -1)
|
if (!newIndex)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Handle optional attribute specifier sequence
|
// Handle optional attribute specifier sequence
|
||||||
index = skipAttributeSpecifierSequence(tokens, index);
|
newIndex = skipAttributeSpecifierSequence(tokens, newIndex.value());
|
||||||
if (index == -1)
|
if (!newIndex)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return index >= 0 && tokens[index].is(T_NAMESPACE);
|
return tokens[newIndex.value()].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)
|
static QTextBlock previousNonEmptyBlock(const QTextBlock ¤tBlock)
|
||||||
@@ -271,24 +289,6 @@ static bool allowAutoClosingBraceAtEmptyLine(
|
|||||||
&& !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)
|
static QChar firstNonSpace(const QTextCursor &cursor)
|
||||||
{
|
{
|
||||||
int position = cursor.position();
|
int position = cursor.position();
|
||||||
@@ -320,21 +320,20 @@ static bool allowAutoClosingBrace(const QTextCursor &cursor,
|
|||||||
if (MatchingText::isInCommentHelper(cursor))
|
if (MatchingText::isInCommentHelper(cursor))
|
||||||
return false;
|
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();
|
const QTextBlock block = cursor.block();
|
||||||
if (isEmptyOrWhitespace(block.text()))
|
if (isEmptyOrWhitespace(block.text()))
|
||||||
return allowAutoClosingBraceAtEmptyLine(cursor.block(), isNextIndented);
|
return allowAutoClosingBraceAtEmptyLine(cursor.block(), isNextIndented);
|
||||||
|
|
||||||
int prevState;
|
if (isCursorAtEndOfLineButMaybeBeforeComment(cursor))
|
||||||
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()))
|
|
||||||
return !(isNextIndented && isNextIndented(block));
|
return !(isNextIndented && isNextIndented(block));
|
||||||
|
|
||||||
return allowAutoClosingBraceByLookahead(cursor);
|
return allowAutoClosingBraceByLookahead(cursor);
|
||||||
|
Reference in New Issue
Block a user