File indexing completed on 2024-05-05 16:46:03

0001 /*
0002     SPDX-FileCopyrightText: 2010, 2015 Alex Richardson <alex.richardson@gmx.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "outlinemodel.h"
0008 
0009 #include <language/duchain/duchain.h>
0010 #include <language/duchain/duchainlock.h>
0011 #include <language/duchain/duchainutils.h>
0012 #include <language/duchain/topducontext.h>
0013 #include <language/duchain/ducontext.h>
0014 #include <language/duchain/declaration.h>
0015 #include <interfaces/icore.h>
0016 #include <interfaces/idocument.h>
0017 #include <interfaces/idocumentcontroller.h>
0018 
0019 #include <debug.h>
0020 #include "outlinenode.h"
0021 
0022 using namespace KDevelop;
0023 
0024 OutlineModel::OutlineModel(QObject* parent)
0025     : QAbstractItemModel(parent)
0026     , m_lastDoc(nullptr)
0027 {
0028     auto docController = ICore::self()->documentController();
0029     // build the initial outline now
0030     rebuildOutline(docController->activeDocument());
0031     // we must always have a valid root node
0032     Q_ASSERT(m_rootNode);
0033 
0034     // we want to rebuild the outline whenever the current document has been reparsed
0035     connect(DUChain::self(), &DUChain::updateReady,
0036             this, [this] (const IndexedString& document, const ReferencedTopDUContext& /*topContext*/) {
0037                 if (document == m_lastUrl) {
0038                     rebuildOutline(m_lastDoc);
0039                 }
0040             });
0041     // and also when we switch the current document
0042     connect(docController, &IDocumentController::documentActivated,
0043             this, &OutlineModel::rebuildOutline);
0044     connect(docController, &IDocumentController::documentClosed,
0045             this, [this](IDocument* doc) {
0046         if (doc == m_lastDoc) {
0047             m_lastDoc = nullptr;
0048             m_lastUrl = IndexedString();
0049             rebuildOutline(nullptr);
0050         }
0051     });
0052     connect(docController, &IDocumentController::documentUrlChanged,
0053             this, [this](IDocument* doc) {
0054         if (doc == m_lastDoc) {
0055             m_lastUrl = IndexedString(doc->url());
0056         }
0057     });
0058 
0059 }
0060 
0061 OutlineModel::~OutlineModel()
0062 {
0063 }
0064 
0065 Qt::ItemFlags OutlineModel::flags(const QModelIndex& index) const
0066 {
0067     if (!index.isValid()) {
0068         return Qt::NoItemFlags;
0069     } else {
0070         return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
0071     }
0072 }
0073 
0074 int OutlineModel::columnCount(const QModelIndex& parent) const
0075 {
0076     Q_UNUSED(parent)
0077     return 1;
0078 }
0079 
0080 QVariant OutlineModel::data(const QModelIndex& index, int role) const
0081 {
0082     if (!index.isValid()) {
0083         return QVariant();
0084     }
0085     if (index.column() != 0) {
0086         return QVariant();
0087     }
0088 
0089     auto* node = static_cast<OutlineNode*>(index.internalPointer());
0090     Q_ASSERT(node);
0091     if (role == Qt::DecorationRole) {
0092         return node->icon();
0093     }
0094     if (role == Qt::DisplayRole) {
0095         return node->text();
0096     }
0097     return QVariant();
0098 }
0099 
0100 bool OutlineModel::hasChildren(const QModelIndex& parent) const
0101 {
0102     return rowCount(parent) > 0;
0103 }
0104 
0105 int OutlineModel::rowCount(const QModelIndex& parent) const
0106 {
0107     if (!parent.isValid()) {
0108         Q_ASSERT(m_rootNode);
0109         return m_rootNode->childCount();
0110     } else if (parent.column() != 0) {
0111         return 0;
0112     } else {
0113         const auto* node = static_cast<const OutlineNode*>(parent.internalPointer());
0114         return node->childCount();
0115     }
0116 }
0117 
0118 QModelIndex OutlineModel::index(int row, int column, const QModelIndex &parent) const
0119 {
0120     if (!hasIndex(row, column, parent)) {
0121         return QModelIndex();
0122     }
0123     if (!parent.isValid()) {
0124         // topLevelItem
0125         if (row < m_rootNode->childCount()) {
0126             return createIndex(row, column, const_cast<OutlineNode*>(m_rootNode->childAt(row)));
0127         }
0128         return QModelIndex();
0129     } else {
0130         if (parent.column() != 0) {
0131             return QModelIndex(); //only column 0 should have children
0132         }
0133         auto* node = static_cast<OutlineNode*>(parent.internalPointer());
0134         if (row < node->childCount()) {
0135             return createIndex(row, column, const_cast<OutlineNode*>(node->childAt(row)));
0136         }
0137         return QModelIndex(); // out of range
0138     }
0139     return QModelIndex();
0140 }
0141 
0142 QModelIndex OutlineModel::parent(const QModelIndex& index) const
0143 {
0144     if (!index.isValid()) {
0145         return QModelIndex();
0146     }
0147 
0148     const auto* node = static_cast<const OutlineNode*>(index.internalPointer());
0149 
0150     const OutlineNode* parentNode = node->parent();
0151     Q_ASSERT(parentNode);
0152     if (parentNode == m_rootNode.get()) {
0153         return QModelIndex(); //node is a top level item
0154     }
0155 
0156     // parent node was not m_rootNode -> parent() must be valid
0157     const OutlineNode* parentParentNode = parentNode->parent();
0158     Q_ASSERT(parentNode);
0159     const int row = parentParentNode->indexOf(parentNode);
0160     return createIndex(row, 0, const_cast<OutlineNode*>(parentNode));
0161 }
0162 
0163 void OutlineModel::rebuildOutline(IDocument* doc)
0164 {
0165     beginResetModel();
0166     if (!doc) {
0167         m_rootNode = OutlineNode::dummyNode();
0168     } else {
0169         // TODO: do this in a separate thread? Might take a while for large documents
0170         // and we really shouldn't be blocking the GUI thread!
0171         DUChainReadLocker lock;
0172         TopDUContext* topContext = DUChainUtils::standardContextForUrl(doc->url());
0173         if (topContext) {
0174             m_rootNode = OutlineNode::fromTopContext(topContext);
0175         } else {
0176             m_rootNode = OutlineNode::dummyNode();
0177         }
0178     }
0179     if (doc != m_lastDoc) {
0180         m_lastUrl = doc ? IndexedString(doc->url()) : IndexedString();
0181         m_lastDoc = doc;
0182     }
0183     endResetModel();
0184 }
0185 
0186 void OutlineModel::activate(const QModelIndex& realIndex)
0187 {
0188     if (!realIndex.isValid()) {
0189         qCWarning(PLUGIN_OUTLINE) << "attempting to activate invalid item!";
0190         return;
0191     }
0192     auto* node = static_cast<OutlineNode*>(realIndex.internalPointer());
0193     KTextEditor::Range range;
0194     {
0195         DUChainReadLocker lock;
0196         const DUChainBase* dcb = node->duChainObject();
0197         if (!dcb) {
0198             qCDebug(PLUGIN_OUTLINE) << "No declaration exists for node:" << node->text();
0199             return;
0200         }
0201         //foreground thread == GUI thread? if so then we are fine
0202         range = dcb->rangeInCurrentRevision();
0203         //outline view should ALWAYS correspond to currently active document
0204         Q_ASSERT(dcb->url().toUrl() == ICore::self()->documentController()->activeDocument()->url());
0205         // lock should be released before activating the document
0206     }
0207     ICore::self()->documentController()->activateDocument(m_lastDoc, range);
0208 }
0209 
0210 #include "moc_outlinemodel.cpp"