File indexing completed on 2024-05-12 04:37:43

0001 /*
0002     SPDX-FileCopyrightText: 2009 David Nolden <david.nolden.kdevelop@art-master.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "parseprojectjob.h"
0008 
0009 #include <debug.h>
0010 
0011 #include <interfaces/icore.h>
0012 #include <interfaces/ilanguagecontroller.h>
0013 #include <interfaces/idocumentcontroller.h>
0014 #include <interfaces/iproject.h>
0015 #include <interfaces/icompletionsettings.h>
0016 
0017 #include <language/backgroundparser/backgroundparser.h>
0018 
0019 #include <KLocalizedString>
0020 
0021 #include <QCoreApplication>
0022 #include <QPointer>
0023 #include <QSet>
0024 #include <QTimer>
0025 
0026 using namespace KDevelop;
0027 
0028 class KDevelop::ParseProjectJobPrivate
0029 {
0030 public:
0031     explicit ParseProjectJobPrivate(bool forceUpdate, bool parseAllProjectSources)
0032         : forceUpdate(forceUpdate)
0033         , parseAllProjectSources{parseAllProjectSources}
0034     {
0035     }
0036 
0037     const bool forceUpdate;
0038     const bool parseAllProjectSources;
0039     int fileCountLeftToParse = 0;
0040     QSet<IndexedString> filesToParse;
0041 };
0042 
0043 bool ParseProjectJob::doKill()
0044 {
0045     qCDebug(LANGUAGE) << "stopping project parse job";
0046     ICore::self()->languageController()->backgroundParser()->revertAllRequests(this);
0047     return true;
0048 }
0049 
0050 ParseProjectJob::~ParseProjectJob() = default;
0051 
0052 ParseProjectJob::ParseProjectJob(IProject* project, bool forceUpdate, bool parseAllProjectSources)
0053     : d_ptr{new ParseProjectJobPrivate(forceUpdate, parseAllProjectSources)}
0054 {
0055     Q_D(ParseProjectJob);
0056 
0057     if (parseAllProjectSources) {
0058         d->filesToParse = project->fileSet();
0059     } else {
0060         // In case we don't want to parse the whole project, still add all currently open files that belong to the project to the background-parser
0061         const auto documents = ICore::self()->documentController()->openDocuments();
0062         const auto projectFiles = project->fileSet();
0063         for (auto* document : documents) {
0064             const auto path = IndexedString(document->url());
0065             if (projectFiles.contains(path)) {
0066                 d->filesToParse.insert(path);
0067             }
0068         }
0069     }
0070     d->fileCountLeftToParse = d->filesToParse.size();
0071 
0072     setCapabilities(Killable);
0073 
0074     setObjectName(i18np("Process 1 file in %2", "Process %1 files in %2", d->filesToParse.size(), project->name()));
0075 }
0076 
0077 void ParseProjectJob::updateReady(const IndexedString& url, const ReferencedTopDUContext& topContext)
0078 {
0079     Q_D(ParseProjectJob);
0080 
0081     Q_UNUSED(url);
0082     Q_UNUSED(topContext);
0083     --d->fileCountLeftToParse;
0084     Q_ASSERT(d->fileCountLeftToParse >= 0);
0085     if (d->fileCountLeftToParse == 0) {
0086         deleteLater();
0087     }
0088 }
0089 
0090 void ParseProjectJob::start()
0091 {
0092     Q_D(ParseProjectJob);
0093 
0094     if (d->filesToParse.isEmpty()) {
0095         deleteLater();
0096         return;
0097     }
0098 
0099     qCDebug(LANGUAGE) << "starting project parse job";
0100     // Avoid calling QCoreApplication::processEvents() directly in start() to prevent
0101     // a crash in RunController::checkState().
0102     QTimer::singleShot(0, this, &ParseProjectJob::queueFilesToParse);
0103 }
0104 
0105 void ParseProjectJob::queueFilesToParse()
0106 {
0107     Q_D(ParseProjectJob);
0108 
0109     const auto isJobKilled = [this] {
0110         if (Q_UNLIKELY(isFinished())) {
0111             qCDebug(LANGUAGE) << "Aborting queuing project files to parse."
0112                                  " This job has been killed:" << objectName();
0113             return true;
0114         }
0115         return false;
0116     };
0117 
0118     if (isJobKilled()) {
0119         return;
0120     }
0121 
0122     TopDUContext::Features processingLevel = d->filesToParse.size() <
0123                                              ICore::self()->languageController()->completionSettings()->
0124                                              minFilesForSimplifiedParsing() ?
0125                                              TopDUContext::VisibleDeclarationsAndContexts : TopDUContext::
0126                                              SimplifiedVisibleDeclarationsAndContexts;
0127     TopDUContext::Features openDocumentProcessingLevel{TopDUContext::AllDeclarationsContextsAndUses};
0128 
0129     if (d->forceUpdate) {
0130         if (processingLevel & TopDUContext::VisibleDeclarationsAndContexts) {
0131             processingLevel = TopDUContext::AllDeclarationsContextsAndUses;
0132         }
0133         processingLevel |= TopDUContext::ForceUpdate;
0134         openDocumentProcessingLevel |= TopDUContext::ForceUpdate;
0135     }
0136 
0137     if (auto currentDocument = ICore::self()->documentController()->activeDocument()) {
0138         const auto path = IndexedString(currentDocument->url());
0139         const auto fileIt = d->filesToParse.constFind(path);
0140         if (fileIt != d->filesToParse.cend()) {
0141             ICore::self()->languageController()->backgroundParser()->addDocument(path,
0142                     openDocumentProcessingLevel, BackgroundParser::BestPriority, this);
0143             d->filesToParse.erase(fileIt);
0144         }
0145     }
0146 
0147     int priority{BackgroundParser::InitialParsePriority};
0148     const int openDocumentPriority{10};
0149     if (d->parseAllProjectSources) {
0150         // Add all currently open files that belong to the project to the
0151         // background-parser, so that they'll be parsed first of all.
0152         const auto documents = ICore::self()->documentController()->openDocuments();
0153         for (auto* document : documents) {
0154             const auto path = IndexedString(document->url());
0155             const auto fileIt = d->filesToParse.constFind(path);
0156             if (fileIt != d->filesToParse.cend()) {
0157                 ICore::self()->languageController()->backgroundParser()->addDocument(path,
0158                         openDocumentProcessingLevel, openDocumentPriority, this);
0159                 d->filesToParse.erase(fileIt);
0160             }
0161         }
0162     } else {
0163         // In this case the constructor inserts only open documents into d->filesToParse.
0164         processingLevel = openDocumentProcessingLevel;
0165         priority = openDocumentPriority;
0166     }
0167 
0168     // prevent UI-lockup by processing events after some files
0169     // esp. noticeable when dealing with huge projects
0170     const int processAfter = 1000;
0171     int processed = 0;
0172     // guard against reentrancy issues, see also bug 345480
0173     auto crashGuard = QPointer<ParseProjectJob> {this};
0174     for (const IndexedString& url : qAsConst(d->filesToParse)) {
0175         ICore::self()->languageController()->backgroundParser()->addDocument(url, processingLevel,
0176                                                                              priority,
0177                                                                              this);
0178         ++processed;
0179         if (processed == processAfter) {
0180             QCoreApplication::processEvents();
0181             if (Q_UNLIKELY(!crashGuard)) {
0182                 qCDebug(LANGUAGE) << "Aborting queuing project files to parse."
0183                                      " This job has been destroyed.";
0184                 return;
0185             }
0186             if (isJobKilled()) {
0187                 return;
0188             }
0189             processed = 0;
0190         }
0191     }
0192 
0193     d->filesToParse = {}; // free memory or prevent detaching
0194 }
0195 
0196 #include "moc_parseprojectjob.cpp"