diff options
author | Marco Bubke <marco.bubke@theqtcompany.com> | 2015-07-23 13:01:02 +0200 |
---|---|---|
committer | Nikolai Kosjar <nikolai.kosjar@theqtcompany.com> | 2015-07-23 11:13:05 +0000 |
commit | ae5d92d6182ecdf64b3d5453ebcff9fc0c589016 (patch) | |
tree | bb6d233102ca890ff5afa6ce3daec9c962cc26ec /src | |
parent | 5a791e88394513a7fd632263f95b632dd84e1cfd (diff) | |
download | qt-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.cpp | 249 | ||||
-rw-r--r-- | src/plugins/clangcodemodel/activationsequencecontextprocessor.h (renamed from src/plugins/clangcodemodel/test/clangcompletioncontextanalyzertest.h) | 63 | ||||
-rw-r--r-- | src/plugins/clangcodemodel/activationsequenceprocessor.cpp | 4 | ||||
-rw-r--r-- | src/plugins/clangcodemodel/clangcodemodel.pro | 9 | ||||
-rw-r--r-- | src/plugins/clangcodemodel/clangcodemodel.qbs | 4 | ||||
-rw-r--r-- | src/plugins/clangcodemodel/clangcodemodelplugin.cpp | 2 | ||||
-rw-r--r-- | src/plugins/clangcodemodel/clangcodemodelunittestfiles.pri | 8 | ||||
-rw-r--r-- | src/plugins/clangcodemodel/clangcompletioncontextanalyzer.cpp | 301 | ||||
-rw-r--r-- | src/plugins/clangcodemodel/clangcompletioncontextanalyzer.h | 15 | ||||
-rw-r--r-- | src/plugins/clangcodemodel/test/clangcompletioncontextanalyzertest.cpp | 234 |
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())); -} |