summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMarco Bubke <marco.bubke@theqtcompany.com>2015-07-23 13:01:02 +0200
committerNikolai Kosjar <nikolai.kosjar@theqtcompany.com>2015-07-23 11:13:05 +0000
commitae5d92d6182ecdf64b3d5453ebcff9fc0c589016 (patch)
treebb6d233102ca890ff5afa6ce3daec9c962cc26ec /src
parent5a791e88394513a7fd632263f95b632dd84e1cfd (diff)
downloadqt-creator-ae5d92d6182ecdf64b3d5453ebcff9fc0c589016.tar.gz
Clang: Refactor ClangCompletionContextAnalyzer
Change-Id: Ib42ddc672da8b068591129e2e0b9652d3e07ad58 Reviewed-by: Nikolai Kosjar <nikolai.kosjar@theqtcompany.com>
Diffstat (limited to 'src')
-rw-r--r--src/plugins/clangcodemodel/activationsequencecontextprocessor.cpp249
-rw-r--r--src/plugins/clangcodemodel/activationsequencecontextprocessor.h (renamed from src/plugins/clangcodemodel/test/clangcompletioncontextanalyzertest.h)63
-rw-r--r--src/plugins/clangcodemodel/activationsequenceprocessor.cpp4
-rw-r--r--src/plugins/clangcodemodel/clangcodemodel.pro9
-rw-r--r--src/plugins/clangcodemodel/clangcodemodel.qbs4
-rw-r--r--src/plugins/clangcodemodel/clangcodemodelplugin.cpp2
-rw-r--r--src/plugins/clangcodemodel/clangcodemodelunittestfiles.pri8
-rw-r--r--src/plugins/clangcodemodel/clangcompletioncontextanalyzer.cpp301
-rw-r--r--src/plugins/clangcodemodel/clangcompletioncontextanalyzer.h15
-rw-r--r--src/plugins/clangcodemodel/test/clangcompletioncontextanalyzertest.cpp234
10 files changed, 396 insertions, 493 deletions
diff --git a/src/plugins/clangcodemodel/activationsequencecontextprocessor.cpp b/src/plugins/clangcodemodel/activationsequencecontextprocessor.cpp
new file mode 100644
index 0000000000..6edb842613
--- /dev/null
+++ b/src/plugins/clangcodemodel/activationsequencecontextprocessor.cpp
@@ -0,0 +1,249 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms and
+** conditions see http://www.qt.io/terms-conditions. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#include "activationsequencecontextprocessor.h"
+
+#include "activationsequenceprocessor.h"
+
+#include <cplusplus/BackwardsScanner.h>
+#include <cplusplus/ExpressionUnderCursor.h>
+#include <cplusplus/SimpleLexer.h>
+
+#include <QRegExp>
+#include <QTextDocument>
+
+namespace ClangCodeModel {
+namespace Internal {
+
+ActivationSequenceContextProcessor::ActivationSequenceContextProcessor(const ClangCompletionAssistInterface *assistInterface)
+ : m_textCursor(assistInterface->textDocument()),
+ m_assistInterface(assistInterface),
+ m_positionInDocument(assistInterface->position()),
+ m_positionAfterOperator(m_positionInDocument),
+ m_positionBeforeOperator(m_positionAfterOperator)
+
+{
+ m_textCursor.setPosition(m_positionInDocument);
+
+ process();
+}
+
+CPlusPlus::Kind ActivationSequenceContextProcessor::completionKind() const
+{
+ return m_completionKind;
+}
+
+const QTextCursor &ActivationSequenceContextProcessor::textCursor_forTestOnly() const
+{
+ return m_textCursor;
+}
+
+int ActivationSequenceContextProcessor::positionAfterOperator() const
+{
+ return m_positionAfterOperator;
+}
+
+int ActivationSequenceContextProcessor::positionBeforeOperator() const
+{
+ return m_positionBeforeOperator;
+}
+
+void ActivationSequenceContextProcessor::process()
+{
+ skipeWhiteSpacesAndIdentifierBeforeCursor();
+ processActivationSequence();
+
+ if (m_completionKind != CPlusPlus::T_EOF_SYMBOL) {
+ processStringLiteral();
+ processComma();
+ generateTokens();
+ processDoxygenComment();
+ processComment();
+ processInclude();
+ processSlashOutsideOfAString();
+ processLeftParen();
+ processPreprocessorInclude();
+ resetPositionForEOFCompletionKind();
+ }
+}
+
+void ActivationSequenceContextProcessor::processActivationSequence()
+{
+ const auto activationSequence = m_assistInterface->textAt(m_positionInDocument - 3, 3);
+ ActivationSequenceProcessor activationSequenceProcessor(activationSequence,
+ m_positionInDocument,
+ true);
+
+ m_completionKind = activationSequenceProcessor.completionKind();
+ m_positionBeforeOperator = activationSequenceProcessor.position();
+
+}
+
+void ActivationSequenceContextProcessor::processStringLiteral()
+{
+ if (m_completionKind == CPlusPlus::T_STRING_LITERAL) {
+ QTextCursor selectionTextCursor = m_textCursor;
+ selectionTextCursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
+ QString selection = selectionTextCursor.selectedText();
+ if (selection.indexOf(QLatin1Char('"')) < selection.length() - 1)
+ m_completionKind = CPlusPlus::T_EOF_SYMBOL;
+ }
+}
+
+void ActivationSequenceContextProcessor::processComma()
+{
+ if (m_completionKind == CPlusPlus::T_COMMA) {
+ CPlusPlus::ExpressionUnderCursor expressionUnderCursor(m_assistInterface->languageFeatures());
+ if (expressionUnderCursor.startOfFunctionCall(m_textCursor) == -1)
+ m_completionKind = CPlusPlus::T_EOF_SYMBOL;
+ }
+}
+
+void ActivationSequenceContextProcessor::generateTokens()
+{
+ CPlusPlus::SimpleLexer tokenize;
+ tokenize.setLanguageFeatures(m_assistInterface->languageFeatures());
+ tokenize.setSkipComments(false);
+ auto state = CPlusPlus::BackwardsScanner::previousBlockState(m_textCursor.block());
+ m_tokens = tokenize(m_textCursor.block().text(), state);
+ int leftOfCursorTokenIndex = std::max(0, m_textCursor.positionInBlock() - 1);
+ m_tokenIndex= CPlusPlus::SimpleLexer::tokenBefore(m_tokens, leftOfCursorTokenIndex); // get the token at the left of the cursor
+ if (m_tokenIndex > -1)
+ m_token = m_tokens.at(m_tokenIndex);
+}
+
+void ActivationSequenceContextProcessor::processDoxygenComment()
+{
+ if (m_completionKind == CPlusPlus::T_DOXY_COMMENT
+ && !(m_token.is(CPlusPlus::T_DOXY_COMMENT)
+ || m_token.is(CPlusPlus::T_CPP_DOXY_COMMENT)))
+ m_completionKind = CPlusPlus::T_EOF_SYMBOL;
+}
+
+void ActivationSequenceContextProcessor::processComment()
+{
+ if (m_token.is(CPlusPlus::T_COMMENT) || m_token.is(CPlusPlus::T_CPP_COMMENT))
+ m_completionKind = CPlusPlus::T_EOF_SYMBOL;
+}
+
+void ActivationSequenceContextProcessor::processInclude()
+{
+ if (m_token.isLiteral() && !isCompletionKindStringLiteralOrSlash())
+ m_completionKind = CPlusPlus::T_EOF_SYMBOL;
+}
+
+void ActivationSequenceContextProcessor::processSlashOutsideOfAString()
+{
+ if (m_completionKind ==CPlusPlus::T_SLASH
+ && (m_token.isNot(CPlusPlus::T_STRING_LITERAL)
+ && m_token.isNot(CPlusPlus::T_ANGLE_STRING_LITERAL)))
+ m_completionKind = CPlusPlus::T_EOF_SYMBOL;
+}
+
+void ActivationSequenceContextProcessor::processLeftParen()
+{
+ if (m_completionKind == CPlusPlus::T_LPAREN) {
+ if (m_tokenIndex > 0) {
+ // look at the token at the left of T_LPAREN
+ const CPlusPlus::Token &previousToken = m_tokens.at(m_tokenIndex - 1);
+ switch (previousToken.kind()) {
+ case CPlusPlus::T_IDENTIFIER:
+ case CPlusPlus::T_GREATER:
+ case CPlusPlus::T_SIGNAL:
+ case CPlusPlus::T_SLOT:
+ break; // good
+
+ default:
+ // that's a bad token :)
+ m_completionKind = CPlusPlus::T_EOF_SYMBOL;
+ }
+ }
+ }
+}
+
+bool ActivationSequenceContextProcessor::isCompletionKindStringLiteralOrSlash() const
+{
+ return m_completionKind == CPlusPlus::T_STRING_LITERAL
+ || m_completionKind == CPlusPlus::T_ANGLE_STRING_LITERAL
+ || m_completionKind == CPlusPlus::T_SLASH;
+}
+
+bool ActivationSequenceContextProcessor::isProbablyPreprocessorIncludeDirective() const
+{
+ return m_tokens.size() >= 3
+ && m_tokens.at(0).is(CPlusPlus::T_POUND)
+ && m_tokens.at(1).is(CPlusPlus::T_IDENTIFIER)
+ && (m_tokens.at(2).is(CPlusPlus::T_STRING_LITERAL)
+ || m_tokens.at(2).is(CPlusPlus::T_ANGLE_STRING_LITERAL));
+}
+
+void ActivationSequenceContextProcessor::processPreprocessorInclude()
+{
+ if (isCompletionKindStringLiteralOrSlash()) {
+ if (isProbablyPreprocessorIncludeDirective()) {
+ const CPlusPlus::Token &directiveToken = m_tokens.at(1);
+ QString directive = m_textCursor.block().text().mid(directiveToken.bytesBegin(),
+ directiveToken.bytes());
+ if (directive != QStringLiteral("include")
+ && directive != QStringLiteral("include_next")
+ && directive != QStringLiteral("import"))
+ m_completionKind = CPlusPlus::T_EOF_SYMBOL;
+ } else {
+ m_completionKind = CPlusPlus::T_EOF_SYMBOL;
+ }
+ }
+}
+
+void ActivationSequenceContextProcessor::resetPositionForEOFCompletionKind()
+{
+ if (m_completionKind == CPlusPlus::T_EOF_SYMBOL)
+ m_positionBeforeOperator = m_positionInDocument;
+}
+
+void ActivationSequenceContextProcessor::skipeWhiteSpacesAndIdentifierBeforeCursor()
+{
+ QTextDocument *document = m_assistInterface->textDocument();
+
+ const QRegExp findNonWhiteSpaceRegularExpression(QStringLiteral("[^\\s\\w]"));
+
+ auto nonWhiteSpaceTextCursor = document->find(findNonWhiteSpaceRegularExpression,
+ m_positionInDocument,
+ QTextDocument::FindBackward);
+
+ if (!nonWhiteSpaceTextCursor.isNull()) {
+ m_positionInDocument = nonWhiteSpaceTextCursor.position();
+ m_positionAfterOperator = m_positionInDocument;
+ m_textCursor.setPosition(m_positionInDocument);
+ }
+}
+
+} // namespace Internal
+} // namespace ClangCodeModel
+
diff --git a/src/plugins/clangcodemodel/test/clangcompletioncontextanalyzertest.h b/src/plugins/clangcodemodel/activationsequencecontextprocessor.h
index 4b8dbbc7ef..268c8112d7 100644
--- a/src/plugins/clangcodemodel/test/clangcompletioncontextanalyzertest.h
+++ b/src/plugins/clangcodemodel/activationsequencecontextprocessor.h
@@ -28,32 +28,65 @@
**
****************************************************************************/
-#ifndef CLANGCOMPLETIONCONTEXTANALYZERTEST_H
-#define CLANGCOMPLETIONCONTEXTANALYZERTEST_H
+#ifndef CLANGCODEMODEL_INTERNAL_ACTIVATIONSEQUENCECONTEXTPROCESSOR_H
+#define CLANGCODEMODEL_INTERNAL_ACTIVATIONSEQUENCECONTEXTPROCESSOR_H
-#include <QObject>
+#include <clangcodemodel/clangcompletionassistinterface.h>
+
+#include <cplusplus/Token.h>
+
+#include <QTextCursor>
+
+QT_BEGIN_NAMESPACE
+class QTextDocument;
+QT_END_NAMESPACE
namespace ClangCodeModel {
namespace Internal {
-namespace Tests {
-class ClangCompletionContextAnalyzerTest : public QObject
+class ActivationSequenceContextProcessor
{
- Q_OBJECT
+public:
+ ActivationSequenceContextProcessor(const ClangCompletionAssistInterface *assistInterface);
+
+ CPlusPlus::Kind completionKind() const;
+
+ const QTextCursor &textCursor_forTestOnly() const;
+
+ int positionAfterOperator() const;
+ int positionBeforeOperator() const;
-private slots:
- void testPassThroughToClangAndSignalSlotRecognition();
- void testPassThroughToClangAndSignalSlotRecognition_data();
+protected:
+ void process();
+ void skipeWhiteSpacesAndIdentifierBeforeCursor();
+ void processActivationSequence();
+ void processStringLiteral();
+ void processComma();
+ void generateTokens();
+ void processDoxygenComment();
+ void processComment();
+ void processInclude();
+ void processSlashOutsideOfAString();
+ void processLeftParen();
+ void processPreprocessorInclude();
+ void resetPositionForEOFCompletionKind();
- void testSpecialCompletionRecognition();
- void testSpecialCompletionRecognition_data();
+ bool isCompletionKindStringLiteralOrSlash() const;
+ bool isProbablyPreprocessorIncludeDirective() const;
- void testAvoidSpecialCompletionRecognition();
- void testAvoidSpecialCompletionRecognition_data();
+private:
+ QVector<CPlusPlus::Token> m_tokens;
+ QTextCursor m_textCursor;
+ CPlusPlus::Token m_token;
+ const ClangCompletionAssistInterface *m_assistInterface;
+ int m_tokenIndex;
+ int m_positionInDocument;
+ int m_positionAfterOperator;
+ int m_positionBeforeOperator;
+ CPlusPlus::Kind m_completionKind;
};
-} // namespace Tests
} // namespace Internal
} // namespace ClangCodeModel
-#endif // CLANGCOMPLETIONCONTEXTANALYZERTEST_H
+#endif // CLANGCODEMODEL_INTERNAL_ACTIVATIONSEQUENCECONTEXTPROCESSOR_H
diff --git a/src/plugins/clangcodemodel/activationsequenceprocessor.cpp b/src/plugins/clangcodemodel/activationsequenceprocessor.cpp
index fac5a58c28..a6a5076a31 100644
--- a/src/plugins/clangcodemodel/activationsequenceprocessor.cpp
+++ b/src/plugins/clangcodemodel/activationsequenceprocessor.cpp
@@ -164,8 +164,8 @@ void ActivationSequenceProcessor::processArrowStar()
void ActivationSequenceProcessor::processDoxyGenComment()
{
- if ((m_char2 == QLatin1Char('\\') || m_char2 == QLatin1Char('@'))
- && (m_char3.isNull() || m_char3.isSpace())) {
+ if ((m_char2.isNull() || m_char2.isSpace())
+ && (m_char3 == QLatin1Char('\\') || m_char3 == QLatin1Char('@'))) {
m_completionKind = CPlusPlus::T_DOXY_COMMENT;
m_offset = 1;
}
diff --git a/src/plugins/clangcodemodel/clangcodemodel.pro b/src/plugins/clangcodemodel/clangcodemodel.pro
index 9ad3b7a989..b4758730d8 100644
--- a/src/plugins/clangcodemodel/clangcodemodel.pro
+++ b/src/plugins/clangcodemodel/clangcodemodel.pro
@@ -12,6 +12,7 @@ DEFINES += "\"CLANG_RESOURCE_DIR=\\\"$${LLVM_LIBDIR}/clang/$${LLVM_VERSION}/incl
unix:QMAKE_LFLAGS += -Wl,-rpath,\'$$LLVM_LIBDIR\'
SOURCES += \
+ activationsequencecontextprocessor.cpp \
activationsequenceprocessor.cpp \
clangassistproposal.cpp \
clangassistproposalitem.cpp \
@@ -48,6 +49,7 @@ SOURCES += \
HEADERS += \
+ activationsequencecontextprocessor.h \
activationsequenceprocessor.h \
clangassistproposal.h \
clangassistproposalitem.h \
@@ -107,13 +109,10 @@ equals(TEST, 1) {
test/clang_tests_database.qrc
HEADERS += \
- test/clangcodecompletion_test.h \
- test/clangcompletioncontextanalyzertest.h
+ test/clangcodecompletion_test.h
SOURCES += \
- test/clangcodecompletion_test.cpp \
- test/clangcompletioncontextanalyzertest.cpp
-
+ test/clangcodecompletion_test.cpp
DISTFILES += \
test/mysource.cpp \
diff --git a/src/plugins/clangcodemodel/clangcodemodel.qbs b/src/plugins/clangcodemodel/clangcodemodel.qbs
index e0ba967fb7..ad6d25dda0 100644
--- a/src/plugins/clangcodemodel/clangcodemodel.qbs
+++ b/src/plugins/clangcodemodel/clangcodemodel.qbs
@@ -53,6 +53,8 @@ QtcPlugin {
name: "Completion support"
condition: product.clangCompletion
files: [
+ "activationsequencecontextprocessor.cpp",
+ "activationsequencecontextprocessor.h",
"activationsequenceprocessor.cpp",
"activationsequenceprocessor.h",
"clangassistproposal.cpp",
@@ -104,8 +106,6 @@ QtcPlugin {
"clang_tests_database.qrc",
"clangcodecompletion_test.cpp",
"clangcodecompletion_test.h",
- "clangcompletioncontextanalyzertest.cpp",
- "clangcompletioncontextanalyzertest.h",
]
}
diff --git a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
index 8f68803eb0..c6c42855a9 100644
--- a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
+++ b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
@@ -36,7 +36,6 @@
#ifdef WITH_TESTS
# include "test/clangcodecompletion_test.h"
-# include "test/clangcompletioncontextanalyzertest.h"
#endif
#include <cpptools/cppmodelmanager.h>
@@ -93,7 +92,6 @@ QList<QObject *> ClangCodeModelPlugin::createTestObjects() const
{
return {
new Tests::ClangCodeCompletionTest,
- new Tests::ClangCompletionContextAnalyzerTest
};
}
#endif
diff --git a/src/plugins/clangcodemodel/clangcodemodelunittestfiles.pri b/src/plugins/clangcodemodel/clangcodemodelunittestfiles.pri
index e40a4c8fa4..7105594178 100644
--- a/src/plugins/clangcodemodel/clangcodemodelunittestfiles.pri
+++ b/src/plugins/clangcodemodel/clangcodemodelunittestfiles.pri
@@ -1,7 +1,11 @@
INCLUDEPATH += $$PWD
SOURCES += $$PWD/completionchunkstotextconverter.cpp \
- $$PWD/activationsequenceprocessor.cpp
+ $$PWD/activationsequenceprocessor.cpp \
+ $$PWD/activationsequencecontextprocessor.cpp \
+ $$PWD/clangcompletioncontextanalyzer.cpp
HEADERS += $$PWD/completionchunkstotextconverter.h \
- $$PWD/activationsequenceprocessor.h
+ $$PWD/activationsequenceprocessor.h \
+ $$PWD/activationsequencecontextprocessor.h \
+ $$PWD/clangcompletioncontextanalyzer.h
diff --git a/src/plugins/clangcodemodel/clangcompletioncontextanalyzer.cpp b/src/plugins/clangcodemodel/clangcompletioncontextanalyzer.cpp
index 568eb9e1e8..f8d63a4a3e 100644
--- a/src/plugins/clangcodemodel/clangcompletioncontextanalyzer.cpp
+++ b/src/plugins/clangcodemodel/clangcompletioncontextanalyzer.cpp
@@ -31,6 +31,9 @@
#include "clangcompletioncontextanalyzer.h"
+#include "activationsequenceprocessor.h"
+#include "activationsequencecontextprocessor.h"
+
#include <texteditor/codeassist/assistinterface.h>
#include <cplusplus/BackwardsScanner.h>
@@ -47,80 +50,6 @@ using namespace CPlusPlus;
namespace {
-int activationSequenceChar(const QChar &ch, const QChar &ch2, const QChar &ch3,
- unsigned *kind, bool wantFunctionCall)
-{
- int referencePosition = 0;
- int completionKind = T_EOF_SYMBOL;
- switch (ch.toLatin1()) {
- case '.':
- if (ch2 != QLatin1Char('.')) {
- completionKind = T_DOT;
- referencePosition = 1;
- }
- break;
- case ',':
- completionKind = T_COMMA;
- referencePosition = 1;
- break;
- case '(':
- if (wantFunctionCall) {
- completionKind = T_LPAREN;
- referencePosition = 1;
- }
- break;
- case ':':
- if (ch3 != QLatin1Char(':') && ch2 == QLatin1Char(':')) {
- completionKind = T_COLON_COLON;
- referencePosition = 2;
- }
- break;
- case '>':
- if (ch2 == QLatin1Char('-')) {
- completionKind = T_ARROW;
- referencePosition = 2;
- }
- break;
- case '*':
- if (ch2 == QLatin1Char('.')) {
- completionKind = T_DOT_STAR;
- referencePosition = 2;
- } else if (ch3 == QLatin1Char('-') && ch2 == QLatin1Char('>')) {
- completionKind = T_ARROW_STAR;
- referencePosition = 3;
- }
- break;
- case '\\':
- case '@':
- if (ch2.isNull() || ch2.isSpace()) {
- completionKind = T_DOXY_COMMENT;
- referencePosition = 1;
- }
- break;
- case '<':
- completionKind = T_ANGLE_STRING_LITERAL;
- referencePosition = 1;
- break;
- case '"':
- completionKind = T_STRING_LITERAL;
- referencePosition = 1;
- break;
- case '/':
- completionKind = T_SLASH;
- referencePosition = 1;
- break;
- case '#':
- completionKind = T_POUND;
- referencePosition = 1;
- break;
- }
-
- if (kind)
- *kind = completionKind;
-
- return referencePosition;
-}
-
bool isTokenForIncludePathCompletion(unsigned tokenKind)
{
return tokenKind == T_STRING_LITERAL
@@ -143,7 +72,7 @@ namespace ClangCodeModel {
namespace Internal {
ClangCompletionContextAnalyzer::ClangCompletionContextAnalyzer(
- const TextEditor::AssistInterface *assistInterface,
+ const ClangCompletionAssistInterface *assistInterface,
CPlusPlus::LanguageFeatures languageFeatures)
: m_interface(assistInterface)
, m_languageFeatures(languageFeatures)
@@ -153,67 +82,20 @@ ClangCompletionContextAnalyzer::ClangCompletionContextAnalyzer(
void ClangCompletionContextAnalyzer::analyze()
{
QTC_ASSERT(m_interface, return);
- const int startOfName = findStartOfName();
- m_positionForProposal = startOfName;
setActionAndClangPosition(PassThroughToLibClang, -1);
- const int endOfOperator = skipPrecedingWhitespace(startOfName);
- m_completionOperator = T_EOF_SYMBOL;
- m_positionEndOfExpression = startOfOperator(endOfOperator, &m_completionOperator,
- /*want function call =*/ true);
+ ActivationSequenceContextProcessor activationSequenceContextProcessor(m_interface);
+ m_completionOperator = activationSequenceContextProcessor.completionKind();
+ int afterOperatorPosition = activationSequenceContextProcessor.positionAfterOperator();
+ m_positionEndOfExpression = activationSequenceContextProcessor.positionBeforeOperator();
+ m_positionForProposal = activationSequenceContextProcessor.positionAfterOperator();
- if (isTokenForPassThrough(m_completionOperator)) {
- setActionAndClangPosition(PassThroughToLibClang, endOfOperator);
- return;
- } else if (m_completionOperator == T_DOXY_COMMENT) {
- setActionAndClangPosition(CompleteDoxygenKeyword, -1);
- return;
- } else if (m_completionOperator == T_POUND) {
- // TODO: Check if libclang can complete preprocessor directives
- setActionAndClangPosition(CompletePreprocessorDirective, -1);
- return;
- } else if (isTokenForIncludePathCompletion(m_completionOperator)) {
- setActionAndClangPosition(CompleteIncludePath, -1);
- return;
- }
-
- ExpressionUnderCursor expressionUnderCursor(m_languageFeatures);
- QTextCursor textCursor(m_interface->textDocument());
+ const bool actionIsSet = handleNonFunctionCall(afterOperatorPosition);
- if (m_completionOperator == T_COMMA) { // For function hints
- textCursor.setPosition(m_positionEndOfExpression);
- const int start = expressionUnderCursor.startOfFunctionCall(textCursor);
- QTC_ASSERT(start != -1, setActionAndClangPosition(PassThroughToLibClang, startOfName); return);
- m_positionEndOfExpression = start;
- m_positionForProposal = start + 1; // After '(' of function call
- m_completionOperator = T_LPAREN;
- }
-
- if (m_completionOperator == T_LPAREN) {
- textCursor.setPosition(m_positionEndOfExpression);
- const QString expression = expressionUnderCursor(textCursor);
-
- if (expression.endsWith(QLatin1String("SIGNAL"))) {
- setActionAndClangPosition(CompleteSignal, endOfOperator);
- } else if (expression.endsWith(QLatin1String("SLOT"))) {
- setActionAndClangPosition(CompleteSlot, endOfOperator);
- } else if (m_interface->position() != endOfOperator) {
- // No function completion if cursor is not after '(' or ','
- m_positionForProposal = startOfName;
- setActionAndClangPosition(PassThroughToLibClang, endOfOperator);
- } else {
- const FunctionInfo functionInfo = analyzeFunctionCall(endOfOperator);
- m_functionName = functionInfo.functionName;
- setActionAndClangPosition(PassThroughToLibClangAfterLeftParen,
- functionInfo.functionNamePosition);
- }
-
- return;
+ if (!actionIsSet) {
+ handleCommaInFunctionCall();
+ handleFunctionCall(afterOperatorPosition);
}
-
- QTC_CHECK(!"Unexpected completion context");
- setActionAndClangPosition(PassThroughToLibClang, startOfName);
- return;
}
ClangCompletionContextAnalyzer::FunctionInfo ClangCompletionContextAnalyzer::analyzeFunctionCall(
@@ -260,110 +142,75 @@ int ClangCompletionContextAnalyzer::skipPrecedingWhitespace(int position) const
return position;
}
-int ClangCompletionContextAnalyzer::startOfOperator(int pos,
- unsigned *kind,
- bool wantFunctionCall) const
+void ClangCompletionContextAnalyzer::setActionAndClangPosition(CompletionAction action,
+ int position)
{
- const QChar ch = pos > -1 ? m_interface->characterAt(pos - 1) : QChar();
- const QChar ch2 = pos > 0 ? m_interface->characterAt(pos - 2) : QChar();
- const QChar ch3 = pos > 1 ? m_interface->characterAt(pos - 3) : QChar();
-
- int start = pos - activationSequenceChar(ch, ch2, ch3, kind, wantFunctionCall);
- if (start != pos) {
- QTextCursor tc(m_interface->textDocument());
- tc.setPosition(pos);
-
- // Include completion: make sure the quote character is the first one on the line
- if (*kind == T_STRING_LITERAL) {
- QTextCursor s = tc;
- s.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
- QString sel = s.selectedText();
- if (sel.indexOf(QLatin1Char('"')) < sel.length() - 1) {
- *kind = T_EOF_SYMBOL;
- start = pos;
- }
- } else if (*kind == T_COMMA) {
- ExpressionUnderCursor expressionUnderCursor(m_languageFeatures);
- if (expressionUnderCursor.startOfFunctionCall(tc) == -1) {
- *kind = T_EOF_SYMBOL;
- start = pos;
- }
- }
+ QTC_CHECK(position >= -1);
+ m_completionAction = action;
+ m_positionForClang = position;
+}
- SimpleLexer tokenize;
- tokenize.setLanguageFeatures(m_languageFeatures);
- tokenize.setSkipComments(false);
- const Tokens &tokens = tokenize(tc.block().text(), BackwardsScanner::previousBlockState(tc.block()));
- const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1)); // get the token at the left of the cursor
- const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx);
+void ClangCompletionContextAnalyzer::setAction(ClangCompletionContextAnalyzer::CompletionAction action)
+{
+ setActionAndClangPosition(action, -1);
+}
- if (*kind == T_DOXY_COMMENT && !(tk.is(T_DOXY_COMMENT) || tk.is(T_CPP_DOXY_COMMENT))) {
- *kind = T_EOF_SYMBOL;
- start = pos;
- }
- // Don't complete in comments or strings, but still check for include completion
- else if (tk.is(T_COMMENT) || tk.is(T_CPP_COMMENT) ||
- (tk.isLiteral() && (*kind != T_STRING_LITERAL
- && *kind != T_ANGLE_STRING_LITERAL
- && *kind != T_SLASH))) {
- *kind = T_EOF_SYMBOL;
- start = pos;
- }
- // Include completion: can be triggered by slash, but only in a string
- else if (*kind == T_SLASH && (tk.isNot(T_STRING_LITERAL) && tk.isNot(T_ANGLE_STRING_LITERAL))) {
- *kind = T_EOF_SYMBOL;
- start = pos;
- }
- else if (*kind == T_LPAREN) {
- if (tokenIdx > 0) {
- const Token &previousToken = tokens.at(tokenIdx - 1); // look at the token at the left of T_LPAREN
- switch (previousToken.kind()) {
- case T_IDENTIFIER:
- case T_GREATER:
- case T_SIGNAL:
- case T_SLOT:
- break; // good
-
- default:
- // that's a bad token :)
- *kind = T_EOF_SYMBOL;
- start = pos;
- }
- }
- }
- // Check for include preprocessor directive
- else if (*kind == T_STRING_LITERAL || *kind == T_ANGLE_STRING_LITERAL || *kind == T_SLASH) {
- bool include = false;
- if (tokens.size() >= 3) {
- if (tokens.at(0).is(T_POUND) && tokens.at(1).is(T_IDENTIFIER) && (tokens.at(2).is(T_STRING_LITERAL) ||
- tokens.at(2).is(T_ANGLE_STRING_LITERAL))) {
- const Token &directiveToken = tokens.at(1);
- QString directive = tc.block().text().mid(directiveToken.bytesBegin(),
- directiveToken.bytes());
- if (directive == QLatin1String("include") ||
- directive == QLatin1String("include_next") ||
- directive == QLatin1String("import")) {
- include = true;
- }
- }
- }
-
- if (!include) {
- *kind = T_EOF_SYMBOL;
- start = pos;
- }
- }
+void ClangCompletionContextAnalyzer::handleCommaInFunctionCall()
+{
+ if (m_completionOperator == T_COMMA) {
+ ExpressionUnderCursor expressionUnderCursor(m_languageFeatures);
+ QTextCursor textCursor(m_interface->textDocument());
+ textCursor.setPosition(m_positionEndOfExpression);
+ const int start = expressionUnderCursor.startOfFunctionCall(textCursor);
+ m_positionEndOfExpression = start;
+ m_positionForProposal = start + 1; // After '(' of function call
+ m_completionOperator = T_LPAREN;
}
+}
+
+void ClangCompletionContextAnalyzer::handleFunctionCall(int afterOperatorPosition)
+{
+ if (m_completionOperator == T_LPAREN) {
+ ExpressionUnderCursor expressionUnderCursor(m_languageFeatures);
+ QTextCursor textCursor(m_interface->textDocument());
+ textCursor.setPosition(m_positionEndOfExpression);
+ const QString expression = expressionUnderCursor(textCursor);
- return start;
+ if (expression.endsWith(QLatin1String("SIGNAL"))) {
+ setActionAndClangPosition(CompleteSignal, afterOperatorPosition);
+ } else if (expression.endsWith(QLatin1String("SLOT"))) {
+ setActionAndClangPosition(CompleteSlot, afterOperatorPosition);
+ } else if (m_interface->position() != afterOperatorPosition) {
+ // No function completion if cursor is not after '(' or ','
+ m_positionForProposal = afterOperatorPosition;
+ setActionAndClangPosition(PassThroughToLibClang, afterOperatorPosition);
+ } else {
+ const FunctionInfo functionInfo = analyzeFunctionCall(afterOperatorPosition);
+ m_functionName = functionInfo.functionName;
+ setActionAndClangPosition(PassThroughToLibClangAfterLeftParen,
+ functionInfo.functionNamePosition);
+ }
+ }
}
-void ClangCompletionContextAnalyzer::setActionAndClangPosition(CompletionAction action,
- int position)
+bool ClangCompletionContextAnalyzer::handleNonFunctionCall(int position)
{
- QTC_CHECK(position >= -1);
- m_completionAction = action;
- m_positionForClang = position;
+ if (isTokenForPassThrough(m_completionOperator)) {
+ setActionAndClangPosition(PassThroughToLibClang, position);
+ return true;
+ } else if (m_completionOperator == T_DOXY_COMMENT) {
+ setAction(CompleteDoxygenKeyword);
+ return true;
+ } else if (m_completionOperator == T_POUND) {
+ // TODO: Check if libclang can complete preprocessor directives
+ setAction(CompletePreprocessorDirective);
+ return true;
+ } else if (isTokenForIncludePathCompletion(m_completionOperator)) {
+ setAction(CompleteIncludePath);
+ return true;
+ }
+
+ return false;
}
} // namespace Internal
diff --git a/src/plugins/clangcodemodel/clangcompletioncontextanalyzer.h b/src/plugins/clangcodemodel/clangcompletioncontextanalyzer.h
index 3a0a8f21ff..2234adf604 100644
--- a/src/plugins/clangcodemodel/clangcompletioncontextanalyzer.h
+++ b/src/plugins/clangcodemodel/clangcompletioncontextanalyzer.h
@@ -40,10 +40,12 @@ namespace TextEditor { class AssistInterface; }
namespace ClangCodeModel {
namespace Internal {
+class ClangCompletionAssistInterface;
+
class ClangCompletionContextAnalyzer
{
public:
- ClangCompletionContextAnalyzer(const TextEditor::AssistInterface *assistInterface,
+ ClangCompletionContextAnalyzer(const ClangCompletionAssistInterface *assistInterface,
CPlusPlus::LanguageFeatures languageFeatures);
void analyze();
@@ -72,16 +74,21 @@ private:
int findStartOfName(int position = -1) const;
int skipPrecedingWhitespace(int position) const;
- int startOfOperator(int position, unsigned *kind, bool wantFunctionCall) const;
void setActionAndClangPosition(CompletionAction action, int position);
+ void setAction(CompletionAction action);
+
+ bool handleNonFunctionCall(int position);
+ void handleCommaInFunctionCall();
+ void handleFunctionCall(int endOfOperator);
- const TextEditor::AssistInterface * const m_interface; // Not owned
+private:
+ const ClangCompletionAssistInterface *m_interface; // Not owned
const CPlusPlus::LanguageFeatures m_languageFeatures; // TODO: Get from assistInterface?!
// Results
CompletionAction m_completionAction = PassThroughToLibClang;
- unsigned m_completionOperator = CPlusPlus::T_EOF_SYMBOL;
+ CPlusPlus::Kind m_completionOperator = CPlusPlus::T_EOF_SYMBOL;
int m_positionForProposal = -1;
int m_positionForClang = -1;
int m_positionEndOfExpression = -1;
diff --git a/src/plugins/clangcodemodel/test/clangcompletioncontextanalyzertest.cpp b/src/plugins/clangcodemodel/test/clangcompletioncontextanalyzertest.cpp
deleted file mode 100644
index afa881eb05..0000000000
--- a/src/plugins/clangcodemodel/test/clangcompletioncontextanalyzertest.cpp
+++ /dev/null
@@ -1,234 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing
-**
-** This file is part of Qt Creator.
-**
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms and
-** conditions see http://www.qt.io/terms-conditions. For further information
-** use the contact form at http://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 2.1 or version 3 as published by the Free
-** Software Foundation and appearing in the file LICENSE.LGPLv21 and
-** LICENSE.LGPLv3 included in the packaging of this file. Please review the
-** following information to ensure the GNU Lesser General Public License
-** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
-** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
-**
-** In addition, as a special exception, The Qt Company gives you certain additional
-** rights. These rights are described in The Qt Company LGPL Exception
-** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
-**
-****************************************************************************/
-
-#include "clangcompletioncontextanalyzertest.h"
-
-#include <clangcodemodel/clangcompletioncontextanalyzer.h>
-#include <texteditor/codeassist/assistinterface.h>
-
-#include <utils/qtcassert.h>
-
-#include <QDebug>
-#include <QTest>
-#include <QTextDocument>
-
-using namespace CPlusPlus;
-using namespace ClangCodeModel;
-using namespace ClangCodeModel::Internal;
-using namespace ClangCodeModel::Internal::Tests;
-
-Q_DECLARE_METATYPE(ClangCodeModel::Internal::ClangCompletionContextAnalyzer::CompletionAction)
-
-QT_BEGIN_NAMESPACE
-namespace QTest {
-
-template<> char *toString(const ClangCompletionContextAnalyzer::CompletionAction &action)
-{
- using CCA = ClangCompletionContextAnalyzer;
-
- switch (action) {
- case CCA::PassThroughToLibClang:
- return qstrdup("PassThroughToLibClang");
- case CCA::PassThroughToLibClangAfterLeftParen:
- return qstrdup("PassThroughToLibClangAfterLeftParen");
- case CCA::CompleteDoxygenKeyword:
- return qstrdup("CompleteDoxygenKeyword");
- case CCA::CompleteIncludePath:
- return qstrdup("CompleteIncludePath");
- case CCA::CompletePreprocessorDirective:
- return qstrdup("CompletePreprocessorDirective");
- case CCA::CompleteSignal:
- return qstrdup("CompleteSignal");
- case CCA::CompleteSlot:
- return qstrdup("CompleteSlot");
- }
- return qstrdup("Unexpected Value");
-}
-
-} // namespace QTest
-QT_END_NAMESPACE
-
-namespace {
-
-typedef QByteArray _;
-
-class DummyAssistInterface : public TextEditor::AssistInterface
-{
-public:
- DummyAssistInterface(const QByteArray &text, int position)
- : AssistInterface(new QTextDocument(QString::fromUtf8(text)),
- position,
- QLatin1String("<testdocument>"),
- TextEditor::ActivationCharacter)
- {}
- ~DummyAssistInterface() { delete textDocument(); }
-};
-
-class TestDocument
-{
-public:
- TestDocument(const QByteArray &theSource)
- : source(theSource)
- , position(theSource.lastIndexOf('@')) // Use 'lastIndexOf' due to doxygen: "//! @keyword"
- {
- QTC_CHECK(position != -1);
- source.remove(position, 1);
- }
-
- QByteArray source;
- int position;
-};
-
-bool isAPassThroughToLibClangAction(ClangCompletionContextAnalyzer::CompletionAction action)
-{
- return action == ClangCompletionContextAnalyzer::PassThroughToLibClang
- || action == ClangCompletionContextAnalyzer::PassThroughToLibClangAfterLeftParen;
-}
-
-ClangCompletionContextAnalyzer runAnalyzer(const TestDocument &testDocument)
-{
- DummyAssistInterface assistInterface(testDocument.source, testDocument.position);
- ClangCompletionContextAnalyzer analyzer(&assistInterface, LanguageFeatures::defaultFeatures());
- analyzer.analyze();
- return analyzer;
-}
-
-} // anonymous namespace
-
-void ClangCompletionContextAnalyzerTest::testPassThroughToClangAndSignalSlotRecognition_data()
-{
- QTest::addColumn<QByteArray>("givenSource");
- QTest::addColumn<ClangCompletionContextAnalyzer::CompletionAction>("expectedCompletionAction");
- QTest::addColumn<int>("expectedDiffBetweenCursorAndCalculatedClangPosition");
- QTest::addColumn<int>("expectedDiffBetweenCursorAndCalculatedProposalPosition");
-
- using CCA = ClangCompletionContextAnalyzer;
-
- QTest::newRow("members - dot 1") << _("o.mem@") << CCA::PassThroughToLibClang << -3 << -3;
- QTest::newRow("members - dot 2") << _("o. mem@") << CCA::PassThroughToLibClang << -4 << -3;
- QTest::newRow("members - dot 3") << _("o.@mem") << CCA::PassThroughToLibClang << 0 << 0;
- QTest::newRow("members - dot 4") << _("o. @ mem") << CCA::PassThroughToLibClang << -1 << 0;
- QTest::newRow("members - arrow 1") << _("o->mem@") << CCA::PassThroughToLibClang << -3 << -3;
- QTest::newRow("members - arrow 2") << _("o-> mem@") << CCA::PassThroughToLibClang << -4 << -3;
- QTest::newRow("members - arrow 3") << _("o->@mem") << CCA::PassThroughToLibClang << 0 << 0;
- QTest::newRow("members - arrow 4") << _("o-> @ mem") << CCA::PassThroughToLibClang << -1 << 0;
-
- QTest::newRow("call 1") << _("f(@") << CCA::PassThroughToLibClangAfterLeftParen << -2 << 0;
- QTest::newRow("call 2") << _("f(1,@") << CCA::PassThroughToLibClangAfterLeftParen << -4 << -2;
- QTest::newRow("call 3") << _("f(1, @") << CCA::PassThroughToLibClang << -1 << 0;
-
- QTest::newRow("qt4 signals 1") << _("SIGNAL(@") << CCA::CompleteSignal << 0 << 0;
- QTest::newRow("qt4 signals 2") << _("SIGNAL(foo@") << CCA::CompleteSignal << -3 << -3;
- QTest::newRow("qt4 slots 1") << _("SLOT(@") << CCA::CompleteSlot << 0 << 0;
- QTest::newRow("qt4 slots 2") << _("SLOT(foo@") << CCA::CompleteSlot << -3 << -3;
-}
-
-void ClangCompletionContextAnalyzerTest::testPassThroughToClangAndSignalSlotRecognition()
-{
- QFETCH(QByteArray, givenSource);
- QFETCH(ClangCompletionContextAnalyzer::CompletionAction, expectedCompletionAction);
- QFETCH(int, expectedDiffBetweenCursorAndCalculatedClangPosition);
- QFETCH(int, expectedDiffBetweenCursorAndCalculatedProposalPosition);
-
- const TestDocument testDocument(givenSource);
- ClangCompletionContextAnalyzer analyzer = runAnalyzer(testDocument);
-
- QCOMPARE(analyzer.completionAction(), expectedCompletionAction);
- QCOMPARE(analyzer.positionForClang() - testDocument.position,
- expectedDiffBetweenCursorAndCalculatedClangPosition);
- QCOMPARE(analyzer.positionForProposal() - testDocument.position,
- expectedDiffBetweenCursorAndCalculatedProposalPosition);
-}
-
-void ClangCompletionContextAnalyzerTest::testSpecialCompletionRecognition_data()
-{
- QTest::addColumn<QByteArray>("givenSource");
- QTest::addColumn<ClangCompletionContextAnalyzer::CompletionAction>("expectedCompletionAction");
- QTest::addColumn<int>("expectedDiffBetweenCursorAndCalculatedProposalPosition");
-
- using CCA = ClangCompletionContextAnalyzer;
-
- QTest::newRow("doxygen keywords 1") << _("//! \\@") << CCA::CompleteDoxygenKeyword << 0;
- QTest::newRow("doxygen keywords 3") << _("//! @@") << CCA::CompleteDoxygenKeyword << 0;
- QTest::newRow("doxygen keywords 2") << _("//! \\par@") << CCA::CompleteDoxygenKeyword << -3;
-
- QTest::newRow("pp directives 1") << _("#@") << CCA::CompletePreprocessorDirective << 0;
- QTest::newRow("pp directives 2") << _("#if@") << CCA::CompletePreprocessorDirective << -2;
-
- QTest::newRow("pp include path 1") << _("#include \"foo@\"") << CCA::CompleteIncludePath << -3;
- QTest::newRow("pp include path 2") << _("#include <foo@>") << CCA::CompleteIncludePath << -3;
- QTest::newRow("pp include path 3") << _("#include <foo/@>") << CCA::CompleteIncludePath << 0;
-}
-
-void ClangCompletionContextAnalyzerTest::testSpecialCompletionRecognition()
-{
- QFETCH(QByteArray, givenSource);
- QFETCH(ClangCompletionContextAnalyzer::CompletionAction, expectedCompletionAction);
- QFETCH(int, expectedDiffBetweenCursorAndCalculatedProposalPosition);
-
- const TestDocument testDocument(givenSource);
- ClangCompletionContextAnalyzer analyzer = runAnalyzer(testDocument);
-
- QCOMPARE(analyzer.completionAction(), expectedCompletionAction);
- QCOMPARE(analyzer.positionForClang(), -1);
- QCOMPARE(analyzer.positionForProposal() - testDocument.position,
- expectedDiffBetweenCursorAndCalculatedProposalPosition);
-}
-
-void ClangCompletionContextAnalyzerTest::testAvoidSpecialCompletionRecognition_data()
-{
- QTest::addColumn<QByteArray>("givenSource");
-
- QTest::newRow("no special completion for literals 1") << _("\"@");
- QTest::newRow("no special completion for literals 2") << _(" \"@");
- QTest::newRow("no special completion for literals 3") << _("\"text\"@");
- QTest::newRow("no special completion for literals 4") << _("\"hello cruel@ world\"");
- QTest::newRow("no special completion for literals 5") << _("'@'");
- QTest::newRow("no special completion for literals 6") << _("'a@'");
- QTest::newRow("no special completion for comma operator") << _("a = b,@\"");
- QTest::newRow("no special completion for doxygen marker not in doxygen comment 1") << _("@@");
- QTest::newRow("no special completion for doxygen marker not in doxygen comment 2") << _("\\@");
- QTest::newRow("no special completion in comments 1") << _("// text@");
- QTest::newRow("no special completion in comments 2") << _("/* text@ */");
- QTest::newRow("no special completion for slash") << _("5 /@");
- QTest::newRow("no special completion for '(' 1") << _("(@");
- QTest::newRow("no special completion for '(' 2") << _("((@");
- QTest::newRow("no special completion for '(' 3") << _("*(@");
-}
-
-void ClangCompletionContextAnalyzerTest::testAvoidSpecialCompletionRecognition()
-{
- QFETCH(QByteArray, givenSource);
-
- const TestDocument testDocument(givenSource);
- ClangCompletionContextAnalyzer analyzer = runAnalyzer(testDocument);
-
- QVERIFY(isAPassThroughToLibClangAction(analyzer.completionAction()));
-}