diff --git a/language/backgroundparser/backgroundparser.cpp b/language/backgroundparser/backgroundparser.cpp index 27e1e9f1a6..28bdfeef56 100644 --- a/language/backgroundparser/backgroundparser.cpp +++ b/language/backgroundparser/backgroundparser.cpp @@ -1,704 +1,699 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2007 Kris Wong * Copyright 2007-2008 David Nolden * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../interfaces/ilanguagesupport.h" #include "parsejob.h" #include "parserdependencypolicy.h" #include #include #include #include #include const bool separateThreadForHighPriority = true; 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 it = m_parseJobs; while (it.hasNext()) { it.next(); - it.value()->setBackgroundParser(0); 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 jobs; for (QMap >::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::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; } kDebug(9505) << "creating parse-job" << *it << "new count of active parse-jobs:" << m_parseJobs.count() + 1; ParseJob* job = createParseJob(*it, m_documents[*it].features(), m_documents[*it].notifyWhenReady()); if(m_parseJobs.count() == m_threads+1 && !specialParseJob) specialParseJob = job; //This parse-job is allocated into the reserved thread if(job) jobs.append(job); 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); 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 KUrl& url, TopDUContext::Features features, QList > notifyWhenReady) { QList languages = m_languageController->languagesForUrl(url); foreach (ILanguage* language, languages) { if(!language) { kWarning() << "got zero language for" << url; 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->setMinimumFeatures(features); - job->setBackgroundParser(m_parser); job->setNotifyWhenReady(notifyWhenReady); - job->setTracker(m_parser->trackerForUrl(IndexedString(url))); 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" << url; else kDebug() << "could not create parse-job for url" << url; //Notify that we failed typedef QWeakPointer Notify; foreach(const Notify& n, notifyWhenReady) if(n) QMetaObject::invokeMethod(n.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, 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 specialParseJob; QTimer m_timer; int m_delay; int m_threads; bool m_shuttingDown; struct DocumentParseTarget { QWeakPointer notifyWhenReady; int priority; TopDUContext::Features features; bool operator==(const DocumentParseTarget& rhs) const { return notifyWhenReady == rhs.notifyWhenReady && priority == rhs.priority && features == rhs.features; } }; struct DocumentParsePlan { QList targets; 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 > notifyWhenReady() { QList > 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 QMap m_documents; // The documents ordered by priority QMap > m_documentsForPriority; // Currently running parse jobs QHash m_parseJobs; // A change tracker for each managed document QHash m_managed; // The url for each managed document. Those may temporarily differ from the real url. QHash m_managedTextDocumentUrls; ThreadWeaver::Weaver m_weaver; ParserDependencyPolicy m_dependencyPolicy; QMutex m_mutex; int m_maxParseJobs; int m_doneParseJobs; QMap m_jobProgress; int m_neededPriority; //The minimum priority needed for processed jobs }; 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(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit())); } 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 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) { QWeakPointer p(notifyWhenReady); QMutexLocker lock(&d->m_mutex); for(QMap::iterator it = d->m_documents.begin(); it != d->m_documents.end(); ) { d->m_documentsForPriority[it.value().priority()].remove(it.key()); int index = -1; for(int a = 0; a < (*it).targets.size(); ++a) if((*it).targets[a].notifyWhenReady.data() == notifyWhenReady) index = a; if(index != -1) { (*it).targets.removeAt(index); 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 KUrl& url, TopDUContext::Features features, int priority, QObject* notifyWhenReady) { // kDebug(9505) << "BackgroundParser::addDocument" << url.prettyUrl(); QMutexLocker lock(&d->m_mutex); { Q_ASSERT(url.isValid()); BackgroundParserPrivate::DocumentParseTarget target; target.priority = priority; target.features = features; target.notifyWhenReady = QWeakPointer(notifyWhenReady); QMap::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" << url; 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::addDocumentList(const KUrl::List &urls, TopDUContext::Features features, int priority) { foreach (const KUrl &url, urls) addDocument(url, features, priority); } void BackgroundParser::removeDocument(const KUrl &url, QObject* notifyWhenReady) { QMutexLocker lock(&d->m_mutex); Q_ASSERT(url.isValid()); if(d->m_documents.contains(url)) { d->m_documentsForPriority[d->m_documents[url].priority()].remove(url); foreach(BackgroundParserPrivate::DocumentParseTarget target, d->m_documents[url].targets) if(target.notifyWhenReady.data() == notifyWhenReady) d->m_documents[url].targets.removeAll(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() { QMutexLocker lock(&d->m_mutex); d->parseDocumentsInternal(); } void BackgroundParser::parseComplete(ThreadWeaver::Job* job) { if (ParseJob* parseJob = qobject_cast(job)) { emit parseJobFinished(parseJob); { { QMutexLocker lock(&d->m_mutex); d->m_parseJobs.remove(parseJob->document().str()); d->m_jobProgress.remove(parseJob); - parseJob->setBackgroundParser(0); - ++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); } bool BackgroundParser::isQueued(KUrl url) const { 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(); } 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(QMap::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 KUrl& document) const { QMutexLocker lock(&d->m_mutex); if (d->m_parseJobs.contains(document)) { return d->m_parseJobs[document]; } return 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 } } 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 { QMutexLocker l(&d->m_mutex); QHash< IndexedString, DocumentChangeTracker* >::iterator it = d->m_managed.find(url); if(it != d->m_managed.end()) return *it; else return 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); } } #include "backgroundparser.moc" diff --git a/language/backgroundparser/parsejob.cpp b/language/backgroundparser/parsejob.cpp index 465cd465fa..230968f38c 100644 --- a/language/backgroundparser/parsejob.cpp +++ b/language/backgroundparser/parsejob.cpp @@ -1,464 +1,417 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2006-2008 Hamish Rodda * * 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 "parsejob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "backgroundparser.h" #include "parserdependencypolicy.h" #include "duchain/topducontext.h" #include "duchain/duchainlock.h" #include "duchain/duchain.h" #include "duchain/parsingenvironment.h" #include #include #include #include #include #include using namespace KTextEditor; Q_DECLARE_METATYPE(KDevelop::IndexedString) Q_DECLARE_METATYPE(KDevelop::IndexedTopDUContext) Q_DECLARE_METATYPE(KDevelop::ReferencedTopDUContext) static QMutex minimumFeaturesMutex; static QHash > staticMinimumFeatures; namespace KDevelop { class ParseJobPrivate { public: ParseJobPrivate(const KUrl& url) : document( IndexedString(url.pathOrUrl()) ) , backgroundParser( 0 ) , abortMutex(new QMutex) , hasReadContents( false ) , abortRequested( false ) , aborted( false ) , features( TopDUContext::VisibleDeclarationsAndContexts ) { } ~ParseJobPrivate() { delete abortMutex; } ReferencedTopDUContext duContext; KDevelop::IndexedString document; QString errorMessage; BackgroundParser* backgroundParser; QMutex* abortMutex; ParseJob::Contents contents; bool hasReadContents : 1; volatile bool abortRequested : 1; bool aborted : 1; TopDUContext::Features features; QList > notify; QWeakPointer tracker; RevisionReference revision; RevisionReference previousRevision; }; ParseJob::ParseJob( const KUrl &url ) : ThreadWeaver::JobSequence(), d(new ParseJobPrivate(url)) {} -void ParseJob::setTracker ( DocumentChangeTracker* tracker ) -{ - d->tracker = tracker; -} - -DocumentChangeTracker* ParseJob::tracker() const -{ - return d->tracker.data(); -} - ParseJob::~ParseJob() { typedef QWeakPointer QObjectPointer; foreach(const QObjectPointer &p, d->notify) if(p) QMetaObject::invokeMethod(p.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, d->document), Q_ARG(KDevelop::ReferencedTopDUContext, d->duContext)); delete d; } IndexedString ParseJob::document() const { return d->document; } bool ParseJob::success() const { return !d->aborted; } void ParseJob::setMinimumFeatures(TopDUContext::Features features) { d->features = features; } bool ParseJob::hasStaticMinimumFeatures() { QMutexLocker lock(&minimumFeaturesMutex); return ::staticMinimumFeatures.size(); } TopDUContext::Features ParseJob::staticMinimumFeatures(IndexedString url) { QMutexLocker lock(&minimumFeaturesMutex); TopDUContext::Features features = (TopDUContext::Features)0; if(::staticMinimumFeatures.contains(url)) foreach(const TopDUContext::Features &f, ::staticMinimumFeatures[url]) features = (TopDUContext::Features)(features | f); return features; } TopDUContext::Features ParseJob::minimumFeatures() const { return (TopDUContext::Features)(d->features | staticMinimumFeatures(d->document)); } void ParseJob::setDuChain(ReferencedTopDUContext duChain) { d->duContext = duChain; } ReferencedTopDUContext ParseJob::duChain() const { return d->duContext; } -void ParseJob::addJob(Job* job) -{ - if (backgroundParser()) - job->assignQueuePolicy(backgroundParser()->dependencyPolicy()); - - JobSequence::addJob(job); -} - -BackgroundParser* ParseJob::backgroundParser() const -{ - return d->backgroundParser; -} - -void ParseJob::setBackgroundParser(BackgroundParser* parser) -{ - if (parser) { - assignQueuePolicy(parser->dependencyPolicy()); - - for (int i = 0; i < jobListLength(); ++i) - jobAt(i)->assignQueuePolicy(parser->dependencyPolicy()); - - } else if (d->backgroundParser) { - - removeQueuePolicy(d->backgroundParser->dependencyPolicy()); - - for (int i = 0; i < jobListLength(); ++i) - jobAt(i)->removeQueuePolicy(d->backgroundParser->dependencyPolicy()); - } - - d->backgroundParser = parser; -} - -bool ParseJob::addDependency(ParseJob* dependency, ThreadWeaver::Job* actualDependee) -{ - if (!backgroundParser()) - return false; - - return backgroundParser()->dependencyPolicy()->addDependency(dependency, this, actualDependee); -} - bool ParseJob::abortRequested() const { QMutexLocker lock(d->abortMutex); return d->abortRequested; } void ParseJob::requestAbort() { QMutexLocker lock(d->abortMutex); d->abortRequested = true; } void ParseJob::abortJob() { d->aborted = true; setFinished(true); } void ParseJob::setNotifyWhenReady(QList< QWeakPointer< QObject > > notify ) { d->notify = notify; } void ParseJob::setStaticMinimumFeatures(IndexedString url, TopDUContext::Features features) { QMutexLocker lock(&minimumFeaturesMutex); ::staticMinimumFeatures[url].append(features); } void ParseJob::unsetStaticMinimumFeatures(IndexedString url, TopDUContext::Features features) { QMutexLocker lock(&minimumFeaturesMutex); ::staticMinimumFeatures[url].removeOne(features); if(::staticMinimumFeatures[url].isEmpty()) ::staticMinimumFeatures.remove(url); } KDevelop::ProblemPointer ParseJob::readContents() { Q_ASSERT(!d->hasReadContents); d->hasReadContents = true; QString localFile(document().toUrl().toLocalFile()); QFileInfo fileInfo( localFile ); QDateTime lastModified = fileInfo.lastModified(); ForegroundLock lock; + + d->tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document()); //Try using an artificial code-representation, which overrides everything else if(artificialCodeRepresentationExists(document())) { CodeRepresentation::Ptr repr = createCodeRepresentation(document()); d->contents.contents = repr->text().toUtf8(); kDebug() << "took contents for " << document().str() << " from artificial code-representation"; return KDevelop::ProblemPointer(); } if(DocumentChangeTracker* t = d->tracker.data()) { // The file is open in an editor d->previousRevision = t->revisionAtLastReset(); t->reset(); // Reset the tracker to the current revision Q_ASSERT(t->revisionAtLastReset()); d->contents.contents = t->textAtLastReset().toUtf8(); d->contents.modification = KDevelop::ModificationRevision( lastModified, t->revisionAtLastReset()->revision() ); d->revision = t->acquireRevision(d->contents.modification.revision); }else{ // We have to load the file from disk lock.unlock(); // Unlock the foreground lock before reading from disk, so the UI won't block due to I/O QFile file( localFile ); if ( !file.open( QIODevice::ReadOnly ) ) { KDevelop::ProblemPointer p(new Problem()); p->setSource(KDevelop::ProblemData::Disk); p->setDescription(i18n( "Could not open file '%1'", localFile )); switch (file.error()) { case QFile::ReadError: p->setExplanation(i18n("File could not be read from disk.")); break; case QFile::OpenError: p->setExplanation(i18n("File could not be opened.")); break; case QFile::PermissionsError: p->setExplanation(i18n("File could not be read from disk due to permissions.")); break; default: break; } p->setFinalLocation(DocumentRange(document(), SimpleRange::invalid())); kWarning( 9007 ) << "Could not open file" << document().str() << "(path" << localFile << ")" ; return p; } d->contents.contents = file.readAll(); ///@todo Convert from local encoding to utf-8 if they don't match d->contents.modification = KDevelop::ModificationRevision(lastModified); file.close(); } + // To make the parsing more robust, we add some zeroes to the end of the buffer. d->contents.contents.push_back((char)0); d->contents.contents.push_back((char)0); d->contents.contents.push_back((char)0); d->contents.contents.push_back((char)0); return KDevelop::ProblemPointer(); } const KDevelop::ParseJob::Contents& ParseJob::contents() const { Q_ASSERT(d->hasReadContents); return d->contents; } struct MovingRangeTranslator : public DUChainVisitor { MovingRangeTranslator(qint64 _source, qint64 _target, MovingInterface* _moving) : source(_source), target(_target), moving(_moving) { } virtual void visit(DUContext* context) { translateRange(context); ///@todo Also map import-positions // Translate uses uint usesCount = context->usesCount(); for(uint u = 0; u < usesCount; ++u) { RangeInRevision r = context->uses()[u].m_range; translateRange(r); context->changeUseRange(u, r); } } virtual void visit(Declaration* declaration) { translateRange(declaration); } void translateRange(DUChainBase* object) { RangeInRevision r = object->range(); translateRange(r); object->setRange(r); } void translateRange(RangeInRevision& r) { moving->transformCursor(r.start.line, r.start.column, MovingCursor::MoveOnInsert, source, target); moving->transformCursor(r.end.line, r.end.column, MovingCursor::StayOnInsert, source, target); } KTextEditor::Range range; qint64 source; qint64 target; MovingInterface* moving; }; void ParseJob::translateDUChainToRevision(TopDUContext* context) { qint64 targetRevision = d->contents.modification.revision; if(targetRevision == -1) { kDebug() << "invalid target revision" << targetRevision; return; } qint64 sourceRevision; { DUChainReadLocker duChainLock; Q_ASSERT(context->parsingEnvironmentFile()); // Cannot map if there is no source revision sourceRevision = context->parsingEnvironmentFile()->modificationRevision().revision; if(sourceRevision == -1) { kDebug() << "invalid source revision" << sourceRevision; return; } } if(sourceRevision > targetRevision) { kDebug() << "for document" << document().str() << ": source revision is higher than target revision:" << sourceRevision << " > " << targetRevision; return; } ForegroundLock lock; if(DocumentChangeTracker* t = d->tracker.data()) { if(!d->previousRevision) { kDebug() << "not translating because there is no valid predecessor-revision"; return; } if(sourceRevision != d->previousRevision->revision() || !d->previousRevision->valid()) { kDebug() << "not translating because the document revision does not match the tracker start revision (maybe the document was cleared)"; return; } if(!t->holdingRevision(sourceRevision) || !t->holdingRevision(targetRevision)) { kDebug() << "lost one of the translation revisions, not doing the map"; return; } // Perform translation MovingInterface* moving = t->documentMovingInterface(); DUChainWriteLocker wLock; MovingRangeTranslator translator(sourceRevision, targetRevision, moving); context->visit(translator); QList< ProblemPointer > problems = context->problems(); for(QList< ProblemPointer >::iterator problem = problems.begin(); problem != problems.end(); ++problem) { RangeInRevision r = (*problem)->range(); translator.translateRange(r); (*problem)->setRange(r); } // Update the modification revision in the meta-data ModificationRevision modRev = context->parsingEnvironmentFile()->modificationRevision(); modRev.revision = targetRevision; context->parsingEnvironmentFile()->setModificationRevision(modRev); } } } #include "parsejob.moc" diff --git a/language/backgroundparser/parsejob.h b/language/backgroundparser/parsejob.h index c13afd606f..135319c2b8 100644 --- a/language/backgroundparser/parsejob.h +++ b/language/backgroundparser/parsejob.h @@ -1,174 +1,149 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2006-2008 Hamish Rodda * * 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. */ #ifndef PARSEJOB_H #define PARSEJOB_H #include #include #include #include "../duchain/indexedstring.h" -#include "documentchangetracker.h" #include #include namespace KDevelop { class BackgroundParser; class TopDUContext; class ReferencedTopDUContext; /** * The base class for background parser jobs. * * In your language plugin, don't forget to use acquire an UrlParseLock before starting to the actual parsing. */ class KDEVPLATFORMLANGUAGE_EXPORT ParseJob : public ThreadWeaver::JobSequence { Q_OBJECT public: ParseJob( const KUrl &url ); /** * _No_ mutexes/locks are allowed to be locked when this object is destroyed (except for optionally the foreground lock) * */ virtual ~ParseJob(); - Q_SCRIPTABLE BackgroundParser* backgroundParser() const; - Q_SCRIPTABLE void setBackgroundParser(BackgroundParser* parser); - struct Contents { // Modification-time of the read content ModificationRevision modification; // The contents in utf-8 format QByteArray contents; }; /** * _No_ mutexes/locks are allowed to be locked when this is called (except for optionally the foreground lock) * * Locks the document revision so that mapping from/to the revision in the editor using MovingInterface will be possible. * * Returns an invalid pointer if the call succeeds, and a valid one if the reading fails. * */ KDevelop::ProblemPointer readContents(); /** * After reading the contents, you can call this to retrieve it. * */ const Contents& contents() const; /** * Translates the given context from its previous revision to the revision that has * been retrieved during readContents(). The top-context meta-data will be updated * with the revision. * * This can be done after reading the context before updating, so * that the correct ranges are matched onto each other during the update. * * _No_ mutexes/locks are allowed to be locked when this is called (except for optionally the foreground lock) */ void translateDUChainToRevision(TopDUContext* context); - /** - * Assigns a document change tracker to this job. - * */ - void setTracker(DocumentChangeTracker* tracker); - - /** - * Returns the document change tracker for this job. May be zero if the document is not open in an editor. - * */ - DocumentChangeTracker* tracker() const; - /// \returns the indexed url of the document to be parsed. Q_SCRIPTABLE KDevelop::IndexedString document() const; /** * Sets a list of QObjects that should contain a slot * "void updateReady(KDevelop::IndexedString url, KDevelop::ReferencedTopDUContext topContext)". * The notification is guaranteed to be called once the parse-job finishes, from within its destructor. * The given top-context may be invalid if the update failed. */ Q_SCRIPTABLE void setNotifyWhenReady(QList > notify); /// Sets the du-context that was created by this parse-job Q_SCRIPTABLE virtual void setDuChain(ReferencedTopDUContext duChain); /// Returns the set du-context, or zero of none was set. Q_SCRIPTABLE virtual ReferencedTopDUContext duChain() const; /// Overridden to allow jobs to determine if they've been requested to abort Q_SCRIPTABLE virtual void requestAbort(); /// Determine if the job has been requested to abort Q_SCRIPTABLE bool abortRequested() const; /// Sets success to false, causing failed() to be emitted Q_SCRIPTABLE void abortJob(); /// Overridden to convey whether the job succeeded or not. Q_SCRIPTABLE virtual bool success() const; - /// Overridden to set the DependencyPolicy on subjobs. - Q_SCRIPTABLE virtual void addJob(Job* job); - /// Set the minimum features the resulting top-context should have Q_SCRIPTABLE void setMinimumFeatures(TopDUContext::Features features); /// Minimum set of features the resulting top-context should have Q_SCRIPTABLE TopDUContext::Features minimumFeatures() const; /// Allows statically specifying an amount of features required for an url. /// These features will automatically be or'ed with the minimumFeatures() returned /// by any ParseJob with the given url. /// Since this causes some additional complixity in update-checking, minimum features should not /// be set permanently. static void setStaticMinimumFeatures(IndexedString url, TopDUContext::Features features); /// Must be called exactly once for each call to setStaticMinimumFeatures, with the same features. static void unsetStaticMinimumFeatures(IndexedString url, TopDUContext::Features features); /// Returns the statically set minimum features for the given url, or zero. static TopDUContext::Features staticMinimumFeatures(IndexedString url); /// Returns whether there is minimum features set up for some url static bool hasStaticMinimumFeatures(); - - /** - * Attempt to add \a dependency as a dependency of \a actualDependee, which must - * be a subjob of this job, or null (in which case, the dependency is added - * to this job). If a circular dependency is detected, the dependency will - * not be added and the method will return false. - */ - Q_SCRIPTABLE bool addDependency(ParseJob* dependency, ThreadWeaver::Job* actualDependee = 0); Q_SIGNALS: /**Can be used to give progress feedback to the background-parser. @param value should be between 0 and 1, where 0 = 0% and 1 = 100% * @param text may be a text that describes the current state of parsing * Do not trigger this too often, for performance reasons. */ void progress(KDevelop::ParseJob*, float value, QString text); private: class ParseJobPrivate* const d; }; } #endif