diff --git a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp index 8a4919e3786..99c6a670339 100644 --- a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp +++ b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp @@ -72,6 +72,11 @@ ClangEditorDocumentProcessor::ClangEditorDocumentProcessor( , m_semanticHighlighter(document) , m_builtinProcessor(document, /*enableSemanticHighlighter=*/ false) { + m_updateTranslationUnitTimer.setSingleShot(true); + m_updateTranslationUnitTimer.setInterval(350); + connect(&m_updateTranslationUnitTimer, &QTimer::timeout, + this, &ClangEditorDocumentProcessor::updateTranslationUnitIfProjectPartExists); + // Forwarding the semantic info from the builtin processor enables us to provide all // editor (widget) related features that are not yet implemented by the clang plugin. connect(&m_builtinProcessor, &CppTools::BuiltinEditorDocumentProcessor::cppDocumentUpdated, @@ -82,6 +87,8 @@ ClangEditorDocumentProcessor::ClangEditorDocumentProcessor( ClangEditorDocumentProcessor::~ClangEditorDocumentProcessor() { + m_updateTranslationUnitTimer.stop(); + m_parserWatcher.cancel(); m_parserWatcher.waitForFinished(); @@ -93,7 +100,7 @@ ClangEditorDocumentProcessor::~ClangEditorDocumentProcessor() void ClangEditorDocumentProcessor::run() { - updateTranslationUnitIfProjectPartExists(); + m_updateTranslationUnitTimer.start(); // Run clang parser disconnect(&m_parserWatcher, &QFutureWatcher::finished, @@ -251,6 +258,11 @@ void ClangEditorDocumentProcessor::addDiagnosticToolTipToLayout(uint line, addToolTipToLayout(diagnostic, target); } +void ClangEditorDocumentProcessor::editorDocumentTimerRestarted() +{ + m_updateTranslationUnitTimer.stop(); // Wait for the next call to run(). +} + ClangBackEnd::FileContainer ClangEditorDocumentProcessor::fileContainerWithArguments() const { return fileContainerWithArguments(m_projectPart.data()); diff --git a/src/plugins/clangcodemodel/clangeditordocumentprocessor.h b/src/plugins/clangcodemodel/clangeditordocumentprocessor.h index e9ec8ebd185..a23d93d72fb 100644 --- a/src/plugins/clangcodemodel/clangeditordocumentprocessor.h +++ b/src/plugins/clangcodemodel/clangeditordocumentprocessor.h @@ -32,6 +32,7 @@ #include #include +#include namespace ClangBackEnd { class DiagnosticContainer; @@ -78,6 +79,8 @@ public: bool hasDiagnosticsAt(uint line, uint column) const override; void addDiagnosticToolTipToLayout(uint line, uint column, QLayout *target) const override; + void editorDocumentTimerRestarted() override; + ClangBackEnd::FileContainer fileContainerWithArguments() const; void clearDiagnosticsWithFixIts(); @@ -102,6 +105,7 @@ private: QSharedPointer m_parser; CppTools::ProjectPart::Ptr m_projectPart; QFutureWatcher m_parserWatcher; + QTimer m_updateTranslationUnitTimer; unsigned m_parserRevision; CppTools::SemanticHighlighter m_semanticHighlighter; diff --git a/src/plugins/cppeditor/cppeditordocument.cpp b/src/plugins/cppeditor/cppeditordocument.cpp index 00ff24aca9b..91918f9d3bd 100644 --- a/src/plugins/cppeditor/cppeditordocument.cpp +++ b/src/plugins/cppeditor/cppeditordocument.cpp @@ -230,12 +230,14 @@ void CppEditorDocument::scheduleProcessDocument() { m_processorRevision = document()->revision(); m_processorTimer.start(); + processor()->editorDocumentTimerRestarted(); } void CppEditorDocument::processDocument() { if (processor()->isParserRunning() || m_processorRevision != contentsRevision()) { m_processorTimer.start(); + processor()->editorDocumentTimerRestarted(); return; } diff --git a/src/plugins/cpptools/baseeditordocumentprocessor.cpp b/src/plugins/cpptools/baseeditordocumentprocessor.cpp index 82aa058b90d..1ae7f11b98e 100644 --- a/src/plugins/cpptools/baseeditordocumentprocessor.cpp +++ b/src/plugins/cpptools/baseeditordocumentprocessor.cpp @@ -67,6 +67,10 @@ void BaseEditorDocumentProcessor::addDiagnosticToolTipToLayout(uint, uint, QLayo { } +void BaseEditorDocumentProcessor::editorDocumentTimerRestarted() +{ +} + void BaseEditorDocumentProcessor::runParser(QFutureInterface &future, BaseEditorDocumentParser::Ptr parser, const WorkingCopy workingCopy) diff --git a/src/plugins/cpptools/baseeditordocumentprocessor.h b/src/plugins/cpptools/baseeditordocumentprocessor.h index 77723fcd4ce..da6a20ca943 100644 --- a/src/plugins/cpptools/baseeditordocumentprocessor.h +++ b/src/plugins/cpptools/baseeditordocumentprocessor.h @@ -67,6 +67,8 @@ public: virtual bool hasDiagnosticsAt(uint line, uint column) const; virtual void addDiagnosticToolTipToLayout(uint line, uint column, QLayout *layout) const; + virtual void editorDocumentTimerRestarted(); + signals: // Signal interface to implement void codeWarningsUpdated(unsigned revision, diff --git a/src/tools/clangbackend/ipcsource/clangbackend_global.h b/src/tools/clangbackend/ipcsource/clangbackend_global.h index ff17d1c1046..0dccaa87ad4 100644 --- a/src/tools/clangbackend/ipcsource/clangbackend_global.h +++ b/src/tools/clangbackend/ipcsource/clangbackend_global.h @@ -31,6 +31,7 @@ enum class PreferredTranslationUnit { RecentlyParsed, PreviouslyParsed, + LastUninitialized, }; } // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri b/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri index 5fbe996d06b..cf0a0e6682e 100644 --- a/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri +++ b/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri @@ -47,6 +47,9 @@ HEADERS += $$PWD/clangcodemodelserver.h \ $$PWD/clangdocumentprocessors.h \ $$PWD/clangtranslationunits.h \ $$PWD/clangclock.h \ + $$PWD/clangsupportivetranslationunitinitializer.h \ + $$PWD/clangparsesupportivetranslationunitjob.h \ + $$PWD/clangreparsesupportivetranslationunitjob.h \ SOURCES += $$PWD/clangcodemodelserver.cpp \ $$PWD/codecompleter.cpp \ @@ -88,4 +91,7 @@ SOURCES += $$PWD/clangcodemodelserver.cpp \ $$PWD/clangexceptions.cpp \ $$PWD/clangdocumentprocessor.cpp \ $$PWD/clangdocumentprocessors.cpp \ - $$PWD/clangtranslationunits.cpp + $$PWD/clangtranslationunits.cpp \ + $$PWD/clangsupportivetranslationunitinitializer.cpp \ + $$PWD/clangparsesupportivetranslationunitjob.cpp \ + $$PWD/clangreparsesupportivetranslationunitjob.cpp \ diff --git a/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp b/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp index 1b26c39fadf..bea5eacea49 100644 --- a/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp +++ b/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp @@ -27,6 +27,7 @@ #include "clangdocuments.h" #include "clangfilesystemwatcher.h" +#include "clangtranslationunits.h" #include "codecompleter.h" #include "diagnosticset.h" #include "highlightingmarks.h" @@ -126,10 +127,16 @@ void ClangCodeModelServer::updateTranslationUnitsForEditor(const UpdateTranslati try { const auto newerFileContainers = documents.newerFileContainers(message.fileContainers()); if (newerFileContainers.size() > 0) { - documents.update(newerFileContainers); + const std::vector updateDocuments = documents.update(newerFileContainers); unsavedFiles.createOrUpdate(newerFileContainers); - updateDocumentAnnotationsTimer.start(updateDocumentAnnotationsTimeOutInMs); + // Start the jobs on the next event loop iteration since otherwise + // we might block the translation unit for a completion request + // that comes right after this message. + updateDocumentAnnotationsTimer.start(0); + QTimer::singleShot(0, [this, updateDocuments](){ + startInitializingSupportiveTranslationUnits(updateDocuments); + }); } } catch (const std::exception &exception) { qWarning() << "Error in ClangCodeModelServer::updateTranslationUnitsForEditor:" << exception.what(); @@ -286,7 +293,9 @@ void ClangCodeModelServer::processJobsForDirtyAndVisibleDocuments() for (const auto &document : documents.documents()) { if (document.isNeedingReparse() && document.isVisibleInEditor()) { DocumentProcessor processor = documentProcessors().processor(document); - processor.addJob(createJobRequest(document, JobRequest::Type::UpdateDocumentAnnotations)); + processor.addJob(createJobRequest(document, + JobRequest::Type::UpdateDocumentAnnotations, + PreferredTranslationUnit::PreviouslyParsed)); } } @@ -297,14 +306,37 @@ void ClangCodeModelServer::processInitialJobsForDocuments(const std::vector &documents) +{ + for (const Document &document : documents) { + try { + DocumentProcessor processor = documentProcessors().processor(document); + if (!processor.hasSupportiveTranslationUnit()) + processor.startInitializingSupportiveTranslationUnit(); + } catch (const DocumentProcessorDoesNotExist &) { + // OK, document was already closed. + } + } +} + +JobRequest ClangCodeModelServer::createJobRequest( + const Document &document, + JobRequest::Type type, + PreferredTranslationUnit preferredTranslationUnit) const { JobRequest jobRequest; jobRequest.type = type; @@ -313,6 +345,7 @@ JobRequest ClangCodeModelServer::createJobRequest(const Document &document, jobRequest.projectPartId = document.projectPartId(); jobRequest.unsavedFilesChangeTimePoint = unsavedFiles.lastChangeTimePoint(); jobRequest.documentRevision = document.documentRevision(); + jobRequest.preferredTranslationUnit = preferredTranslationUnit; const ProjectPart &projectPart = projects.project(document.projectPartId()); jobRequest.projectChangeTimePoint = projectPart.lastChangeTimePoint(); diff --git a/src/tools/clangbackend/ipcsource/clangcodemodelserver.h b/src/tools/clangbackend/ipcsource/clangcodemodelserver.h index efebb054da9..ab4efdb7e29 100644 --- a/src/tools/clangbackend/ipcsource/clangcodemodelserver.h +++ b/src/tools/clangbackend/ipcsource/clangcodemodelserver.h @@ -65,16 +65,20 @@ public: // for tests int queueSizeForTestsOnly(); bool isTimerRunningForTestOnly() const; void setUpdateDocumentAnnotationsTimeOutInMsForTestsOnly(int value); + DocumentProcessors &documentProcessors(); private: - DocumentProcessors &documentProcessors(); void startDocumentAnnotationsTimerIfFileIsNotOpenAsDocument(const Utf8String &filePath); void addJobRequestsForDirtyAndVisibleDocuments(); void processJobsForDirtyAndVisibleDocuments(); void processInitialJobsForDocuments(const std::vector &documents); + void startInitializingSupportiveTranslationUnits(const std::vector &documents); - JobRequest createJobRequest(const Document &document, JobRequest::Type type) const; + JobRequest createJobRequest(const Document &document, + JobRequest::Type type, + PreferredTranslationUnit preferredTranslationUnit + = PreferredTranslationUnit::RecentlyParsed) const; private: ProjectParts projects; diff --git a/src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp b/src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp index 8d13869bb8a..d45f53fdda0 100644 --- a/src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp +++ b/src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp @@ -58,7 +58,8 @@ IAsyncJob::AsyncPrepareResult CompleteCodeJob::prepareAsyncRun() try { m_pinnedDocument = context().documentForJobRequest(); - const TranslationUnit translationUnit = m_pinnedDocument.translationUnit(); + const TranslationUnit translationUnit + = m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit); const UnsavedFiles unsavedFiles = *context().unsavedFiles; const quint32 line = jobRequest.line; const quint32 column = jobRequest.column; diff --git a/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.cpp b/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.cpp index f24a3978e9e..a9bf6698661 100644 --- a/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.cpp +++ b/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.cpp @@ -48,7 +48,8 @@ IAsyncJob::AsyncPrepareResult CreateInitialDocumentPreambleJob::prepareAsyncRun( m_pinnedDocument = context().documentForJobRequest(); m_pinnedFileContainer = m_pinnedDocument.fileContainer(); - const TranslationUnit translationUnit = m_pinnedDocument.translationUnit(); + const TranslationUnit translationUnit + = m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit); const TranslationUnitUpdateInput updateInput = m_pinnedDocument.createUpdateInput(); setRunner([translationUnit, updateInput]() { return runAsyncHelper(translationUnit, updateInput); diff --git a/src/tools/clangbackend/ipcsource/clangdocumentprocessor.cpp b/src/tools/clangbackend/ipcsource/clangdocumentprocessor.cpp index 0b4faf9251f..b0a0e0122f1 100644 --- a/src/tools/clangbackend/ipcsource/clangdocumentprocessor.cpp +++ b/src/tools/clangbackend/ipcsource/clangdocumentprocessor.cpp @@ -25,9 +25,14 @@ #include "clangdocumentprocessor.h" +#include "clangdocuments.h" #include "clangjobs.h" +#include "clangsupportivetranslationunitinitializer.h" #include "clangdocument.h" +#include "clangtranslationunits.h" + +#include namespace ClangBackEnd { @@ -40,12 +45,24 @@ public: ProjectParts &projects, ClangCodeModelClientInterface &client) : document(document) + , documents(documents) , jobs(documents, unsavedFiles, projects, client) - {} + , supportiveTranslationUnitInitializer(document, jobs) + { + const auto isDocumentClosedChecker = [this](const Utf8String &filePath, + const Utf8String &projectPartId) { + return !this->documents.hasDocument(filePath, projectPartId); + }; + supportiveTranslationUnitInitializer.setIsDocumentClosedChecker(isDocumentClosedChecker); + } public: Document document; + Documents &documents; Jobs jobs; + + SupportiveTranslationUnitInitializer supportiveTranslationUnitInitializer; + JobRequestCreator jobRequestCreator; }; DocumentProcessor::DocumentProcessor(const Document &document, @@ -61,6 +78,11 @@ DocumentProcessor::DocumentProcessor(const Document &document, { } +void DocumentProcessor::setJobRequestCreator(const JobRequestCreator &creator) +{ + d->supportiveTranslationUnitInitializer.setJobRequestCreator(creator); +} + void DocumentProcessor::addJob(const JobRequest &jobRequest) { d->jobs.add(jobRequest); @@ -76,6 +98,23 @@ Document DocumentProcessor::document() const return d->document; } +bool DocumentProcessor::hasSupportiveTranslationUnit() const +{ + return d->supportiveTranslationUnitInitializer.state() + != SupportiveTranslationUnitInitializer::State::NotInitialized; +} + +void DocumentProcessor::startInitializingSupportiveTranslationUnit() +{ + d->supportiveTranslationUnitInitializer.startInitializing(); +} + +bool DocumentProcessor::isSupportiveTranslationUnitInitialized() const +{ + return d->supportiveTranslationUnitInitializer.state() + == SupportiveTranslationUnitInitializer::State::Initialized; +} + QList DocumentProcessor::runningJobs() const { return d->jobs.runningJobs(); diff --git a/src/tools/clangbackend/ipcsource/clangdocumentprocessor.h b/src/tools/clangbackend/ipcsource/clangdocumentprocessor.h index 0ad452b314a..364d1fecaea 100644 --- a/src/tools/clangbackend/ipcsource/clangdocumentprocessor.h +++ b/src/tools/clangbackend/ipcsource/clangdocumentprocessor.h @@ -49,12 +49,18 @@ public: ProjectParts &projects, ClangCodeModelClientInterface &client); + void setJobRequestCreator(const JobRequestCreator &creator); + void addJob(const JobRequest &jobRequest); JobRequests process(); Document document() const; + bool hasSupportiveTranslationUnit() const; + void startInitializingSupportiveTranslationUnit(); + public: // for tests + bool isSupportiveTranslationUnitInitialized() const; QList runningJobs() const; int queueSize() const; diff --git a/src/tools/clangbackend/ipcsource/clangiasyncjob.cpp b/src/tools/clangbackend/ipcsource/clangiasyncjob.cpp index 8c433c40928..d6fc0ff659f 100644 --- a/src/tools/clangbackend/ipcsource/clangiasyncjob.cpp +++ b/src/tools/clangbackend/ipcsource/clangiasyncjob.cpp @@ -27,6 +27,8 @@ #include "clangcompletecodejob.h" #include "clangcreateinitialdocumentpreamblejob.h" +#include "clangparsesupportivetranslationunitjob.h" +#include "clangreparsesupportivetranslationunitjob.h" #include "clangrequestdocumentannotationsjob.h" #include "clangupdatedocumentannotationsjob.h" @@ -39,6 +41,10 @@ IAsyncJob *IAsyncJob::create(JobRequest::Type type) switch (type) { case JobRequest::Type::UpdateDocumentAnnotations: return new UpdateDocumentAnnotationsJob(); + case JobRequest::Type::ParseSupportiveTranslationUnit: + return new ParseSupportiveTranslationUnitJob(); + case JobRequest::Type::ReparseSupportiveTranslationUnit: + return new ReparseSupportiveTranslationUnitJob(); case JobRequest::Type::CreateInitialDocumentPreamble: return new CreateInitialDocumentPreambleJob(); case JobRequest::Type::CompleteCode: diff --git a/src/tools/clangbackend/ipcsource/clangjobrequest.cpp b/src/tools/clangbackend/ipcsource/clangjobrequest.cpp index 13cf7da26dc..73fe6f4e68b 100644 --- a/src/tools/clangbackend/ipcsource/clangjobrequest.cpp +++ b/src/tools/clangbackend/ipcsource/clangjobrequest.cpp @@ -34,6 +34,8 @@ static const char *JobRequestTypeToText(JobRequest::Type type) { switch (type) { RETURN_TEXT_FOR_CASE(UpdateDocumentAnnotations); + RETURN_TEXT_FOR_CASE(ParseSupportiveTranslationUnit); + RETURN_TEXT_FOR_CASE(ReparseSupportiveTranslationUnit); RETURN_TEXT_FOR_CASE(CreateInitialDocumentPreamble); RETURN_TEXT_FOR_CASE(CompleteCode); RETURN_TEXT_FOR_CASE(RequestDocumentAnnotations); @@ -49,6 +51,7 @@ const char *preferredTranslationUnitToText(PreferredTranslationUnit type) switch (type) { RETURN_TEXT_FOR_CASE(RecentlyParsed); RETURN_TEXT_FOR_CASE(PreviouslyParsed); + RETURN_TEXT_FOR_CASE(LastUninitialized); } return "UnhandledPreferredTranslationUnitType"; @@ -93,6 +96,8 @@ JobRequest::Requirements JobRequest::requirementsForType(Type type) |JobRequest::CurrentDocumentRevision); case JobRequest::Type::CompleteCode: case JobRequest::Type::CreateInitialDocumentPreamble: + case JobRequest::Type::ParseSupportiveTranslationUnit: + case JobRequest::Type::ReparseSupportiveTranslationUnit: return JobRequest::Requirements(JobRequest::DocumentValid); } diff --git a/src/tools/clangbackend/ipcsource/clangjobrequest.h b/src/tools/clangbackend/ipcsource/clangjobrequest.h index f9a5604979a..b744f119780 100644 --- a/src/tools/clangbackend/ipcsource/clangjobrequest.h +++ b/src/tools/clangbackend/ipcsource/clangjobrequest.h @@ -34,14 +34,22 @@ #include #include +#include + namespace ClangBackEnd { +class Document; + class JobRequest { public: enum class Type { UpdateDocumentAnnotations, CreateInitialDocumentPreamble, + + ParseSupportiveTranslationUnit, + ReparseSupportiveTranslationUnit, + CompleteCode, RequestDocumentAnnotations, }; @@ -83,6 +91,9 @@ public: }; using JobRequests = QVector; +using JobRequestCreator = std::function; QDebug operator<<(QDebug debug, const JobRequest &jobRequest); diff --git a/src/tools/clangbackend/ipcsource/clangjobs.cpp b/src/tools/clangbackend/ipcsource/clangjobs.cpp index bd5a982d014..fee7c0097a1 100644 --- a/src/tools/clangbackend/ipcsource/clangjobs.cpp +++ b/src/tools/clangbackend/ipcsource/clangjobs.cpp @@ -120,12 +120,22 @@ void Jobs::onJobFinished(IAsyncJob *asyncJob) { qCDebug(jobsLog) << "Finishing" << asyncJob->context().jobRequest; + if (m_jobFinishedCallback) { + const RunningJob runningJob = m_running.value(asyncJob); + m_jobFinishedCallback(runningJob); + } + m_running.remove(asyncJob); delete asyncJob; process(); } +void Jobs::setJobFinishedCallback(const JobFinishedCallback &jobFinishedCallback) +{ + m_jobFinishedCallback = jobFinishedCallback; +} + QList Jobs::runningJobs() const { return m_running.values(); diff --git a/src/tools/clangbackend/ipcsource/clangjobs.h b/src/tools/clangbackend/ipcsource/clangjobs.h index 21c3576a48f..1faf2e3dbdd 100644 --- a/src/tools/clangbackend/ipcsource/clangjobs.h +++ b/src/tools/clangbackend/ipcsource/clangjobs.h @@ -31,6 +31,8 @@ #include +#include + namespace ClangBackEnd { class ClangCodeModelClientInterface; @@ -46,7 +48,9 @@ public: Utf8String translationUnitId; QFuture future; }; + using RunningJobs = QHash; + using JobFinishedCallback = std::function; public: Jobs(Documents &documents, @@ -59,6 +63,8 @@ public: JobRequests process(); + void setJobFinishedCallback(const JobFinishedCallback &jobFinishedCallback); + public /*for tests*/: QList runningJobs() const; JobRequests queue() const; @@ -76,6 +82,7 @@ private: JobQueue m_queue; RunningJobs m_running; + JobFinishedCallback m_jobFinishedCallback; }; } // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangparsesupportivetranslationunitjob.cpp b/src/tools/clangbackend/ipcsource/clangparsesupportivetranslationunitjob.cpp new file mode 100644 index 00000000000..fa8937c1951 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangparsesupportivetranslationunitjob.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "clangparsesupportivetranslationunitjob.h" + +#include + +#include + +namespace ClangBackEnd { + +static ParseSupportiveTranslationUnitJob::AsyncResult runAsyncHelper( + const TranslationUnit &translationUnit, + const TranslationUnitUpdateInput &translationUnitUpdateInput) +{ + TIME_SCOPE_DURATION("ParseSupportiveTranslationUnitJob"); + + TranslationUnitUpdateInput updateInput = translationUnitUpdateInput; + updateInput.parseNeeded = true; + + ParseSupportiveTranslationUnitJob::AsyncResult asyncResult; + asyncResult.updateResult = translationUnit.update(updateInput); + + return asyncResult; +} + +IAsyncJob::AsyncPrepareResult ParseSupportiveTranslationUnitJob::prepareAsyncRun() +{ + const JobRequest jobRequest = context().jobRequest; + QTC_ASSERT(jobRequest.type == JobRequest::Type::ParseSupportiveTranslationUnit, return AsyncPrepareResult()); + + try { + m_pinnedDocument = context().documentForJobRequest(); + m_pinnedFileContainer = m_pinnedDocument.fileContainer(); + + const TranslationUnit translationUnit + = m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit); + const TranslationUnitUpdateInput updateInput = m_pinnedDocument.createUpdateInput(); + setRunner([translationUnit, updateInput]() { + return runAsyncHelper(translationUnit, updateInput); + }); + return AsyncPrepareResult{translationUnit.id()}; + + } catch (const std::exception &exception) { + qWarning() << "Error in ParseForSupportiveTranslationUnitJob::prepareAsyncRun:" + << exception.what(); + return AsyncPrepareResult(); + } +} + +void ParseSupportiveTranslationUnitJob::finalizeAsyncRun() +{ +} + +} // namespace ClangBackEnd + diff --git a/src/tools/clangbackend/ipcsource/clangparsesupportivetranslationunitjob.h b/src/tools/clangbackend/ipcsource/clangparsesupportivetranslationunitjob.h new file mode 100644 index 00000000000..6d4d01131e2 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangparsesupportivetranslationunitjob.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "clangasyncjob.h" +#include "clangdocument.h" + +namespace ClangBackEnd { + +struct ParseSupportiveTranslationUnitJobResult +{ + TranslationUnitUpdateResult updateResult; +}; + +class ParseSupportiveTranslationUnitJob : public AsyncJob +{ +public: + using AsyncResult = ParseSupportiveTranslationUnitJobResult; + + AsyncPrepareResult prepareAsyncRun() override; + void finalizeAsyncRun() override; + +private: + Document m_pinnedDocument; + FileContainer m_pinnedFileContainer; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangreparsesupportivetranslationunitjob.cpp b/src/tools/clangbackend/ipcsource/clangreparsesupportivetranslationunitjob.cpp new file mode 100644 index 00000000000..da922db8e9d --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangreparsesupportivetranslationunitjob.cpp @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "clangreparsesupportivetranslationunitjob.h" + +#include + +#include + +namespace ClangBackEnd { + +static ReparseSupportiveTranslationUnitJob::AsyncResult runAsyncHelper( + const TranslationUnit &translationUnit, + const TranslationUnitUpdateInput &translationUnitUpdateInput) +{ + TIME_SCOPE_DURATION("ReparseSupportiveTranslationUnitJob"); + + TranslationUnitUpdateInput updateInput = translationUnitUpdateInput; + updateInput.reparseNeeded = true; + + ReparseSupportiveTranslationUnitJob::AsyncResult asyncResult; + asyncResult.updateResult = translationUnit.reparse(updateInput); + + return asyncResult; +} + +IAsyncJob::AsyncPrepareResult ReparseSupportiveTranslationUnitJob::prepareAsyncRun() +{ + const JobRequest jobRequest = context().jobRequest; + QTC_ASSERT(jobRequest.type == JobRequest::Type::ReparseSupportiveTranslationUnit, return AsyncPrepareResult()); + + try { + m_pinnedDocument = context().documentForJobRequest(); + m_pinnedFileContainer = m_pinnedDocument.fileContainer(); + + const TranslationUnit translationUnit + = m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit); + const TranslationUnitUpdateInput updateInput = m_pinnedDocument.createUpdateInput(); + setRunner([translationUnit, updateInput]() { + return runAsyncHelper(translationUnit, updateInput); + }); + return AsyncPrepareResult{translationUnit.id()}; + + } catch (const std::exception &exception) { + qWarning() << "Error in ReparseSupportiveTranslationUnitJob::prepareAsyncRun:" + << exception.what(); + return AsyncPrepareResult(); + } +} + +void ReparseSupportiveTranslationUnitJob::finalizeAsyncRun() +{ + if (!context().isOutdated()) { + const AsyncResult result = asyncResult(); + m_pinnedDocument.incorporateUpdaterResult(result.updateResult); + } +} + +} // namespace ClangBackEnd + diff --git a/src/tools/clangbackend/ipcsource/clangreparsesupportivetranslationunitjob.h b/src/tools/clangbackend/ipcsource/clangreparsesupportivetranslationunitjob.h new file mode 100644 index 00000000000..1b352c2c361 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangreparsesupportivetranslationunitjob.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "clangasyncjob.h" +#include "clangdocument.h" + +namespace ClangBackEnd { + +struct ReparseSupportiveTranslationUnitJobResult +{ + TranslationUnitUpdateResult updateResult; +}; + +class ReparseSupportiveTranslationUnitJob : public AsyncJob +{ +public: + using AsyncResult = ReparseSupportiveTranslationUnitJobResult; + + AsyncPrepareResult prepareAsyncRun() override; + void finalizeAsyncRun() override; + +private: + Document m_pinnedDocument; + FileContainer m_pinnedFileContainer; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.cpp b/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.cpp index e53ff0b8007..2fd806b192b 100644 --- a/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.cpp +++ b/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.cpp @@ -57,7 +57,8 @@ IAsyncJob::AsyncPrepareResult RequestDocumentAnnotationsJob::prepareAsyncRun() m_pinnedDocument = context().documentForJobRequest(); m_pinnedFileContainer = m_pinnedDocument.fileContainer(); - const TranslationUnit translationUnit = m_pinnedDocument.translationUnit(); + const TranslationUnit translationUnit + = m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit); setRunner([translationUnit]() { return runAsyncHelper(translationUnit); }); diff --git a/src/tools/clangbackend/ipcsource/clangsupportivetranslationunitinitializer.cpp b/src/tools/clangbackend/ipcsource/clangsupportivetranslationunitinitializer.cpp new file mode 100644 index 00000000000..d421ca05cbb --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangsupportivetranslationunitinitializer.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "clangsupportivetranslationunitinitializer.h" + +#include "clangjobs.h" +#include "clangtranslationunits.h" + +#include + +namespace ClangBackEnd { + + // TODO: Check translation unit id? + +SupportiveTranslationUnitInitializer::SupportiveTranslationUnitInitializer( + const Document &document, + Jobs &jobs) + : m_document(document) + , m_jobs(jobs) +{ +} + +void SupportiveTranslationUnitInitializer::setJobRequestCreator(const JobRequestCreator &creator) +{ + m_jobRequestCreator = creator; +} + +void SupportiveTranslationUnitInitializer::setIsDocumentClosedChecker( + const IsDocumentClosedChecker &isDocumentClosedChecker) +{ + m_isDocumentClosedChecker = isDocumentClosedChecker; +} + +SupportiveTranslationUnitInitializer::State SupportiveTranslationUnitInitializer::state() const +{ + return m_state; +} + +void SupportiveTranslationUnitInitializer::startInitializing() +{ + QTC_CHECK(m_state == State::NotInitialized); + if (abortIfDocumentIsClosed()) + return; + + m_document.translationUnits().createAndAppend(); + + m_jobs.setJobFinishedCallback([this](const Jobs::RunningJob &runningJob) { + checkIfParseJobFinished(runningJob); + }); + addJob(JobRequest::Type::ParseSupportiveTranslationUnit); + m_jobs.process(); + + m_state = State::WaitingForParseJob; +} + +void SupportiveTranslationUnitInitializer::checkIfParseJobFinished(const Jobs::RunningJob &job) +{ + QTC_CHECK(m_state == State::WaitingForParseJob); + if (abortIfDocumentIsClosed()) + return; + + if (job.jobRequest.type == JobRequest::Type::ParseSupportiveTranslationUnit) { + m_jobs.setJobFinishedCallback([this](const Jobs::RunningJob &runningJob) { + checkIfReparseJobFinished(runningJob); + }); + + addJob(JobRequest::Type::ReparseSupportiveTranslationUnit); + + m_state = State::WaitingForReparseJob; + } +} + +void SupportiveTranslationUnitInitializer::checkIfReparseJobFinished(const Jobs::RunningJob &job) +{ + QTC_CHECK(m_state == State::WaitingForReparseJob); + if (abortIfDocumentIsClosed()) + return; + + if (job.jobRequest.type == JobRequest::Type::ReparseSupportiveTranslationUnit) { + if (m_document.translationUnits().areAllTranslationUnitsParsed()) { + m_jobs.setJobFinishedCallback(nullptr); + m_state = State::Initialized; + } else { + // The supportive translation unit was reparsed, but the document + // revision changed in the meanwhile, so try again. + addJob(JobRequest::Type::ReparseSupportiveTranslationUnit); + } + } +} + +bool SupportiveTranslationUnitInitializer::abortIfDocumentIsClosed() +{ + QTC_CHECK(m_isDocumentClosedChecker); + + if (m_isDocumentClosedChecker(m_document.filePath(), m_document.projectPartId())) { + m_state = State::Aborted; + return true; + } + + return false; +} + +void SupportiveTranslationUnitInitializer::addJob(JobRequest::Type jobRequestType) +{ + QTC_CHECK(m_jobRequestCreator); + + const JobRequest jobRequest = m_jobRequestCreator(m_document, + jobRequestType, + PreferredTranslationUnit::LastUninitialized); + + m_jobs.add(jobRequest); +} + +void SupportiveTranslationUnitInitializer::setState(const State &state) +{ + m_state = state; +} + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangsupportivetranslationunitinitializer.h b/src/tools/clangbackend/ipcsource/clangsupportivetranslationunitinitializer.h new file mode 100644 index 00000000000..f65cc4afd22 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangsupportivetranslationunitinitializer.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "clangdocument.h" +#include "clangjobrequest.h" +#include "clangjobs.h" + +#include + +#pragma once + +namespace ClangBackEnd { + +class SupportiveTranslationUnitInitializer +{ +public: + using IsDocumentClosedChecker = std::function; + + enum class State { + NotInitialized, + WaitingForParseJob, + WaitingForReparseJob, + Initialized, + Aborted + }; + +public: + SupportiveTranslationUnitInitializer(const Document &document, Jobs &jobs); + + void setJobRequestCreator(const JobRequestCreator &creator); + void setIsDocumentClosedChecker(const IsDocumentClosedChecker &isDocumentClosedChecker); + + State state() const; + void startInitializing(); + +public: // for tests + void setState(const State &state); + void checkIfParseJobFinished(const Jobs::RunningJob &job); + void checkIfReparseJobFinished(const Jobs::RunningJob &job); + +private: + + bool abortIfDocumentIsClosed(); + void addJob(JobRequest::Type jobRequestType); + +private: + Document m_document; + Jobs &m_jobs; + + State m_state = State::NotInitialized; + JobRequestCreator m_jobRequestCreator; + IsDocumentClosedChecker m_isDocumentClosedChecker; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunits.cpp b/src/tools/clangbackend/ipcsource/clangtranslationunits.cpp index 394d653e483..7f65a9bf247 100644 --- a/src/tools/clangbackend/ipcsource/clangtranslationunits.cpp +++ b/src/tools/clangbackend/ipcsource/clangtranslationunits.cpp @@ -70,10 +70,15 @@ TranslationUnit TranslationUnits::get(PreferredTranslationUnit type) if (m_tuDatas.isEmpty()) throw TranslationUnitDoesNotExist(m_filePath); - if (m_tuDatas.size() == 1 || !areAllTranslationUnitsParsed()) + if (m_tuDatas.size() == 1) return toTranslationUnit(m_tuDatas.first()); - return getPreferredTranslationUnit(type); + if (areAllTranslationUnitsParsed()) + return getPreferredTranslationUnit(type); + else if (type == PreferredTranslationUnit::LastUninitialized) + return toTranslationUnit(m_tuDatas.last()); + + return toTranslationUnit(m_tuDatas.first()); } void TranslationUnits::updateParseTimePoint(const Utf8String &translationUnitId, @@ -89,6 +94,11 @@ void TranslationUnits::updateParseTimePoint(const Utf8String &translationUnitId, << "PreviouslyParsed:" << get(PreferredTranslationUnit::PreviouslyParsed).id(); } +TimePoint TranslationUnits::parseTimePoint(const Utf8String &translationUnitId) +{ + return findUnit(translationUnitId).parseTimePoint; +} + bool TranslationUnits::areAllTranslationUnitsParsed() const { return Utils::allOf(m_tuDatas, [](const TranslationUnitData &unit) { @@ -96,6 +106,11 @@ bool TranslationUnits::areAllTranslationUnitsParsed() const }); } +int TranslationUnits::size() const +{ + return m_tuDatas.size(); +} + TranslationUnit TranslationUnits::getPreferredTranslationUnit(PreferredTranslationUnit type) { using TuData = TranslationUnitData; diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunits.h b/src/tools/clangbackend/ipcsource/clangtranslationunits.h index 77f771cd98a..5a9d02f0e37 100644 --- a/src/tools/clangbackend/ipcsource/clangtranslationunits.h +++ b/src/tools/clangbackend/ipcsource/clangtranslationunits.h @@ -63,8 +63,13 @@ public: TranslationUnit get(PreferredTranslationUnit type = PreferredTranslationUnit::RecentlyParsed); void updateParseTimePoint(const Utf8String &translationUnitId, TimePoint timePoint); -private: bool areAllTranslationUnitsParsed() const; + +public: // for tests + int size() const; + TimePoint parseTimePoint(const Utf8String &translationUnitId); + +private: TranslationUnit getPreferredTranslationUnit(PreferredTranslationUnit type); TranslationUnitData &findUnit(const Utf8String &translationUnitId); TranslationUnit toTranslationUnit(TranslationUnitData &unit); diff --git a/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.cpp b/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.cpp index 94fb8314bc3..4ece1fb2a0e 100644 --- a/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.cpp +++ b/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.cpp @@ -62,7 +62,8 @@ IAsyncJob::AsyncPrepareResult UpdateDocumentAnnotationsJob::prepareAsyncRun() m_pinnedDocument = context().documentForJobRequest(); m_pinnedFileContainer = m_pinnedDocument.fileContainer(); - const TranslationUnit translationUnit = m_pinnedDocument.translationUnit(); + const TranslationUnit translationUnit + = m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit); const TranslationUnitUpdateInput updateInput = m_pinnedDocument.createUpdateInput(); setRunner([translationUnit, updateInput]() { return runAsyncHelper(translationUnit, updateInput); diff --git a/tests/unit/unittest/clangipcserver-test.cpp b/tests/unit/unittest/clangipcserver-test.cpp index 3aa7ed33f38..4ce972e3ed3 100644 --- a/tests/unit/unittest/clangipcserver-test.cpp +++ b/tests/unit/unittest/clangipcserver-test.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -135,6 +136,8 @@ protected: void completeCodeInFileA(); void completeCodeInFileB(); + bool isSupportiveTranslationUnitInitialized(const Utf8String &filePath); + void expectDocumentAnnotationsChanged(int count); void expectCompletion(const CodeCompletion &completion); void expectCompletionFromFileA(); @@ -286,6 +289,50 @@ TEST_F(ClangClangCodeModelServer, SetCurrentAndVisibleEditor) ASSERT_TRUE(functionDocument.isVisibleInEditor()); } +TEST_F(ClangClangCodeModelServer, StartCompletionJobFirstOnEditThatTriggersCompletion) +{ + registerProjectAndFile(filePathA, 2); + ASSERT_TRUE(waitUntilAllJobsFinished()); + expectCompletionFromFileA(); + + updateUnsavedContent(filePathA, unsavedContent(filePathAUnsavedVersion2), 1); + completeCodeInFileA(); + + const QList jobs = clangServer.runningJobsForTestsOnly(); + ASSERT_THAT(jobs.size(), Eq(1)); + ASSERT_THAT(jobs.first().jobRequest.type, Eq(JobRequest::Type::CompleteCode)); +} + +TEST_F(ClangClangCodeModelServer, SupportiveTranslationUnitNotInitializedAfterRegister) +{ + registerProjectAndFile(filePathA, 1); + + ASSERT_TRUE(waitUntilAllJobsFinished()); + ASSERT_FALSE(isSupportiveTranslationUnitInitialized(filePathA)); +} + +TEST_F(ClangClangCodeModelServer, SupportiveTranslationUnitIsSetupAfterFirstEdit) +{ + registerProjectAndFile(filePathA, 2); + ASSERT_TRUE(waitUntilAllJobsFinished()); + + updateUnsavedContent(filePathA, unsavedContent(filePathAUnsavedVersion2), 1); + + ASSERT_TRUE(waitUntilAllJobsFinished()); + ASSERT_TRUE(isSupportiveTranslationUnitInitialized(filePathA)); +} + +TEST_F(ClangClangCodeModelServer, OpenDocumentAndEdit) +{ + registerProjectAndFile(filePathA, 4); + ASSERT_TRUE(waitUntilAllJobsFinished()); + + for (unsigned revision = 1; revision <= 3; ++revision) { + updateUnsavedContent(filePathA, unsavedContent(filePathAUnsavedVersion2), revision); + ASSERT_TRUE(waitUntilAllJobsFinished()); + } +} + TEST_F(ClangClangCodeModelServer, IsNotCurrentCurrentAndVisibleEditorAnymore) { registerProjectAndFilesAndWaitForFinished(); @@ -411,6 +458,16 @@ void ClangClangCodeModelServer::completeCodeInFileB() completeCode(filePathB, 35, 1); } +bool ClangClangCodeModelServer::isSupportiveTranslationUnitInitialized(const Utf8String &filePath) +{ + Document document = clangServer.documentsForTestOnly().document(filePath, projectPartId); + DocumentProcessor documentProcessor = clangServer.documentProcessors().processor(document); + + return document.translationUnits().size() == 2 + && documentProcessor.hasSupportiveTranslationUnit() + && documentProcessor.isSupportiveTranslationUnitInitialized(); +} + void ClangClangCodeModelServer::expectCompletion(const CodeCompletion &completion) { EXPECT_CALL(mockClangCodeModelClient, diff --git a/tests/unit/unittest/clangparsesupportivetranslationunitjobtest.cpp b/tests/unit/unittest/clangparsesupportivetranslationunitjobtest.cpp new file mode 100644 index 00000000000..4b7d5bc23bf --- /dev/null +++ b/tests/unit/unittest/clangparsesupportivetranslationunitjobtest.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "clangasyncjob-base.h" + +#include +#include + +using namespace ClangBackEnd; + +using testing::Eq; +using testing::Not; +using testing::_; + +namespace { + +class ParseSupportiveTranslationUnitJob : public ClangAsyncJobTest +{ +protected: + void SetUp() override { BaseSetUp(JobRequest::Type::ParseSupportiveTranslationUnit, job); } + + TimePoint parseTimePointOfDocument(); + +protected: + ClangBackEnd::ParseSupportiveTranslationUnitJob job; +}; + +TEST_F(ParseSupportiveTranslationUnitJob, PrepareAsyncRun) +{ + job.setContext(jobContext); + + ASSERT_TRUE(job.prepareAsyncRun()); +} + +TEST_F(ParseSupportiveTranslationUnitJob, RunAsync) +{ + job.setContext(jobContext); + job.prepareAsyncRun(); + + job.runAsync(); + + ASSERT_TRUE(waitUntilJobFinished(job)); +} + +TEST_F(ParseSupportiveTranslationUnitJob, DoNotIncorporateUpdaterResult) +{ + const TimePoint parseTimePointBefore = parseTimePointOfDocument(); + job.setContext(jobContext); + job.prepareAsyncRun(); + + job.runAsync(); + + ASSERT_TRUE(waitUntilJobFinished(job)); + ASSERT_THAT(parseTimePointOfDocument(), Eq(parseTimePointBefore)); +} + +TimePoint ParseSupportiveTranslationUnitJob::parseTimePointOfDocument() +{ + const Utf8String translationUnitId = document.translationUnit().id(); + + return document.translationUnits().parseTimePoint(translationUnitId); +} + +} // anonymous diff --git a/tests/unit/unittest/clangreparsesupportivetranslationunitjobtest.cpp b/tests/unit/unittest/clangreparsesupportivetranslationunitjobtest.cpp new file mode 100644 index 00000000000..1796b183c42 --- /dev/null +++ b/tests/unit/unittest/clangreparsesupportivetranslationunitjobtest.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "clangasyncjob-base.h" + +#include +#include + +using namespace ClangBackEnd; + +using testing::Eq; +using testing::Not; +using testing::_; + +namespace { + +class ReparseSupportiveTranslationUnitJob : public ClangAsyncJobTest +{ +protected: + void SetUp() override { BaseSetUp(JobRequest::Type::ReparseSupportiveTranslationUnit, job); } + + TimePoint parseTimePointOfDocument(); + void parse(); + +protected: + ClangBackEnd::ReparseSupportiveTranslationUnitJob job; +}; + +TEST_F(ReparseSupportiveTranslationUnitJob, PrepareAsyncRun) +{ + job.setContext(jobContext); + + ASSERT_TRUE(job.prepareAsyncRun()); +} + +TEST_F(ReparseSupportiveTranslationUnitJob, RunAsync) +{ + parse(); + job.setContext(jobContext); + job.prepareAsyncRun(); + + job.runAsync(); + + ASSERT_TRUE(waitUntilJobFinished(job)); +} + +TEST_F(ReparseSupportiveTranslationUnitJob, IncorporateUpdaterResult) +{ + parse(); + const TimePoint parseTimePointBefore = parseTimePointOfDocument(); + job.setContext(jobContext); + job.prepareAsyncRun(); + + job.runAsync(); + + ASSERT_TRUE(waitUntilJobFinished(job)); + ASSERT_THAT(parseTimePointOfDocument(), Not(Eq(parseTimePointBefore))); +} + +TEST_F(ReparseSupportiveTranslationUnitJob, DoNotIncorporateUpdaterResultIfDocumentWasClosed) +{ + parse(); + const TimePoint parseTimePointBefore = parseTimePointOfDocument(); + job.setContext(jobContext); + job.prepareAsyncRun(); + + job.runAsync(); + documents.remove({FileContainer{filePath, projectPartId}}); + + ASSERT_TRUE(waitUntilJobFinished(job)); + ASSERT_THAT(parseTimePointOfDocument(), Eq(parseTimePointBefore)); +} + +TimePoint ReparseSupportiveTranslationUnitJob::parseTimePointOfDocument() +{ + const Utf8String translationUnitId = document.translationUnit().id(); + + return document.translationUnits().parseTimePoint(translationUnitId); +} + +void ReparseSupportiveTranslationUnitJob::parse() +{ + projects.createOrUpdate({ProjectPartContainer{projectPartId, Utf8StringVector()}}); + document.parse(); +} + +} // anonymous diff --git a/tests/unit/unittest/clangsupportivetranslationunitinitializertest.cpp b/tests/unit/unittest/clangsupportivetranslationunitinitializertest.cpp new file mode 100644 index 00000000000..dcb970e4100 --- /dev/null +++ b/tests/unit/unittest/clangsupportivetranslationunitinitializertest.cpp @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "dummyclangipcclient.h" +#include "processevents-utilities.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include "gtest-qt-printing.h" + +using namespace ClangBackEnd; + +using testing::Eq; + +namespace { + +class Data { +public: + Data() + { + projects.createOrUpdate({ProjectPartContainer(projectPartId)}); + + const QVector fileContainer{FileContainer(filePath, projectPartId)}; + document = documents.create(fileContainer).front(); + documents.setVisibleInEditors({filePath}); + documents.setUsedByCurrentEditor(filePath); + + const auto isDocumentClosed = [this](const Utf8String &filePath, + const Utf8String &projectPartId) { + return !documents.hasDocument(filePath, projectPartId); + }; + const auto jobRequestCreator = [this](const Document &document, + JobRequest::Type type, + PreferredTranslationUnit preferredTranslationUnit) { + return createJobRequest(document, type, preferredTranslationUnit); + }; + initializer.reset(new ClangBackEnd::SupportiveTranslationUnitInitializer{document, jobs}); + initializer->setIsDocumentClosedChecker(isDocumentClosed); + initializer->setJobRequestCreator(jobRequestCreator); + } + + JobRequest createJobRequest(const Document &document, + JobRequest::Type type, + PreferredTranslationUnit preferredTranslationUnit) const + { + JobRequest jobRequest; + jobRequest.type = type; + jobRequest.requirements = JobRequest::requirementsForType(type); + jobRequest.filePath = document.filePath(); + jobRequest.projectPartId = document.projectPartId(); + jobRequest.unsavedFilesChangeTimePoint = unsavedFiles.lastChangeTimePoint(); + jobRequest.documentRevision = document.documentRevision(); + jobRequest.preferredTranslationUnit = preferredTranslationUnit; + const ProjectPart &projectPart = projects.project(document.projectPartId()); + jobRequest.projectChangeTimePoint = projectPart.lastChangeTimePoint(); + + return jobRequest; + } + +public: + Utf8String filePath{Utf8StringLiteral(TESTDATA_DIR"/translationunits.cpp")}; + Utf8String projectPartId{Utf8StringLiteral("/path/to/projectfile")}; + + ProjectParts projects; + UnsavedFiles unsavedFiles; + Documents documents{projects, unsavedFiles}; + Document document; + DummyIpcClient dummyClientInterface; + + Jobs jobs{documents, unsavedFiles, projects, dummyClientInterface}; + + std::unique_ptr initializer; +}; + +class SupportiveTranslationUnitInitializer : public ::testing::Test +{ +protected: + void parse(); + Jobs::RunningJob createRunningJob(JobRequest::Type type) const; + + void assertNoJobIsRunningAndEmptyQueue(); + void assertSingleJobRunningAndEmptyQueue(); + + bool waitUntilJobChainFinished(int timeOutInMs = 10000) const; + +protected: + Data d; + + Utf8String &filePath{d.filePath}; + Utf8String &projectPartId{d.projectPartId}; + + ProjectParts projects{d.projects}; + Document &document{d.document}; + Documents &documents{d.documents}; + Jobs &jobs{d.jobs}; + ClangBackEnd::SupportiveTranslationUnitInitializer &initializer{*d.initializer}; +}; + +TEST_F(SupportiveTranslationUnitInitializer, HasInitiallyNotInitializedState) +{ + ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::NotInitialized)); +} + +TEST_F(SupportiveTranslationUnitInitializer, StartInitializingAbortsIfDocumentIsClosed) +{ + documents.remove({FileContainer(filePath, projectPartId)}); + + initializer.startInitializing(); + + assertNoJobIsRunningAndEmptyQueue(); + ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Aborted)); +} + +TEST_F(SupportiveTranslationUnitInitializer, StartInitializingAddsTranslationUnit) +{ + initializer.startInitializing(); + + ASSERT_THAT(document.translationUnits().size(), Eq(2)); + ASSERT_FALSE(document.translationUnits().areAllTranslationUnitsParsed()); +} + +TEST_F(SupportiveTranslationUnitInitializer, StartInitializingStartsJob) +{ + initializer.startInitializing(); + + assertSingleJobRunningAndEmptyQueue(); + const Jobs::RunningJob runningJob = jobs.runningJobs().first(); + ASSERT_THAT(runningJob.jobRequest.type, JobRequest::Type::ParseSupportiveTranslationUnit); +} + +TEST_F(SupportiveTranslationUnitInitializer, CheckIfParseJobFinishedAbortsIfDocumentIsClosed) +{ + documents.remove({FileContainer(filePath, projectPartId)}); + initializer.setState(ClangBackEnd::SupportiveTranslationUnitInitializer::State::WaitingForParseJob); + const Jobs::RunningJob runningJob = createRunningJob(JobRequest::Type::ParseSupportiveTranslationUnit); + + initializer.checkIfParseJobFinished(runningJob); + + assertNoJobIsRunningAndEmptyQueue(); + ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Aborted)); +} + +TEST_F(SupportiveTranslationUnitInitializer, CheckIfParseJobFinishedStartsJob) +{ + parse(); + initializer.setState(ClangBackEnd::SupportiveTranslationUnitInitializer::State::WaitingForParseJob); + Jobs::RunningJob runningJob = createRunningJob(JobRequest::Type::ParseSupportiveTranslationUnit); + + initializer.checkIfParseJobFinished(runningJob); + jobs.process(); + + assertSingleJobRunningAndEmptyQueue(); + runningJob = jobs.runningJobs().first(); + ASSERT_THAT(runningJob.jobRequest.type, JobRequest::Type::ReparseSupportiveTranslationUnit); +} + +TEST_F(SupportiveTranslationUnitInitializer, CheckIfReparseJobFinishedAbortsIfDocumentIsClosed) +{ + documents.remove({FileContainer(filePath, projectPartId)}); + initializer.setState(ClangBackEnd::SupportiveTranslationUnitInitializer::State::WaitingForReparseJob); + const Jobs::RunningJob runningJob = createRunningJob(JobRequest::Type::ReparseSupportiveTranslationUnit); + + initializer.checkIfReparseJobFinished(runningJob); + + assertNoJobIsRunningAndEmptyQueue(); + ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Aborted)); +} + +TEST_F(SupportiveTranslationUnitInitializer, CheckIfReparseJobFinishedStartsJob) +{ + parse(); + initializer.setState(ClangBackEnd::SupportiveTranslationUnitInitializer::State::WaitingForReparseJob); + Jobs::RunningJob runningJob = createRunningJob(JobRequest::Type::ReparseSupportiveTranslationUnit); + + initializer.checkIfReparseJobFinished(runningJob); + jobs.process(); + + assertNoJobIsRunningAndEmptyQueue(); + ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Initialized)); +} + +TEST_F(SupportiveTranslationUnitInitializer, FullRun) +{ + parse(); + initializer.startInitializing(); + + waitUntilJobChainFinished(); + ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Initialized)); +} + +void SupportiveTranslationUnitInitializer::parse() +{ + projects.createOrUpdate({ProjectPartContainer{projectPartId, Utf8StringVector()}}); + document.parse(); +} + +Jobs::RunningJob SupportiveTranslationUnitInitializer::createRunningJob(JobRequest::Type type) const +{ + const JobRequest jobRequest = d.createJobRequest(document, + type, + PreferredTranslationUnit::LastUninitialized); + return Jobs::RunningJob{jobRequest, Utf8String(), QFuture()}; +} + +void SupportiveTranslationUnitInitializer::assertNoJobIsRunningAndEmptyQueue() +{ + ASSERT_TRUE(jobs.runningJobs().isEmpty()); + ASSERT_TRUE(jobs.queue().isEmpty()); +} + +void SupportiveTranslationUnitInitializer::assertSingleJobRunningAndEmptyQueue() +{ + ASSERT_THAT(jobs.runningJobs().size(), Eq(1)); + ASSERT_TRUE(jobs.queue().isEmpty()); +} + +bool SupportiveTranslationUnitInitializer::waitUntilJobChainFinished(int timeOutInMs) const +{ + const auto noJobsRunningAnymore = [this]() { + return jobs.runningJobs().isEmpty() && jobs.queue().isEmpty(); + }; + + return ProcessEventUtilities::processEventsUntilTrue(noJobsRunningAnymore, timeOutInMs); +} + +} // anonymous namespace diff --git a/tests/unit/unittest/clangtranslationunits-test.cpp b/tests/unit/unittest/clangtranslationunits-test.cpp index ddd386b6bf0..d89f3ba83e0 100644 --- a/tests/unit/unittest/clangtranslationunits-test.cpp +++ b/tests/unit/unittest/clangtranslationunits-test.cpp @@ -106,6 +106,17 @@ TEST_F(TranslationUnits, GetFirstForMultipleTranslationUnitsAndOnlySecondParsed) ASSERT_THAT(queried.id(), Eq(created1.id())); } +TEST_F(TranslationUnits, GetLastUnitializedForMultipleTranslationUnits) +{ + const TranslationUnit created1 = translationUnits.createAndAppend(); + translationUnits.updateParseTimePoint(created1.id(), Clock::now()); + const TranslationUnit created2 = translationUnits.createAndAppend(); + + const TranslationUnit queried = translationUnits.get(PreferredTranslationUnit::LastUninitialized); + + ASSERT_THAT(queried.id(), Eq(created2.id())); +} + TEST_F(TranslationUnits, GetRecentForMultipleTranslationUnits) { const TranslationUnit created1 = translationUnits.createAndAppend(); diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro index 168047cca55..e847b8347fa 100644 --- a/tests/unit/unittest/unittest.pro +++ b/tests/unit/unittest/unittest.pro @@ -56,7 +56,10 @@ SOURCES += \ clangisdiagnosticrelatedtolocation-test.cpp \ clangjobqueue-test.cpp \ clangjobs-test.cpp \ + clangparsesupportivetranslationunitjobtest.cpp \ + clangreparsesupportivetranslationunitjobtest.cpp \ clangrequestdocumentannotationsjob-test.cpp \ + clangsupportivetranslationunitinitializertest.cpp \ clangstring-test.cpp \ clangtranslationunits-test.cpp \ clangupdatedocumentannotationsjob-test.cpp \