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:
Nikolai Kosjar
2015-05-08 15:48:17 +02:00
parent 264132da27
commit 23b8a3b2e8
59 changed files with 3820 additions and 634 deletions

View File

@@ -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 \

View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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

View File

@@ -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());

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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);
}

View 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

View File

@@ -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";
}
}

View File

@@ -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>

View 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

View 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

View File

@@ -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()));
}

View File

@@ -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

View 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 */
}

View File

@@ -0,0 +1 @@
//! \ /* COMPLETE HERE */

View 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 */
}

View File

@@ -0,0 +1,8 @@
int globalVariable;
void globalFunction();
class GlobalClass {};
void f()
{
/* COMPLETE HERE */
}

View File

@@ -0,0 +1 @@
#include < /* COMPLETE HERE */

View File

@@ -0,0 +1,9 @@
int globalVariable;
struct S { int member; };
void f()
{
S s;
s. /* COMPLETE HERE */
}

View File

@@ -0,0 +1,2 @@
int globalFromHeader;

View File

@@ -0,0 +1,6 @@
#include "myheader.h"
void f()
{
/* COMPLETE HERE */
}

View File

@@ -0,0 +1 @@
# /* COMPLETE HERE */

View 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();
}

View File

@@ -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;
}

View 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

View 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>

View File

@@ -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