forked from qt-creator/qt-creator
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: Marco Bubke <marco.bubke@theqtcompany.com>
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
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();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
{
|
||||
public:
|
||||
ClangCompletionAssistProvider();
|
||||
Q_OBJECT
|
||||
|
||||
virtual TextEditor::IAssistProcessor *createProcessor() const;
|
||||
virtual TextEditor::AssistInterface *createAssistInterface(
|
||||
const QString &filePath, QTextDocument *document,
|
||||
public:
|
||||
ClangCompletionAssistProvider(IpcCommunicator::Ptr ipcCommunicator);
|
||||
|
||||
IAssistProvider::RunType runType() const override;
|
||||
|
||||
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 CLANG_EXPORT ClangCompletionAssistInterface: public TextEditor::AssistInterface
|
||||
class ClangAssistProposalItem : public TextEditor::AssistProposalItem
|
||||
{
|
||||
public:
|
||||
ClangCompletionAssistInterface(ClangCodeModel::ClangCompleter::Ptr clangWrapper,
|
||||
QTextDocument *document,
|
||||
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 ClangCompletionAssistInterface: public TextEditor::AssistInterface
|
||||
{
|
||||
public:
|
||||
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();
|
||||
|
||||
virtual TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface);
|
||||
TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface) override;
|
||||
|
||||
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
|
||||
|
||||
370
src/plugins/clangcodemodel/clangcompletioncontextanalyzer.cpp
Normal file
370
src/plugins/clangcodemodel/clangcompletioncontextanalyzer.cpp
Normal file
@@ -0,0 +1,370 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
QTC_CHECK(!"Unexpected completion context");
|
||||
setActionAndClangPosition(PassThroughToLibClang, startOfName);
|
||||
return;
|
||||
}
|
||||
|
||||
ClangCompletionContextAnalyzer::FunctionInfo ClangCompletionContextAnalyzer::analyzeFunctionCall(
|
||||
int endOfOperator) const
|
||||
{
|
||||
int index = skipPrecedingWhitespace(endOfOperator);
|
||||
QTextCursor textCursor(m_interface->textDocument());
|
||||
textCursor.setPosition(index);
|
||||
|
||||
ExpressionUnderCursor euc(m_languageFeatures);
|
||||
index = euc.startOfFunctionCall(textCursor);
|
||||
const int functionNameStart = findStartOfName(index);
|
||||
|
||||
QTextCursor textCursor2(m_interface->textDocument());
|
||||
textCursor2.setPosition(functionNameStart);
|
||||
textCursor2.setPosition(index, QTextCursor::KeepAnchor);
|
||||
|
||||
FunctionInfo info;
|
||||
info.functionNamePosition = functionNameStart;
|
||||
info.functionName = textCursor2.selectedText().trimmed();
|
||||
return info;
|
||||
}
|
||||
|
||||
int ClangCompletionContextAnalyzer::findStartOfName(int position) const
|
||||
{
|
||||
if (position == -1)
|
||||
position = m_interface->position();
|
||||
QChar chr;
|
||||
|
||||
do {
|
||||
chr = m_interface->characterAt(--position);
|
||||
// TODO: Check also chr.isHighSurrogate() / ch.isLowSurrogate()?
|
||||
// See also CppTools::isValidFirstIdentifierChar
|
||||
} while (chr.isLetterOrNumber() || chr == QLatin1Char('_'));
|
||||
|
||||
return position + 1;
|
||||
}
|
||||
|
||||
int ClangCompletionContextAnalyzer::skipPrecedingWhitespace(int position) const
|
||||
{
|
||||
QTC_ASSERT(position >= 0, return position);
|
||||
while (m_interface->characterAt(position - 1).isSpace())
|
||||
--position;
|
||||
return position;
|
||||
}
|
||||
|
||||
int ClangCompletionContextAnalyzer::startOfOperator(int pos,
|
||||
unsigned *kind,
|
||||
bool wantFunctionCall) const
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
void ClangCompletionContextAnalyzer::setActionAndClangPosition(CompletionAction action,
|
||||
int position)
|
||||
{
|
||||
QTC_CHECK(position >= -1);
|
||||
m_completionAction = action;
|
||||
m_positionForClang = position;
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace ClangCodeModel
|
||||
94
src/plugins/clangcodemodel/clangcompletioncontextanalyzer.h
Normal file
94
src/plugins/clangcodemodel/clangcompletioncontextanalyzer.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef CLANGCOMPLETIONCONTEXTANALYZER_H
|
||||
#define CLANGCOMPLETIONCONTEXTANALYZER_H
|
||||
|
||||
#include <cplusplus/Token.h>
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace TextEditor { class AssistInterface; }
|
||||
|
||||
namespace ClangCodeModel {
|
||||
namespace Internal {
|
||||
|
||||
class ClangCompletionContextAnalyzer
|
||||
{
|
||||
public:
|
||||
ClangCompletionContextAnalyzer(const TextEditor::AssistInterface *assistInterface,
|
||||
CPlusPlus::LanguageFeatures languageFeatures);
|
||||
void analyze();
|
||||
|
||||
enum CompletionAction {
|
||||
PassThroughToLibClang,
|
||||
PassThroughToLibClangAfterLeftParen,
|
||||
CompleteDoxygenKeyword,
|
||||
CompleteIncludePath,
|
||||
CompletePreprocessorDirective,
|
||||
CompleteSignal,
|
||||
CompleteSlot
|
||||
};
|
||||
CompletionAction completionAction() const { return m_completionAction; }
|
||||
unsigned completionOperator() const { return m_completionOperator; }
|
||||
int positionForProposal() const { return m_positionForProposal; }
|
||||
int positionForClang() const { return m_positionForClang; }
|
||||
int positionEndOfExpression() const { return m_positionEndOfExpression; }
|
||||
|
||||
QString functionName() const { return m_functionName; }
|
||||
|
||||
private:
|
||||
ClangCompletionContextAnalyzer();
|
||||
|
||||
struct FunctionInfo { int functionNamePosition; QString functionName; };
|
||||
FunctionInfo analyzeFunctionCall(int endOfExpression) const;
|
||||
|
||||
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);
|
||||
|
||||
const TextEditor::AssistInterface * const 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;
|
||||
int m_positionForProposal = -1;
|
||||
int m_positionForClang = -1;
|
||||
int m_positionEndOfExpression = -1;
|
||||
QString m_functionName;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace ClangCodeModel
|
||||
|
||||
#endif // CLANGCOMPLETIONCONTEXTANALYZER_H
|
||||
@@ -46,7 +46,9 @@ static Q_LOGGING_CATEGORY(log, "qtc.clangcodemodel.clangeditordocumentparser")
|
||||
|
||||
namespace {
|
||||
|
||||
QStringList createOptions(const QString &filePath, const CppTools::ProjectPart::Ptr &part)
|
||||
QStringList createOptions(const QString &filePath,
|
||||
const CppTools::ProjectPart::Ptr &part,
|
||||
bool includeSpellCheck = false)
|
||||
{
|
||||
using namespace ClangCodeModel;
|
||||
|
||||
@@ -54,7 +56,9 @@ QStringList createOptions(const QString &filePath, const CppTools::ProjectPart::
|
||||
if (part.isNull())
|
||||
return options;
|
||||
|
||||
options += QLatin1String("-fspell-checking");
|
||||
if (includeSpellCheck)
|
||||
options += QLatin1String("-fspell-checking");
|
||||
|
||||
options += ClangCodeModel::Utils::createClangOptions(part, filePath);
|
||||
|
||||
if (Internal::PchInfo::Ptr pchInfo = Internal::PchManager::instance()->pchInfo(part))
|
||||
@@ -91,7 +95,7 @@ void ClangEditorDocumentParser::update(CppTools::WorkingCopy workingCopy)
|
||||
QMutexLocker lock2(&m_mutex);
|
||||
|
||||
updateProjectPart();
|
||||
const QStringList options = createOptions(filePath(), projectPart());
|
||||
const QStringList options = createOptions(filePath(), projectPart(), true);
|
||||
|
||||
qCDebug(log, "Reparse options (cmd line equivalent): %s",
|
||||
commandLine(options, filePath()).toUtf8().constData());
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
|
||||
#include "clangeditordocumentprocessor.h"
|
||||
|
||||
#include "clangmodelmanagersupport.h"
|
||||
#include "clangutils.h"
|
||||
#include "cppcreatemarkers.h"
|
||||
#include "diagnostic.h"
|
||||
#include "pchinfo.h"
|
||||
@@ -91,9 +93,13 @@ QList<TextEditor::BlockRange> toTextEditorBlocks(
|
||||
} // anonymous namespace
|
||||
|
||||
namespace ClangCodeModel {
|
||||
namespace Internal {
|
||||
|
||||
ClangEditorDocumentProcessor::ClangEditorDocumentProcessor(TextEditor::TextDocument *document)
|
||||
ClangEditorDocumentProcessor::ClangEditorDocumentProcessor(
|
||||
ModelManagerSupportClang *modelManagerSupport,
|
||||
TextEditor::TextDocument *document)
|
||||
: BaseEditorDocumentProcessor(document)
|
||||
, m_modelManagerSupport(modelManagerSupport)
|
||||
, m_parser(document->filePath().toString())
|
||||
, m_parserRevision(0)
|
||||
, m_semanticHighlighter(document)
|
||||
@@ -122,6 +128,17 @@ ClangEditorDocumentProcessor::~ClangEditorDocumentProcessor()
|
||||
{
|
||||
m_parserWatcher.cancel();
|
||||
m_parserWatcher.waitForFinished();
|
||||
|
||||
const CppTools::ProjectPart::Ptr projectPart = m_parser.projectPart();
|
||||
QTC_ASSERT(projectPart, return);
|
||||
|
||||
QString projectFilePath;
|
||||
if (Utils::isProjectPartValid(projectPart))
|
||||
projectFilePath = projectPart->projectFile; // OK, Project Part is still loaded
|
||||
|
||||
QTC_ASSERT(m_modelManagerSupport, return);
|
||||
m_modelManagerSupport->ipcCommunicator()->unregisterFilesForCodeCompletion(
|
||||
{CodeModelBackEnd::FileContainer(filePath(), projectFilePath)});
|
||||
}
|
||||
|
||||
void ClangEditorDocumentProcessor::run()
|
||||
@@ -188,4 +205,5 @@ void ClangEditorDocumentProcessor::onParserFinished()
|
||||
m_semanticHighlighter.run();
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace ClangCodeModel
|
||||
|
||||
@@ -38,15 +38,20 @@
|
||||
#include <cpptools/semantichighlighter.h>
|
||||
|
||||
#include <QFutureWatcher>
|
||||
#include <QPointer>
|
||||
|
||||
namespace ClangCodeModel {
|
||||
namespace Internal {
|
||||
|
||||
class ModelManagerSupportClang;
|
||||
|
||||
class ClangEditorDocumentProcessor : public CppTools::BaseEditorDocumentProcessor
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ClangEditorDocumentProcessor(TextEditor::TextDocument *document);
|
||||
ClangEditorDocumentProcessor(ModelManagerSupportClang *modelManagerSupport,
|
||||
TextEditor::TextDocument *document);
|
||||
~ClangEditorDocumentProcessor();
|
||||
|
||||
// BaseEditorDocumentProcessor interface
|
||||
@@ -61,6 +66,8 @@ private slots:
|
||||
void onParserFinished();
|
||||
|
||||
private:
|
||||
QPointer<ModelManagerSupportClang> m_modelManagerSupport;
|
||||
|
||||
ClangEditorDocumentParser m_parser;
|
||||
QFutureWatcher<void> m_parserWatcher;
|
||||
unsigned m_parserRevision;
|
||||
@@ -69,6 +76,7 @@ private:
|
||||
CppTools::BuiltinEditorDocumentProcessor m_builtinProcessor;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace ClangCodeModel
|
||||
|
||||
#endif // CLANGEDITORDOCUMENTPROCESSOR_H
|
||||
|
||||
@@ -28,43 +28,178 @@
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "clangmodelmanagersupport.h"
|
||||
|
||||
#include "constants.h"
|
||||
#include "clangcompletion.h"
|
||||
#include "clangeditordocumentprocessor.h"
|
||||
#include "clangmodelmanagersupport.h"
|
||||
#include "clangutils.h"
|
||||
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <cpptools/baseeditordocumentparser.h>
|
||||
#include <cpptools/editordocumenthandle.h>
|
||||
#include <projectexplorer/project.h>
|
||||
|
||||
#include <codemodelbackendipc/cmbregisterprojectsforcodecompletioncommand.h>
|
||||
#include <codemodelbackendipc/filecontainer.h>
|
||||
#include <codemodelbackendipc/projectpartcontainer.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
using namespace ClangCodeModel;
|
||||
using namespace ClangCodeModel::Internal;
|
||||
|
||||
ModelManagerSupport::ModelManagerSupport()
|
||||
: m_completionAssistProvider(new ClangCompletionAssistProvider)
|
||||
static ModelManagerSupportClang *m_instance = 0;
|
||||
|
||||
static CppTools::CppModelManager *cppModelManager()
|
||||
{
|
||||
return CppTools::CppModelManager::instance();
|
||||
}
|
||||
|
||||
ModelManagerSupport::~ModelManagerSupport()
|
||||
ModelManagerSupportClang::ModelManagerSupportClang()
|
||||
: m_ipcCommunicator(new IpcCommunicator)
|
||||
, m_completionAssistProvider(new ClangCompletionAssistProvider(m_ipcCommunicator))
|
||||
{
|
||||
QTC_CHECK(!m_instance);
|
||||
m_instance = this;
|
||||
|
||||
Core::EditorManager *editorManager = Core::EditorManager::instance();
|
||||
connect(editorManager, &Core::EditorManager::currentEditorChanged,
|
||||
this, &ModelManagerSupportClang::onCurrentEditorChanged);
|
||||
connect(editorManager, &Core::EditorManager::editorOpened,
|
||||
this, &ModelManagerSupportClang::onEditorOpened);
|
||||
|
||||
CppTools::CppModelManager *modelManager = cppModelManager();
|
||||
connect(modelManager, &CppTools::CppModelManager::projectPartsUpdated,
|
||||
this, &ModelManagerSupportClang::onProjectPartsUpdated);
|
||||
connect(modelManager, &CppTools::CppModelManager::projectPartsRemoved,
|
||||
this, &ModelManagerSupportClang::onProjectPartsRemoved);
|
||||
}
|
||||
|
||||
QString ModelManagerSupport::id() const
|
||||
ModelManagerSupportClang::~ModelManagerSupportClang()
|
||||
{
|
||||
return QLatin1String("ClangCodeMode.ClangCodeMode");
|
||||
m_instance = 0;
|
||||
}
|
||||
|
||||
QString ModelManagerSupport::displayName() const
|
||||
CppTools::CppCompletionAssistProvider *ModelManagerSupportClang::completionAssistProvider()
|
||||
{
|
||||
return m_completionAssistProvider.data();
|
||||
}
|
||||
|
||||
CppTools::BaseEditorDocumentProcessor *ModelManagerSupportClang::editorDocumentProcessor(
|
||||
TextEditor::TextDocument *baseTextDocument)
|
||||
{
|
||||
return new ClangEditorDocumentProcessor(this, baseTextDocument);
|
||||
}
|
||||
|
||||
void ModelManagerSupportClang::onCurrentEditorChanged(Core::IEditor *newCurrent)
|
||||
{
|
||||
// If we switch away from a cpp editor, update the backend about
|
||||
// the document's unsaved content.
|
||||
if (m_previousCppEditor && m_previousCppEditor->document()->isModified()) {
|
||||
m_ipcCommunicator->updateUnsavedFileFromCppEditorDocument(
|
||||
m_previousCppEditor->document()->filePath().toString());
|
||||
}
|
||||
|
||||
// Remember previous editor
|
||||
if (newCurrent && cppModelManager()->isCppEditor(newCurrent))
|
||||
m_previousCppEditor = newCurrent;
|
||||
else
|
||||
m_previousCppEditor.clear();
|
||||
}
|
||||
|
||||
void ModelManagerSupportClang::onEditorOpened(Core::IEditor *editor)
|
||||
{
|
||||
QTC_ASSERT(editor, return);
|
||||
Core::IDocument *document = editor->document();
|
||||
QTC_ASSERT(document, return);
|
||||
TextEditor::TextDocument *textDocument = qobject_cast<TextEditor::TextDocument *>(document);
|
||||
QTC_ASSERT(textDocument, return);
|
||||
|
||||
if (cppModelManager()->isCppEditor(editor)) {
|
||||
// Handle externally changed documents
|
||||
connect(textDocument, &Core::IDocument::reloadFinished,
|
||||
this, &ModelManagerSupportClang::onCppDocumentReloadFinished,
|
||||
Qt::UniqueConnection);
|
||||
|
||||
// Handle changes from e.g. refactoring actions
|
||||
connect(textDocument, &TextEditor::TextDocument::contentsChanged,
|
||||
this, &ModelManagerSupportClang::onCppDocumentContentsChanged,
|
||||
Qt::UniqueConnection);
|
||||
|
||||
// TODO: Ensure that not fully loaded documents are updated?
|
||||
}
|
||||
}
|
||||
|
||||
void ModelManagerSupportClang::onCppDocumentReloadFinished(bool success)
|
||||
{
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
Core::IDocument *document = qobject_cast<Core::IDocument *>(sender());
|
||||
m_ipcCommunicator->updateUnsavedFileIfNotCurrentDocument(document);
|
||||
}
|
||||
|
||||
void ModelManagerSupportClang::onCppDocumentContentsChanged()
|
||||
{
|
||||
Core::IDocument *document = qobject_cast<Core::IDocument *>(sender());
|
||||
m_ipcCommunicator->updateUnsavedFileIfNotCurrentDocument(document);
|
||||
}
|
||||
|
||||
void ModelManagerSupportClang::onAbstractEditorSupportContentsUpdated(const QString &filePath,
|
||||
const QByteArray &content)
|
||||
{
|
||||
QTC_ASSERT(!filePath.isEmpty(), return);
|
||||
m_ipcCommunicator->updateUnsavedFile(filePath, content);
|
||||
}
|
||||
|
||||
void ModelManagerSupportClang::onAbstractEditorSupportRemoved(const QString &filePath)
|
||||
{
|
||||
QTC_ASSERT(!filePath.isEmpty(), return);
|
||||
if (!cppModelManager()->cppEditorDocument(filePath)) {
|
||||
const QString projectFilePath = Utils::projectFilePathForFile(filePath);
|
||||
m_ipcCommunicator->unregisterFilesForCodeCompletion(
|
||||
{CodeModelBackEnd::FileContainer(filePath, projectFilePath)});
|
||||
}
|
||||
}
|
||||
|
||||
void ModelManagerSupportClang::onProjectPartsUpdated(ProjectExplorer::Project *project)
|
||||
{
|
||||
QTC_ASSERT(project, return);
|
||||
const CppTools::ProjectInfo projectInfo = cppModelManager()->projectInfo(project);
|
||||
QTC_ASSERT(projectInfo.isValid(), return);
|
||||
m_ipcCommunicator->registerProjectsParts(projectInfo.projectParts());
|
||||
}
|
||||
|
||||
void ModelManagerSupportClang::onProjectPartsRemoved(const QStringList &projectFiles)
|
||||
{
|
||||
m_ipcCommunicator->unregisterProjectPartsForCodeCompletion(projectFiles);
|
||||
}
|
||||
|
||||
ModelManagerSupportClang *ModelManagerSupportClang::instance()
|
||||
{
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
IpcCommunicator::Ptr ModelManagerSupportClang::ipcCommunicator()
|
||||
{
|
||||
return m_ipcCommunicator;
|
||||
}
|
||||
|
||||
QString ModelManagerSupportProviderClang::id() const
|
||||
{
|
||||
return QLatin1String(Constants::CLANG_MODELMANAGERSUPPORT_ID);
|
||||
}
|
||||
|
||||
QString ModelManagerSupportProviderClang::displayName() const
|
||||
{
|
||||
//: Display name
|
||||
return QCoreApplication::translate("ClangCodeModel::Internal::ModelManagerSupport",
|
||||
"Clang");
|
||||
}
|
||||
|
||||
CppTools::CppCompletionAssistProvider *ModelManagerSupport::completionAssistProvider()
|
||||
CppTools::ModelManagerSupport::Ptr ModelManagerSupportProviderClang::createModelManagerSupport()
|
||||
{
|
||||
return m_completionAssistProvider.data();
|
||||
}
|
||||
|
||||
CppTools::BaseEditorDocumentProcessor *ModelManagerSupport::editorDocumentProcessor(
|
||||
TextEditor::TextDocument *baseTextDocument)
|
||||
{
|
||||
return new ClangEditorDocumentProcessor(baseTextDocument);
|
||||
return CppTools::ModelManagerSupport::Ptr(new ModelManagerSupportClang);
|
||||
}
|
||||
|
||||
@@ -31,30 +31,62 @@
|
||||
#ifndef CLANGCODEMODEL_INTERNAL_CLANGMODELMANAGERSUPPORT_H
|
||||
#define CLANGCODEMODEL_INTERNAL_CLANGMODELMANAGERSUPPORT_H
|
||||
|
||||
#include "clangcompletion.h"
|
||||
|
||||
#include <cpptools/cppmodelmanagersupport.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
|
||||
namespace Core { class IDocument; }
|
||||
|
||||
namespace ClangCodeModel {
|
||||
namespace Internal {
|
||||
|
||||
class ModelManagerSupport: public CppTools::ModelManagerSupport
|
||||
class ModelManagerSupportClang:
|
||||
public QObject,
|
||||
public CppTools::ModelManagerSupport
|
||||
{
|
||||
Q_DISABLE_COPY(ModelManagerSupport)
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(ModelManagerSupportClang)
|
||||
|
||||
public:
|
||||
ModelManagerSupport();
|
||||
virtual ~ModelManagerSupport();
|
||||
ModelManagerSupportClang();
|
||||
~ModelManagerSupportClang();
|
||||
|
||||
virtual QString id() const;
|
||||
virtual QString displayName() const;
|
||||
CppTools::CppCompletionAssistProvider *completionAssistProvider() override;
|
||||
CppTools::BaseEditorDocumentProcessor *editorDocumentProcessor(
|
||||
TextEditor::TextDocument *baseTextDocument) override;
|
||||
|
||||
virtual CppTools::CppCompletionAssistProvider *completionAssistProvider();
|
||||
virtual CppTools::BaseEditorDocumentProcessor *editorDocumentProcessor(
|
||||
TextEditor::TextDocument *baseTextDocument);
|
||||
IpcCommunicator::Ptr ipcCommunicator();
|
||||
|
||||
public: // for tests
|
||||
static ModelManagerSupportClang *instance();
|
||||
|
||||
private:
|
||||
QScopedPointer<CppTools::CppCompletionAssistProvider> m_completionAssistProvider;
|
||||
void onEditorOpened(Core::IEditor *editor);
|
||||
void onCurrentEditorChanged(Core::IEditor *newCurrent);
|
||||
void onCppDocumentReloadFinished(bool success);
|
||||
void onCppDocumentContentsChanged();
|
||||
|
||||
void onAbstractEditorSupportContentsUpdated(const QString &filePath, const QByteArray &content);
|
||||
void onAbstractEditorSupportRemoved(const QString &filePath);
|
||||
|
||||
void onProjectPartsUpdated(ProjectExplorer::Project *project);
|
||||
void onProjectPartsRemoved(const QStringList &projectFiles);
|
||||
|
||||
IpcCommunicator::Ptr m_ipcCommunicator;
|
||||
QScopedPointer<ClangCompletionAssistProvider> m_completionAssistProvider;
|
||||
QPointer<Core::IEditor> m_previousCppEditor;
|
||||
};
|
||||
|
||||
class ModelManagerSupportProviderClang : public CppTools::ModelManagerSupportProvider
|
||||
{
|
||||
public:
|
||||
QString id() const override;
|
||||
QString displayName() const override;
|
||||
|
||||
CppTools::ModelManagerSupport::Ptr createModelManagerSupport() override;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
@@ -36,9 +36,12 @@
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/idocument.h>
|
||||
|
||||
#include <cpptools/baseeditordocumentparser.h>
|
||||
#include <cpptools/cppprojects.h>
|
||||
#include <cpptools/cppworkingcopy.h>
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QLoggingCategory>
|
||||
@@ -202,5 +205,28 @@ QStringList createPCHInclusionOptions(const QString &pchFile)
|
||||
return createPCHInclusionOptions(QStringList() << pchFile);
|
||||
}
|
||||
|
||||
ProjectPart::Ptr projectPartForFile(const QString &filePath)
|
||||
{
|
||||
if (CppTools::BaseEditorDocumentParser *parser = CppTools::BaseEditorDocumentParser::get(filePath))
|
||||
return parser->projectPart();
|
||||
return ProjectPart::Ptr();
|
||||
}
|
||||
|
||||
bool isProjectPartValid(const ProjectPart::Ptr projectPart)
|
||||
{
|
||||
if (projectPart)
|
||||
return CppModelManager::instance()->projectPartForProjectFile(projectPart->projectFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
QString projectFilePathForFile(const QString &filePath)
|
||||
{
|
||||
const ProjectPart::Ptr projectPart = projectPartForFile(filePath);
|
||||
|
||||
if (isProjectPartValid(projectPart))
|
||||
return projectPart->projectFile; // OK, Project Part is still loaded
|
||||
return QString();
|
||||
}
|
||||
|
||||
} // namespace Utils
|
||||
} // namespace Clang
|
||||
|
||||
@@ -51,6 +51,10 @@ QStringList createClangOptions(const CppTools::ProjectPart::Ptr &pPart,
|
||||
const QString &fileName = QString());
|
||||
QStringList createPCHInclusionOptions(const QString &pchFile);
|
||||
|
||||
CppTools::ProjectPart::Ptr projectPartForFile(const QString &filePath);
|
||||
bool isProjectPartValid(const CppTools::ProjectPart::Ptr projectPart);
|
||||
QString projectFilePathForFile(const QString &filePath);
|
||||
|
||||
} // namespace Utils
|
||||
} // namespace Clang
|
||||
|
||||
|
||||
455
src/plugins/clangcodemodel/codemodelbackendipcintegration.cpp
Normal file
455
src/plugins/clangcodemodel/codemodelbackendipcintegration.cpp
Normal file
@@ -0,0 +1,455 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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 "codemodelbackendipcintegration.h"
|
||||
|
||||
#include "clangcompletion.h"
|
||||
#include "clangmodelmanagersupport.h"
|
||||
#include "clangutils.h"
|
||||
#include "pchmanager.h"
|
||||
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <cpptools/abstracteditorsupport.h>
|
||||
#include <cpptools/baseeditordocumentprocessor.h>
|
||||
#include <cpptools/editordocumenthandle.h>
|
||||
|
||||
#include <texteditor/codeassist/functionhintproposal.h>
|
||||
#include <texteditor/codeassist/iassistprocessor.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
|
||||
#include <utils/hostosinfo.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <codemodelbackendipc/cmbcodecompletedcommand.h>
|
||||
#include <codemodelbackendipc/cmbcompletecodecommand.h>
|
||||
#include <codemodelbackendipc/cmbechocommand.h>
|
||||
#include <codemodelbackendipc/cmbregistertranslationunitsforcodecompletioncommand.h>
|
||||
#include <codemodelbackendipc/cmbregisterprojectsforcodecompletioncommand.h>
|
||||
#include <codemodelbackendipc/cmbunregistertranslationunitsforcodecompletioncommand.h>
|
||||
#include <codemodelbackendipc/cmbunregisterprojectsforcodecompletioncommand.h>
|
||||
#include <codemodelbackendipc/cmbcommands.h>
|
||||
#include <codemodelbackendipc/projectpartsdonotexistcommand.h>
|
||||
#include <codemodelbackendipc/translationunitdoesnotexistcommand.h>
|
||||
|
||||
#include <cplusplus/Icons.h>
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QLoggingCategory>
|
||||
#include <QProcess>
|
||||
|
||||
static Q_LOGGING_CATEGORY(log, "qtc.clangcodemodel.ipc")
|
||||
|
||||
using namespace CPlusPlus;
|
||||
using namespace ClangCodeModel;
|
||||
using namespace ClangCodeModel::Internal;
|
||||
using namespace CodeModelBackEnd;
|
||||
using namespace TextEditor;
|
||||
|
||||
namespace {
|
||||
|
||||
QString backendProcessPath()
|
||||
{
|
||||
return QCoreApplication::applicationDirPath()
|
||||
+ QStringLiteral("/codemodelbackend")
|
||||
+ QStringLiteral(QTC_HOST_EXE_SUFFIX);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
IpcReceiver::IpcReceiver()
|
||||
{
|
||||
}
|
||||
|
||||
IpcReceiver::~IpcReceiver()
|
||||
{
|
||||
deleteAndClearWaitingAssistProcessors();
|
||||
}
|
||||
|
||||
void IpcReceiver::setAliveHandler(const IpcReceiver::AliveHandler &handler)
|
||||
{
|
||||
m_aliveHandler = handler;
|
||||
}
|
||||
|
||||
void IpcReceiver::addExpectedCodeCompletedCommand(
|
||||
quint64 ticket,
|
||||
ClangCompletionAssistProcessor *processor)
|
||||
{
|
||||
QTC_ASSERT(processor, return);
|
||||
QTC_CHECK(!m_assistProcessorsTable.contains(ticket));
|
||||
m_assistProcessorsTable.insert(ticket, processor);
|
||||
}
|
||||
|
||||
void IpcReceiver::deleteAndClearWaitingAssistProcessors()
|
||||
{
|
||||
qDeleteAll(m_assistProcessorsTable.begin(), m_assistProcessorsTable.end());
|
||||
m_assistProcessorsTable.clear();
|
||||
}
|
||||
|
||||
void IpcReceiver::deleteProcessorsOfEditorWidget(TextEditor::TextEditorWidget *textEditorWidget)
|
||||
{
|
||||
QMutableHashIterator<quint64, ClangCompletionAssistProcessor *> it(m_assistProcessorsTable);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
ClangCompletionAssistProcessor *assistProcessor = it.value();
|
||||
if (assistProcessor->textEditorWidget() == textEditorWidget) {
|
||||
delete assistProcessor;
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IpcReceiver::alive()
|
||||
{
|
||||
qCDebug(log) << "<<< AliveCommand";
|
||||
QTC_ASSERT(m_aliveHandler, return);
|
||||
m_aliveHandler();
|
||||
}
|
||||
|
||||
void IpcReceiver::echo(const EchoCommand &command)
|
||||
{
|
||||
qCDebug(log) << "<<<" << command;
|
||||
}
|
||||
|
||||
void IpcReceiver::codeCompleted(const CodeCompletedCommand &command)
|
||||
{
|
||||
qCDebug(log) << "<<< CodeCompletedCommand with" << command.codeCompletions().size() << "items";
|
||||
|
||||
const quint64 ticket = command.ticketNumber();
|
||||
QScopedPointer<ClangCompletionAssistProcessor> processor(m_assistProcessorsTable.take(ticket));
|
||||
if (processor)
|
||||
processor->asyncCompletionsAvailable(command.codeCompletions());
|
||||
}
|
||||
|
||||
void IpcReceiver::translationUnitDoesNotExist(const TranslationUnitDoesNotExistCommand &command)
|
||||
{
|
||||
QTC_CHECK(!"Got TranslationUnitDoesNotExistCommand");
|
||||
qCDebug(log) << "<<< ERROR:" << command;
|
||||
}
|
||||
|
||||
void IpcReceiver::projectPartsDoNotExist(const ProjectPartsDoNotExistCommand &command)
|
||||
{
|
||||
QTC_CHECK(!"Got ProjectDoesNotExistCommand");
|
||||
qCDebug(log) << "<<< ERROR:" << command;
|
||||
}
|
||||
|
||||
class IpcSender : public IpcSenderInterface
|
||||
{
|
||||
public:
|
||||
IpcSender(CodeModelBackEnd::ConnectionClient &connectionClient)
|
||||
: m_connection(connectionClient)
|
||||
{}
|
||||
|
||||
void end() override;
|
||||
void registerTranslationUnitsForCodeCompletion(const CodeModelBackEnd::RegisterTranslationUnitForCodeCompletionCommand &command) override;
|
||||
void unregisterTranslationUnitsForCodeCompletion(const CodeModelBackEnd::UnregisterTranslationUnitsForCodeCompletionCommand &command) override;
|
||||
void registerProjectPartsForCodeCompletion(const CodeModelBackEnd::RegisterProjectPartsForCodeCompletionCommand &command) override;
|
||||
void unregisterProjectPartsForCodeCompletion(const CodeModelBackEnd::UnregisterProjectPartsForCodeCompletionCommand &command) override;
|
||||
void completeCode(const CodeModelBackEnd::CompleteCodeCommand &command) override;
|
||||
|
||||
private:
|
||||
CodeModelBackEnd::ConnectionClient &m_connection;
|
||||
};
|
||||
|
||||
void IpcSender::end()
|
||||
{
|
||||
QTC_CHECK(m_connection.isConnected());
|
||||
m_connection.sendEndCommand();
|
||||
}
|
||||
|
||||
void IpcSender::registerTranslationUnitsForCodeCompletion(const RegisterTranslationUnitForCodeCompletionCommand &command)
|
||||
{
|
||||
QTC_CHECK(m_connection.isConnected());
|
||||
m_connection.serverProxy().registerTranslationUnitsForCodeCompletion(command);
|
||||
}
|
||||
|
||||
void IpcSender::unregisterTranslationUnitsForCodeCompletion(const UnregisterTranslationUnitsForCodeCompletionCommand &command)
|
||||
{
|
||||
QTC_CHECK(m_connection.isConnected());
|
||||
m_connection.serverProxy().unregisterTranslationUnitsForCodeCompletion(command);
|
||||
}
|
||||
|
||||
void IpcSender::registerProjectPartsForCodeCompletion(const RegisterProjectPartsForCodeCompletionCommand &command)
|
||||
{
|
||||
QTC_CHECK(m_connection.isConnected());
|
||||
m_connection.serverProxy().registerProjectPartsForCodeCompletion(command);
|
||||
}
|
||||
|
||||
void IpcSender::unregisterProjectPartsForCodeCompletion(const UnregisterProjectPartsForCodeCompletionCommand &command)
|
||||
{
|
||||
QTC_CHECK(m_connection.isConnected());
|
||||
m_connection.serverProxy().unregisterProjectPartsForCodeCompletion(command);
|
||||
}
|
||||
|
||||
void IpcSender::completeCode(const CompleteCodeCommand &command)
|
||||
{
|
||||
QTC_CHECK(m_connection.isConnected());
|
||||
m_connection.serverProxy().completeCode(command);
|
||||
}
|
||||
|
||||
IpcCommunicator::IpcCommunicator()
|
||||
: m_connection(&m_ipcReceiver)
|
||||
, m_ipcSender(new IpcSender(m_connection))
|
||||
{
|
||||
m_ipcReceiver.setAliveHandler([this]() { m_connection.resetProcessAliveTimer(); });
|
||||
|
||||
connect(Core::EditorManager::instance(), &Core::EditorManager::editorAboutToClose,
|
||||
this, &IpcCommunicator::onEditorAboutToClose);
|
||||
|
||||
connect(Core::ICore::instance(), &Core::ICore::coreAboutToClose, [this]() {
|
||||
m_sendMode = IgnoreSendRequests;
|
||||
});
|
||||
|
||||
initializeBackend();
|
||||
}
|
||||
|
||||
static bool areCommandsRegistered = false;
|
||||
|
||||
void IpcCommunicator::initializeBackend()
|
||||
{
|
||||
// TODO: Add a asynchron API to ConnectionClient, otherwise we might hang here
|
||||
|
||||
if (!areCommandsRegistered) {
|
||||
areCommandsRegistered = true;
|
||||
Commands::registerCommands();
|
||||
}
|
||||
QElapsedTimer timer; timer.start();
|
||||
|
||||
const QString codeModelBackEndProcessPath = backendProcessPath();
|
||||
qCDebug(log) << "Starting" << codeModelBackEndProcessPath;
|
||||
QTC_ASSERT(QFileInfo(codeModelBackEndProcessPath).exists(), return);
|
||||
|
||||
m_connection.setProcessAliveTimerInterval(10 * 1000);
|
||||
m_connection.setProcessPath(codeModelBackEndProcessPath);
|
||||
|
||||
connect(&m_connection, &ConnectionClient::processRestarted,
|
||||
this, &IpcCommunicator::onBackendRestarted);
|
||||
|
||||
if (m_connection.connectToServer()) {
|
||||
qCDebug(log) << "...started and connected in" << timer.elapsed() << "ms.";
|
||||
initializeBackendWithCurrentData();
|
||||
} else {
|
||||
qCDebug(log) << "...failed.";
|
||||
}
|
||||
}
|
||||
|
||||
void IpcCommunicator::registerEmptyProjectForProjectLessFiles()
|
||||
{
|
||||
QTC_CHECK(m_connection.isConnected());
|
||||
registerProjectPartsForCodeCompletion({CodeModelBackEnd::ProjectPartContainer(
|
||||
Utf8String(),
|
||||
Utf8StringVector())});
|
||||
}
|
||||
|
||||
void IpcCommunicator::registerCurrentProjectParts()
|
||||
{
|
||||
using namespace CppTools;
|
||||
|
||||
const QList<ProjectInfo> projectInfos = CppModelManager::instance()->projectInfos();
|
||||
foreach (const ProjectInfo &projectInfo, projectInfos)
|
||||
registerProjectsParts(projectInfo.projectParts());
|
||||
}
|
||||
|
||||
void IpcCommunicator::registerCurrentUnsavedFiles()
|
||||
{
|
||||
using namespace CppTools;
|
||||
|
||||
const auto cppEditorDocuments = CppModelManager::instance()->cppEditorDocuments();
|
||||
foreach (const CppEditorDocumentHandle *cppEditorDocument, cppEditorDocuments) {
|
||||
if (cppEditorDocument->processor()->baseTextDocument()->isModified())
|
||||
updateUnsavedFileFromCppEditorDocument(cppEditorDocument->filePath());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void IpcCommunicator::registerCurrrentCodeModelUiHeaders()
|
||||
{
|
||||
using namespace CppTools;
|
||||
|
||||
const auto editorSupports = CppModelManager::instance()->abstractEditorSupports();
|
||||
foreach (const AbstractEditorSupport *es, editorSupports)
|
||||
updateUnsavedFile(es->fileName(), es->contents());
|
||||
}
|
||||
|
||||
static QStringList projectPartCommandLine(const CppTools::ProjectPart::Ptr &projectPart)
|
||||
{
|
||||
QStringList options = ClangCodeModel::Utils::createClangOptions(projectPart,
|
||||
CppTools::ProjectFile::Unclassified); // No language option
|
||||
if (PchInfo::Ptr pchInfo = PchManager::instance()->pchInfo(projectPart))
|
||||
options += ClangCodeModel::Utils::createPCHInclusionOptions(pchInfo->fileName());
|
||||
return options;
|
||||
}
|
||||
|
||||
static CodeModelBackEnd::ProjectPartContainer toProjectPartContainer(
|
||||
const CppTools::ProjectPart::Ptr &projectPart)
|
||||
{
|
||||
const QStringList arguments = projectPartCommandLine(projectPart);
|
||||
return CodeModelBackEnd::ProjectPartContainer(projectPart->projectFile,
|
||||
Utf8StringVector(arguments));
|
||||
}
|
||||
|
||||
static QVector<CodeModelBackEnd::ProjectPartContainer> toProjectPartContainers(
|
||||
const QList<CppTools::ProjectPart::Ptr> projectParts)
|
||||
{
|
||||
QVector<CodeModelBackEnd::ProjectPartContainer> projectPartContainers;
|
||||
projectPartContainers.reserve(projectParts.size());
|
||||
foreach (const CppTools::ProjectPart::Ptr &projectPart, projectParts)
|
||||
projectPartContainers << toProjectPartContainer(projectPart);
|
||||
return projectPartContainers;
|
||||
}
|
||||
|
||||
void IpcCommunicator::registerProjectsParts(const QList<CppTools::ProjectPart::Ptr> projectParts)
|
||||
{
|
||||
const auto projectPartContainers = toProjectPartContainers(projectParts);
|
||||
registerProjectPartsForCodeCompletion(projectPartContainers);
|
||||
}
|
||||
|
||||
void IpcCommunicator::updateUnsavedFileFromCppEditorDocument(const QString &filePath)
|
||||
{
|
||||
const QByteArray unsavedContent = CppTools::CppModelManager::instance()
|
||||
->cppEditorDocument(filePath)->contents();
|
||||
updateUnsavedFile(filePath, unsavedContent);
|
||||
}
|
||||
|
||||
void IpcCommunicator::updateUnsavedFile(const QString &filePath, const QByteArray &contents)
|
||||
{
|
||||
const QString projectFilePath = Utils::projectFilePathForFile(filePath);
|
||||
const bool hasUnsavedContent = true;
|
||||
|
||||
// TODO: Send new only if changed
|
||||
registerFilesForCodeCompletion({
|
||||
CodeModelBackEnd::FileContainer(filePath,
|
||||
projectFilePath,
|
||||
Utf8String::fromByteArray(contents),
|
||||
hasUnsavedContent)
|
||||
});
|
||||
}
|
||||
|
||||
void IpcCommunicator::updateUnsavedFileIfNotCurrentDocument(Core::IDocument *document)
|
||||
{
|
||||
QTC_ASSERT(document, return);
|
||||
|
||||
if (Core::EditorManager::currentDocument() != document)
|
||||
updateUnsavedFileFromCppEditorDocument(document->filePath().toString());
|
||||
}
|
||||
|
||||
void IpcCommunicator::onBackendRestarted()
|
||||
{
|
||||
qWarning("Codemodelbackend finished unexpectedly, restarted.");
|
||||
qCDebug(log) << "Backend restarted, re-initializing with project data and unsaved files.";
|
||||
|
||||
m_ipcReceiver.deleteAndClearWaitingAssistProcessors();
|
||||
initializeBackendWithCurrentData();
|
||||
}
|
||||
|
||||
void IpcCommunicator::onEditorAboutToClose(Core::IEditor *editor)
|
||||
{
|
||||
if (auto *textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor))
|
||||
m_ipcReceiver.deleteProcessorsOfEditorWidget(textEditor->editorWidget());
|
||||
}
|
||||
|
||||
void IpcCommunicator::initializeBackendWithCurrentData()
|
||||
{
|
||||
registerEmptyProjectForProjectLessFiles();
|
||||
registerCurrentProjectParts();
|
||||
registerCurrentUnsavedFiles();
|
||||
registerCurrrentCodeModelUiHeaders();
|
||||
|
||||
emit backendReinitialized();
|
||||
}
|
||||
|
||||
IpcSenderInterface *IpcCommunicator::setIpcSender(IpcSenderInterface *ipcSender)
|
||||
{
|
||||
IpcSenderInterface *previousCommandSender = m_ipcSender.take();
|
||||
m_ipcSender.reset(ipcSender);
|
||||
return previousCommandSender;
|
||||
}
|
||||
|
||||
void IpcCommunicator::killBackendProcess()
|
||||
{
|
||||
m_connection.processForTestOnly()->kill();
|
||||
}
|
||||
|
||||
void IpcCommunicator::registerFilesForCodeCompletion(const FileContainers &fileContainers)
|
||||
{
|
||||
if (m_sendMode == IgnoreSendRequests)
|
||||
return;
|
||||
|
||||
const RegisterTranslationUnitForCodeCompletionCommand command(fileContainers);
|
||||
qCDebug(log) << ">>>" << command;
|
||||
m_ipcSender->registerTranslationUnitsForCodeCompletion(command);
|
||||
}
|
||||
|
||||
void IpcCommunicator::unregisterFilesForCodeCompletion(const FileContainers &fileContainers)
|
||||
{
|
||||
if (m_sendMode == IgnoreSendRequests)
|
||||
return;
|
||||
|
||||
const UnregisterTranslationUnitsForCodeCompletionCommand command(fileContainers);
|
||||
qCDebug(log) << ">>>" << command;
|
||||
m_ipcSender->unregisterTranslationUnitsForCodeCompletion(command);
|
||||
}
|
||||
|
||||
void IpcCommunicator::registerProjectPartsForCodeCompletion(
|
||||
const ProjectPartContainers &projectPartContainers)
|
||||
{
|
||||
if (m_sendMode == IgnoreSendRequests)
|
||||
return;
|
||||
|
||||
const RegisterProjectPartsForCodeCompletionCommand command(projectPartContainers);
|
||||
qCDebug(log) << ">>>" << command;
|
||||
m_ipcSender->registerProjectPartsForCodeCompletion(command);
|
||||
}
|
||||
|
||||
void IpcCommunicator::unregisterProjectPartsForCodeCompletion(const QStringList &filePaths)
|
||||
{
|
||||
if (m_sendMode == IgnoreSendRequests)
|
||||
return;
|
||||
|
||||
const UnregisterProjectPartsForCodeCompletionCommand command((Utf8StringVector(filePaths)));
|
||||
qCDebug(log) << ">>>" << command;
|
||||
m_ipcSender->unregisterProjectPartsForCodeCompletion(command);
|
||||
}
|
||||
|
||||
void IpcCommunicator::completeCode(ClangCompletionAssistProcessor *assistProcessor,
|
||||
const QString &filePath,
|
||||
quint32 line,
|
||||
quint32 column,
|
||||
const QString &projectFilePath)
|
||||
{
|
||||
if (m_sendMode == IgnoreSendRequests)
|
||||
return;
|
||||
|
||||
const CompleteCodeCommand command(filePath, line, column, projectFilePath);
|
||||
qCDebug(log) << ">>>" << command;
|
||||
m_ipcSender->completeCode(command);
|
||||
m_ipcReceiver.addExpectedCodeCompletedCommand(command.ticketNumber(), assistProcessor);
|
||||
}
|
||||
157
src/plugins/clangcodemodel/codemodelbackendipcintegration.h
Normal file
157
src/plugins/clangcodemodel/codemodelbackendipcintegration.h
Normal file
@@ -0,0 +1,157 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef CLANGCODEMODEL_INTERNAL_CODEMODELBACKENDIPCINTEGRATION_H
|
||||
#define CLANGCODEMODEL_INTERNAL_CODEMODELBACKENDIPCINTEGRATION_H
|
||||
|
||||
#include <cpptools/cppprojects.h>
|
||||
|
||||
#include <codemodelbackendipc/connectionclient.h>
|
||||
#include <codemodelbackendipc/filecontainer.h>
|
||||
#include <codemodelbackendipc/ipcclientinterface.h>
|
||||
#include <codemodelbackendipc/projectpartcontainer.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
#include <QVector>
|
||||
|
||||
namespace Core {
|
||||
class IEditor;
|
||||
class IDocument;
|
||||
}
|
||||
|
||||
namespace TextEditor {
|
||||
class TextEditorWidget;
|
||||
}
|
||||
|
||||
namespace ClangCodeModel {
|
||||
namespace Internal {
|
||||
|
||||
class ModelManagerSupportClang;
|
||||
|
||||
class ClangCompletionAssistProcessor;
|
||||
|
||||
class IpcReceiver : public CodeModelBackEnd::IpcClientInterface
|
||||
{
|
||||
public:
|
||||
IpcReceiver();
|
||||
~IpcReceiver();
|
||||
|
||||
using AliveHandler = std::function<void ()>;
|
||||
void setAliveHandler(const AliveHandler &handler);
|
||||
|
||||
void addExpectedCodeCompletedCommand(quint64 ticket, ClangCompletionAssistProcessor *processor);
|
||||
void deleteAndClearWaitingAssistProcessors();
|
||||
void deleteProcessorsOfEditorWidget(TextEditor::TextEditorWidget *textEditorWidget);
|
||||
|
||||
private:
|
||||
void alive() override;
|
||||
void echo(const CodeModelBackEnd::EchoCommand &command) override;
|
||||
void codeCompleted(const CodeModelBackEnd::CodeCompletedCommand &command) override;
|
||||
|
||||
void translationUnitDoesNotExist(const CodeModelBackEnd::TranslationUnitDoesNotExistCommand &command) override;
|
||||
void projectPartsDoNotExist(const CodeModelBackEnd::ProjectPartsDoNotExistCommand &command) override;
|
||||
|
||||
private:
|
||||
AliveHandler m_aliveHandler;
|
||||
QHash<quint64, ClangCompletionAssistProcessor *> m_assistProcessorsTable;
|
||||
};
|
||||
|
||||
class IpcSenderInterface
|
||||
{
|
||||
public:
|
||||
virtual ~IpcSenderInterface() {}
|
||||
|
||||
virtual void end() = 0;
|
||||
virtual void registerTranslationUnitsForCodeCompletion(const CodeModelBackEnd::RegisterTranslationUnitForCodeCompletionCommand &command) = 0;
|
||||
virtual void unregisterTranslationUnitsForCodeCompletion(const CodeModelBackEnd::UnregisterTranslationUnitsForCodeCompletionCommand &command) = 0;
|
||||
virtual void registerProjectPartsForCodeCompletion(const CodeModelBackEnd::RegisterProjectPartsForCodeCompletionCommand &command) = 0;
|
||||
virtual void unregisterProjectPartsForCodeCompletion(const CodeModelBackEnd::UnregisterProjectPartsForCodeCompletionCommand &command) = 0;
|
||||
virtual void completeCode(const CodeModelBackEnd::CompleteCodeCommand &command) = 0;
|
||||
};
|
||||
|
||||
class IpcCommunicator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using Ptr = QSharedPointer<IpcCommunicator>;
|
||||
using FileContainers = QVector<CodeModelBackEnd::FileContainer>;
|
||||
using ProjectPartContainers = QVector<CodeModelBackEnd::ProjectPartContainer>;
|
||||
|
||||
public:
|
||||
IpcCommunicator();
|
||||
|
||||
void registerFilesForCodeCompletion(const FileContainers &fileContainers);
|
||||
void unregisterFilesForCodeCompletion(const FileContainers &fileContainers);
|
||||
void registerProjectPartsForCodeCompletion(const ProjectPartContainers &projectPartContainers);
|
||||
void unregisterProjectPartsForCodeCompletion(const QStringList &filePaths);
|
||||
void completeCode(ClangCompletionAssistProcessor *assistProcessor, const QString &filePath,
|
||||
quint32 line,
|
||||
quint32 column,
|
||||
const QString &projectFilePath);
|
||||
|
||||
void registerProjectsParts(const QList<CppTools::ProjectPart::Ptr> projectParts);
|
||||
|
||||
void updateUnsavedFileIfNotCurrentDocument(Core::IDocument *document);
|
||||
void updateUnsavedFileFromCppEditorDocument(const QString &filePath);
|
||||
void updateUnsavedFile(const QString &filePath, const QByteArray &contents);
|
||||
|
||||
public: // for tests
|
||||
IpcSenderInterface *setIpcSender(IpcSenderInterface *ipcSender);
|
||||
void killBackendProcess();
|
||||
|
||||
signals: // for tests
|
||||
void backendReinitialized();
|
||||
|
||||
private:
|
||||
enum SendMode { RespectSendRequests, IgnoreSendRequests };
|
||||
|
||||
void initializeBackend();
|
||||
void initializeBackendWithCurrentData();
|
||||
void registerEmptyProjectForProjectLessFiles();
|
||||
void registerCurrentProjectParts();
|
||||
void registerCurrentUnsavedFiles();
|
||||
void registerCurrrentCodeModelUiHeaders();
|
||||
|
||||
void onBackendRestarted();
|
||||
void onEditorAboutToClose(Core::IEditor *editor);
|
||||
|
||||
IpcReceiver m_ipcReceiver;
|
||||
CodeModelBackEnd::ConnectionClient m_connection;
|
||||
QScopedPointer<IpcSenderInterface> m_ipcSender;
|
||||
|
||||
SendMode m_sendMode = RespectSendRequests;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace ClangCodeModel
|
||||
|
||||
#endif // CLANGCODEMODEL_INTERNAL_CODEMODELBACKENDIPCINTEGRATION_H
|
||||
@@ -55,6 +55,8 @@ static const QLatin1Char kDoubleQuote('"');
|
||||
static const QLatin1Char kNewLine('\n');
|
||||
static const QLatin1Char kHorizontalTab('\t');
|
||||
|
||||
const char CLANG_MODELMANAGERSUPPORT_ID[] = "ClangCodeMode.ClangCodeMode";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,5 +16,22 @@
|
||||
<file>objc_messages_1.mm</file>
|
||||
<file>objc_messages_2.mm</file>
|
||||
<file>objc_messages_3.mm</file>
|
||||
<file>completionWithProject.cpp</file>
|
||||
<file>memberCompletion.cpp</file>
|
||||
<file>myheader.h</file>
|
||||
<file>mysource.cpp</file>
|
||||
<file>qt-widgets-app/main.cpp</file>
|
||||
<file>qt-widgets-app/mainwindow.cpp</file>
|
||||
<file>qt-widgets-app/mainwindow.h</file>
|
||||
<file>qt-widgets-app/mainwindow.ui</file>
|
||||
<file>qt-widgets-app/qt-widgets-app.pro</file>
|
||||
<file>functionCompletion.cpp</file>
|
||||
<file>doxygenKeywordsCompletion.cpp</file>
|
||||
<file>preprocessorKeywordsCompletion.cpp</file>
|
||||
<file>includeDirectiveCompletion.cpp</file>
|
||||
<file>exampleIncludeDir/file.h</file>
|
||||
<file>exampleIncludeDir/otherFile.h</file>
|
||||
<file>exampleIncludeDir/mylib/mylib.h</file>
|
||||
<file>globalCompletion.cpp</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
966
src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp
Normal file
966
src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp
Normal file
@@ -0,0 +1,966 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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 "clangcodecompletion_test.h"
|
||||
|
||||
#include "../codemodelbackendipcintegration.h"
|
||||
#include "../clangmodelmanagersupport.h"
|
||||
|
||||
#include <clangcodemodel/clangcompletion.h>
|
||||
#include <clangcodemodel/constants.h>
|
||||
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <coreplugin/editormanager/ieditor.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <cpptools/cppcodemodelsettings.h>
|
||||
#include <cpptools/cpptoolsconstants.h>
|
||||
#include <cpptools/cpptoolsreuse.h>
|
||||
#include <cpptools/cpptoolstestcase.h>
|
||||
#include <cpptools/modelmanagertesthelper.h>
|
||||
#include <texteditor/codeassist/assistinterface.h>
|
||||
#include <texteditor/codeassist/completionassistprovider.h>
|
||||
#include <texteditor/codeassist/genericproposalmodel.h>
|
||||
#include <texteditor/codeassist/iassistprocessor.h>
|
||||
#include <texteditor/codeassist/iassistproposal.h>
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
|
||||
#include <codemodelbackendipc/cmbcompletecodecommand.h>
|
||||
#include <codemodelbackendipc/cmbendcommand.h>
|
||||
#include <codemodelbackendipc/cmbregisterprojectsforcodecompletioncommand.h>
|
||||
#include <codemodelbackendipc/cmbregistertranslationunitsforcodecompletioncommand.h>
|
||||
#include <codemodelbackendipc/cmbunregisterprojectsforcodecompletioncommand.h>
|
||||
#include <codemodelbackendipc/cmbunregistertranslationunitsforcodecompletioncommand.h>
|
||||
#include <utils/changeset.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QtTest>
|
||||
|
||||
using namespace CodeModelBackEnd;
|
||||
using namespace ClangCodeModel;
|
||||
using namespace ClangCodeModel::Internal;
|
||||
|
||||
namespace {
|
||||
|
||||
QString _(const char text[]) { return QString::fromUtf8(text); }
|
||||
QString qrcPath(const QByteArray relativeFilePath)
|
||||
{ return QLatin1String(":/unittests/ClangCodeModel/") + QString::fromUtf8(relativeFilePath); }
|
||||
|
||||
struct LogOutput
|
||||
{
|
||||
LogOutput(const QString &text) : text(text.toUtf8()) {}
|
||||
LogOutput(const char text[]) : text(text) {}
|
||||
QByteArray text;
|
||||
};
|
||||
|
||||
void printRawLines(QTextStream &out, const QList<QByteArray> &lines)
|
||||
{
|
||||
foreach (const QByteArray &line, lines) {
|
||||
QByteArray rawLine = line;
|
||||
rawLine.prepend("\"");
|
||||
rawLine.append("\\n\"\n");
|
||||
out << rawLine;
|
||||
}
|
||||
}
|
||||
|
||||
void printDifference(const LogOutput &actual, const LogOutput &expected)
|
||||
{
|
||||
QTextStream out(stderr);
|
||||
|
||||
const QList<QByteArray> actualLines = actual.text.split('\n');
|
||||
const QList<QByteArray> expectedLines = expected.text.split('\n');
|
||||
|
||||
out << "-- ACTUAL:\n";
|
||||
printRawLines(out, actualLines);
|
||||
out << "-- EXPECTED:\n";
|
||||
printRawLines(out, expectedLines);
|
||||
|
||||
if (actualLines.size() != expectedLines.size()) {
|
||||
out << "-- DIFFERENCE IN LINE COUNT:\n"
|
||||
<< " actual lines:" << actualLines.size() << '\n'
|
||||
<< " expected lines:" << expectedLines.size() << '\n';
|
||||
}
|
||||
|
||||
out << "-- FIRST LINE THAT DIFFERS:\n";
|
||||
auto actualLineIt = actualLines.cbegin();
|
||||
auto expectedLineIt = expectedLines.cbegin();
|
||||
int line = 1;
|
||||
forever {
|
||||
if (actualLineIt == actualLines.cend() && expectedLineIt != expectedLines.cend()) {
|
||||
out << " line: " << line << '\n';
|
||||
out << " actual: <none>\n";
|
||||
out << " expected: \"" << *expectedLineIt << "\"\n";
|
||||
} else if (actualLineIt != actualLines.cend() && expectedLineIt == expectedLines.cend()) {
|
||||
out << " line: " << line << '\n';
|
||||
out << " actual: \"" << *actualLineIt << "\"\n";
|
||||
out << " expected: <none>\n";
|
||||
} else {
|
||||
if (*actualLineIt != *expectedLineIt) {
|
||||
out << " line: " << line << '\n';
|
||||
out << " actual: \"" << *actualLineIt << "\"\n";
|
||||
out << " expected: \"" << *expectedLineIt << "\"\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
++line;
|
||||
++actualLineIt;
|
||||
++expectedLineIt;
|
||||
}
|
||||
}
|
||||
|
||||
bool compare(const LogOutput &actual, const LogOutput &expected)
|
||||
{
|
||||
const bool isEqual = actual.text == expected.text;
|
||||
if (!isEqual)
|
||||
printDifference(actual, expected);
|
||||
return isEqual;
|
||||
}
|
||||
|
||||
QByteArray readFile(const QString &filePath)
|
||||
{
|
||||
QFile file(filePath);
|
||||
QTC_ASSERT(file.open(QFile::ReadOnly | QFile::Text), return QByteArray());
|
||||
return file.readAll();
|
||||
}
|
||||
|
||||
bool writeFile(const QString &filePath, const QByteArray &contents)
|
||||
{
|
||||
QFile file(filePath);
|
||||
if (!file.open(QFile::WriteOnly | QFile::Text))
|
||||
return false;
|
||||
if (file.write(contents) != contents.size())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void insertTextAtTopOfEditor(TextEditor::BaseTextEditor *editor, const QByteArray &text)
|
||||
{
|
||||
QTC_ASSERT(editor, return);
|
||||
Utils::ChangeSet cs;
|
||||
cs.insert(0, QString::fromUtf8(text));
|
||||
QTextCursor textCursor = editor->textCursor();
|
||||
cs.apply(&textCursor);
|
||||
}
|
||||
|
||||
class WaitForAsyncCompletions
|
||||
{
|
||||
public:
|
||||
enum WaitResult { GotResults, GotInvalidResults, Timeout };
|
||||
WaitResult wait(TextEditor::IAssistProcessor *processor,
|
||||
TextEditor::AssistInterface *assistInterface);
|
||||
|
||||
TextEditor::IAssistProposalModel *proposalModel;
|
||||
};
|
||||
|
||||
WaitForAsyncCompletions::WaitResult WaitForAsyncCompletions::wait(
|
||||
TextEditor::IAssistProcessor *processor,
|
||||
TextEditor::AssistInterface *assistInterface)
|
||||
{
|
||||
QTC_ASSERT(processor, return Timeout);
|
||||
QTC_ASSERT(assistInterface, return Timeout);
|
||||
|
||||
bool gotResults = false;
|
||||
|
||||
processor->setAsyncCompletionAvailableHandler(
|
||||
[this, &gotResults] (TextEditor::IAssistProposal *proposal) {
|
||||
QTC_ASSERT(proposal, return);
|
||||
proposalModel = proposal->model();
|
||||
delete proposal;
|
||||
gotResults = true;
|
||||
});
|
||||
|
||||
// Are there any immediate results?
|
||||
if (TextEditor::IAssistProposal *proposal = processor->perform(assistInterface)) {
|
||||
delete processor;
|
||||
proposalModel = proposal->model();
|
||||
delete proposal;
|
||||
QTC_ASSERT(proposalModel, return GotInvalidResults);
|
||||
return GotResults;
|
||||
}
|
||||
|
||||
// There are not any, so wait for async results.
|
||||
QElapsedTimer timer; timer.start();
|
||||
while (!gotResults) {
|
||||
if (timer.elapsed() >= 5 * 1000)
|
||||
return Timeout;
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
return proposalModel ? GotResults : GotInvalidResults;
|
||||
}
|
||||
|
||||
class ChangeDocumentReloadSetting
|
||||
{
|
||||
public:
|
||||
ChangeDocumentReloadSetting(Core::IDocument::ReloadSetting reloadSetting)
|
||||
: m_previousValue(Core::EditorManager::reloadSetting())
|
||||
{
|
||||
Core::EditorManager::setReloadSetting(reloadSetting);
|
||||
}
|
||||
|
||||
~ChangeDocumentReloadSetting()
|
||||
{
|
||||
Core::EditorManager::setReloadSetting(m_previousValue);
|
||||
}
|
||||
|
||||
private:
|
||||
Core::IDocument::ReloadSetting m_previousValue;
|
||||
};
|
||||
|
||||
class ChangeIpcSender
|
||||
{
|
||||
public:
|
||||
ChangeIpcSender(IpcSenderInterface *ipcSender)
|
||||
{
|
||||
m_previousSender = ModelManagerSupportClang::instance()->ipcCommunicator()
|
||||
->setIpcSender(ipcSender);
|
||||
}
|
||||
|
||||
~ChangeIpcSender()
|
||||
{
|
||||
ModelManagerSupportClang::instance()->ipcCommunicator()->setIpcSender(m_previousSender);
|
||||
}
|
||||
|
||||
private:
|
||||
IpcSenderInterface *m_previousSender;
|
||||
};
|
||||
|
||||
QString toString(const FileContainer &fileContainer)
|
||||
{
|
||||
QString out;
|
||||
QTextStream ts(&out);
|
||||
ts << " Path: " << QFileInfo(fileContainer.filePath().toString()).fileName()
|
||||
<< " ProjectPart: " << fileContainer.projectPartId().toString() << "\n";
|
||||
return out;
|
||||
}
|
||||
|
||||
QString toString(const QVector<FileContainer> &fileContainers)
|
||||
{
|
||||
QString out;
|
||||
QTextStream ts(&out);
|
||||
foreach (const FileContainer &fileContainer, fileContainers)
|
||||
ts << toString(fileContainer);
|
||||
return out;
|
||||
}
|
||||
|
||||
QString toString(const ProjectPartContainer &projectPartContainer)
|
||||
{
|
||||
QString out;
|
||||
QTextStream ts(&out);
|
||||
ts << " ProjectPartContainer"
|
||||
<< " id: " << QFileInfo(projectPartContainer.projectPartId().toString()).fileName();
|
||||
return out;
|
||||
}
|
||||
|
||||
QString toString(const QVector<ProjectPartContainer> &projectPartContainers)
|
||||
{
|
||||
QString out;
|
||||
QTextStream ts(&out);
|
||||
foreach (const ProjectPartContainer &projectPartContainer, projectPartContainers)
|
||||
ts << toString(projectPartContainer);
|
||||
return out;
|
||||
}
|
||||
|
||||
QString toString(const EndCommand &)
|
||||
{
|
||||
return QLatin1String("EndCommand\n");
|
||||
}
|
||||
|
||||
QString toString(const RegisterTranslationUnitForCodeCompletionCommand &command)
|
||||
{
|
||||
QString out;
|
||||
QTextStream ts(&out);
|
||||
|
||||
ts << "RegisterTranslationUnitForCodeCompletionCommand\n"
|
||||
<< toString(command.fileContainers());
|
||||
return out;
|
||||
|
||||
return QLatin1String("RegisterTranslationUnitForCodeCompletionCommand\n");
|
||||
}
|
||||
|
||||
QString toString(const UnregisterTranslationUnitsForCodeCompletionCommand &)
|
||||
{
|
||||
return QLatin1String("UnregisterTranslationUnitsForCodeCompletionCommand\n");
|
||||
}
|
||||
|
||||
QString toString(const RegisterProjectPartsForCodeCompletionCommand &command)
|
||||
{
|
||||
QString out;
|
||||
QTextStream ts(&out);
|
||||
|
||||
ts << "RegisterProjectPartsForCodeCompletionCommand\n"
|
||||
<< toString(command.projectContainers()) << "\n";
|
||||
return out;
|
||||
}
|
||||
|
||||
QString toString(const UnregisterProjectPartsForCodeCompletionCommand &command)
|
||||
{
|
||||
QString out;
|
||||
QTextStream ts(&out);
|
||||
|
||||
ts << "UnregisterProjectPartsForCodeCompletionCommand\n"
|
||||
<< command.filePaths().join(Utf8String::fromUtf8(",")).toByteArray() << "\n";
|
||||
return out;
|
||||
}
|
||||
|
||||
QString toString(const CompleteCodeCommand &)
|
||||
{
|
||||
return QLatin1String("CompleteCodeCommand\n");
|
||||
}
|
||||
|
||||
class IpcSenderSpy : public IpcSenderInterface
|
||||
{
|
||||
public:
|
||||
void end() override
|
||||
{ senderLog.append(toString(EndCommand())); }
|
||||
|
||||
void registerTranslationUnitsForCodeCompletion(const RegisterTranslationUnitForCodeCompletionCommand &command) override
|
||||
{ senderLog.append(toString(command)); }
|
||||
|
||||
void unregisterTranslationUnitsForCodeCompletion(const UnregisterTranslationUnitsForCodeCompletionCommand &command) override
|
||||
{ senderLog.append(toString(command)); }
|
||||
|
||||
void registerProjectPartsForCodeCompletion(const RegisterProjectPartsForCodeCompletionCommand &command) override
|
||||
{ senderLog.append(toString(command)); }
|
||||
|
||||
void unregisterProjectPartsForCodeCompletion(const UnregisterProjectPartsForCodeCompletionCommand &command) override
|
||||
{ senderLog.append(toString(command)); }
|
||||
|
||||
void completeCode(const CompleteCodeCommand &command) override
|
||||
{ senderLog.append(toString(command)); }
|
||||
|
||||
public:
|
||||
QString senderLog;
|
||||
};
|
||||
|
||||
const CppTools::ProjectPart::HeaderPaths toHeaderPaths(const QStringList &paths)
|
||||
{
|
||||
using namespace CppTools;
|
||||
|
||||
ProjectPart::HeaderPaths result;
|
||||
foreach (const QString &path, paths)
|
||||
result << ProjectPart::HeaderPath(path, ProjectPart::HeaderPath::IncludePath);
|
||||
return result;
|
||||
}
|
||||
|
||||
using ProposalModel = QSharedPointer<TextEditor::IAssistProposalModel>;
|
||||
|
||||
ProposalModel completionResults(
|
||||
TextEditor::BaseTextEditor *textEditor,
|
||||
const QStringList &includePaths = QStringList())
|
||||
{
|
||||
using namespace TextEditor;
|
||||
|
||||
TextEditorWidget *textEditorWidget = qobject_cast<TextEditorWidget *>(textEditor->widget());
|
||||
QTC_ASSERT(textEditorWidget, return ProposalModel());
|
||||
AssistInterface *assistInterface = textEditorWidget->createAssistInterface(
|
||||
TextEditor::Completion, TextEditor::ExplicitlyInvoked);
|
||||
QTC_ASSERT(assistInterface, return ProposalModel());
|
||||
if (!includePaths.isEmpty()) {
|
||||
auto clangAssistInterface = static_cast<ClangCompletionAssistInterface *>(assistInterface);
|
||||
clangAssistInterface->setHeaderPaths(toHeaderPaths(includePaths));
|
||||
}
|
||||
|
||||
CompletionAssistProvider *assistProvider
|
||||
= textEditor->textDocument()->completionAssistProvider();
|
||||
QTC_ASSERT(qobject_cast<ClangCompletionAssistProvider *>(assistProvider),
|
||||
return ProposalModel());
|
||||
QTC_ASSERT(assistProvider, return ProposalModel());
|
||||
QTC_ASSERT(assistProvider->runType() == IAssistProvider::Asynchronous, return ProposalModel());
|
||||
|
||||
IAssistProcessor *processor = assistProvider->createProcessor();
|
||||
QTC_ASSERT(processor, return ProposalModel());
|
||||
|
||||
WaitForAsyncCompletions waitForCompletions;
|
||||
const WaitForAsyncCompletions::WaitResult result = waitForCompletions.wait(processor,
|
||||
assistInterface);
|
||||
QTC_ASSERT(result == WaitForAsyncCompletions::GotResults, return ProposalModel());
|
||||
return QSharedPointer<TextEditor::IAssistProposalModel>(waitForCompletions.proposalModel);
|
||||
}
|
||||
|
||||
class TestDocument
|
||||
{
|
||||
public:
|
||||
TestDocument(const QByteArray &fileName, CppTools::Tests::TemporaryDir *temporaryDir = 0)
|
||||
: cursorPosition(-1)
|
||||
{
|
||||
QTC_ASSERT(!fileName.isEmpty(), return);
|
||||
const QResource resource(qrcPath(fileName));
|
||||
QTC_ASSERT(resource.isValid(), return);
|
||||
const QByteArray contents = QByteArray(reinterpret_cast<const char*>(resource.data()),
|
||||
resource.size());
|
||||
cursorPosition = findCursorMarkerPosition(contents);
|
||||
if (!contents.isEmpty()) {
|
||||
if (!temporaryDir) {
|
||||
m_temporaryDir.reset(new CppTools::Tests::TemporaryDir);
|
||||
temporaryDir = m_temporaryDir.data();
|
||||
}
|
||||
|
||||
filePath = temporaryDir->createFile(fileName, contents);
|
||||
}
|
||||
}
|
||||
|
||||
static TestDocument fromExistingFile(const QString &filePath)
|
||||
{
|
||||
TestDocument testDocument;
|
||||
QTC_ASSERT(!filePath.isEmpty(), return testDocument);
|
||||
testDocument.filePath = filePath;
|
||||
testDocument.cursorPosition = findCursorMarkerPosition(readFile(filePath));
|
||||
return testDocument;
|
||||
}
|
||||
|
||||
static int findCursorMarkerPosition(const QByteArray &contents)
|
||||
{
|
||||
return contents.indexOf(" /* COMPLETE HERE */");
|
||||
}
|
||||
|
||||
bool isCreated() const { return !filePath.isEmpty(); }
|
||||
bool hasValidCursorPosition() const { return cursorPosition >= 0; }
|
||||
bool isCreatedAndHasValidCursorPosition() const
|
||||
{ return isCreated() && hasValidCursorPosition(); }
|
||||
|
||||
QString filePath;
|
||||
int cursorPosition;
|
||||
|
||||
private:
|
||||
TestDocument() : cursorPosition(-1) {}
|
||||
QSharedPointer<CppTools::Tests::TemporaryDir> m_temporaryDir;
|
||||
};
|
||||
|
||||
class OpenEditorAtCursorPosition
|
||||
{
|
||||
public:
|
||||
OpenEditorAtCursorPosition(const TestDocument &testDocument);
|
||||
~OpenEditorAtCursorPosition(); // Close editor
|
||||
|
||||
bool succeeded() const { return m_editor; }
|
||||
TextEditor::BaseTextEditor *editor() const { return m_editor; }
|
||||
|
||||
private:
|
||||
TextEditor::BaseTextEditor *m_editor;
|
||||
};
|
||||
|
||||
OpenEditorAtCursorPosition::OpenEditorAtCursorPosition(const TestDocument &testDocument)
|
||||
{
|
||||
Core::IEditor *coreEditor = Core::EditorManager::openEditor(testDocument.filePath);
|
||||
m_editor = qobject_cast<TextEditor::BaseTextEditor *>(coreEditor);
|
||||
QTC_CHECK(m_editor);
|
||||
if (m_editor && testDocument.hasValidCursorPosition())
|
||||
m_editor->setCursorPosition(testDocument.cursorPosition);
|
||||
}
|
||||
|
||||
OpenEditorAtCursorPosition::~OpenEditorAtCursorPosition()
|
||||
{
|
||||
if (m_editor)
|
||||
Core::EditorManager::closeEditor(m_editor, /* askAboutModifiedEditors= */ false);
|
||||
}
|
||||
|
||||
CppTools::ProjectPart::Ptr createProjectPart(const QStringList &files,
|
||||
const QString &defines)
|
||||
{
|
||||
using namespace CppTools;
|
||||
|
||||
ProjectPart::Ptr projectPart(new ProjectPart);
|
||||
projectPart->projectFile = QLatin1String("myproject.project");
|
||||
foreach (const QString &file, files)
|
||||
projectPart->files.append(ProjectFile(file, ProjectFile::classify(file)));
|
||||
projectPart->languageVersion = ProjectPart::CXX11;
|
||||
projectPart->qtVersion = ProjectPart::NoQt;
|
||||
projectPart->projectDefines = defines.toUtf8();
|
||||
|
||||
return projectPart;
|
||||
}
|
||||
|
||||
CppTools::ProjectInfo createProjectInfo(ProjectExplorer::Project *project,
|
||||
const QStringList &files,
|
||||
const QString &defines)
|
||||
{
|
||||
using namespace CppTools;
|
||||
QTC_ASSERT(project, return ProjectInfo());
|
||||
|
||||
const CppTools::ProjectPart::Ptr projectPart = createProjectPart(files, defines);
|
||||
ProjectInfo projectInfo = ProjectInfo(project);
|
||||
projectInfo.appendProjectPart(projectPart);
|
||||
projectInfo.finish();
|
||||
return projectInfo;
|
||||
}
|
||||
|
||||
class ProjectLoader
|
||||
{
|
||||
public:
|
||||
ProjectLoader(const QStringList &projectFiles,
|
||||
const QString &projectDefines,
|
||||
bool testOnlyForCleanedProjects = false)
|
||||
: m_project(0)
|
||||
, m_projectFiles(projectFiles)
|
||||
, m_projectDefines(projectDefines)
|
||||
, m_helper(0, testOnlyForCleanedProjects)
|
||||
{
|
||||
}
|
||||
|
||||
bool load()
|
||||
{
|
||||
m_project = m_helper.createProject(QLatin1String("testProject"));
|
||||
const CppTools::ProjectInfo projectInfo = createProjectInfo(m_project,
|
||||
m_projectFiles,
|
||||
m_projectDefines);
|
||||
const QSet<QString> filesIndexedAfterLoading = m_helper.updateProjectInfo(projectInfo);
|
||||
return m_projectFiles.size() == filesIndexedAfterLoading.size();
|
||||
}
|
||||
|
||||
bool updateProject(const QString &updatedProjectDefines)
|
||||
{
|
||||
QTC_ASSERT(m_project, return false);
|
||||
const CppTools::ProjectInfo updatedProjectInfo = createProjectInfo(m_project,
|
||||
m_projectFiles,
|
||||
updatedProjectDefines);
|
||||
return updateProjectInfo(updatedProjectInfo);
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
bool updateProjectInfo(const CppTools::ProjectInfo &projectInfo)
|
||||
{
|
||||
const QSet<QString> filesIndexedAfterLoading = m_helper.updateProjectInfo(projectInfo);
|
||||
return m_projectFiles.size() == filesIndexedAfterLoading.size();
|
||||
}
|
||||
|
||||
ProjectExplorer::Project *m_project;
|
||||
QStringList m_projectFiles;
|
||||
QString m_projectDefines;
|
||||
CppTools::Tests::ModelManagerTestHelper m_helper;
|
||||
};
|
||||
|
||||
class ProjectLessCompletionTest
|
||||
{
|
||||
public:
|
||||
ProjectLessCompletionTest(const QByteArray &testFileName,
|
||||
const QStringList &includePaths = QStringList())
|
||||
{
|
||||
CppTools::Tests::TestCase garbageCollectionGlobalSnapshot;
|
||||
QVERIFY(garbageCollectionGlobalSnapshot.succeededSoFar());
|
||||
|
||||
const TestDocument testDocument(testFileName);
|
||||
QVERIFY(testDocument.isCreatedAndHasValidCursorPosition());
|
||||
OpenEditorAtCursorPosition openEditor(testDocument);
|
||||
|
||||
QVERIFY(openEditor.succeeded());
|
||||
proposal = completionResults(openEditor.editor(), includePaths);
|
||||
}
|
||||
|
||||
ProposalModel proposal;
|
||||
};
|
||||
|
||||
bool hasItem(ProposalModel model, const QByteArray &text)
|
||||
{
|
||||
if (!model)
|
||||
return false;
|
||||
|
||||
for (int i = 0, size = model->size(); i < size; ++i) {
|
||||
const QString itemText = model->text(i);
|
||||
if (itemText == QString::fromUtf8(text))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasSnippet(ProposalModel model, const QByteArray &text)
|
||||
{
|
||||
if (!model)
|
||||
return false;
|
||||
|
||||
// Snippets seem to end with a whitespace
|
||||
const QString snippetText = QString::fromUtf8(text) + QLatin1Char(' ');
|
||||
|
||||
auto *genericModel = static_cast<TextEditor::GenericProposalModel *>(model.data());
|
||||
for (int i = 0, size = genericModel->size(); i < size; ++i) {
|
||||
TextEditor::AssistProposalItem *item = genericModel->proposalItem(i);
|
||||
QTC_ASSERT(item, continue);
|
||||
if (item->text() == snippetText)
|
||||
return item->data().toString().contains(QLatin1Char('$'));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace ClangCodeModel {
|
||||
namespace Internal {
|
||||
namespace Tests {
|
||||
|
||||
typedef QSharedPointer<CppTools::CppCodeModelSettings> CppCodeModelSettingsPtr;
|
||||
|
||||
class ActivateClangModelManagerSupport
|
||||
{
|
||||
public:
|
||||
ActivateClangModelManagerSupport(CppCodeModelSettingsPtr codeModelSettings);
|
||||
~ActivateClangModelManagerSupport();
|
||||
|
||||
private:
|
||||
ActivateClangModelManagerSupport();
|
||||
|
||||
CppCodeModelSettingsPtr m_codeModelSettings;
|
||||
QHash<QString, QString> m_previousValues;
|
||||
};
|
||||
|
||||
ActivateClangModelManagerSupport::ActivateClangModelManagerSupport(
|
||||
CppCodeModelSettingsPtr codeModelSettings)
|
||||
: m_codeModelSettings(codeModelSettings)
|
||||
{
|
||||
QTC_CHECK(m_codeModelSettings);
|
||||
const QString clangModelManagerSupportId
|
||||
= QLatin1String(Constants::CLANG_MODELMANAGERSUPPORT_ID);
|
||||
foreach (const QString &mimeType, CppTools::CppCodeModelSettings::supportedMimeTypes()) {
|
||||
m_previousValues.insert(mimeType,
|
||||
m_codeModelSettings->modelManagerSupportIdForMimeType(mimeType));
|
||||
m_codeModelSettings->setModelManagerSupportIdForMimeType(mimeType,
|
||||
clangModelManagerSupportId);
|
||||
}
|
||||
m_codeModelSettings->emitChanged();
|
||||
}
|
||||
|
||||
ActivateClangModelManagerSupport::~ActivateClangModelManagerSupport()
|
||||
{
|
||||
QHash<QString, QString>::const_iterator i = m_previousValues.constBegin();
|
||||
for (; i != m_previousValues.end(); ++i)
|
||||
m_codeModelSettings->setModelManagerSupportIdForMimeType(i.key(), i.value());
|
||||
m_codeModelSettings->emitChanged();
|
||||
}
|
||||
|
||||
ClangCodeCompletionTest::ClangCodeCompletionTest()
|
||||
{
|
||||
}
|
||||
|
||||
ClangCodeCompletionTest::~ClangCodeCompletionTest()
|
||||
{
|
||||
}
|
||||
|
||||
void ClangCodeCompletionTest::initTestCase()
|
||||
{
|
||||
m_activater.reset(new ActivateClangModelManagerSupport(CppTools::codeModelSettings()));
|
||||
}
|
||||
|
||||
void ClangCodeCompletionTest::testCompleteDoxygenKeywords()
|
||||
{
|
||||
ProjectLessCompletionTest t("doxygenKeywordsCompletion.cpp");
|
||||
|
||||
QVERIFY(hasItem(t.proposal, "brief"));
|
||||
QVERIFY(hasItem(t.proposal, "param"));
|
||||
QVERIFY(hasItem(t.proposal, "return"));
|
||||
QVERIFY(!hasSnippet(t.proposal, "class"));
|
||||
}
|
||||
|
||||
void ClangCodeCompletionTest::testCompletePreprocessorKeywords()
|
||||
{
|
||||
ProjectLessCompletionTest t("preprocessorKeywordsCompletion.cpp");
|
||||
|
||||
QVERIFY(hasItem(t.proposal, "ifdef"));
|
||||
QVERIFY(hasItem(t.proposal, "endif"));
|
||||
QVERIFY(!hasSnippet(t.proposal, "class"));
|
||||
}
|
||||
|
||||
void ClangCodeCompletionTest::testCompleteIncludeDirective()
|
||||
{
|
||||
CppTools::Tests::TemporaryCopiedDir testDir(qrcPath("exampleIncludeDir"));
|
||||
ProjectLessCompletionTest t("includeDirectiveCompletion.cpp", QStringList(testDir.path()));
|
||||
|
||||
QVERIFY(hasItem(t.proposal, "file.h"));
|
||||
QVERIFY(hasItem(t.proposal, "otherFile.h"));
|
||||
QVERIFY(hasItem(t.proposal, "mylib/"));
|
||||
QVERIFY(!hasSnippet(t.proposal, "class"));
|
||||
}
|
||||
|
||||
void ClangCodeCompletionTest::testCompleteGlobals()
|
||||
{
|
||||
ProjectLessCompletionTest t("globalCompletion.cpp");
|
||||
|
||||
QVERIFY(hasItem(t.proposal, "globalVariable"));
|
||||
QVERIFY(hasItem(t.proposal, "globalFunction"));
|
||||
QVERIFY(hasItem(t.proposal, "GlobalClass"));
|
||||
QVERIFY(hasItem(t.proposal, "class")); // Keyword
|
||||
QVERIFY(hasSnippet(t.proposal, "class")); // Snippet
|
||||
}
|
||||
|
||||
void ClangCodeCompletionTest::testCompleteMembers()
|
||||
{
|
||||
ProjectLessCompletionTest t("memberCompletion.cpp");
|
||||
|
||||
QVERIFY(hasItem(t.proposal, "member"));
|
||||
QVERIFY(!hasItem(t.proposal, "globalVariable"));
|
||||
QVERIFY(!hasItem(t.proposal, "class")); // Keyword
|
||||
QVERIFY(!hasSnippet(t.proposal, "class")); // Snippet
|
||||
}
|
||||
|
||||
void ClangCodeCompletionTest::testCompleteFunctions()
|
||||
{
|
||||
ProjectLessCompletionTest t("functionCompletion.cpp");
|
||||
|
||||
QVERIFY(hasItem(t.proposal, "void f()"));
|
||||
QVERIFY(hasItem(t.proposal, "void f(int a)"));
|
||||
QVERIFY(hasItem(t.proposal, "void f(const QString &s)"));
|
||||
QVERIFY(hasItem(t.proposal, "void f(char c<i>, int optional</i>)")); // TODO: No default argument?
|
||||
QVERIFY(hasItem(t.proposal, "void f(char c<i>, int optional1, int optional2</i>)")); // TODO: No default argument?
|
||||
QVERIFY(hasItem(t.proposal, "void f(const TType<QString> *t)"));
|
||||
QVERIFY(hasItem(t.proposal, "TType<QString> f(bool)"));
|
||||
}
|
||||
|
||||
void ClangCodeCompletionTest::testProjectDependentCompletion()
|
||||
{
|
||||
const TestDocument testDocument("completionWithProject.cpp");
|
||||
QVERIFY(testDocument.isCreatedAndHasValidCursorPosition());
|
||||
|
||||
ProjectLoader projectLoader(QStringList(testDocument.filePath),
|
||||
_("#define PROJECT_CONFIGURATION_1\n"));
|
||||
QVERIFY(projectLoader.load());
|
||||
|
||||
OpenEditorAtCursorPosition openEditor(testDocument);
|
||||
QVERIFY(openEditor.succeeded());
|
||||
|
||||
ProposalModel proposal = completionResults(openEditor.editor());
|
||||
QVERIFY(hasItem(proposal, "projectConfiguration1"));
|
||||
}
|
||||
|
||||
void ClangCodeCompletionTest::testChangingProjectDependentCompletion()
|
||||
{
|
||||
const TestDocument testDocument("completionWithProject.cpp");
|
||||
QVERIFY(testDocument.isCreatedAndHasValidCursorPosition());
|
||||
|
||||
OpenEditorAtCursorPosition openEditor(testDocument);
|
||||
QVERIFY(openEditor.succeeded());
|
||||
|
||||
// Check completion without project
|
||||
ProposalModel proposal = completionResults(openEditor.editor());
|
||||
QVERIFY(hasItem(proposal, "noProjectConfigurationDetected"));
|
||||
|
||||
{
|
||||
// Check completion with project configuration 1
|
||||
ProjectLoader projectLoader(QStringList(testDocument.filePath),
|
||||
_("#define PROJECT_CONFIGURATION_1\n"),
|
||||
/* testOnlyForCleanedProjects= */ true);
|
||||
QVERIFY(projectLoader.load());
|
||||
proposal = completionResults(openEditor.editor());
|
||||
|
||||
QVERIFY(hasItem(proposal, "projectConfiguration1"));
|
||||
QVERIFY(!hasItem(proposal, "projectConfiguration2"));
|
||||
|
||||
// Check completion with project configuration 2
|
||||
QVERIFY(projectLoader.updateProject(_("#define PROJECT_CONFIGURATION_2\n")));
|
||||
proposal = completionResults(openEditor.editor());
|
||||
|
||||
QVERIFY(!hasItem(proposal, "projectConfiguration1"));
|
||||
QVERIFY(hasItem(proposal, "projectConfiguration2"));
|
||||
} // Project closed
|
||||
|
||||
// Check again completion without project
|
||||
proposal = completionResults(openEditor.editor());
|
||||
QVERIFY(hasItem(proposal, "noProjectConfigurationDetected"));
|
||||
}
|
||||
|
||||
void ClangCodeCompletionTest::testUnsavedFilesTrackingByModifyingIncludedFileInCurrentEditor()
|
||||
{
|
||||
CppTools::Tests::TemporaryDir temporaryDir;
|
||||
const TestDocument sourceDocument("mysource.cpp", &temporaryDir);
|
||||
QVERIFY(sourceDocument.isCreatedAndHasValidCursorPosition());
|
||||
const TestDocument headerDocument("myheader.h", &temporaryDir);
|
||||
QVERIFY(headerDocument.isCreated());
|
||||
|
||||
// Test that declarations from header file are visible in source file
|
||||
OpenEditorAtCursorPosition openSource(sourceDocument);
|
||||
QVERIFY(openSource.succeeded());
|
||||
ProposalModel proposal = completionResults(openSource.editor());
|
||||
QVERIFY(hasItem(proposal, "globalFromHeader"));
|
||||
|
||||
// Open header and insert a new declaration
|
||||
OpenEditorAtCursorPosition openHeader(headerDocument);
|
||||
QVERIFY(openHeader.succeeded());
|
||||
insertTextAtTopOfEditor(openHeader.editor(), "int globalFromHeaderUnsaved;\n");
|
||||
|
||||
// Switch back to source file and check if modified header is reflected in completions.
|
||||
Core::EditorManager::activateEditor(openSource.editor());
|
||||
proposal = completionResults(openSource.editor());
|
||||
QVERIFY(hasItem(proposal, "globalFromHeader"));
|
||||
QVERIFY(hasItem(proposal, "globalFromHeaderUnsaved"));
|
||||
}
|
||||
|
||||
void ClangCodeCompletionTest::testUnsavedFilesTrackingByModifyingIncludedFileInNotCurrentEditor()
|
||||
{
|
||||
CppTools::Tests::TemporaryDir temporaryDir;
|
||||
const TestDocument sourceDocument("mysource.cpp", &temporaryDir);
|
||||
QVERIFY(sourceDocument.isCreatedAndHasValidCursorPosition());
|
||||
const TestDocument headerDocument("myheader.h", &temporaryDir);
|
||||
QVERIFY(headerDocument.isCreated());
|
||||
|
||||
// Open header
|
||||
OpenEditorAtCursorPosition openHeader(headerDocument);
|
||||
QVERIFY(openHeader.succeeded());
|
||||
|
||||
// Open source and test that declaration from header file is visible in source file
|
||||
OpenEditorAtCursorPosition openSource(sourceDocument);
|
||||
QVERIFY(openSource.succeeded());
|
||||
ProposalModel proposal = completionResults(openSource.editor());
|
||||
QVERIFY(hasItem(proposal, "globalFromHeader"));
|
||||
|
||||
// Modify header document without switching to its editor.
|
||||
// This simulates e.g. changes from refactoring actions.
|
||||
Utils::ChangeSet cs;
|
||||
cs.insert(0, QLatin1String("int globalFromHeaderUnsaved;\n"));
|
||||
QTextCursor textCursor = openHeader.editor()->textCursor();
|
||||
cs.apply(&textCursor);
|
||||
|
||||
// Check whether modified header is reflected in the completions.
|
||||
proposal = completionResults(openSource.editor());
|
||||
QVERIFY(hasItem(proposal, "globalFromHeader"));
|
||||
QVERIFY(hasItem(proposal, "globalFromHeaderUnsaved"));
|
||||
}
|
||||
|
||||
void ClangCodeCompletionTest::testUnsavedFilesTrackingByModifyingIncludedFileExternally()
|
||||
{
|
||||
ChangeDocumentReloadSetting reloadSettingsChanger(Core::IDocument::ReloadUnmodified);
|
||||
|
||||
CppTools::Tests::TemporaryDir temporaryDir;
|
||||
const TestDocument sourceDocument("mysource.cpp", &temporaryDir);
|
||||
QVERIFY(sourceDocument.isCreatedAndHasValidCursorPosition());
|
||||
const TestDocument headerDocument("myheader.h", &temporaryDir);
|
||||
QVERIFY(headerDocument.isCreated());
|
||||
|
||||
// Open header
|
||||
OpenEditorAtCursorPosition openHeader(headerDocument);
|
||||
QVERIFY(openHeader.succeeded());
|
||||
|
||||
// Open source and test completions
|
||||
OpenEditorAtCursorPosition openSource(sourceDocument);
|
||||
QVERIFY(openSource.succeeded());
|
||||
ProposalModel proposal = completionResults(openSource.editor());
|
||||
QVERIFY(hasItem(proposal, "globalFromHeader"));
|
||||
|
||||
// Simulate external modification
|
||||
QThread::sleep(1); // Ensures different time stamp and thus that the difference will be noticed
|
||||
QVERIFY(writeFile(headerDocument.filePath, "int globalFromHeaderReloaded;\n"));
|
||||
QSignalSpy waitForReloadedDocument(openHeader.editor()->document(),
|
||||
SIGNAL(reloadFinished(bool)));
|
||||
QVERIFY(waitForReloadedDocument.wait());
|
||||
|
||||
// Retrigger completion and check if its updated
|
||||
proposal = completionResults(openSource.editor());
|
||||
QVERIFY(hasItem(proposal, "globalFromHeaderReloaded"));
|
||||
}
|
||||
|
||||
void ClangCodeCompletionTest::testUnsavedFilesTrackingByCompletingUiObject()
|
||||
{
|
||||
CppTools::Tests::TemporaryCopiedDir testDir(qrcPath("qt-widgets-app"));
|
||||
QVERIFY(testDir.isValid());
|
||||
|
||||
// Open project
|
||||
const QString projectFilePath = testDir.absolutePath("qt-widgets-app.pro");
|
||||
CppTools::Tests::ProjectOpenerAndCloser projectManager;
|
||||
const CppTools::ProjectInfo projectInfo = projectManager.open(projectFilePath, true);
|
||||
QVERIFY(projectInfo.isValid());
|
||||
|
||||
// Open file with ui object
|
||||
const QString completionFile = testDir.absolutePath("mainwindow.cpp");
|
||||
const TestDocument testDocument = TestDocument::fromExistingFile(completionFile);
|
||||
QVERIFY(testDocument.isCreatedAndHasValidCursorPosition());
|
||||
OpenEditorAtCursorPosition openSource(testDocument);
|
||||
QVERIFY(openSource.succeeded());
|
||||
|
||||
// ...and check comletions
|
||||
ProposalModel proposal = completionResults(openSource.editor());
|
||||
QVERIFY(hasItem(proposal, "menuBar"));
|
||||
QVERIFY(hasItem(proposal, "statusBar"));
|
||||
QVERIFY(hasItem(proposal, "centralWidget"));
|
||||
QEXPECT_FAIL("", "Signals are not yet done", Abort);
|
||||
QVERIFY(hasItem(proposal, "setupUi"));
|
||||
}
|
||||
|
||||
void ClangCodeCompletionTest::testUpdateBackendAfterRestart()
|
||||
{
|
||||
IpcSenderSpy spy;
|
||||
ChangeIpcSender changeIpcSender(&spy);
|
||||
|
||||
CppTools::Tests::TemporaryCopiedDir testDir(qrcPath("qt-widgets-app"));
|
||||
QVERIFY(testDir.isValid());
|
||||
|
||||
// Open file not part of any project...
|
||||
const TestDocument headerDocument("myheader.h", &testDir);
|
||||
QVERIFY(headerDocument.isCreated());
|
||||
OpenEditorAtCursorPosition openHeader(headerDocument);
|
||||
QVERIFY(openHeader.succeeded());
|
||||
// ... and modify it, so we have an unsaved file.
|
||||
insertTextAtTopOfEditor(openHeader.editor(), "int someGlobal;\n");
|
||||
// Open project ...
|
||||
const QString projectFilePath = testDir.absolutePath("qt-widgets-app.pro");
|
||||
CppTools::Tests::ProjectOpenerAndCloser projectManager;
|
||||
const CppTools::ProjectInfo projectInfo = projectManager.open(projectFilePath, true);
|
||||
QVERIFY(projectInfo.isValid());
|
||||
// ...and a file of the project
|
||||
const QString completionFile = testDir.absolutePath("mainwindow.cpp");
|
||||
const TestDocument testDocument = TestDocument::fromExistingFile(completionFile);
|
||||
QVERIFY(testDocument.isCreatedAndHasValidCursorPosition());
|
||||
OpenEditorAtCursorPosition openSource(testDocument);
|
||||
QVERIFY(openSource.succeeded());
|
||||
|
||||
// Check commands that would have been sent
|
||||
QVERIFY(compare(LogOutput(spy.senderLog),
|
||||
LogOutput(
|
||||
"RegisterProjectPartsForCodeCompletionCommand\n"
|
||||
" ProjectPartContainer id: qt-widgets-app.pro\n"
|
||||
"RegisterTranslationUnitForCodeCompletionCommand\n"
|
||||
" Path: myheader.h ProjectPart: \n"
|
||||
)));
|
||||
spy.senderLog.clear();
|
||||
|
||||
// Kill backend process...
|
||||
IpcCommunicator::Ptr ipcCommunicator = ModelManagerSupportClang::instance()->ipcCommunicator();
|
||||
ipcCommunicator->killBackendProcess();
|
||||
QSignalSpy waitForReinitializedBackend(ipcCommunicator.data(),
|
||||
SIGNAL(backendReinitialized()));
|
||||
QVERIFY(waitForReinitializedBackend.wait());
|
||||
|
||||
// ...and check if code model backend would have been provided with current data
|
||||
QVERIFY(compare(LogOutput(spy.senderLog),
|
||||
LogOutput(
|
||||
"RegisterProjectPartsForCodeCompletionCommand\n"
|
||||
" ProjectPartContainer id: \n"
|
||||
"RegisterProjectPartsForCodeCompletionCommand\n"
|
||||
" ProjectPartContainer id: qt-widgets-app.pro\n"
|
||||
"RegisterTranslationUnitForCodeCompletionCommand\n"
|
||||
" Path: myheader.h ProjectPart: \n"
|
||||
"RegisterTranslationUnitForCodeCompletionCommand\n"
|
||||
" Path: ui_mainwindow.h ProjectPart: \n"
|
||||
)));
|
||||
}
|
||||
|
||||
} // namespace Tests
|
||||
} // namespace Internal
|
||||
} // namespace ClangCodeModel
|
||||
79
src/plugins/clangcodemodel/test/clangcodecompletion_test.h
Normal file
79
src/plugins/clangcodemodel/test/clangcodecompletion_test.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef CLANGCODECOMPLETIONTEST_H
|
||||
#define CLANGCODECOMPLETIONTEST_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace ClangCodeModel {
|
||||
namespace Internal {
|
||||
namespace Tests {
|
||||
|
||||
class ActivateClangModelManagerSupport;
|
||||
|
||||
class ClangCodeCompletionTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ClangCodeCompletionTest();
|
||||
~ClangCodeCompletionTest();
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
|
||||
void testCompleteDoxygenKeywords();
|
||||
void testCompletePreprocessorKeywords();
|
||||
void testCompleteIncludeDirective();
|
||||
|
||||
void testCompleteGlobals();
|
||||
void testCompleteMembers();
|
||||
void testCompleteFunctions();
|
||||
|
||||
void testProjectDependentCompletion();
|
||||
void testChangingProjectDependentCompletion();
|
||||
|
||||
void testUnsavedFilesTrackingByModifyingIncludedFileInCurrentEditor();
|
||||
void testUnsavedFilesTrackingByModifyingIncludedFileInNotCurrentEditor();
|
||||
void testUnsavedFilesTrackingByModifyingIncludedFileExternally();
|
||||
void testUnsavedFilesTrackingByCompletingUiObject();
|
||||
|
||||
void testUpdateBackendAfterRestart();
|
||||
|
||||
private:
|
||||
QScopedPointer<ActivateClangModelManagerSupport> m_activater;
|
||||
};
|
||||
|
||||
} // namespace Tests
|
||||
} // namespace Internal
|
||||
} // namespace ClangCodeModel
|
||||
|
||||
#endif // CLANGCODECOMPLETIONTEST_H
|
||||
@@ -0,0 +1,232 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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)
|
||||
|
||||
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
|
||||
|
||||
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()));
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef CLANGCOMPLETIONCONTEXTANALYZERTEST_H
|
||||
#define CLANGCOMPLETIONCONTEXTANALYZERTEST_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace ClangCodeModel {
|
||||
namespace Internal {
|
||||
namespace Tests {
|
||||
|
||||
class ClangCompletionContextAnalyzerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void testPassThroughToClangAndSignalSlotRecognition();
|
||||
void testPassThroughToClangAndSignalSlotRecognition_data();
|
||||
|
||||
void testSpecialCompletionRecognition();
|
||||
void testSpecialCompletionRecognition_data();
|
||||
|
||||
void testAvoidSpecialCompletionRecognition();
|
||||
void testAvoidSpecialCompletionRecognition_data();
|
||||
};
|
||||
|
||||
} // namespace Tests
|
||||
} // namespace Internal
|
||||
} // namespace ClangCodeModel
|
||||
|
||||
#endif // CLANGCOMPLETIONCONTEXTANALYZERTEST_H
|
||||
12
src/plugins/clangcodemodel/test/completionWithProject.cpp
Normal file
12
src/plugins/clangcodemodel/test/completionWithProject.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifdef PROJECT_CONFIGURATION_1
|
||||
int projectConfiguration1;
|
||||
#elif defined(PROJECT_CONFIGURATION_2)
|
||||
int projectConfiguration2;
|
||||
#else
|
||||
int noProjectConfigurationDetected;
|
||||
#endif
|
||||
|
||||
void f()
|
||||
{
|
||||
/* COMPLETE HERE */
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
//! \ /* COMPLETE HERE */
|
||||
15
src/plugins/clangcodemodel/test/functionCompletion.cpp
Normal file
15
src/plugins/clangcodemodel/test/functionCompletion.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
class QString;
|
||||
template <typename T> class TType { T t; };
|
||||
|
||||
void f();
|
||||
void f(int a);
|
||||
void f(const QString &s);
|
||||
void f(char c, int optional = 3);
|
||||
void f(char c, int optional1 = 3, int optional2 = 3);
|
||||
void f(const TType<QString> *t);
|
||||
TType<QString> f(bool);
|
||||
|
||||
void g()
|
||||
{
|
||||
f( /* COMPLETE HERE */
|
||||
}
|
||||
8
src/plugins/clangcodemodel/test/globalCompletion.cpp
Normal file
8
src/plugins/clangcodemodel/test/globalCompletion.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
int globalVariable;
|
||||
void globalFunction();
|
||||
class GlobalClass {};
|
||||
|
||||
void f()
|
||||
{
|
||||
/* COMPLETE HERE */
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
#include < /* COMPLETE HERE */
|
||||
9
src/plugins/clangcodemodel/test/memberCompletion.cpp
Normal file
9
src/plugins/clangcodemodel/test/memberCompletion.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
int globalVariable;
|
||||
struct S { int member; };
|
||||
|
||||
void f()
|
||||
{
|
||||
S s;
|
||||
s. /* COMPLETE HERE */
|
||||
}
|
||||
|
||||
2
src/plugins/clangcodemodel/test/myheader.h
Normal file
2
src/plugins/clangcodemodel/test/myheader.h
Normal file
@@ -0,0 +1,2 @@
|
||||
int globalFromHeader;
|
||||
|
||||
6
src/plugins/clangcodemodel/test/mysource.cpp
Normal file
6
src/plugins/clangcodemodel/test/mysource.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include "myheader.h"
|
||||
|
||||
void f()
|
||||
{
|
||||
/* COMPLETE HERE */
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
# /* COMPLETE HERE */
|
||||
42
src/plugins/clangcodemodel/test/qt-widgets-app/main.cpp
Normal file
42
src/plugins/clangcodemodel/test/qt-widgets-app/main.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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 "mainwindow.h"
|
||||
#include <QApplication>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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 "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::MainWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui-> /* COMPLETE HERE */
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
53
src/plugins/clangcodemodel/test/qt-widgets-app/mainwindow.h
Normal file
53
src/plugins/clangcodemodel/test/qt-widgets-app/mainwindow.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
}
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
~MainWindow();
|
||||
|
||||
private:
|
||||
Ui::MainWindow *ui;
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
24
src/plugins/clangcodemodel/test/qt-widgets-app/mainwindow.ui
Normal file
24
src/plugins/clangcodemodel/test/qt-widgets-app/mainwindow.ui
Normal file
@@ -0,0 +1,24 @@
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QMenuBar" name="menuBar" />
|
||||
<widget class="QToolBar" name="mainToolBar" />
|
||||
<widget class="QWidget" name="centralWidget" />
|
||||
<widget class="QStatusBar" name="statusBar" />
|
||||
</widget>
|
||||
<layoutDefault spacing="6" margin="11" />
|
||||
<pixmapfunction></pixmapfunction>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,11 @@
|
||||
# "Qt Widgets Application" from Qt Creator's template
|
||||
|
||||
QT += core gui
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
TARGET = qt-widgets-app
|
||||
TEMPLATE = app
|
||||
|
||||
SOURCES += main.cpp mainwindow.cpp
|
||||
HEADERS += mainwindow.h
|
||||
FORMS += mainwindow.ui
|
||||
Reference in New Issue
Block a user