File indexing completed on 2024-04-28 04:37:28
0001 /* 0002 SPDX-FileCopyrightText: 2010 Dmitry Risenberg <dmitry.risenberg@gmail.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "watcheddocumentset.h" 0008 0009 #include <interfaces/icore.h> 0010 #include <interfaces/idocumentcontroller.h> 0011 #include <interfaces/idocument.h> 0012 #include <interfaces/iprojectcontroller.h> 0013 #include <interfaces/iproject.h> 0014 #include <language/duchain/duchain.h> 0015 #include <language/duchain/duchainlock.h> 0016 #include <language/duchain/topducontext.h> 0017 #include <project/interfaces/iprojectfilemanager.h> 0018 #include <project/projectmodel.h> 0019 0020 #include <QFileInfo> 0021 0022 namespace KDevelop 0023 { 0024 0025 enum ActionFlag { 0026 DoUpdate = 1, 0027 DoEmit = 2 0028 }; 0029 Q_DECLARE_FLAGS(ActionFlags, ActionFlag) 0030 Q_DECLARE_OPERATORS_FOR_FLAGS(ActionFlags) 0031 0032 class WatchedDocumentSetPrivate : public QObject 0033 { 0034 Q_OBJECT 0035 0036 public: 0037 using DocumentSet = WatchedDocumentSet::DocumentSet; 0038 0039 explicit WatchedDocumentSetPrivate(WatchedDocumentSet* documentSet) 0040 : m_documentSet(documentSet) 0041 , m_showImports(false) 0042 { 0043 connect(DUChain::self(), &DUChain::updateReady, this, &WatchedDocumentSetPrivate::updateReady); 0044 } 0045 0046 inline bool showImports() const 0047 { 0048 return m_showImports; 0049 } 0050 0051 void setShowImports(bool showImports) 0052 { 0053 if (m_showImports == showImports) 0054 return; 0055 0056 DocumentSet oldImports = m_imports; 0057 0058 m_showImports = showImports; 0059 updateImports(); 0060 0061 if (m_imports != oldImports) 0062 emit m_documentSet->changed(); 0063 } 0064 0065 inline const DocumentSet& documents() const 0066 { 0067 return m_documents; 0068 } 0069 0070 inline const DocumentSet& imports() const 0071 { 0072 return m_imports; 0073 } 0074 0075 inline void doUpdate(ActionFlags flags) 0076 { 0077 if (flags.testFlag(DoUpdate)) 0078 updateImports(); 0079 0080 if (flags.testFlag(DoEmit)) 0081 emit m_documentSet->changed(); 0082 } 0083 0084 void setDocuments(const DocumentSet& docs, ActionFlags flags = {}) 0085 { 0086 m_documents = docs; 0087 doUpdate(flags); 0088 } 0089 0090 bool addDocument(const IndexedString& doc, ActionFlags flags = {}) 0091 { 0092 if (m_documents.contains(doc)) 0093 return false; 0094 0095 m_documents.insert(doc); 0096 doUpdate(flags); 0097 return true; 0098 } 0099 0100 bool delDocument(const IndexedString& doc, ActionFlags flags = {}) 0101 { 0102 const auto documentIt = m_documents.find(doc); 0103 if (documentIt == m_documents.end()) 0104 return false; 0105 0106 m_documents.erase(documentIt); 0107 doUpdate(flags); 0108 return true; 0109 } 0110 0111 void renameDocument(const IndexedString& previousUrl, const IndexedString& currentUrl, ActionFlags flags = {}) 0112 { 0113 const bool deleted = delDocument(previousUrl); 0114 const bool added = addDocument(currentUrl); 0115 if (deleted || added) { 0116 doUpdate(flags); 0117 } 0118 } 0119 0120 void updateImports() 0121 { 0122 if (!m_showImports) { 0123 if (!m_imports.isEmpty()) { 0124 m_imports.clear(); 0125 return; 0126 } 0127 return; 0128 } 0129 0130 getImportsFromDUChain(); 0131 } 0132 0133 void clear() 0134 { 0135 m_documents.clear(); 0136 m_imports.clear(); 0137 emit m_documentSet->changed(); 0138 } 0139 0140 private: 0141 void getImportsFromDU(TopDUContext* context, QSet<TopDUContext*>& visitedContexts) 0142 { 0143 if (!context || visitedContexts.contains(context)) 0144 return; 0145 0146 visitedContexts.insert(context); 0147 const auto importedParentContexts = context->importedParentContexts(); 0148 for (const DUContext::Import& ctx : importedParentContexts) { 0149 auto* topCtx = dynamic_cast<TopDUContext*>(ctx.context(nullptr)); 0150 0151 if (topCtx) 0152 getImportsFromDU(topCtx, visitedContexts); 0153 } 0154 } 0155 0156 void getImportsFromDUChain() 0157 { 0158 KDevelop::DUChainReadLocker lock; 0159 QSet<TopDUContext*> visitedContexts; 0160 0161 m_imports.clear(); 0162 for (const IndexedString& doc : qAsConst(m_documents)) { 0163 TopDUContext* ctx = DUChain::self()->chainForDocument(doc); 0164 getImportsFromDU(ctx, visitedContexts); 0165 visitedContexts.remove(ctx); 0166 } 0167 0168 for (TopDUContext* ctx : qAsConst(visitedContexts)) { 0169 m_imports.insert(ctx->url()); 0170 } 0171 } 0172 0173 void updateReady(const IndexedString& doc, const ReferencedTopDUContext&) 0174 { 0175 if (!m_showImports || !m_documents.contains(doc)) 0176 return; 0177 0178 DocumentSet oldImports = m_imports; 0179 0180 updateImports(); 0181 if (m_imports != oldImports) 0182 emit m_documentSet->changed(); 0183 } 0184 0185 WatchedDocumentSet* m_documentSet; 0186 0187 DocumentSet m_documents; 0188 DocumentSet m_imports; 0189 0190 bool m_showImports; 0191 }; 0192 0193 WatchedDocumentSet::WatchedDocumentSet(QObject* parent) 0194 : QObject(parent) 0195 , d_ptr(new WatchedDocumentSetPrivate(this)) 0196 { 0197 } 0198 0199 WatchedDocumentSet::~WatchedDocumentSet() 0200 { 0201 } 0202 0203 bool WatchedDocumentSet::showImports() const 0204 { 0205 Q_D(const WatchedDocumentSet); 0206 0207 return d->showImports(); 0208 } 0209 0210 void WatchedDocumentSet::setShowImports(bool showImports) 0211 { 0212 Q_D(WatchedDocumentSet); 0213 0214 d->setShowImports(showImports); 0215 } 0216 0217 void WatchedDocumentSet::setCurrentDocument(const IndexedString&) 0218 { 0219 } 0220 0221 WatchedDocumentSet::DocumentSet WatchedDocumentSet::get() const 0222 { 0223 Q_D(const WatchedDocumentSet); 0224 0225 return d->documents(); 0226 } 0227 0228 WatchedDocumentSet::DocumentSet WatchedDocumentSet::imports() const 0229 { 0230 Q_D(const WatchedDocumentSet); 0231 0232 return d->imports(); 0233 } 0234 0235 CurrentDocumentSet::CurrentDocumentSet(const IndexedString& document, QObject* parent) 0236 : WatchedDocumentSet(parent) 0237 { 0238 Q_D(WatchedDocumentSet); 0239 0240 d->setDocuments({document}, DoUpdate); 0241 } 0242 0243 void CurrentDocumentSet::setCurrentDocument(const IndexedString& url) 0244 { 0245 Q_D(WatchedDocumentSet); 0246 0247 d->setDocuments({url}, DoUpdate | DoEmit); 0248 } 0249 0250 ProblemScope CurrentDocumentSet::scope() const 0251 { 0252 return CurrentDocument; 0253 } 0254 0255 OpenDocumentSet::OpenDocumentSet(QObject* parent) 0256 : WatchedDocumentSet(parent) 0257 { 0258 Q_D(WatchedDocumentSet); 0259 0260 const auto documents = ICore::self()->documentController()->openDocuments(); 0261 for (IDocument* doc : documents) { 0262 d->addDocument(IndexedString(doc->url())); 0263 } 0264 d->updateImports(); 0265 0266 connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &OpenDocumentSet::documentClosed); 0267 connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &OpenDocumentSet::documentCreated); 0268 connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, 0269 &OpenDocumentSet::documentUrlChanged); 0270 } 0271 0272 void OpenDocumentSet::documentClosed(IDocument* doc) 0273 { 0274 Q_D(WatchedDocumentSet); 0275 0276 d->delDocument(IndexedString(doc->url()), DoUpdate | DoEmit); 0277 } 0278 0279 void OpenDocumentSet::documentCreated(IDocument* doc) 0280 { 0281 Q_D(WatchedDocumentSet); 0282 0283 d->addDocument(IndexedString(doc->url()), DoUpdate | DoEmit); 0284 } 0285 0286 void OpenDocumentSet::documentUrlChanged(IDocument* doc, const QUrl& previousUrl) 0287 { 0288 Q_D(WatchedDocumentSet); 0289 0290 if (doc->textDocument()) { 0291 d->renameDocument(IndexedString{previousUrl}, IndexedString{doc->url()}, DoUpdate | DoEmit); 0292 } 0293 } 0294 0295 ProblemScope OpenDocumentSet::scope() const 0296 { 0297 return OpenDocuments; 0298 } 0299 0300 ProjectSet::ProjectSet(QObject* parent) 0301 : WatchedDocumentSet(parent) 0302 { 0303 // Multiple projects can share a project file manager, e.g. CMakeManager. When a project P that 0304 // is being opened uses an already tracked project file manager, fileAdded() is invoked for each file 0305 // that belongs to P. If P contains thousands of files with many problems, KDevelop UI freezes, 0306 // because the ProblemReporterModel model is reset on each document set change. 0307 // Suspend adding files while a project is being opened. Derived classes connect to the signal 0308 // IProjectController::projectOpened and add all of the newly opened project's files at once, if needed. 0309 const auto* const projectController = ICore::self()->projectController(); 0310 connect(projectController, &IProjectController::projectAboutToBeOpened, this, [this] { 0311 m_pauseAddingFiles = true; 0312 }); 0313 const auto projectOpeningOver = [this] { 0314 m_pauseAddingFiles = false; 0315 }; 0316 connect(projectController, &IProjectController::projectOpened, this, projectOpeningOver); 0317 connect(projectController, &IProjectController::projectOpeningAborted, this, projectOpeningOver); 0318 } 0319 0320 void ProjectSet::fileAdded(ProjectFileItem* file) 0321 { 0322 Q_D(WatchedDocumentSet); 0323 0324 if (m_pauseAddingFiles) { 0325 return; 0326 } 0327 0328 const auto path = IndexedString(file->indexedPath()); 0329 if (include(path)) { 0330 d->addDocument(path, DoUpdate | DoEmit); 0331 } 0332 } 0333 0334 void ProjectSet::fileRemoved(ProjectFileItem* file) 0335 { 0336 Q_D(WatchedDocumentSet); 0337 0338 d->delDocument(IndexedString(file->indexedPath()), DoUpdate | DoEmit); 0339 } 0340 0341 void ProjectSet::fileRenamed(const Path& oldFile, ProjectFileItem* newFile) 0342 { 0343 Q_D(WatchedDocumentSet); 0344 0345 d->delDocument(IndexedString(oldFile.pathOrUrl())); 0346 fileAdded(newFile); 0347 } 0348 0349 static const QObject* qobjectProjectFileManager(const IProjectFileManager* projectFileManager) 0350 { 0351 // The implementation should derive from QObject somehow 0352 return dynamic_cast<const QObject*>(projectFileManager); 0353 } 0354 0355 void ProjectSet::trackProjectFiles(const IProjectFileManager* projectFileManager) 0356 { 0357 auto* const fileManager = qobjectProjectFileManager(projectFileManager); 0358 if (!fileManager) { 0359 return; 0360 } 0361 // can't use new signal/slot syntax here, IProjectFileManager is no a QObject 0362 connect(fileManager, SIGNAL(fileAdded(KDevelop::ProjectFileItem*)), this, 0363 SLOT(fileAdded(KDevelop::ProjectFileItem*))); 0364 connect(fileManager, SIGNAL(fileRemoved(KDevelop::ProjectFileItem*)), this, 0365 SLOT(fileRemoved(KDevelop::ProjectFileItem*))); 0366 connect(fileManager, SIGNAL(fileRenamed(KDevelop::Path, KDevelop::ProjectFileItem*)), this, 0367 SLOT(fileRenamed(KDevelop::Path, KDevelop::ProjectFileItem*))); 0368 } 0369 0370 void ProjectSet::stopTrackingProjectFiles(const IProjectFileManager* projectFileManager) 0371 { 0372 auto* const fileManager = qobjectProjectFileManager(projectFileManager); 0373 if (!fileManager) { 0374 return; 0375 } 0376 fileManager->disconnect(this); 0377 } 0378 0379 CurrentProjectSet::CurrentProjectSet(const IndexedString& document, QObject* parent) 0380 : ProjectSet(parent) 0381 , m_currentDocumentUrl(document.toUrl()) 0382 { 0383 handleCurrentDocumentChange(); 0384 0385 // Opening or closing a project can change m_currentDocument's associated project. 0386 const auto* const projectController = ICore::self()->projectController(); 0387 connect(projectController, &IProjectController::projectOpened, this, 0388 &CurrentProjectSet::handleCurrentDocumentChange); 0389 connect(projectController, &IProjectController::projectClosed, this, 0390 &CurrentProjectSet::handleCurrentDocumentChange); 0391 } 0392 0393 void CurrentProjectSet::setCurrentDocument(const IndexedString& url) 0394 { 0395 m_currentDocumentUrl = url.toUrl(); 0396 handleCurrentDocumentChange(); 0397 } 0398 0399 void CurrentProjectSet::handleCurrentDocumentChange() 0400 { 0401 Q_D(WatchedDocumentSet); 0402 0403 const auto* const projectForUrl = ICore::self()->projectController()->findProjectForUrl(m_currentDocumentUrl); 0404 if (projectForUrl == m_currentProject) { 0405 return; 0406 } 0407 0408 if (m_currentProject) { 0409 stopTrackingProjectFiles(m_currentProject->projectFileManager()); 0410 } 0411 m_currentProject = projectForUrl; 0412 0413 if (m_currentProject) { 0414 d->setDocuments(m_currentProject->fileSet(), DoUpdate | DoEmit); 0415 trackProjectFiles(m_currentProject->projectFileManager()); 0416 } else { 0417 d->clear(); 0418 } 0419 } 0420 0421 bool CurrentProjectSet::include(const IndexedString& url) const 0422 { 0423 return m_currentProject && m_currentProject->fileSet().contains(url); 0424 } 0425 0426 ProblemScope CurrentProjectSet::scope() const 0427 { 0428 return CurrentProject; 0429 } 0430 0431 AllProjectSet::AllProjectSet(QObject* parent) 0432 : AllProjectSet(InitFlag::LoadOnInit, parent) 0433 { 0434 } 0435 0436 AllProjectSet::AllProjectSet(InitFlag initFlag, QObject* parent) 0437 : ProjectSet(parent) 0438 { 0439 switch (initFlag) { 0440 case InitFlag::LoadOnInit: 0441 reload(); 0442 break; 0443 case InitFlag::SkipLoadOnInit: 0444 break; 0445 } 0446 0447 const auto* const projectController = ICore::self()->projectController(); 0448 connect(projectController, &IProjectController::projectOpened, this, &AllProjectSet::projectOpened); 0449 // When a project is closed, simply reloading can be more efficient than removing the closed project's 0450 // files, especially if this was the only open project. Furthermore, a single file can belong to 0451 // multiple open projects. In this corner case removing the closed project's files is incorrect. 0452 connect(projectController, &IProjectController::projectClosed, this, &AllProjectSet::reload); 0453 } 0454 0455 void AllProjectSet::reload() 0456 { 0457 Q_D(WatchedDocumentSet); 0458 0459 d->clear(); 0460 0461 QSet<const IProjectFileManager*> openProjectFileManagers; 0462 0463 const auto projects = ICore::self()->projectController()->projects(); 0464 for (const IProject* project : projects) { 0465 addProjectFiles(*project); 0466 openProjectFileManagers.insert(project->projectFileManager()); 0467 } 0468 0469 if (m_trackedProjectFileManagers != openProjectFileManagers) { 0470 const auto removedFileManagers = m_trackedProjectFileManagers - openProjectFileManagers; 0471 for (auto* f : removedFileManagers) { 0472 stopTrackingProjectFiles(f); 0473 } 0474 0475 const auto addedFileManagers = openProjectFileManagers - m_trackedProjectFileManagers; 0476 for (auto* f : addedFileManagers) { 0477 trackProjectFiles(f); 0478 } 0479 0480 m_trackedProjectFileManagers = openProjectFileManagers; 0481 } 0482 0483 d->updateImports(); 0484 emit changed(); 0485 } 0486 0487 void AllProjectSet::projectOpened(const IProject* project) 0488 { 0489 Q_D(WatchedDocumentSet); 0490 0491 addProjectFiles(*project); 0492 0493 const auto* const projectFileManager = project->projectFileManager(); 0494 if (!m_trackedProjectFileManagers.contains(projectFileManager)) { 0495 trackProjectFiles(projectFileManager); 0496 m_trackedProjectFileManagers.insert(projectFileManager); 0497 } 0498 0499 d->updateImports(); 0500 emit changed(); 0501 } 0502 0503 void AllProjectSet::addProjectFiles(const IProject& project) 0504 { 0505 Q_D(WatchedDocumentSet); 0506 0507 const auto fileSet = project.fileSet(); 0508 for (const IndexedString& document : fileSet) { 0509 if (include(document)) { 0510 d->addDocument(document); 0511 } 0512 } 0513 } 0514 0515 ProblemScope AllProjectSet::scope() const 0516 { 0517 return AllProjects; 0518 } 0519 0520 DocumentsInPathSet::DocumentsInPathSet(const QString& path, QObject* parent) 0521 : AllProjectSet(InitFlag::SkipLoadOnInit, parent) 0522 , m_path(path) 0523 { 0524 reload(); 0525 } 0526 0527 ProblemScope DocumentsInPathSet::scope() const 0528 { 0529 return DocumentsInPath; 0530 } 0531 0532 void DocumentsInPathSet::setPath(const QString& path) 0533 { 0534 if (m_path == path) { 0535 return; 0536 } 0537 0538 m_path = path; 0539 reload(); 0540 } 0541 0542 bool DocumentsInPathSet::include(const IndexedString& path) const 0543 { 0544 if (m_path.isEmpty()) { 0545 return true; 0546 } 0547 0548 return path.str().contains(m_path, Qt::CaseInsensitive); 0549 } 0550 0551 DocumentsInCurrentPathSet::DocumentsInCurrentPathSet(const IndexedString& document, QObject* parent) 0552 : DocumentsInPathSet(QFileInfo(document.str()).path(), parent) 0553 { 0554 } 0555 0556 ProblemScope DocumentsInCurrentPathSet::scope() const 0557 { 0558 return DocumentsInCurrentPath; 0559 } 0560 0561 void DocumentsInCurrentPathSet::setCurrentDocument(const IndexedString& document) 0562 { 0563 setPath(QFileInfo(document.str()).path()); 0564 } 0565 0566 BypassSet::BypassSet(QObject* parent) 0567 : WatchedDocumentSet(parent) 0568 { 0569 } 0570 0571 ProblemScope BypassSet::scope() const 0572 { 0573 return BypassScopeFilter; 0574 } 0575 0576 } 0577 0578 #include "watcheddocumentset.moc" 0579 #include "moc_watcheddocumentset.cpp"