Commit 380d756a authored by Nikolai Kosjar's avatar Nikolai Kosjar
Browse files

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's avatarDavid Schulz <david.schulz@qt.io>
parent a85d5c72
......@@ -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<void>::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());
......
......@@ -32,6 +32,7 @@
#include <cpptools/semantichighlighter.h>
#include <QFutureWatcher>
#include <QTimer>
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<ClangEditorDocumentParser> m_parser;
CppTools::ProjectPart::Ptr m_projectPart;
QFutureWatcher<void> m_parserWatcher;
QTimer m_updateTranslationUnitTimer;
unsigned m_parserRevision;
CppTools::SemanticHighlighter m_semanticHighlighter;
......
......@@ -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;
}
......
......@@ -67,6 +67,10 @@ void BaseEditorDocumentProcessor::addDiagnosticToolTipToLayout(uint, uint, QLayo
{
}
void BaseEditorDocumentProcessor::editorDocumentTimerRestarted()
{
}
void BaseEditorDocumentProcessor::runParser(QFutureInterface<void> &future,
BaseEditorDocumentParser::Ptr parser,
const WorkingCopy workingCopy)
......
......@@ -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,
......
......@@ -31,6 +31,7 @@ enum class PreferredTranslationUnit
{
RecentlyParsed,
PreviouslyParsed,
LastUninitialized,
};
} // namespace ClangBackEnd
......@@ -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 \
......@@ -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<Document> 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<Docu
{
for (const auto &document : documents) {
DocumentProcessor processor = documentProcessors().create(document);
const auto jobRequestCreator = [this](const Document &document,
JobRequest::Type jobRequestType,
PreferredTranslationUnit preferredTranslationUnit) {
return createJobRequest(document, jobRequestType, preferredTranslationUnit);
};
processor.setJobRequestCreator(jobRequestCreator);
processor.addJob(createJobRequest(document, JobRequest::Type::UpdateDocumentAnnotations));
processor.addJob(createJobRequest(document, JobRequest::Type::CreateInitialDocumentPreamble));
processor.process();
}
}
JobRequest ClangCodeModelServer::createJobRequest(const Document &document,
JobRequest::Type type) const
void ClangCodeModelServer::startInitializingSupportiveTranslationUnits(
const std::vector<Document> &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();
......
......@@ -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<Document> &documents);
void startInitializingSupportiveTranslationUnits(const std::vector<Document> &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;
......
......@@ -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;
......
......@@ -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);
......
......@@ -25,9 +25,14 @@
#include "clangdocumentprocessor.h"
#include "clangdocuments.h"
#include "clangjobs.h"
#include "clangsupportivetranslationunitinitializer.h"
#include "clangdocument.h"
#include "clangtranslationunits.h"
#include <utils/qtcassert.h>
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<Jobs::RunningJob> DocumentProcessor::runningJobs() const
{
return d->jobs.runningJobs();
......
......@@ -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<Jobs::RunningJob> runningJobs() const;
int queueSize() const;
......
......@@ -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:
......
......@@ -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);
}
......
......@@ -34,14 +34,22 @@
#include <QDebug>
#include <QVector>
#include <functional>
namespace ClangBackEnd {
class Document;
class JobRequest
{
public:
enum class Type {
UpdateDocumentAnnotations,
CreateInitialDocumentPreamble,
ParseSupportiveTranslationUnit,
ReparseSupportiveTranslationUnit,
CompleteCode,
RequestDocumentAnnotations,
};
......@@ -83,6 +91,9 @@ public:
};
using JobRequests = QVector<JobRequest>;
using JobRequestCreator = std::function<JobRequest(const Document &,
JobRequest::Type ,
PreferredTranslationUnit)>;
QDebug operator<<(QDebug debug, const JobRequest &jobRequest);
......
......@@ -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::RunningJob> Jobs::runningJobs() const
{
return m_running.values();
......
......@@ -31,6 +31,8 @@
#include <QFuture>
#include <functional>
namespace ClangBackEnd {
class ClangCodeModelClientInterface;
......@@ -46,7 +48,9 @@ public:
Utf8String translationUnitId;
QFuture<void> future;
};
using RunningJobs = QHash<IAsyncJob *, RunningJob>;
using JobFinishedCallback = std::function<void(RunningJob)>;
public:
Jobs(Documents &documents,
......@@ -59,6 +63,8 @@ public:
JobRequests process();
void setJobFinishedCallback(const JobFinishedCallback &jobFinishedCallback);
public /*for tests*/:
QList<RunningJob> runningJobs() const;
JobRequests queue() const;
......@@ -76,6 +82,7 @@ private:
JobQueue m_queue;
RunningJobs m_running;
JobFinishedCallback m_jobFinishedCallback;
};
} // namespace ClangBackEnd
/****************************************************************************
**
** 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 <clangbackendipc/clangbackendipcdebugutils.h>
#include <utils/qtcassert.h>
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
/****************************************************************************
**
** 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 publis