aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/plugins/clangcodemodel/clangautomationutils.cpp140
-rw-r--r--src/plugins/clangcodemodel/clangautomationutils.h46
-rw-r--r--src/plugins/clangcodemodel/clangbatchfileprocessor.cpp799
-rw-r--r--src/plugins/clangcodemodel/clangbatchfileprocessor.h36
-rw-r--r--src/plugins/clangcodemodel/clangcodemodel.pro4
-rw-r--r--src/plugins/clangcodemodel/clangcodemodel.qbs4
-rw-r--r--src/plugins/clangcodemodel/clangcodemodelplugin.cpp20
-rw-r--r--src/plugins/clangcodemodel/clangcodemodelplugin.h3
-rw-r--r--src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp99
9 files changed, 1052 insertions, 99 deletions
diff --git a/src/plugins/clangcodemodel/clangautomationutils.cpp b/src/plugins/clangcodemodel/clangautomationutils.cpp
new file mode 100644
index 0000000000..29dc1c7155
--- /dev/null
+++ b/src/plugins/clangcodemodel/clangautomationutils.cpp
@@ -0,0 +1,140 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "clangautomationutils.h"
+
+#include "clangcompletionassistinterface.h"
+#include "clangcompletionassistprovider.h"
+
+#include <texteditor/codeassist/assistinterface.h>
+#include <texteditor/codeassist/assistproposalitem.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 <utils/qtcassert.h>
+
+#include <QCoreApplication>
+#include <QElapsedTimer>
+
+namespace ClangCodeModel {
+namespace Internal {
+
+class WaitForAsyncCompletions
+{
+public:
+ enum WaitResult { GotResults, GotInvalidResults, Timeout };
+
+ WaitResult wait(TextEditor::IAssistProcessor *processor,
+ TextEditor::AssistInterface *assistInterface,
+ int timeoutInMs)
+ {
+ 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() >= timeoutInMs)
+ return Timeout;
+ QCoreApplication::processEvents();
+ }
+
+ return proposalModel ? GotResults : GotInvalidResults;
+ }
+
+public:
+ TextEditor::IAssistProposalModel *proposalModel;
+};
+
+static const CppTools::ProjectPartHeaderPaths toHeaderPaths(const QStringList &paths)
+{
+ using namespace CppTools;
+
+ ProjectPartHeaderPaths result;
+ foreach (const QString &path, paths)
+ result << ProjectPartHeaderPath(path, ProjectPartHeaderPath::IncludePath);
+ return result;
+}
+
+ProposalModel completionResults(TextEditor::BaseTextEditor *textEditor,
+ const QStringList &includePaths,
+ int timeOutInMs)
+{
+ 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,
+ timeOutInMs);
+ QTC_ASSERT(result == WaitForAsyncCompletions::GotResults, return ProposalModel());
+ return QSharedPointer<TextEditor::IAssistProposalModel>(waitForCompletions.proposalModel);
+}
+
+} // namespace Internal
+} // namespace ClangCodeModel
diff --git a/src/plugins/clangcodemodel/clangautomationutils.h b/src/plugins/clangcodemodel/clangautomationutils.h
new file mode 100644
index 0000000000..9942a597fd
--- /dev/null
+++ b/src/plugins/clangcodemodel/clangautomationutils.h
@@ -0,0 +1,46 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QString>
+#include <QSharedPointer>
+
+namespace TextEditor {
+class BaseTextEditor;
+class IAssistProposalModel;
+}
+
+namespace ClangCodeModel {
+namespace Internal {
+
+using ProposalModel = QSharedPointer<TextEditor::IAssistProposalModel>;
+
+ProposalModel completionResults(TextEditor::BaseTextEditor *textEditor,
+ const QStringList &includePaths = QStringList(),
+ int timeOutInMs = 10000);
+
+} // namespace Internal
+} // namespace ClangCodeModel
diff --git a/src/plugins/clangcodemodel/clangbatchfileprocessor.cpp b/src/plugins/clangcodemodel/clangbatchfileprocessor.cpp
new file mode 100644
index 0000000000..c60aeda650
--- /dev/null
+++ b/src/plugins/clangcodemodel/clangbatchfileprocessor.cpp
@@ -0,0 +1,799 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "clangbatchfileprocessor.h"
+
+#include "clangautomationutils.h"
+
+#include <clangcodemodel/clangeditordocumentprocessor.h>
+
+#include <coreplugin/editormanager/editormanager.h>
+#include <coreplugin/editormanager/ieditor.h>
+#include <coreplugin/icore.h>
+#include <cpptools/cpptoolsreuse.h>
+#include <cpptools/cpptoolstestcase.h>
+#include <cpptools/modelmanagertesthelper.h>
+#include <cpptools/projectinfo.h>
+#include <projectexplorer/projectexplorer.h>
+#include <texteditor/codeassist/assistinterface.h>
+#include <texteditor/codeassist/assistproposalitem.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 <utils/executeondestruction.h>
+#include <utils/qtcassert.h>
+
+#include <QDebug>
+#include <QFileInfo>
+#include <QLoggingCategory>
+#include <QSharedPointer>
+#include <QString>
+#include <QtTest>
+
+using namespace ClangBackEnd;
+using namespace ClangCodeModel;
+using namespace ClangCodeModel::Internal;
+using namespace ProjectExplorer;
+
+static Q_LOGGING_CATEGORY(debug, "qtc.clangcodemodel.batch");
+
+static int timeOutFromEnvironmentVariable()
+{
+ const QByteArray timeoutAsByteArray = qgetenv("QTC_CLANG_BATCH_TIMEOUT");
+
+ bool isConversionOk = false;
+ const int intervalAsInt = timeoutAsByteArray.toInt(&isConversionOk);
+ if (!isConversionOk) {
+ qCDebug(debug, "Environment variable QTC_CLANG_BATCH_TIMEOUT is not set, assuming 30000.");
+ return 30000;
+ }
+
+ return intervalAsInt;
+}
+
+static int timeOutInMs()
+{
+ static int timeOut = timeOutFromEnvironmentVariable();
+ return timeOut;
+}
+
+namespace {
+
+class BatchFileLineTokenizer
+{
+public:
+ BatchFileLineTokenizer(const QString &line);
+
+ QString nextToken();
+
+private:
+ const QChar *advanceToTokenBegin();
+ const QChar *advanceToTokenEnd();
+
+ bool atEnd() const;
+ bool atWhiteSpace() const;
+ bool atQuotationMark() const;
+
+private:
+ bool m_isWithinQuotation = false;
+ QString m_line;
+ const QChar *m_currentChar;
+};
+
+BatchFileLineTokenizer::BatchFileLineTokenizer(const QString &line)
+ : m_line(line)
+ , m_currentChar(m_line.unicode())
+{
+}
+
+QString BatchFileLineTokenizer::nextToken()
+{
+ if (const QChar *tokenBegin = advanceToTokenBegin()) {
+ if (const QChar *tokenEnd = advanceToTokenEnd()) {
+ const int length = tokenEnd - tokenBegin;
+ return QString(tokenBegin, length);
+ }
+ }
+
+ return QString();
+}
+
+const QChar *BatchFileLineTokenizer::advanceToTokenBegin()
+{
+ m_isWithinQuotation = false;
+
+ forever {
+ if (atEnd())
+ return 0;
+
+ if (atQuotationMark()) {
+ m_isWithinQuotation = true;
+ ++m_currentChar;
+ return m_currentChar;
+ }
+
+ if (!atWhiteSpace())
+ return m_currentChar;
+
+ ++m_currentChar;
+ }
+}
+
+const QChar *BatchFileLineTokenizer::advanceToTokenEnd()
+{
+ forever {
+ if (m_isWithinQuotation) {
+ if (atEnd()) {
+ qWarning("ClangBatchFileProcessor: error: unfinished quotation.");
+ return 0;
+ }
+
+ if (atQuotationMark())
+ return m_currentChar++;
+
+ } else if (atWhiteSpace() || atEnd()) {
+ return m_currentChar;
+ }
+
+ ++m_currentChar;
+ }
+}
+
+bool BatchFileLineTokenizer::atEnd() const
+{
+ return *m_currentChar == QLatin1Char('\0');
+}
+
+bool BatchFileLineTokenizer::atWhiteSpace() const
+{
+ return *m_currentChar == ' '
+ || *m_currentChar == '\t'
+ || *m_currentChar == '\n';
+}
+
+bool BatchFileLineTokenizer::atQuotationMark() const
+{
+ return *m_currentChar == '"';
+}
+
+struct CommandContext {
+ QString filePath;
+ int lineNumber = -1;
+};
+
+class Command
+{
+public:
+ using Ptr = QSharedPointer<Command>;
+
+public:
+ Command(const CommandContext &context) : m_commandContext(context) {}
+ virtual ~Command() {}
+
+ const CommandContext &context() const { return m_commandContext; }
+ virtual bool run() { return true; }
+
+private:
+ const CommandContext m_commandContext;
+};
+
+class OpenProjectCommand : public Command
+{
+public:
+ OpenProjectCommand(const CommandContext &context,
+ const QString &projectFilePath);
+
+ bool run() override;
+
+ static Command::Ptr parse(BatchFileLineTokenizer &arguments,
+ const CommandContext &context);
+
+private:
+ QString m_projectFilePath;
+};
+
+OpenProjectCommand::OpenProjectCommand(const CommandContext &context,
+ const QString &projectFilePath)
+ : Command(context)
+ , m_projectFilePath(projectFilePath)
+{
+}
+
+bool OpenProjectCommand::run()
+{
+ qCDebug(debug) << "line" << context().lineNumber << "OpenProjectCommand" << m_projectFilePath;
+
+ const ProjectExplorerPlugin::OpenProjectResult openProjectSucceeded
+ = ProjectExplorerPlugin::openProject(m_projectFilePath);
+ QTC_ASSERT(openProjectSucceeded, return false);
+
+ Project *project = openProjectSucceeded.project();
+ project->configureAsExampleProject({});
+
+ return CppTools::Tests::TestCase::waitUntilCppModelManagerIsAwareOf(project, timeOutInMs());
+}
+
+Command::Ptr OpenProjectCommand::parse(BatchFileLineTokenizer &arguments,
+ const CommandContext &context)
+{
+ const QString projectFilePath = arguments.nextToken();
+ if (projectFilePath.isEmpty()) {
+ qWarning("%s:%d: error: No project file path given.",
+ qPrintable(context.filePath),
+ context.lineNumber);
+ return Command::Ptr();
+ }
+
+ const QString absoluteProjectFilePath = QFileInfo(projectFilePath).absoluteFilePath();
+
+ return Command::Ptr(new OpenProjectCommand(context, absoluteProjectFilePath));
+}
+
+class OpenDocumentCommand : public Command
+{
+public:
+ OpenDocumentCommand(const CommandContext &context,
+ const QString &documentFilePath);
+
+ bool run() override;
+
+ static Command::Ptr parse(BatchFileLineTokenizer &arguments, const CommandContext &context);
+
+private:
+ QString m_documentFilePath;
+};
+
+OpenDocumentCommand::OpenDocumentCommand(const CommandContext &context,
+ const QString &documentFilePath)
+ : Command(context)
+ , m_documentFilePath(documentFilePath)
+{
+}
+
+class WaitForUpdatedCodeWarnings : public QObject
+{
+ Q_OBJECT
+
+public:
+ WaitForUpdatedCodeWarnings(ClangEditorDocumentProcessor *processor);
+
+ bool wait(int timeOutInMs) const;
+
+private:
+ void onCodeWarningsUpdated() { m_gotResults = true; }
+
+private:
+
+ bool m_gotResults = false;
+};
+
+WaitForUpdatedCodeWarnings::WaitForUpdatedCodeWarnings(ClangEditorDocumentProcessor *processor)
+{
+ connect(processor,
+ &ClangEditorDocumentProcessor::codeWarningsUpdated,
+ this, &WaitForUpdatedCodeWarnings::onCodeWarningsUpdated);
+}
+
+bool WaitForUpdatedCodeWarnings::wait(int timeOutInMs) const
+{
+ QTime time;
+ time.start();
+
+ forever {
+ if (time.elapsed() > timeOutInMs) {
+ qWarning("WaitForUpdatedCodeWarnings: timeout of %d ms reached.", timeOutInMs);
+ return false;
+ }
+
+ if (m_gotResults)
+ return true;
+
+ QCoreApplication::processEvents();
+ QThread::msleep(20);
+ }
+}
+
+bool OpenDocumentCommand::run()
+{
+ qCDebug(debug) << "line" << context().lineNumber << "OpenDocumentCommand" << m_documentFilePath;
+
+ const bool openEditorSucceeded = Core::EditorManager::openEditor(m_documentFilePath);
+ QTC_ASSERT(openEditorSucceeded, return false);
+
+ auto *processor = ClangEditorDocumentProcessor::get(m_documentFilePath);
+ QTC_ASSERT(processor, return false);
+
+ WaitForUpdatedCodeWarnings waiter(processor);
+ return waiter.wait(timeOutInMs());
+}
+
+Command::Ptr OpenDocumentCommand::parse(BatchFileLineTokenizer &arguments,
+ const CommandContext &context)
+{
+ const QString documentFilePath = arguments.nextToken();
+ if (documentFilePath.isEmpty()) {
+ qWarning("%s:%d: error: No document file path given.",
+ qPrintable(context.filePath),
+ context.lineNumber);
+ return Command::Ptr();
+ }
+
+ const QString absoluteDocumentFilePath = QFileInfo(documentFilePath).absoluteFilePath();
+
+ return Command::Ptr(new OpenDocumentCommand(context, absoluteDocumentFilePath));
+}
+
+class CloseAllDocuments : public Command
+{
+public:
+ CloseAllDocuments(const CommandContext &context);
+
+ bool run() override;
+
+ static Command::Ptr parse(BatchFileLineTokenizer &arguments, const CommandContext &context);
+};
+
+CloseAllDocuments::CloseAllDocuments(const CommandContext &context)
+ : Command(context)
+{
+}
+
+bool CloseAllDocuments::run()
+{
+ qCDebug(debug) << "line" << context().lineNumber << "CloseAllDocuments";
+
+ return Core::EditorManager::closeAllEditors(/*askAboutModifiedEditors=*/ false);
+}
+
+Command::Ptr CloseAllDocuments::parse(BatchFileLineTokenizer &arguments,
+ const CommandContext &context)
+{
+ const QString argument = arguments.nextToken();
+ if (!argument.isEmpty()) {
+ qWarning("%s:%d: error: Unexpected argument.",
+ qPrintable(context.filePath),
+ context.lineNumber);
+ return Command::Ptr();
+ }
+
+ return Command::Ptr(new CloseAllDocuments(context));
+}
+
+class InsertTextCommand : public Command
+{
+public:
+ // line and column are 1-based
+ InsertTextCommand(const CommandContext &context, const QString &text);
+
+ bool run() override;
+
+ static Command::Ptr parse(BatchFileLineTokenizer &arguments,
+ const CommandContext &context);
+
+private:
+ const QString m_textToInsert;
+};
+
+InsertTextCommand::InsertTextCommand(const CommandContext &context, const QString &text)
+ : Command(context)
+ , m_textToInsert(text)
+{
+}
+
+TextEditor::BaseTextEditor *currentTextEditor()
+{
+ return qobject_cast<TextEditor::BaseTextEditor*>(Core::EditorManager::currentEditor());
+}
+
+bool InsertTextCommand::run()
+{
+ qCDebug(debug) << "line" << context().lineNumber << "InsertTextCommand" << m_textToInsert;
+
+ TextEditor::BaseTextEditor *editor = currentTextEditor();
+ QTC_ASSERT(editor, return false);
+ const QString documentFilePath = editor->document()->filePath().toString();
+ auto *processor = ClangEditorDocumentProcessor::get(documentFilePath);
+ QTC_ASSERT(processor, return false);
+
+ editor->insert(m_textToInsert);
+
+ WaitForUpdatedCodeWarnings waiter(processor);
+ return waiter.wait(timeOutInMs());
+}
+
+Command::Ptr InsertTextCommand::parse(BatchFileLineTokenizer &arguments,
+ const CommandContext &context)
+{
+ const QString textToInsert = arguments.nextToken();
+ if (textToInsert.isEmpty()) {
+ qWarning("%s:%d: error: No text to insert given.",
+ qPrintable(context.filePath),
+ context.lineNumber);
+ return Command::Ptr();
+ }
+
+ return Command::Ptr(new InsertTextCommand(context, textToInsert));
+}
+
+class CompleteCommand : public Command
+{
+public:
+ CompleteCommand(const CommandContext &context);
+
+ bool run() override;
+
+ static Command::Ptr parse(BatchFileLineTokenizer &arguments,
+ const CommandContext &context);
+};
+
+CompleteCommand::CompleteCommand(const CommandContext &context)
+ : Command(context)
+{
+}
+
+bool CompleteCommand::run()
+{
+ qCDebug(debug) << "line" << context().lineNumber << "CompleteCommand";
+
+ TextEditor::BaseTextEditor *editor = currentTextEditor();
+ QTC_ASSERT(editor, return false);
+
+ const QString documentFilePath = editor->document()->filePath().toString();
+ auto *processor = ClangEditorDocumentProcessor::get(documentFilePath);
+ QTC_ASSERT(processor, return false);
+
+ return completionResults(editor, QStringList(), timeOutInMs());
+}
+
+Command::Ptr CompleteCommand::parse(BatchFileLineTokenizer &arguments,
+ const CommandContext &context)
+{
+ Q_UNUSED(arguments)
+ Q_UNUSED(context)
+
+ return Command::Ptr(new CompleteCommand(context));
+}
+
+class SetCursorCommand : public Command
+{
+public:
+ // line and column are 1-based
+ SetCursorCommand(const CommandContext &context, int line, int column);
+
+ bool run() override;
+
+ static Command::Ptr parse(BatchFileLineTokenizer &arguments,
+ const CommandContext &context);
+
+private:
+ int m_line;
+ int m_column;
+};
+
+SetCursorCommand::SetCursorCommand(const CommandContext &context, int line, int column)
+ : Command(context)
+ , m_line(line)
+ , m_column(column)
+{
+}
+
+bool SetCursorCommand::run()
+{
+ qCDebug(debug) << "line" << context().lineNumber << "SetCursorCommand" << m_line << m_column;
+
+ TextEditor::BaseTextEditor *editor = currentTextEditor();
+ QTC_ASSERT(editor, return false);
+
+ editor->gotoLine(m_line, m_column - 1);
+
+ return true;
+}
+
+Command::Ptr SetCursorCommand::parse(BatchFileLineTokenizer &arguments,
+ const CommandContext &context)
+{
+ // Process line
+ const QString line = arguments.nextToken();
+ if (line.isEmpty()) {
+ qWarning("%s:%d: error: No line number given.",
+ qPrintable(context.filePath),
+ context.lineNumber);
+ return Command::Ptr();
+ }
+ bool converted = false;
+ const int lineNumber = line.toInt(&converted);
+ if (!converted) {
+ qWarning("%s:%d: error: Invalid line number.",
+ qPrintable(context.filePath),
+ context.lineNumber);
+ return Command::Ptr();
+ }
+
+ // Process column
+ const QString column = arguments.nextToken();
+ if (column.isEmpty()) {
+ qWarning("%s:%d: error: No column number given.",
+ qPrintable(context.filePath),
+ context.lineNumber);
+ return Command::Ptr();
+ }
+ converted = false;
+ const int columnNumber = column.toInt(&converted);
+ if (!converted) {
+ qWarning("%s:%d: error: Invalid column number.",
+ qPrintable(context.filePath),
+ context.lineNumber);
+ return Command::Ptr();
+ }
+
+ return Command::Ptr(new SetCursorCommand(context, lineNumber, columnNumber));
+}
+
+class ProcessEventsCommand : public Command
+{
+public:
+ ProcessEventsCommand(const CommandContext &context, int durationInMs);
+
+ bool run() override;
+
+ static Command::Ptr parse(BatchFileLineTokenizer &arguments,
+ const CommandContext &context);
+
+private:
+ int m_durationInMs;
+};
+
+ProcessEventsCommand::ProcessEventsCommand(const CommandContext &context,
+ int durationInMs)
+ : Command(context)
+ , m_durationInMs(durationInMs)
+{
+}
+
+bool ProcessEventsCommand::run()
+{
+ qCDebug(debug) << "line" << context().lineNumber << "ProcessEventsCommand" << m_durationInMs;
+
+ QTime time;
+ time.start();
+
+ forever {
+ if (time.elapsed() > m_durationInMs)
+ return true;
+
+ QCoreApplication::processEvents();
+ QThread::msleep(20);
+ }
+}
+
+Command::Ptr ProcessEventsCommand::parse(BatchFileLineTokenizer &arguments,
+ const CommandContext &context)
+{
+ const QString durationInMsText = arguments.nextToken();
+ if (durationInMsText.isEmpty()) {
+ qWarning("%s:%d: error: No duration given.",
+ qPrintable(context.filePath),
+ context.lineNumber);
+ return Command::Ptr();
+ }
+
+ bool converted = false;
+ const int durationInMs = durationInMsText.toInt(&converted);
+ if (!converted) {
+ qWarning("%s:%d: error: Invalid duration given.",
+ qPrintable(context.filePath),
+ context.lineNumber);
+ return Command::Ptr();
+ }
+
+ return Command::Ptr(new ProcessEventsCommand(context, durationInMs));
+}
+
+class BatchFileReader
+{
+public:
+ BatchFileReader(const QString &filePath);
+
+ bool isFilePathValid() const;
+
+ QString read() const;
+
+private:
+ const QString m_batchFilePath;
+};
+
+BatchFileReader::BatchFileReader(const QString &filePath)
+ : m_batchFilePath(filePath)
+{
+}
+
+bool BatchFileReader::isFilePathValid() const
+{
+ QFileInfo fileInfo(m_batchFilePath);
+
+ return !m_batchFilePath.isEmpty()
+ && fileInfo.isFile()
+ && fileInfo.isReadable();
+}
+
+QString BatchFileReader::read() const
+{
+ QFile file(m_batchFilePath);
+ QTC_CHECK(file.open(QFile::ReadOnly | QFile::Text));
+
+ return QString::fromLocal8Bit(file.readAll());
+}
+
+class BatchFileParser
+{
+public:
+ BatchFileParser(const QString &filePath,
+ const QString &commands);
+
+ bool parse();
+ QVector<Command::Ptr> commands() const;
+
+private:
+ bool advanceLine();
+ QString currentLine() const;
+ bool parseLine(const QString &line);
+
+private:
+ using ParseFunction = Command::Ptr (*)(BatchFileLineTokenizer &, const CommandContext &);
+ using CommandToParseFunction = QHash<QString, ParseFunction>;
+ CommandToParseFunction m_commandParsers;
+
+ int m_currentLineIndex = -1;
+ CommandContext m_context;
+ QStringList m_lines;
+ QVector<Command::Ptr> m_commands;
+};
+
+BatchFileParser::BatchFileParser(const QString &filePath,
+ const QString &commands)
+ : m_lines(commands.split('\n'))
+{
+ m_context.filePath = filePath;
+
+ m_commandParsers.insert("openProject", &OpenProjectCommand::parse);
+ m_commandParsers.insert("openDocument", &OpenDocumentCommand::parse);
+ m_commandParsers.insert("closeAllDocuments", &CloseAllDocuments::parse);
+ m_commandParsers.insert("setCursor", &SetCursorCommand::parse);
+ m_commandParsers.insert("insertText", &InsertTextCommand::parse);
+ m_commandParsers.insert("complete", &CompleteCommand::parse);
+ m_commandParsers.insert("processEvents", &ProcessEventsCommand::parse);
+}
+
+bool BatchFileParser::parse()
+{
+ while (advanceLine()) {
+ const QString line = currentLine().trimmed();
+ if (line.isEmpty() || line.startsWith('#'))
+ continue;
+
+ if (!parseLine(line))
+ return false;
+ }
+
+ return true;
+}
+
+QVector<Command::Ptr> BatchFileParser::commands() const
+{
+ return m_commands;
+}
+
+bool BatchFileParser::advanceLine()
+{
+ ++m_currentLineIndex;
+ m_context.lineNumber = m_currentLineIndex + 1;
+ return m_currentLineIndex < m_lines.size();
+}
+
+QString BatchFileParser::currentLine() const
+{
+ return m_lines[m_currentLineIndex];
+}
+
+bool BatchFileParser::parseLine(const QString &line)
+{
+ BatchFileLineTokenizer tokenizer(line);
+ QString command = tokenizer.nextToken();
+ QTC_CHECK(!command.isEmpty());
+
+ if (const ParseFunction parseFunction = m_commandParsers.value(command)) {
+ if (Command::Ptr cmd = parseFunction(tokenizer, m_context)) {
+ m_commands.append(cmd);
+ return true;
+ }
+
+ return false;
+ }
+
+ qWarning("%s:%d: error: Unknown command \"%s\".",
+ qPrintable(m_context.filePath),
+ m_context.lineNumber,
+ qPrintable(command));
+
+ return false;
+}
+
+} // anonymous namespace
+
+namespace ClangCodeModel {
+namespace Internal {
+
+static QString applySubstitutions(const QString &filePath, const QString &text)
+{
+ const QString dirPath = QFileInfo(filePath).absolutePath();
+
+ QString result = text;
+ result.replace("${PWD}", dirPath);
+
+ return result;
+}
+
+bool runClangBatchFile(const QString &filePath)
+{
+ qWarning("ClangBatchFileProcessor: Running \"%s\".", qPrintable(filePath));
+
+ BatchFileReader reader(filePath);
+ QTC_ASSERT(reader.isFilePathValid(), return false);
+ const QString fileContent = reader.read();
+ const QString fileContentWithSubstitutionsApplied = applySubstitutions(filePath, fileContent);
+
+ BatchFileParser parser(filePath, fileContentWithSubstitutionsApplied);
+ QTC_ASSERT(parser.parse(), return false);
+ const QVector<Command::Ptr> commands = parser.commands();
+
+ Utils::ExecuteOnDestruction closeAllEditors([](){
+ qWarning("ClangBatchFileProcessor: Finished, closing all documents.");
+ QTC_CHECK(Core::EditorManager::closeAllEditors(/*askAboutModifiedEditors=*/ false));
+ });
+
+ foreach (const Command::Ptr &command, commands) {
+ const bool runSucceeded = command->run();
+ QCoreApplication::processEvents(); // Update GUI
+
+ if (!runSucceeded) {
+ const CommandContext context = command->context();
+ qWarning("%s:%d: Failed to run.",
+ qPrintable(context.filePath),
+ context.lineNumber);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace Internal
+} // namespace ClangCodeModel
+
+#include "clangbatchfileprocessor.moc"
diff --git a/src/plugins/clangcodemodel/clangbatchfileprocessor.h b/src/plugins/clangcodemodel/clangbatchfileprocessor.h
new file mode 100644
index 0000000000..40f0bceee3
--- /dev/null
+++ b/src/plugins/clangcodemodel/clangbatchfileprocessor.h
@@ -0,0 +1,36 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QString>
+
+namespace ClangCodeModel {
+namespace Internal {
+
+bool runClangBatchFile(const QString &filePath);
+
+} // namespace Internal
+} // namespace ClangCodeModel
diff --git a/src/plugins/clangcodemodel/clangcodemodel.pro b/src/plugins/clangcodemodel/clangcodemodel.pro
index ef9302a1d1..3089483407 100644
--- a/src/plugins/clangcodemodel/clangcodemodel.pro
+++ b/src/plugins/clangcodemodel/clangcodemodel.pro
@@ -9,7 +9,9 @@ SOURCES += \
clangassistproposal.cpp \
clangassistproposalitem.cpp \
clangassistproposalmodel.cpp \
+ clangautomationutils.cpp \
clangbackendipcintegration.cpp \
+ clangbatchfileprocessor.cpp \
clangcodemodelplugin.cpp \
clangcompletionassistinterface.cpp \
clangcompletionassistprocessor.cpp \
@@ -39,7 +41,9 @@ HEADERS += \
clangassistproposal.h \
clangassistproposalitem.h \
clangassistproposalmodel.h \
+ clangautomationutils.h \
clangbackendipcintegration.h \
+ clangbatchfileprocessor.h \
clangcodemodelplugin.h \
clangcompletionassistinterface.h \
clangcompletionassistprocessor.h \
diff --git a/src/plugins/clangcodemodel/clangcodemodel.qbs b/src/plugins/clangcodemodel/clangcodemodel.qbs
index 02ff530760..28e4dffcd8 100644
--- a/src/plugins/clangcodemodel/clangcodemodel.qbs
+++ b/src/plugins/clangcodemodel/clangcodemodel.qbs
@@ -41,8 +41,12 @@ QtcPlugin {
"clangassistproposalitem.h",
"clangassistproposalmodel.cpp",
"clangassistproposalmodel.h",
+ "clangautomationutils.cpp",
+ "clangautomationutils.h",
"clangbackendipcintegration.cpp",
"clangbackendipcintegration.h",
+ "clangbatchfileprocessor.cpp",
+ "clangbatchfileprocessor.h",
"clangcodemodel.qrc",
"clangcodemodelplugin.cpp",
"clangcodemodelplugin.h",
diff --git a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
index ba1bc10376..0aa9cbff3f 100644
--- a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
+++ b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
@@ -25,6 +25,7 @@
#include "clangcodemodelplugin.h"
+#include "clangbatchfileprocessor.h"
#include "clangconstants.h"
#include "clangprojectsettingswidget.h"
@@ -74,8 +75,13 @@ void addProjectPanelWidget()
bool ClangCodeModelPlugin::initialize(const QStringList &arguments, QString *errorMessage)
{
- Q_UNUSED(arguments)
- Q_UNUSED(errorMessage)
+ Q_UNUSED(arguments);
+ Q_UNUSED(errorMessage);
+
+ connect(ProjectExplorer::ProjectExplorerPlugin::instance(),
+ &ProjectExplorer::ProjectExplorerPlugin::finishedInitialization,
+ this,
+ &ClangCodeModelPlugin::maybeHandleBatchFileAndExit);
CppTools::CppModelManager::instance()->activateClangCodeModel(&m_modelManagerSupportProvider);
@@ -89,6 +95,16 @@ void ClangCodeModelPlugin::extensionsInitialized()
{
}
+// For e.g. creation of profile-guided optimization builds.
+void ClangCodeModelPlugin::maybeHandleBatchFileAndExit() const
+{
+ const QString batchFilePath = QString::fromLocal8Bit(qgetenv("QTC_CLANG_BATCH"));
+ if (!batchFilePath.isEmpty() && QTC_GUARD(QFileInfo::exists(batchFilePath))) {
+ const bool runSucceeded = runClangBatchFile(batchFilePath);
+ QCoreApplication::exit(!runSucceeded);
+ }
+}
+
#ifdef WITH_TESTS
QList<QObject *> ClangCodeModelPlugin::createTestObjects() const
{
diff --git a/src/plugins/clangcodemodel/clangcodemodelplugin.h b/src/plugins/clangcodemodel/clangcodemodelplugin.h
index 0b94c93c2c..8e41fe6020 100644
--- a/src/plugins/clangcodemodel/clangcodemodelplugin.h
+++ b/src/plugins/clangcodemodel/clangcodemodelplugin.h
@@ -42,6 +42,9 @@ public:
void extensionsInitialized();
private:
+ void maybeHandleBatchFileAndExit() const;
+
+private:
ModelManagerSupportProviderClang m_modelManagerSupportProvider;
#ifdef WITH_TESTS
diff --git a/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp b/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp
index 0f9793dc15..f5072068f6 100644
--- a/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp
+++ b/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp
@@ -25,6 +25,7 @@
#include "clangcodecompletion_test.h"
+#include "../clangautomationutils.h"
#include "../clangbackendipcintegration.h"
#include "../clangcompletionassistinterface.h"
#include "../clangmodelmanagersupport.h"
@@ -38,12 +39,8 @@
#include <cpptools/cpptoolstestcase.h>
#include <cpptools/modelmanagertesthelper.h>
#include <cpptools/projectinfo.h>
-#include <texteditor/codeassist/assistinterface.h>
#include <texteditor/codeassist/assistproposalitem.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>
@@ -180,53 +177,6 @@ void insertTextAtTopOfEditor(TextEditor::BaseTextEditor *editor, const QByteArra
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() >= 30 * 1000)
- return Timeout;
- QCoreApplication::processEvents();
- }
-
- return proposalModel ? GotResults : GotInvalidResults;
-}
-
class ChangeDocumentReloadSetting
{
public:
@@ -421,51 +371,6 @@ public:
QString senderLog;
};
-const CppTools::ProjectPartHeaderPaths toHeaderPaths(const QStringList &paths)
-{
- using namespace CppTools;
-
- ProjectPartHeaderPaths result;
- foreach (const QString &path, paths)
- result << ProjectPartHeaderPath(path, ProjectPartHeaderPath::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:
@@ -689,7 +594,7 @@ public:
if (!textToInsert.isEmpty())
openEditor.editor()->insert(textToInsert);
- proposal = completionResults(openEditor.editor(), includePaths);
+ proposal = completionResults(openEditor.editor(), includePaths, 15000);
}
ProposalModel proposal;