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"