File indexing completed on 2024-05-12 04:37:44
0001 /* 0002 SPDX-FileCopyrightText: 2009 Lior Mualem <lior.m.kde@gmail.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "documentclassesfolder.h" 0008 0009 #include "../duchain/declaration.h" 0010 #include "../duchain/duchainlock.h" 0011 #include "../duchain/duchain.h" 0012 #include "../duchain/persistentsymboltable.h" 0013 #include "../duchain/codemodel.h" 0014 0015 #include <QIcon> 0016 #include <QTimer> 0017 0018 #include <boost/foreach.hpp> 0019 0020 using namespace KDevelop; 0021 using namespace ClassModelNodes; 0022 0023 ////////////////////////////////////////////////////////////////////////////// 0024 ////////////////////////////////////////////////////////////////////////////// 0025 0026 /// Contains a static list of classes within the namespace. 0027 class ClassModelNodes::StaticNamespaceFolderNode 0028 : public Node 0029 { 0030 public: 0031 StaticNamespaceFolderNode(const KDevelop::QualifiedIdentifier& a_identifier, NodesModelInterface* a_model); 0032 0033 /// Returns the qualified identifier for this node 0034 const KDevelop::QualifiedIdentifier& qualifiedIdentifier() const { return m_identifier; } 0035 0036 public: // Node overrides 0037 bool getIcon(QIcon& a_resultIcon) override; 0038 int score() const override { return 101; } 0039 0040 private: 0041 /// The namespace identifier. 0042 KDevelop::QualifiedIdentifier m_identifier; 0043 }; 0044 0045 StaticNamespaceFolderNode::StaticNamespaceFolderNode(const KDevelop::QualifiedIdentifier& a_identifier, 0046 NodesModelInterface* a_model) 0047 : Node(a_identifier.last().toString(), a_model) 0048 , m_identifier(a_identifier) 0049 { 0050 } 0051 0052 bool StaticNamespaceFolderNode::getIcon(QIcon& a_resultIcon) 0053 { 0054 a_resultIcon = QIcon::fromTheme(QStringLiteral("namespace")); 0055 return true; 0056 } 0057 0058 ////////////////////////////////////////////////////////////////////////////// 0059 ////////////////////////////////////////////////////////////////////////////// 0060 0061 DocumentClassesFolder::OpenedFileClassItem::OpenedFileClassItem(const KDevelop::IndexedString& a_file, 0062 const KDevelop::IndexedQualifiedIdentifier& a_classIdentifier, 0063 ClassModelNodes::ClassNode* a_nodeItem) 0064 : file(a_file) 0065 , classIdentifier(a_classIdentifier) 0066 , nodeItem(a_nodeItem) 0067 { 0068 } 0069 0070 DocumentClassesFolder::DocumentClassesFolder(const QString& a_displayName, NodesModelInterface* a_model) 0071 : DynamicFolderNode(a_displayName, a_model) 0072 , m_updateTimer(new QTimer(this)) 0073 { 0074 // this is the required delay. 0075 m_updateTimer->setInterval(2000); 0076 connect(m_updateTimer, &QTimer::timeout, this, &DocumentClassesFolder::updateChangedFiles); 0077 } 0078 0079 void DocumentClassesFolder::updateChangedFiles() 0080 { 0081 bool hadChanges = false; 0082 0083 // re-parse changed documents. 0084 // TODO: m_updatedFiles seems no longer set, check again and remove if 0085 for (const IndexedString& file : qAsConst(m_updatedFiles)) { 0086 // Make sure it's one of the monitored files. 0087 if (m_openFiles.contains(file)) 0088 hadChanges |= updateDocument(file); 0089 } 0090 0091 // Processed all files. 0092 m_updatedFiles.clear(); 0093 0094 // Sort if had changes. 0095 if (hadChanges) 0096 recursiveSort(); 0097 } 0098 0099 void DocumentClassesFolder::nodeCleared() 0100 { 0101 // Clear cached namespaces list (node was cleared). 0102 m_namespaces.clear(); 0103 0104 // Clear open files and classes list 0105 m_openFiles.clear(); 0106 m_openFilesClasses.clear(); 0107 0108 // Stop the update timer. 0109 m_updateTimer->stop(); 0110 } 0111 0112 void DocumentClassesFolder::populateNode() 0113 { 0114 // Start updates timer 0115 m_updateTimer->start(); 0116 } 0117 0118 QSet<KDevelop::IndexedString> DocumentClassesFolder::allOpenDocuments() const 0119 { 0120 return m_openFiles; 0121 } 0122 0123 ClassNode* DocumentClassesFolder::findClassNode(const IndexedQualifiedIdentifier& a_id) 0124 { 0125 // Make sure that the classes node is populated, otherwise 0126 // the lookup will not work. 0127 performPopulateNode(); 0128 0129 ClassIdentifierIterator iter = m_openFilesClasses.get<ClassIdentifierIndex>().find(a_id); 0130 if (iter == m_openFilesClasses.get<ClassIdentifierIndex>().end()) 0131 return nullptr; 0132 0133 // If the node is invisible - make it visible by going over the identifiers list. 0134 if (iter->nodeItem == nullptr) { 0135 QualifiedIdentifier qualifiedIdentifier = a_id.identifier(); 0136 0137 // Ignore zero length identifiers. 0138 if (qualifiedIdentifier.count() == 0) 0139 return nullptr; 0140 0141 ClassNode* closestNode = nullptr; 0142 int closestNodeIdLen = qualifiedIdentifier.count(); 0143 0144 // First find the closest visible class node by reverse iteration over the id list. 0145 while ((closestNodeIdLen > 0) && (closestNode == nullptr)) { 0146 // Omit one from the end. 0147 --closestNodeIdLen; 0148 0149 // Find the closest class. 0150 closestNode = findClassNode(qualifiedIdentifier.mid(0, closestNodeIdLen)); 0151 } 0152 0153 if (closestNode != nullptr) { 0154 // Start iterating forward from this node by exposing each class. 0155 // By the end of this loop, closestNode should hold the actual node. 0156 while (closestNode && (closestNodeIdLen < qualifiedIdentifier.count())) { 0157 // Try the next Id. 0158 ++closestNodeIdLen; 0159 closestNode = closestNode->findSubClass(qualifiedIdentifier.mid(0, closestNodeIdLen)); 0160 } 0161 } 0162 0163 return closestNode; 0164 } 0165 0166 return iter->nodeItem; 0167 } 0168 0169 void DocumentClassesFolder::closeDocument(const IndexedString& a_file) 0170 { 0171 // Get list of nodes associated with this file and remove them. 0172 std::pair<FileIterator, FileIterator> range = m_openFilesClasses.get<FileIndex>().equal_range(a_file); 0173 if (range.first != m_openFilesClasses.get<FileIndex>().end()) { 0174 BOOST_FOREACH(const OpenedFileClassItem &item, range) 0175 { 0176 if (item.nodeItem) 0177 removeClassNode(item.nodeItem); 0178 } 0179 0180 // Clear the lists 0181 m_openFilesClasses.get<FileIndex>().erase(range.first, range.second); 0182 } 0183 0184 // Clear the file from the list of monitored documents. 0185 m_openFiles.remove(a_file); 0186 } 0187 0188 bool DocumentClassesFolder::updateDocument(const KDevelop::IndexedString& a_file) 0189 { 0190 uint codeModelItemCount = 0; 0191 const CodeModelItem* codeModelItems; 0192 CodeModel::self().items(a_file, codeModelItemCount, codeModelItems); 0193 0194 // List of declared namespaces in this file. 0195 QSet<QualifiedIdentifier> declaredNamespaces; 0196 0197 // List of removed classes - it initially contains all the known classes, we'll eliminate them 0198 // one by one later on when we encounter them in the document. 0199 QMap<IndexedQualifiedIdentifier, FileIterator> removedClasses; 0200 { 0201 std::pair<FileIterator, FileIterator> range = m_openFilesClasses.get<FileIndex>().equal_range(a_file); 0202 for (FileIterator iter = range.first; 0203 iter != range.second; 0204 ++iter) { 0205 removedClasses.insert(iter->classIdentifier, iter); 0206 } 0207 } 0208 0209 bool documentChanged = false; 0210 0211 for (uint codeModelItemIndex = 0; codeModelItemIndex < codeModelItemCount; ++codeModelItemIndex) { 0212 const CodeModelItem& item = codeModelItems[codeModelItemIndex]; 0213 0214 // Don't insert unknown or forward declarations into the class browser 0215 if (item.kind == CodeModelItem::Unknown || (item.kind & CodeModelItem::ForwardDeclaration)) 0216 continue; 0217 0218 KDevelop::QualifiedIdentifier id = item.id.identifier(); 0219 0220 // Don't add empty identifiers. 0221 if (id.count() == 0) 0222 continue; 0223 0224 // If it's a namespace, create it in the list. 0225 if (item.kind & CodeModelItem::Namespace) { 0226 // This should create the namespace folder and add it to the cache. 0227 namespaceFolder(id); 0228 0229 // Add to the locally created namespaces. 0230 declaredNamespaces.insert(id); 0231 } else if (item.kind & CodeModelItem::Class) { 0232 // Ignore empty unnamed classes. 0233 if (id.last().toString().isEmpty()) 0234 continue; 0235 0236 // See if it matches our filter? 0237 if (isClassFiltered(id)) 0238 continue; 0239 0240 // Is this a new class or an existing class? 0241 const auto classIt = removedClasses.find(id); 0242 if (classIt != removedClasses.end()) { 0243 // It already exist - remove it from the known classes and continue. 0244 removedClasses.erase(classIt); 0245 continue; 0246 } 0247 0248 // Where should we put this class? 0249 Node* parentNode = nullptr; 0250 0251 // Check if it's namespaced and add it to the proper namespace. 0252 if (id.count() > 1) { 0253 QualifiedIdentifier parentIdentifier(id.left(-1)); 0254 0255 // Look up the namespace in the cache. 0256 // If we fail to find it we assume that the parent context is a class 0257 // and in that case, when the parent class gets expanded, it will show it. 0258 NamespacesMap::iterator iter = m_namespaces.find(parentIdentifier); 0259 if (iter != m_namespaces.end()) { 0260 // Add to the namespace node. 0261 parentNode = iter.value(); 0262 } else 0263 { 0264 // Reaching here means we didn't encounter any namespace declaration in the document 0265 // But a class might still be declared under a namespace. 0266 // So we'll perform a more through search to see if it's under a namespace. 0267 0268 DUChainReadLocker readLock(DUChain::lock()); 0269 0270 PersistentSymbolTable::self().visitDeclarations( 0271 parentIdentifier, [&](const IndexedDeclaration& indexedDeclaration) { 0272 // Look for the first valid declaration. 0273 if (auto declaration = indexedDeclaration.declaration()) { 0274 // See if it should be namespaced. 0275 if (declaration->kind() == Declaration::Namespace) { 0276 // This should create the namespace folder and add it to the cache. 0277 parentNode = namespaceFolder(parentIdentifier); 0278 0279 // Add to the locally created namespaces. 0280 declaredNamespaces.insert(parentIdentifier); 0281 } 0282 0283 return PersistentSymbolTable::VisitorState::Break; 0284 } 0285 return PersistentSymbolTable::VisitorState::Continue; 0286 }); 0287 } 0288 } else 0289 { 0290 // Add to the main root. 0291 parentNode = this; 0292 } 0293 0294 ClassNode* newNode = nullptr; 0295 if (parentNode != nullptr) { 0296 // Create the new node and add it. 0297 IndexedDeclaration decl; 0298 DUChainReadLocker lock; 0299 PersistentSymbolTable::self().visitDeclarations( 0300 item.id, [&](const IndexedDeclaration& indexedDeclaration) { 0301 if (indexedDeclaration.indexedTopContext().url() == a_file) { 0302 decl = indexedDeclaration; 0303 return PersistentSymbolTable::VisitorState::Break; 0304 } 0305 return PersistentSymbolTable::VisitorState::Continue; 0306 }); 0307 0308 if (decl.isValid()) { 0309 newNode = new ClassNode(decl.declaration(), m_model); 0310 parentNode->addNode(newNode); 0311 } 0312 } 0313 0314 // Insert it to the map - newNode can be 0 - meaning the class is hidden. 0315 m_openFilesClasses.insert(OpenedFileClassItem(a_file, id, newNode)); 0316 documentChanged = true; 0317 } 0318 } 0319 0320 // Remove empty namespaces from the list. 0321 // We need this because when a file gets unloaded, we unload the declared classes in it 0322 // and if a namespace has no class in it, it'll forever exist and no one will remove it 0323 // from the children list. 0324 for (const QualifiedIdentifier& id : qAsConst(declaredNamespaces)) { 0325 removeEmptyNamespace(id); 0326 } 0327 0328 // Clear erased classes. 0329 for (const FileIterator item : qAsConst(removedClasses)) { 0330 if (item->nodeItem) 0331 removeClassNode(item->nodeItem); 0332 m_openFilesClasses.get<FileIndex>().erase(item); 0333 documentChanged = true; 0334 } 0335 0336 return documentChanged; 0337 } 0338 0339 void DocumentClassesFolder::parseDocument(const IndexedString& a_file) 0340 { 0341 // Add the document to the list of open files - this means we monitor it. 0342 if (!m_openFiles.contains(a_file)) 0343 m_openFiles.insert(a_file); 0344 0345 updateDocument(a_file); 0346 } 0347 0348 void DocumentClassesFolder::removeClassNode(ClassModelNodes::ClassNode* a_node) 0349 { 0350 // Get the parent namespace identifier. 0351 QualifiedIdentifier parentNamespaceIdentifier; 0352 if (auto namespaceParent = dynamic_cast<StaticNamespaceFolderNode*>(a_node->parent())) { 0353 parentNamespaceIdentifier = namespaceParent->qualifiedIdentifier(); 0354 } 0355 0356 // Remove the node. 0357 a_node->removeSelf(); 0358 0359 // Remove empty namespace 0360 removeEmptyNamespace(parentNamespaceIdentifier); 0361 } 0362 0363 void DocumentClassesFolder::removeEmptyNamespace(const QualifiedIdentifier& a_identifier) 0364 { 0365 // Stop condition. 0366 if (a_identifier.count() == 0) 0367 return; 0368 0369 // Look it up in the cache. 0370 NamespacesMap::iterator iter = m_namespaces.find(a_identifier); 0371 if (iter != m_namespaces.end()) { 0372 if (!(*iter)->hasChildren()) { 0373 // Remove this node and try to remove the parent node. 0374 QualifiedIdentifier parentIdentifier = (*iter)->qualifiedIdentifier().left(-1); 0375 (*iter)->removeSelf(); 0376 m_namespaces.remove(a_identifier); 0377 removeEmptyNamespace(parentIdentifier); 0378 } 0379 } 0380 } 0381 0382 StaticNamespaceFolderNode* DocumentClassesFolder::namespaceFolder(const KDevelop::QualifiedIdentifier& a_identifier) 0383 { 0384 // Stop condition. 0385 if (a_identifier.count() == 0) 0386 return nullptr; 0387 0388 // Look it up in the cache. 0389 NamespacesMap::iterator iter = m_namespaces.find(a_identifier); 0390 if (iter == m_namespaces.end()) { 0391 // It's not in the cache - create folders up to it. 0392 Node* parentNode = namespaceFolder(a_identifier.left(-1)); 0393 if (parentNode == nullptr) 0394 parentNode = this; 0395 0396 // Create the new node. 0397 auto* newNode = 0398 new StaticNamespaceFolderNode(a_identifier, m_model); 0399 parentNode->addNode(newNode); 0400 0401 // Add it to the cache. 0402 m_namespaces.insert(a_identifier, newNode); 0403 0404 // Return the result. 0405 return newNode; 0406 } else 0407 return *iter; 0408 } 0409 0410 #include "moc_documentclassesfolder.cpp"