diff --git a/language/backgroundparser/parsejob.cpp b/language/backgroundparser/parsejob.cpp index 55b13163d..b38ae36c2 100644 --- a/language/backgroundparser/parsejob.cpp +++ b/language/backgroundparser/parsejob.cpp @@ -1,529 +1,521 @@ /* * 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 "backgroundparser.h" #include "util/debug.h" #include "duchain/topducontext.h" #include "duchain/duchainlock.h" #include "duchain/duchain.h" #include "duchain/parsingenvironment.h" #include #include #include #include #include #include #include #include using namespace KTextEditor; static QMutex minimumFeaturesMutex; static QHash > staticMinimumFeatures; namespace KDevelop { class ParseJobPrivate { public: ParseJobPrivate(const IndexedString& url_, ILanguageSupport* languageSupport_) : url( url_ ) , languageSupport( languageSupport_ ) , abortRequested( 0 ) , hasReadContents( false ) , aborted( false ) , features( TopDUContext::VisibleDeclarationsAndContexts ) , parsePriority( 0 ) , sequentialProcessingFlags( ParseJob::IgnoresSequentialProcessing ) { } ~ParseJobPrivate() { } ReferencedTopDUContext duContext; IndexedString url; ILanguageSupport* languageSupport; ParseJob::Contents contents; QAtomicInt abortRequested; bool hasReadContents : 1; bool aborted : 1; TopDUContext::Features features; QList > notify; QPointer tracker; RevisionReference revision; RevisionReference previousRevision; int parsePriority; ParseJob::SequentialProcessingFlags sequentialProcessingFlags; - ThreadWeaver::QObjectDecorator* decorator; }; ParseJob::ParseJob( const IndexedString& url, KDevelop::ILanguageSupport* languageSupport ) : ThreadWeaver::Sequence(), d(new ParseJobPrivate(url, languageSupport)) { - d->decorator = new ThreadWeaver::QObjectDecorator(this); } ParseJob::~ParseJob() { typedef QPointer QObjectPointer; foreach(const QObjectPointer &p, d->notify) { if(p) { QMetaObject::invokeMethod(p.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, d->url), Q_ARG(KDevelop::ReferencedTopDUContext, d->duContext)); } } delete d; } ILanguageSupport* ParseJob::languageSupport() const { return d->languageSupport; } void ParseJob::setParsePriority(int priority) { d->parsePriority = priority; } int ParseJob::parsePriority() const { return d->parsePriority; } bool ParseJob::requiresSequentialProcessing() const { return d->sequentialProcessingFlags & RequiresSequentialProcessing; } bool ParseJob::respectsSequentialProcessing() const { return d->sequentialProcessingFlags & RespectsSequentialProcessing; } void ParseJob::setSequentialProcessingFlags(SequentialProcessingFlags flags) { d->sequentialProcessingFlags = flags; } IndexedString ParseJob::document() const { return d->url; } 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(const 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->url)); } void ParseJob::setDuChain(ReferencedTopDUContext duChain) { d->duContext = duChain; } ReferencedTopDUContext ParseJob::duChain() const { return d->duContext; } bool ParseJob::abortRequested() const { return d->abortRequested.load(); } void ParseJob::requestAbort() { d->abortRequested = 1; } void ParseJob::abortJob() { d->aborted = true; setStatus(Status_Aborted); } void ParseJob::setNotifyWhenReady(const QList >& notify ) { d->notify = notify; } void ParseJob::setStaticMinimumFeatures(const IndexedString& url, TopDUContext::Features features) { QMutexLocker lock(&minimumFeaturesMutex); ::staticMinimumFeatures[url].append(features); } void ParseJob::unsetStaticMinimumFeatures(const 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(); 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(); qCDebug(LANGUAGE) << "took contents for " << document().str() << " from artificial code-representation"; return KDevelop::ProblemPointer(); } bool hadTracker = false; if(d->tracker) { ForegroundLock lock; 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->document()->text().toUtf8(); d->contents.modification = KDevelop::ModificationRevision( lastModified, t->revisionAtLastReset()->revision() ); d->revision = t->acquireRevision(d->contents.modification.revision); hadTracker = true; } } if (!hadTracker) { // We have to load the file from disk static const int maximumFileSize = 5 * 1024 * 1024; // 5 MB if (fileInfo.size() > maximumFileSize) { KFormat f; KDevelop::ProblemPointer p(new Problem()); p->setSource(IProblem::Disk); p->setDescription(i18nc("%1: filename", "Skipped file that is too large: '%1'", localFile )); p->setExplanation(i18nc("%1: file size, %2: limit file size", "The file is %1 and exceeds the limit of %2.", f.formatByteSize(fileInfo.size()), f.formatByteSize(maximumFileSize))); p->setFinalLocation(DocumentRange(document(), KTextEditor::Range::invalid())); qCWarning(LANGUAGE) << p->description() << p->explanation(); return p; } QFile file( localFile ); if ( !file.open( QIODevice::ReadOnly ) ) { KDevelop::ProblemPointer p(new Problem()); p->setSource(IProblem::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(), KTextEditor::Range::invalid())); qCWarning(LANGUAGE) << "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(); } 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) { } void visit(DUContext* context) override { 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); } } void visit(Declaration* declaration) override { translateRange(declaration); } void translateRange(DUChainBase* object) { RangeInRevision r = object->range(); translateRange(r); object->setRange(r); } void translateRange(RangeInRevision& r) { // PHP and python use top contexts that start at (0, 0) end at INT_MAX, so make sure that doesn't overflow // or translate the start of the top context away from (0, 0) if ( r.start.line != 0 || r.start.column != 0 ) { moving->transformCursor(r.start.line, r.start.column, MovingCursor::MoveOnInsert, source, target); } if ( r.end.line != std::numeric_limits::max() || r.end.column != std::numeric_limits::max() ) { 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) { qCDebug(LANGUAGE) << "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) { qCDebug(LANGUAGE) << "invalid source revision" << sourceRevision; return; } } if(sourceRevision > targetRevision) { qCDebug(LANGUAGE) << "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) { qCDebug(LANGUAGE) << "not translating because there is no valid predecessor-revision"; return; } if(sourceRevision != d->previousRevision->revision() || !d->previousRevision->valid()) { qCDebug(LANGUAGE) << "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)) { qCDebug(LANGUAGE) << "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); } } bool ParseJob::isUpdateRequired(const IndexedString& languageString) { if (abortRequested()) { return false; } if (minimumFeatures() & TopDUContext::ForceUpdate) { return true; } DUChainReadLocker lock; if (abortRequested()) { return false; } foreach(const ParsingEnvironmentFilePointer &file, DUChain::self()->allEnvironmentFiles(document())) { if (file->language() != languageString) { continue; } if (!file->needsUpdate(environment()) && file->featuresSatisfied(minimumFeatures())) { qCDebug(LANGUAGE) << "Already up to date" << document().str(); setDuChain(file->topContext()); lock.unlock(); highlightDUChain(); return false; } break; } return !abortRequested(); } const ParsingEnvironment* ParseJob::environment() const { return nullptr; } void ParseJob::highlightDUChain() { ENSURE_CHAIN_NOT_LOCKED if (!d->languageSupport->codeHighlighting() || !duChain() || abortRequested()) { // language doesn't support highlighting return; } if (!d->hasReadContents && !d->tracker) { d->tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document()); } if (d->tracker) { d->languageSupport->codeHighlighting()->highlightDUChain(duChain()); } } ControlFlowGraph* ParseJob::controlFlowGraph() { return nullptr; } DataAccessRepository* ParseJob::dataAccessInformation() { return nullptr; } bool ParseJob::hasTracker() const { return d->tracker; } -ThreadWeaver::QObjectDecorator* ParseJob::decorator() const -{ - return d->decorator; -} - } diff --git a/language/backgroundparser/parsejob.h b/language/backgroundparser/parsejob.h index f79b41c22..a08e6ba85 100644 --- a/language/backgroundparser/parsejob.h +++ b/language/backgroundparser/parsejob.h @@ -1,243 +1,241 @@ /* * 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 KDEVPLATFORM_PARSEJOB_H #define KDEVPLATFORM_PARSEJOB_H #include #include #include #include #include namespace ThreadWeaver { class QObjectDecorator; } namespace KDevelop { class ParsingEnvironment; class ControlFlowGraph; class DataAccessRepository; 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 QObject, public ThreadWeaver::Sequence { Q_OBJECT public: explicit ParseJob( const IndexedString &url, ILanguageSupport* languageSupport ); /** * _No_ mutexes/locks are allowed to be locked when this object is destroyed (except for optionally the foreground lock) * */ ~ParseJob() override; /** * @return the language support that created this parse job. */ ILanguageSupport* languageSupport() const; struct Contents { // Modification-time of the read content ModificationRevision modification; // The contents in utf-8 format QByteArray contents; }; enum SequentialProcessingFlag { IgnoresSequentialProcessing = 0, RequiresSequentialProcessing = 1, RespectsSequentialProcessing = 2, FullSequentialProcessing = 3 }; Q_DECLARE_FLAGS(SequentialProcessingFlags, SequentialProcessingFlag) ///Sets the priority of this parse job. This is just for the purpose of ///reading it later, and does not affect the actual behaviour in any way. void setParsePriority(int priority); ///Get the priority of this parse job. ///Other than priority(), this will give you the "KDevelop-priority" of the job, ///not the QThread one (which is always zero). int parsePriority() const; /** * _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); /** * Query whether this job is needed to be waited for when trying to process a job with a lower priority. **/ bool respectsSequentialProcessing() const; /** * Query whether this job requires all higher-priority jobs to finish before being processed itself. **/ bool requiresSequentialProcessing() const; void setSequentialProcessingFlags(SequentialProcessingFlags flags); /// \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(const 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 void requestAbort() override; /// 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 bool success() const override; /// 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(const IndexedString& url, TopDUContext::Features features); /// Must be called exactly once for each call to setStaticMinimumFeatures, with the same features. static void unsetStaticMinimumFeatures(const IndexedString& url, TopDUContext::Features features); /// Returns the statically set minimum features for the given url, or zero. static TopDUContext::Features staticMinimumFeatures(const IndexedString& url); /// Returns whether there is minimum features set up for some url static bool hasStaticMinimumFeatures(); ///Returns a structure containing information about data accesses in the parsed file. /// It's up to the caller to remove the returned instance virtual KDevelop::DataAccessRepository* dataAccessInformation(); ///Returns a control flow graph for the code in the parsed file. /// It's up to the caller to remove the returned instance virtual KDevelop::ControlFlowGraph* controlFlowGraph(); - ThreadWeaver::QObjectDecorator* decorator() const; - 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, const QString& text); protected: /** * Should return an environment for this parse job. * * This is then used to check whether existing cached data of previous parse jobs need an update. * The default implementation returns a nullptr. */ virtual const ParsingEnvironment* environment() const; /** * Checks whether there is already an up to date context available for the * current document. If so, it returns true and ensures that the document * is highlighted properly. Otherwise returns false. * * NOTE: This should be called while holding an URLParseLock for the * current document. * * @param languageString The unique string identifying your language. * This must be the same as you assign to the DUChain's environment file. * * @return True if an update is required, false if the job can return early. */ bool isUpdateRequired(const IndexedString& languageString); /** * Trigger an update to the code highlighting of the current file based * on the DUChain set in setDuChain. * * If the file for this parse job is not opened in an editor or if the language * support does not return a code highlighter, this will do nothing. * * NOTE: No DUChain lock should be held when you call this. */ void highlightDUChain(); /** * Returns whether there is a tracker for the current document. */ bool hasTracker() const; private: class ParseJobPrivate* const d; }; } Q_DECLARE_OPERATORS_FOR_FLAGS(KDevelop::ParseJob::SequentialProcessingFlags) Q_DECLARE_METATYPE(KDevelop::ParseJob*) #endif