From 380d756a03346e5bbb83101f49ed6f6696c42c6a Mon Sep 17 00:00:00 2001 From: Nikolai Kosjar Date: Wed, 14 Sep 2016 16:16:10 +0200 Subject: [PATCH] Clang: Hook up supportive translation unit on first edit Parsing happens rotationally on the translation units. The recently parsed translation unit is used for completion jobs while the older version is used for parse jobs. Advantages: A1. A completion job cannot be blocked anymore by currently running parse job. A2. Faster triggering of parse jobs. A reparse was triggered about 1650ms after the last keystroke. This is down to 500ms now since we do not have a blocking translation unit for the completion anymore. Disadvantages: D1. Memory consumption is doubled for an edited document. This could be addressed by suspending the second translation unit after some time of inactivity. D2. Setup of the supportive translation unit takes some time. Change-Id: I958c883c01f274530f5482c788c15cd38d6f4c3e Reviewed-by: David Schulz --- .../clangeditordocumentprocessor.cpp | 14 +- .../clangeditordocumentprocessor.h | 4 + src/plugins/cppeditor/cppeditordocument.cpp | 2 + .../cpptools/baseeditordocumentprocessor.cpp | 4 + .../cpptools/baseeditordocumentprocessor.h | 2 + .../ipcsource/clangbackend_global.h | 1 + .../ipcsource/clangbackendclangipc-source.pri | 8 +- .../ipcsource/clangcodemodelserver.cpp | 43 ++- .../ipcsource/clangcodemodelserver.h | 8 +- .../ipcsource/clangcompletecodejob.cpp | 3 +- .../clangcreateinitialdocumentpreamblejob.cpp | 3 +- .../ipcsource/clangdocumentprocessor.cpp | 41 ++- .../ipcsource/clangdocumentprocessor.h | 6 + .../clangbackend/ipcsource/clangiasyncjob.cpp | 6 + .../ipcsource/clangjobrequest.cpp | 5 + .../clangbackend/ipcsource/clangjobrequest.h | 11 + .../clangbackend/ipcsource/clangjobs.cpp | 10 + src/tools/clangbackend/ipcsource/clangjobs.h | 7 + ...clangparsesupportivetranslationunitjob.cpp | 78 ++++++ .../clangparsesupportivetranslationunitjob.h | 51 ++++ ...angreparsesupportivetranslationunitjob.cpp | 82 ++++++ ...clangreparsesupportivetranslationunitjob.h | 51 ++++ .../clangrequestdocumentannotationsjob.cpp | 3 +- ...ngsupportivetranslationunitinitializer.cpp | 141 ++++++++++ ...langsupportivetranslationunitinitializer.h | 77 +++++ .../ipcsource/clangtranslationunits.cpp | 19 +- .../ipcsource/clangtranslationunits.h | 7 +- .../clangupdatedocumentannotationsjob.cpp | 3 +- tests/unit/unittest/clangipcserver-test.cpp | 57 ++++ ...gparsesupportivetranslationunitjobtest.cpp | 86 ++++++ ...eparsesupportivetranslationunitjobtest.cpp | 109 ++++++++ ...pportivetranslationunitinitializertest.cpp | 262 ++++++++++++++++++ .../unittest/clangtranslationunits-test.cpp | 11 + tests/unit/unittest/unittest.pro | 3 + 34 files changed, 1201 insertions(+), 17 deletions(-) create mode 100644 src/tools/clangbackend/ipcsource/clangparsesupportivetranslationunitjob.cpp create mode 100644 src/tools/clangbackend/ipcsource/clangparsesupportivetranslationunitjob.h create mode 100644 src/tools/clangbackend/ipcsource/clangreparsesupportivetranslationunitjob.cpp create mode 100644 src/tools/clangbackend/ipcsource/clangreparsesupportivetranslationunitjob.h create mode 100644 src/tools/clangbackend/ipcsource/clangsupportivetranslationunitinitializer.cpp create mode 100644 src/tools/clangbackend/ipcsource/clangsupportivetranslationunitinitializer.h create mode 100644 tests/unit/unittest/clangparsesupportivetranslationunitjobtest.cpp create mode 100644 tests/unit/unittest/clangreparsesupportivetranslationunitjobtest.cpp create mode 100644 tests/unit/unittest/clangsupportivetranslationunitinitializertest.cpp 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 \