Commit 23b8a3b2 authored by Nikolai Kosjar's avatar Nikolai Kosjar
Browse files

Clang: Use completion through backend process



This makes us independent of libclang crashes for completion.
Re-parsing for highlighting still happens in the Qt Creator process.

Run in verbose mode:
    qtc.clangcodemodel.ipc=true

Run tests:
    -test "ClangCodeModel"

Task-number: QTCREATORBUG-14108
Task-number: QTCREATORBUG-12819
Change-Id: Id3e95bd2afdb6508bbd1d35fddc69534a909b905
Reviewed-by: default avatarMarco Bubke <marco.bubke@theqtcompany.com>
parent 264132da
......@@ -37,7 +37,7 @@ QT_BEGIN_NAMESPACE
class QVariant;
QT_END_NAMESPACE
#include <codemodelbackendipc_global.h>
#include "codemodelbackendipc_global.h"
namespace CodeModelBackEnd {
......
......@@ -14,6 +14,7 @@ unix:QMAKE_LFLAGS += -Wl,-rpath,\'$$LLVM_LIBDIR\'
SOURCES += \
$$PWD/clangcodemodelplugin.cpp \
$$PWD/clangcompleter.cpp \
$$PWD/clangcompletioncontextanalyzer.cpp \
$$PWD/clangcompletion.cpp \
$$PWD/clangeditordocumentparser.cpp \
$$PWD/clangeditordocumentprocessor.cpp \
......@@ -21,6 +22,8 @@ SOURCES += \
$$PWD/clangprojectsettings.cpp \
$$PWD/clangprojectsettingspropertiespage.cpp \
$$PWD/clangutils.cpp \
$$PWD/codemodelbackendipcintegration.cpp \
$$PWD/completionchunkstotextconverter.cpp \
$$PWD/completionproposalsbuilder.cpp \
$$PWD/cppcreatemarkers.cpp \
$$PWD/cxprettyprinter.cpp \
......@@ -41,6 +44,7 @@ SOURCES += \
HEADERS += \
$$PWD/clangcodemodelplugin.h \
$$PWD/clangcompleter.h \
$$PWD/clangcompletioncontextanalyzer.h \
$$PWD/clangcompletion.h \
$$PWD/clangeditordocumentparser.h \
$$PWD/clangeditordocumentprocessor.h \
......@@ -49,6 +53,8 @@ HEADERS += \
$$PWD/clangprojectsettings.h \
$$PWD/clangprojectsettingspropertiespage.h \
$$PWD/clangutils.h \
$$PWD/codemodelbackendipcintegration.h \
$$PWD/completionchunkstotextconverter.h \
$$PWD/completionproposalsbuilder.h \
$$PWD/constants.h \
$$PWD/cppcreatemarkers.h \
......@@ -89,13 +95,24 @@ equals(TEST, 1) {
$$PWD/test/clang_tests_database.qrc
HEADERS += \
$$PWD/test/clangcodecompletion_test.h \
$$PWD/test/clangcompletioncontextanalyzertest.h \
$$PWD/test/completiontesthelper.h
SOURCES += \
$$PWD/test/clangcodecompletion_test.cpp \
$$PWD/test/clangcompletioncontextanalyzertest.cpp \
$$PWD/test/clangcompletion_test.cpp \
$$PWD/test/completiontesthelper.cpp
DISTFILES += \
$$PWD/test/mysource.cpp \
$$PWD/test/myheader.cpp \
$$PWD/test/completionWithProject.cpp \
$$PWD/test/memberCompletion.cpp \
$$PWD/test/doxygenKeywordsCompletion.cpp \
$$PWD/test/preprocessorKeywordsCompletion.cpp \
$$PWD/test/includeDirectiveCompletion.cpp \
$$PWD/test/cxx_regression_1.cpp \
$$PWD/test/cxx_regression_2.cpp \
$$PWD/test/cxx_regression_3.cpp \
......
......@@ -12,6 +12,12 @@ QtcPlugin {
Depends { name: "ProjectExplorer" }
Depends { name: "TextEditor" }
Depends { name: "Utils" }
Depends { name: "CodeModelBackEndIpc" }
pluginTestDepends: [
"CppEditor",
"QmakeProjectManager",
]
property bool clangCompletion: true
property bool clangHighlighting: true
......@@ -89,6 +95,10 @@ QtcPlugin {
"clangcompletion_test.cpp",
"completiontesthelper.cpp",
"completiontesthelper.h",
"clangcodecompletion_test.cpp",
"clangcodecompletion_test.h",
"clangcompletioncontextanalyzertest.cpp",
"clangcompletioncontextanalyzertest.h",
]
}
......@@ -97,6 +107,13 @@ QtcPlugin {
prefix: "test/"
fileTags: "none"
files: [
"mysource.cpp",
"myheader.h",
"completionWithProject.cpp",
"memberCompletion.cpp",
"doxygenKeywordsCompletion.cpp",
"preprocessorKeywordsCompletion.cpp",
"includeDirectiveCompletion.cpp",
"cxx_regression_1.cpp",
"cxx_regression_2.cpp",
"cxx_regression_3.cpp",
......@@ -118,6 +135,8 @@ QtcPlugin {
files: [
"clang_global.h",
"clangcompletioncontextanalyzer.cpp",
"clangcompletioncontextanalyzer.h",
"clangeditordocumentparser.cpp",
"clangeditordocumentparser.h",
"clangeditordocumentprocessor.cpp",
......@@ -133,6 +152,10 @@ QtcPlugin {
"clangprojectsettingspropertiespage.ui",
"clangutils.cpp",
"clangutils.h",
"codemodelbackendipcintegration.cpp",
"codemodelbackendipcintegration.h",
"completionchunkstotextconverter.cpp",
"completionchunkstotextconverter.h",
"constants.h",
"cxprettyprinter.cpp",
"cxprettyprinter.h",
......
QTC_PLUGIN_NAME = ClangCodeModel
QTC_LIB_DEPENDS += \
utils
utils \
codemodelbackendipc
QTC_PLUGIN_DEPENDS += \
coreplugin \
cpptools \
texteditor
QTC_TEST_DEPENDS += \
cppeditor \
qmakeprojectmanager
......@@ -34,6 +34,11 @@
#include "pchmanager.h"
#include "utils.h"
#ifdef WITH_TESTS
# include "test/clangcodecompletion_test.h"
# include "test/clangcompletioncontextanalyzertest.h"
#endif
#include <cpptools/cppmodelmanager.h>
#include <projectexplorer/projectpanelfactory.h>
......@@ -73,9 +78,8 @@ bool ClangCodeModelPlugin::initialize(const QStringList &arguments, QString *err
connect(cppModelManager, &CppTools::CppModelManager::projectPartsUpdated,
pchManager, &PchManager::onProjectPartsUpdated);
// Register ModelManagerSupport
m_modelManagerSupport.reset(new ModelManagerSupport);
cppModelManager->addModelManagerSupport(m_modelManagerSupport.data());
// Register ModelManagerSupportProvider
cppModelManager->addModelManagerSupportProvider(&m_modelManagerSupportProvider);
return true;
}
......@@ -84,5 +88,16 @@ void ClangCodeModelPlugin::extensionsInitialized()
{
}
#ifdef WITH_TESTS
QList<QObject *> ClangCodeModelPlugin::createTestObjects() const
{
return {
new Tests::ClangCodeCompletionTest,
new Tests::ClangCompletionContextAnalyzerTest
};
}
#endif
} // namespace Internal
} // namespace Clang
......@@ -54,12 +54,14 @@ public:
void extensionsInitialized();
private:
QScopedPointer<ModelManagerSupport> m_modelManagerSupport;
ModelManagerSupportProviderClang m_modelManagerSupportProvider;
#ifdef CLANG_INDEXING
QScopedPointer<ClangIndexer> m_indexer;
#endif // CLANG_INDEXING
#ifdef WITH_TESTS
QList<QObject *> createTestObjects() const;
private slots:
void test_CXX_regressions();
void test_CXX_regressions_data();
......
......@@ -32,115 +32,166 @@
#define CPPEDITOR_INTERNAL_CLANGCOMPLETION_H
#include "clangcompleter.h"
#include <cplusplus/Icons.h>
#include "codemodelbackendipcintegration.h"
#include <cpptools/cppcompletionassistprocessor.h>
#include <cpptools/cppcompletionassistprovider.h>
#include <cpptools/cppmodelmanager.h>
#include <texteditor/codeassist/assistinterface.h>
#include <texteditor/codeassist/assistproposalitem.h>
#include <texteditor/codeassist/completionassistprovider.h>
#include <texteditor/codeassist/assistinterface.h>
#include <texteditor/codeassist/ifunctionhintproposalmodel.h>
#include <codemodelbackendipc/codecompletion.h>
#include <QStringList>
#include <QTextCursor>
namespace ClangCodeModel {
namespace Internal {
using CodeCompletions = QVector<CodeModelBackEnd::CodeCompletion>;
class ClangAssistProposalModel;
class ClangCompletionAssistProvider : public CppTools::CppCompletionAssistProvider
{
Q_OBJECT
public:
ClangCompletionAssistProvider();
ClangCompletionAssistProvider(IpcCommunicator::Ptr ipcCommunicator);
IAssistProvider::RunType runType() const override;
virtual TextEditor::IAssistProcessor *createProcessor() const;
virtual TextEditor::AssistInterface *createAssistInterface(
const QString &filePath, QTextDocument *document,
TextEditor::IAssistProcessor *createProcessor() const override;
TextEditor::AssistInterface *createAssistInterface(
const QString &filePath,
const TextEditor::TextEditorWidget *textEditorWidget,
const CPlusPlus::LanguageFeatures &languageFeatures,
int position, TextEditor::AssistReason reason) const;
int position,
TextEditor::AssistReason reason) const override;
private:
ClangCodeModel::ClangCompleter::Ptr m_clangCompletionWrapper;
IpcCommunicator::Ptr m_ipcCommunicator;
};
} // namespace Internal
class ClangAssistProposalItem : public TextEditor::AssistProposalItem
{
public:
ClangAssistProposalItem() {}
bool prematurelyApplies(const QChar &c) const override;
void applyContextualContent(TextEditor::TextEditorWidget *editorWidget, int basePosition) const override;
void keepCompletionOperator(unsigned compOp) { m_completionOperator = compOp; }
bool isOverloaded() const;
void addOverload(const CodeModelBackEnd::CodeCompletion &ccr);
CodeModelBackEnd::CodeCompletion originalItem() const;
bool isCodeCompletion() const;
private:
unsigned m_completionOperator;
mutable QChar m_typedChar;
QList<CodeModelBackEnd::CodeCompletion> m_overloads;
};
class ClangFunctionHintModel : public TextEditor::IFunctionHintProposalModel
{
public:
ClangFunctionHintModel(const CodeCompletions &functionSymbols);
void reset() override {}
int size() const override { return m_functionSymbols.size(); }
QString text(int index) const override;
int activeArgument(const QString &prefix) const override;
private:
CodeCompletions m_functionSymbols;
mutable int m_currentArg;
};
class CLANG_EXPORT ClangCompletionAssistInterface: public TextEditor::AssistInterface
class ClangCompletionAssistInterface: public TextEditor::AssistInterface
{
public:
ClangCompletionAssistInterface(ClangCodeModel::ClangCompleter::Ptr clangWrapper,
QTextDocument *document,
ClangCompletionAssistInterface(ClangCodeModel::Internal::IpcCommunicator::Ptr ipcCommunicator,
const TextEditor::TextEditorWidget *textEditorWidget,
int position,
const QString &fileName,
TextEditor::AssistReason reason,
const QStringList &options,
const QList<CppTools::ProjectPart::HeaderPath> &headerPaths,
const CppTools::ProjectPart::HeaderPaths &headerPaths,
const Internal::PchInfo::Ptr &pchInfo,
const CPlusPlus::LanguageFeatures &features);
ClangCodeModel::ClangCompleter::Ptr clangWrapper() const
{ return m_clangWrapper; }
const ClangCodeModel::Internal::UnsavedFiles &unsavedFiles() const
{ return m_unsavedFiles; }
ClangCodeModel::Internal::IpcCommunicator::Ptr ipcCommunicator() const;
const ClangCodeModel::Internal::UnsavedFiles &unsavedFiles() const;
bool objcEnabled() const;
const CppTools::ProjectPart::HeaderPaths &headerPaths() const;
CPlusPlus::LanguageFeatures languageFeatures() const;
const TextEditor::TextEditorWidget *textEditorWidget() const;
const QStringList &options() const
{ return m_options; }
const QList<CppTools::ProjectPart::HeaderPath> &headerPaths() const
{ return m_headerPaths; }
CPlusPlus::LanguageFeatures languageFeatures() const
{ return m_languageFeatures; }
void setHeaderPaths(const CppTools::ProjectPart::HeaderPaths &headerPaths); // For tests
private:
ClangCodeModel::ClangCompleter::Ptr m_clangWrapper;
ClangCodeModel::Internal::IpcCommunicator::Ptr m_ipcCommunicator;
ClangCodeModel::Internal::UnsavedFiles m_unsavedFiles;
QStringList m_options;
QList<CppTools::ProjectPart::HeaderPath> m_headerPaths;
CppTools::ProjectPart::HeaderPaths m_headerPaths;
Internal::PchInfo::Ptr m_savedPchPointer;
CPlusPlus::LanguageFeatures m_languageFeatures;
const TextEditor::TextEditorWidget *m_textEditorWidget;
};
class CLANG_EXPORT ClangCompletionAssistProcessor : public CppTools::CppCompletionAssistProcessor
class ClangCompletionAssistProcessor : public CppTools::CppCompletionAssistProcessor
{
Q_DECLARE_TR_FUNCTIONS(ClangCodeModel::Internal::ClangCompletionAssistProcessor)
public:
ClangCompletionAssistProcessor();
virtual ~ClangCompletionAssistProcessor();
~ClangCompletionAssistProcessor();
TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface) override;
virtual TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface);
void asyncCompletionsAvailable(const CodeCompletions &completions);
const TextEditor::TextEditorWidget *textEditorWidget() const;
private:
int startCompletionHelper();
TextEditor::IAssistProposal *startCompletionHelper();
int startOfOperator(int pos, unsigned *kind, bool wantFunctionCall) const;
int findStartOfName(int pos = -1) const;
bool accepts() const;
TextEditor::IAssistProposal *createContentProposal();
int startCompletionInternal(const QString fileName,
unsigned line, unsigned column,
int endOfExpression);
TextEditor::IAssistProposal *createProposal() const;
bool completeInclude(const QTextCursor &cursor);
bool completeInclude(int position);
void completeIncludePath(const QString &realPath, const QStringList &suffixes);
void completePreprocessor();
bool completePreprocessorDirectives();
bool completeDoxygenKeywords();
void addCompletionItem(const QString &text,
const QIcon &icon = QIcon(),
int order = 0,
const QVariant &data = QVariant());
void sendFileContent(const QString &projectFilePath, const QByteArray &modifiedFileContent);
void sendCompletionRequest(int position, const QByteArray &modifiedFileContent);
void onCompletionsAvailable(const CodeCompletions &completions);
void onFunctionHintCompletionsAvailable(const CodeCompletions &completions);
private:
QScopedPointer<const ClangCompletionAssistInterface> m_interface;
QScopedPointer<Internal::ClangAssistProposalModel> m_model;
unsigned m_completionOperator;
enum CompletionRequestType { NormalCompletion, FunctionHintCompletion } m_sentRequestType;
QString m_functionName; // For type == Type::FunctionHintCompletion
bool m_addSnippets = false; // For type == Type::NormalCompletion
};
} // namespace Internal
} // namespace Clang
#endif // CPPEDITOR_INTERNAL_CLANGCOMPLETION_H
/****************************************************************************
**
** 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 "clangcompletioncontextanalyzer.h"
#include <texteditor/codeassist/assistinterface.h>
#include <cplusplus/BackwardsScanner.h>
#include <cplusplus/ExpressionUnderCursor.h>
#include <cplusplus/SimpleLexer.h>
#include <utils/qtcassert.h>
#include <QDebug>
#include <QTextBlock>
#include <QTextCursor>
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
|| tokenKind == T_ANGLE_STRING_LITERAL
|| tokenKind == T_SLASH;
}
bool isTokenForPassThrough(unsigned tokenKind)
{
return tokenKind == T_EOF_SYMBOL
|| tokenKind == T_DOT
|| tokenKind == T_COLON_COLON
|| tokenKind == T_ARROW
|| tokenKind == T_DOT_STAR;
}
} // anonymous namespace
namespace ClangCodeModel {
namespace Internal {
ClangCompletionContextAnalyzer::ClangCompletionContextAnalyzer(
const TextEditor::AssistInterface *assistInterface,
CPlusPlus::LanguageFeatures languageFeatures)
: m_interface(assistInterface)
, m_languageFeatures(languageFeatures)
{
}
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);
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());
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;