kde-extraapps/kdevplatform/language/backgroundparser/backgroundparser.cpp
2015-07-26 14:23:17 +03:00

862 lines
29 KiB
C++

/*
* This file is part of KDevelop
*
* Copyright 2006 Adam Treat <treat@kde.org>
* Copyright 2007 Kris Wong <kris.p.wong@gmail.com>
* Copyright 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "backgroundparser.h"
#include <QList>
#include <QFile>
#include <QTimer>
#include <QMutex>
#include <QWaitCondition>
#include <QMutexLocker>
#include <QThread>
#include <QtCore/QWeakPointer>
#include <kdebug.h>
#include <kglobal.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include <klocale.h>
#include <ktexteditor/document.h>
#include <threadweaver/State.h>
#include <threadweaver/ThreadWeaver.h>
#include <threadweaver/JobCollection.h>
#include <threadweaver/DebuggingAids.h>
#include <interfaces/ilanguagecontroller.h>
#include <interfaces/ilanguage.h>
#include "../interfaces/ilanguagesupport.h"
#include "parsejob.h"
#include "parserdependencypolicy.h"
#include <editor/modificationrevisionset.h>
#include <interfaces/icore.h>
#include <qcoreapplication.h>
#include <interfaces/idocumentcontroller.h>
#include <interfaces/isession.h>
#include <interfaces/iprojectcontroller.h>
const bool separateThreadForHighPriority = true;
/**
* Elides string in @p path, e.g. "VEEERY/LONG/PATH" -> ".../LONG/PATH"
* - probably much faster than QFontMetrics::elidedText()
* - we dont need a widget context
* - takes path separators into account
*
* @p width Maximum number of characters
*
* TODO: Move to kdevutil?
*/
static QString elidedPathLeft(const QString& path, int width)
{
static const QChar separator = QDir::separator();
static const QString placeholder = "...";
if (path.size() <= width) {
return path;
}
int start = (path.size() - width) + placeholder.size();
int pos = path.indexOf(separator, start);
if (pos == -1) {
pos = start; // no separator => just cut off the path at the beginning
}
Q_ASSERT(path.size() - pos >= 0 && path.size() - pos <= width);
QStringRef elidedText = path.rightRef(path.size() - pos);
QString result = placeholder;
result.append(elidedText);
return result;
}
namespace {
/**
* @return true if @p url is non-empty, valid and has a clean path, false otherwise.
*/
inline bool isValidURL(const KDevelop::IndexedString& url)
{
if (url.isEmpty()) {
return false;
}
KUrl original = url.toUrl();
if (!original.isValid()) {
return false;
}
KUrl cleaned = original;
cleaned.cleanPath();
return original == cleaned;
}
}
namespace KDevelop
{
class BackgroundParserPrivate
{
public:
BackgroundParserPrivate(BackgroundParser *parser, ILanguageController *languageController)
:m_parser(parser), m_languageController(languageController), m_shuttingDown(false), m_mutex(QMutex::Recursive)
{
parser->d = this; //Set this so we can safely call back BackgroundParser from within loadSettings()
m_timer.setSingleShot(true);
m_delay = 500;
m_threads = 1;
m_doneParseJobs = 0;
m_maxParseJobs = 0;
m_neededPriority = BackgroundParser::WorstPriority;
ThreadWeaver::setDebugLevel(true, 1);
QObject::connect(&m_timer, SIGNAL(timeout()), m_parser, SLOT(parseDocuments()));
}
void startTimerThreadSafe() {
QMetaObject::invokeMethod(m_parser, "startTimer", Qt::QueuedConnection);
}
~BackgroundParserPrivate()
{
suspend();
m_weaver.dequeue();
m_weaver.requestAbort();
m_weaver.finish();
// Release dequeued jobs
QHashIterator<IndexedString, ParseJob*> it = m_parseJobs;
while (it.hasNext()) {
it.next();
delete it.value();
}
}
// Non-mutex guarded functions, only call with m_mutex acquired.
void parseDocumentsInternal()
{
if(m_shuttingDown)
return;
// Create delayed jobs, that is, jobs for documents which have been changed
// by the user.
QList<ParseJob*> jobs;
// Before starting a new job, first wait for all higher-priority ones to finish.
// That way, parse job priorities can be used for dependency handling.
int bestRunningPriority = BackgroundParser::WorstPriority;
foreach (const ParseJob* job, m_parseJobs) {
if (job->respectsSequentialProcessing() && job->parsePriority() < bestRunningPriority) {
bestRunningPriority = job->parsePriority();
}
}
bool done = false;
for (QMap<int, QSet<IndexedString> >::Iterator it1 = m_documentsForPriority.begin();
it1 != m_documentsForPriority.end(); ++it1 )
{
if(it1.key() > m_neededPriority)
break; //The priority is not good enough to be processed right now
for(QSet<IndexedString>::Iterator it = it1.value().begin(); it != it1.value().end();) {
//Only create parse-jobs for up to thread-count * 2 documents, so we don't fill the memory unnecessarily
if(m_parseJobs.count() >= m_threads+1 || (m_parseJobs.count() >= m_threads && !separateThreadForHighPriority) )
break;
if(m_parseJobs.count() >= m_threads && it1.key() > BackgroundParser::NormalPriority && !specialParseJob)
break; //The additional parsing thread is reserved for higher priority parsing
// When a document is scheduled for parsing while it is being parsed, it will be parsed
// again once the job finished, but not now.
if (m_parseJobs.contains(*it) ) {
++it;
continue;
}
Q_ASSERT(m_documents.contains(*it));
const DocumentParsePlan& parsePlan = m_documents[*it];
// If the current job requires sequential processing, but not all jobs with a better priority have been
// completed yet, it will not be created now.
if ( parsePlan.sequentialProcessingFlags() & ParseJob::RequiresSequentialProcessing
&& parsePlan.priority() > bestRunningPriority )
{
++it;
continue;
}
kDebug(9505) << "creating parse-job" << it->toUrl() << "new count of active parse-jobs:" << m_parseJobs.count() + 1;
const QString elidedPathString = elidedPathLeft(it->toUrl().toLocalFile(), 70);
emit m_parser->showMessage(m_parser, i18n("Parsing: %1", elidedPathString));
ParseJob* job = createParseJob(*it, parsePlan.features(), parsePlan.notifyWhenReady(), parsePlan.priority());
if(m_parseJobs.count() == m_threads+1 && !specialParseJob)
specialParseJob = job; //This parse-job is allocated into the reserved thread
if(job) {
job->setSequentialProcessingFlags(parsePlan.sequentialProcessingFlags());
jobs.append(job);
// update the currently best processed priority, if the created job respects sequential processing
if ( parsePlan.sequentialProcessingFlags() & ParseJob::RespectsSequentialProcessing
&& parsePlan.priority() < bestRunningPriority)
{
bestRunningPriority = parsePlan.priority();
}
}
// Remove all mentions of this document.
foreach(const DocumentParseTarget& target, parsePlan.targets) {
if (target.priority != it1.key()) {
m_documentsForPriority[target.priority].remove(*it);
}
}
m_documents.remove(*it);
it = it1.value().erase(it);
--m_maxParseJobs; //We have added one when putting the document into m_documents
if(!m_documents.isEmpty())
{
// Only try creating one parse-job at a time, else we might iterate through thousands of files
// without finding a language-support, and block the UI for a long time.
// If there are more documents to parse, instantly re-try.
QMetaObject::invokeMethod(m_parser, "parseDocuments", Qt::QueuedConnection);
done = true;
break;
}
}
if ( done ) break;
}
// Ok, enqueueing is fine because m_parseJobs contains all of the jobs now
foreach (ParseJob* job, jobs)
m_weaver.enqueue(job);
m_parser->updateProgressBar();
//We don't hide the progress-bar in updateProgressBar, so it doesn't permanently flash when a document is reparsed again and again.
if(m_doneParseJobs == m_maxParseJobs
|| (m_neededPriority == BackgroundParser::BestPriority && m_weaver.queueLength() == 0))
{
emit m_parser->hideProgress(m_parser);
}
}
ParseJob* createParseJob(const IndexedString& url, TopDUContext::Features features, const QList<QWeakPointer<QObject> >& notifyWhenReady, int priority = 0)
{
///FIXME: use IndexedString in the other APIs as well! Esp. for createParseJob!
KUrl kUrl = url.toUrl();
QList<ILanguage*> languages = m_languageController->languagesForUrl(kUrl);
foreach (ILanguage* language, languages) {
if(!language) {
kWarning() << "got zero language for" << kUrl;
continue;
}
if(!language->languageSupport()) {
kWarning() << "language has no language support assigned:" << language->name();
continue;
}
ParseJob* job = language->languageSupport()->createParseJob(url);
if (!job) {
continue; // Language part did not produce a valid ParseJob.
}
job->setParsePriority(priority);
job->setMinimumFeatures(features);
job->setNotifyWhenReady(notifyWhenReady);
QObject::connect(job, SIGNAL(done(ThreadWeaver::Job*)),
m_parser, SLOT(parseComplete(ThreadWeaver::Job*)));
QObject::connect(job, SIGNAL(failed(ThreadWeaver::Job*)),
m_parser, SLOT(parseComplete(ThreadWeaver::Job*)));
QObject::connect(job, SIGNAL(progress(KDevelop::ParseJob*,float,QString)),
m_parser, SLOT(parseProgress(KDevelop::ParseJob*,float,QString)), Qt::QueuedConnection);
m_parseJobs.insert(url, job);
++m_maxParseJobs;
// TODO more thinking required here to support multiple parse jobs per url (where multiple language plugins want to parse)
return job;
}
if(languages.isEmpty())
kDebug() << "found no languages for url" << kUrl;
else
kDebug() << "could not create parse-job for url" << kUrl;
//Notify that we failed
typedef QWeakPointer<QObject> Notify;
foreach(const Notify& n, notifyWhenReady)
if(n)
QMetaObject::invokeMethod(n.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, url), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext()));
return 0;
}
void loadSettings()
{
///@todo re-load settings when they have been changed!
Q_ASSERT(ICore::self()->activeSession());
KConfigGroup config(ICore::self()->activeSession()->config(), "Background Parser");
// stay backwards compatible
KConfigGroup oldConfig(KGlobal::config(), "Background Parser");
#define BACKWARDS_COMPATIBLE_ENTRY(entry, default) \
config.readEntry(entry, oldConfig.readEntry(entry, default))
m_delay = BACKWARDS_COMPATIBLE_ENTRY("Delay", 500);
m_timer.setInterval(m_delay);
m_threads = 0;
m_parser->setThreadCount(BACKWARDS_COMPATIBLE_ENTRY("Number of Threads", 1));
resume();
if (BACKWARDS_COMPATIBLE_ENTRY("Enabled", true)) {
m_parser->enableProcessing();
} else {
m_parser->disableProcessing();
}
}
void suspend()
{
bool s = m_weaver.state().stateId() == ThreadWeaver::Suspended ||
m_weaver.state().stateId() == ThreadWeaver::Suspending;
if (s) { // Already suspending
return;
}
m_timer.stop();
m_weaver.suspend();
}
void resume()
{
bool s = m_weaver.state().stateId() == ThreadWeaver::Suspended ||
m_weaver.state().stateId() == ThreadWeaver::Suspending;
if (m_timer.isActive() && !s) { // Not suspending
return;
}
m_timer.start(m_delay);
m_weaver.resume();
}
BackgroundParser *m_parser;
ILanguageController* m_languageController;
//Current parse-job that is executed in the additional thread
QWeakPointer<ParseJob> specialParseJob;
QTimer m_timer;
int m_delay;
int m_threads;
bool m_shuttingDown;
struct DocumentParseTarget {
QWeakPointer<QObject> notifyWhenReady;
int priority;
TopDUContext::Features features;
ParseJob::SequentialProcessingFlags sequentialProcessingFlags;
bool operator==(const DocumentParseTarget& rhs) const {
return notifyWhenReady == rhs.notifyWhenReady && priority == rhs.priority && features == rhs.features;
}
};
struct DocumentParsePlan {
QSet<DocumentParseTarget> targets;
ParseJob::SequentialProcessingFlags sequentialProcessingFlags() const {
//Pick the strictest possible flags
ParseJob::SequentialProcessingFlags ret = ParseJob::IgnoresSequentialProcessing;
foreach(const DocumentParseTarget &target, targets) {
ret |= target.sequentialProcessingFlags;
}
return ret;
}
int priority() const {
//Pick the best priority
int ret = BackgroundParser::WorstPriority;
foreach(const DocumentParseTarget &target, targets) {
if(target.priority < ret) {
ret = target.priority;
}
}
return ret;
}
TopDUContext::Features features() const {
//Pick the best features
TopDUContext::Features ret = (TopDUContext::Features)0;
foreach(const DocumentParseTarget &target, targets) {
ret = (TopDUContext::Features) (ret | target.features);
}
return ret;
}
QList<QWeakPointer<QObject> > notifyWhenReady() const {
QList<QWeakPointer<QObject> > ret;
foreach(const DocumentParseTarget &target, targets)
if(target.notifyWhenReady)
ret << target.notifyWhenReady;
return ret;
}
};
// A list of documents that are planned to be parsed, and their priority
QHash<IndexedString, DocumentParsePlan > m_documents;
// The documents ordered by priority
QMap<int, QSet<IndexedString> > m_documentsForPriority;
// Currently running parse jobs
QHash<IndexedString, ParseJob*> m_parseJobs;
// A change tracker for each managed document
QHash<IndexedString, DocumentChangeTracker*> m_managed;
// The url for each managed document. Those may temporarily differ from the real url.
QHash<KTextEditor::Document*, IndexedString> m_managedTextDocumentUrls;
// Projects currently in progress of loading
QSet<IProject*> m_loadingProjects;
ThreadWeaver::Weaver m_weaver;
ParserDependencyPolicy m_dependencyPolicy;
QMutex m_mutex;
int m_maxParseJobs;
int m_doneParseJobs;
QHash<KDevelop::ParseJob*, float> m_jobProgress;
int m_neededPriority; //The minimum priority needed for processed jobs
};
inline uint qHash(const BackgroundParserPrivate::DocumentParseTarget& target) {
return target.features * 7 + target.priority * 13 + target.sequentialProcessingFlags * 17
+ reinterpret_cast<size_t>(target.notifyWhenReady.data());
};
BackgroundParser::BackgroundParser(ILanguageController *languageController)
: QObject(languageController), d(new BackgroundParserPrivate(this, languageController))
{
Q_ASSERT(ICore::self()->documentController());
connect(ICore::self()->documentController(), SIGNAL(documentLoaded(KDevelop::IDocument*)), this, SLOT(documentLoaded(KDevelop::IDocument*)));
connect(ICore::self()->documentController(), SIGNAL(documentUrlChanged(KDevelop::IDocument*)), this, SLOT(documentUrlChanged(KDevelop::IDocument*)));
connect(ICore::self()->documentController(), SIGNAL(documentClosed(KDevelop::IDocument*)), this, SLOT(documentClosed(KDevelop::IDocument*)));
connect(ICore::self(), SIGNAL(aboutToShutdown()), this, SLOT(aboutToQuit()));
bool connected = QObject::connect(ICore::self()->projectController(),
SIGNAL(projectAboutToBeOpened(KDevelop::IProject*)),
this, SLOT(projectAboutToBeOpened(KDevelop::IProject*)));
Q_ASSERT(connected);
connected = QObject::connect(ICore::self()->projectController(),
SIGNAL(projectOpened(KDevelop::IProject*)),
this, SLOT(projectOpened(KDevelop::IProject*)));
Q_ASSERT(connected);
connected = QObject::connect(ICore::self()->projectController(),
SIGNAL(projectOpeningAborted(KDevelop::IProject*)),
this, SLOT(projectOpeningAborted(KDevelop::IProject*)));
Q_ASSERT(connected);
Q_UNUSED(connected);
}
void BackgroundParser::aboutToQuit()
{
d->m_shuttingDown = true;
}
BackgroundParser::~BackgroundParser()
{
delete d;
}
QString BackgroundParser::statusName() const
{
return i18n("Background Parser");
}
void BackgroundParser::clear(QObject* parent)
{
QMutexLocker lock(&d->m_mutex);
QHashIterator<IndexedString, ParseJob*> it = d->m_parseJobs;
while (it.hasNext()) {
it.next();
if (it.value()->parent() == parent) {
it.value()->requestAbort();
}
}
}
void BackgroundParser::loadSettings()
{
d->loadSettings();
}
void BackgroundParser::parseProgress(KDevelop::ParseJob* job, float value, QString text)
{
Q_UNUSED(text)
d->m_jobProgress[job] = value;
updateProgressBar();
}
void BackgroundParser::revertAllRequests(QObject* notifyWhenReady)
{
QMutexLocker lock(&d->m_mutex);
for(QHash<IndexedString, BackgroundParserPrivate::DocumentParsePlan >::iterator it = d->m_documents.begin(); it != d->m_documents.end(); ) {
d->m_documentsForPriority[it.value().priority()].remove(it.key());
foreach ( const BackgroundParserPrivate::DocumentParseTarget& target, (*it).targets ) {
if ( target.notifyWhenReady.data() == notifyWhenReady ) {
(*it).targets.remove(target);
}
}
if((*it).targets.isEmpty()) {
it = d->m_documents.erase(it);
--d->m_maxParseJobs;
continue;
}
d->m_documentsForPriority[it.value().priority()].insert(it.key());
++it;
}
}
void BackgroundParser::addDocument(const IndexedString& url, TopDUContext::Features features, int priority, QObject* notifyWhenReady, ParseJob::SequentialProcessingFlags flags)
{
// kDebug(9505) << "BackgroundParser::addDocument" << url.toUrl();
Q_ASSERT(isValidURL(url));
QMutexLocker lock(&d->m_mutex);
{
BackgroundParserPrivate::DocumentParseTarget target;
target.priority = priority;
target.features = features;
target.sequentialProcessingFlags = flags;
target.notifyWhenReady = QWeakPointer<QObject>(notifyWhenReady);
QHash<IndexedString, BackgroundParserPrivate::DocumentParsePlan>::iterator it = d->m_documents.find(url);
if (it != d->m_documents.end()) {
//Update the stored plan
d->m_documentsForPriority[it.value().priority()].remove(url);
it.value().targets << target;
d->m_documentsForPriority[it.value().priority()].insert(url);
}else{
// kDebug(9505) << "BackgroundParser::addDocument: queuing" << cleanedUrl;
d->m_documents[url].targets << target;
d->m_documentsForPriority[d->m_documents[url].priority()].insert(url);
++d->m_maxParseJobs; //So the progress-bar waits for this document
}
d->startTimerThreadSafe();
}
}
void BackgroundParser::removeDocument(const IndexedString& url, QObject* notifyWhenReady)
{
Q_ASSERT(isValidURL(url));
QMutexLocker lock(&d->m_mutex);
if(d->m_documents.contains(url)) {
d->m_documentsForPriority[d->m_documents[url].priority()].remove(url);
foreach(const BackgroundParserPrivate::DocumentParseTarget& target, d->m_documents[url].targets) {
if(target.notifyWhenReady.data() == notifyWhenReady) {
d->m_documents[url].targets.remove(target);
}
}
if(d->m_documents[url].targets.isEmpty()) {
d->m_documents.remove(url);
--d->m_maxParseJobs;
}else{
//Insert with an eventually different priority
d->m_documentsForPriority[d->m_documents[url].priority()].insert(url);
}
}
}
void BackgroundParser::parseDocuments()
{
if (!d->m_loadingProjects.empty()) {
startTimer();
return;
}
QMutexLocker lock(&d->m_mutex);
d->parseDocumentsInternal();
}
void BackgroundParser::parseComplete(ThreadWeaver::Job* job)
{
if (ParseJob* parseJob = qobject_cast<ParseJob*>(job)) {
emit parseJobFinished(parseJob);
{
{
QMutexLocker lock(&d->m_mutex);
d->m_parseJobs.remove(parseJob->document());
d->m_jobProgress.remove(parseJob);
++d->m_doneParseJobs;
updateProgressBar();
}
//Unlock the mutex before deleting the parse-job, because the parse-job
//has a virtual destructor that may lock the duchain, leading to deadlocks
delete parseJob;
}
//Continue creating more parse-jobs
QMetaObject::invokeMethod(this, "parseDocuments", Qt::QueuedConnection);
}
}
void BackgroundParser::disableProcessing()
{
setNeededPriority(BestPriority);
}
void BackgroundParser::enableProcessing()
{
setNeededPriority(WorstPriority);
}
int BackgroundParser::priorityForDocument(const IndexedString& url) const
{
Q_ASSERT(isValidURL(url));
QMutexLocker lock(&d->m_mutex);
return d->m_documents[url].priority();
}
bool BackgroundParser::isQueued(const IndexedString& url) const
{
Q_ASSERT(isValidURL(url));
QMutexLocker lock(&d->m_mutex);
return d->m_documents.contains(url);
}
int BackgroundParser::queuedCount() const
{
QMutexLocker lock(&d->m_mutex);
return d->m_documents.count();
}
bool BackgroundParser::isIdle() const
{
QMutexLocker lock(&d->m_mutex);
return d->m_documents.isEmpty() && d->m_weaver.isIdle();
}
void BackgroundParser::setNeededPriority(int priority)
{
QMutexLocker lock(&d->m_mutex);
d->m_neededPriority = priority;
d->startTimerThreadSafe();
}
void BackgroundParser::suspend()
{
d->suspend();
emit hideProgress(this);
}
void BackgroundParser::resume()
{
d->resume();
updateProgressBar();
}
void BackgroundParser::updateProgressBar()
{
if (d->m_doneParseJobs >= d->m_maxParseJobs) {
if(d->m_doneParseJobs > d->m_maxParseJobs) {
kDebug() << "m_doneParseJobs larger than m_maxParseJobs:" << d->m_doneParseJobs << d->m_maxParseJobs;
}
d->m_doneParseJobs = 0;
d->m_maxParseJobs = 0;
} else {
float additionalProgress = 0;
for(QHash<KDevelop::ParseJob*, float>::const_iterator it = d->m_jobProgress.constBegin(); it != d->m_jobProgress.constEnd(); ++it)
additionalProgress += *it;
emit showProgress(this, 0, d->m_maxParseJobs*1000, (additionalProgress + d->m_doneParseJobs)*1000);
}
}
ParserDependencyPolicy* BackgroundParser::dependencyPolicy() const
{
return &d->m_dependencyPolicy;
}
ParseJob* BackgroundParser::parseJobForDocument(const IndexedString& document) const
{
Q_ASSERT(isValidURL(document));
QMutexLocker lock(&d->m_mutex);
return d->m_parseJobs.value(document, 0);
}
void BackgroundParser::setThreadCount(int threadCount)
{
if (d->m_threads != threadCount) {
d->m_threads = threadCount;
d->m_weaver.setMaximumNumberOfThreads(d->m_threads+1); //1 Additional thread for high-priority parsing
}
}
int BackgroundParser::threadCount() const
{
return d->m_threads;
}
void BackgroundParser::setDelay(int miliseconds)
{
if (d->m_delay != miliseconds) {
d->m_delay = miliseconds;
d->m_timer.setInterval(d->m_delay);
}
}
QList< IndexedString > BackgroundParser::managedDocuments()
{
QMutexLocker l(&d->m_mutex);
return d->m_managed.keys();
}
DocumentChangeTracker* BackgroundParser::trackerForUrl(const KDevelop::IndexedString& url) const
{
if (url.isEmpty()) {
// this happens e.g. when setting the final location of a problem that is not
// yet associated with a top ctx.
return 0;
}
Q_ASSERT(isValidURL(url));
QMutexLocker l(&d->m_mutex);
return d->m_managed.value(url, 0);
}
void BackgroundParser::documentClosed(IDocument* document)
{
QMutexLocker l(&d->m_mutex);
if(document->textDocument())
{
KTextEditor::Document* textDocument = document->textDocument();
if(!d->m_managedTextDocumentUrls.contains(textDocument))
return; // Probably the document had an invalid url, and thus it wasn't added to the background parser
Q_ASSERT(d->m_managedTextDocumentUrls.contains(textDocument));
IndexedString url(d->m_managedTextDocumentUrls[textDocument]);
Q_ASSERT(d->m_managed.contains(url));
kDebug() << "removing" << url.str() << "from background parser";
delete d->m_managed[url];
d->m_managedTextDocumentUrls.remove(textDocument);
d->m_managed.remove(url);
}
}
void BackgroundParser::documentLoaded( IDocument* document )
{
QMutexLocker l(&d->m_mutex);
if(document->textDocument() && document->textDocument()->url().isValid())
{
KTextEditor::Document* textDocument = document->textDocument();
IndexedString url(document->url());
// Some debugging because we had issues with this
if(d->m_managed.contains(url) && d->m_managed[url]->document() == textDocument)
{
kDebug() << "Got redundant documentLoaded from" << document->url() << textDocument;
return;
}
kDebug() << "Creating change tracker for " << document->url();
Q_ASSERT(!d->m_managed.contains(url));
Q_ASSERT(!d->m_managedTextDocumentUrls.contains(textDocument));
d->m_managedTextDocumentUrls[textDocument] = url;
d->m_managed.insert(url, new DocumentChangeTracker(textDocument));
}else{
kDebug() << "NOT creating change tracker for" << document->url();
}
}
void BackgroundParser::documentUrlChanged(IDocument* document)
{
documentClosed(document);
// Only call documentLoaded if the file wasn't renamed to a filename that is already tracked.
if(document->textDocument() && !d->m_managed.contains(IndexedString(document->textDocument()->url())))
documentLoaded(document);
}
void BackgroundParser::startTimer() {
d->m_timer.start(d->m_delay);
}
void BackgroundParser::projectAboutToBeOpened(IProject* project)
{
d->m_loadingProjects.insert(project);
}
void BackgroundParser::projectOpened(IProject* project)
{
d->m_loadingProjects.remove(project);
}
void BackgroundParser::projectOpeningAborted(IProject* project)
{
d->m_loadingProjects.remove(project);
}
}
Q_DECLARE_TYPEINFO(KDevelop::BackgroundParserPrivate::DocumentParseTarget, Q_MOVABLE_TYPE);
Q_DECLARE_TYPEINFO(KDevelop::BackgroundParserPrivate::DocumentParsePlan, Q_MOVABLE_TYPE);
#include "moc_backgroundparser.cpp"