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

0001 /*
0002     SPDX-FileCopyrightText: 2006-2008 Hamish Rodda <rodda@kde.org>
0003     SPDX-FileCopyrightText: 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "codecompletionmodel.h"
0009 
0010 #include <QThread>
0011 #include <KTextEditor/View>
0012 #include <KTextEditor/Document>
0013 
0014 #include "../duchain/declaration.h"
0015 #include "../duchain/classfunctiondeclaration.h"
0016 #include "../duchain/ducontext.h"
0017 #include "../duchain/duchain.h"
0018 #include "../duchain/namespacealiasdeclaration.h"
0019 #include "../duchain/parsingenvironment.h"
0020 #include "../duchain/duchainlock.h"
0021 #include "../duchain/duchainbase.h"
0022 #include "../duchain/topducontext.h"
0023 #include "../duchain/duchainutils.h"
0024 #include "../interfaces/quickopendataprovider.h"
0025 #include "../interfaces/icore.h"
0026 #include "../interfaces/ilanguagecontroller.h"
0027 #include "../interfaces/icompletionsettings.h"
0028 #include <debug.h>
0029 
0030 #include "codecompletionworker.h"
0031 #include "codecompletioncontext.h"
0032 #include <duchain/specializationstore.h>
0033 
0034 using namespace KTextEditor;
0035 
0036 //Multi-threaded completion creates some multi-threading related crashes, and sometimes shows the completions in the wrong position if the cursor was moved
0037 // #define SINGLE_THREADED_COMPLETION
0038 
0039 namespace KDevelop {
0040 class CompletionWorkerThread
0041     : public QThread
0042 {
0043     Q_OBJECT
0044 
0045 public:
0046 
0047     explicit CompletionWorkerThread(CodeCompletionModel* model)
0048         : QThread(model)
0049         , m_model(model)
0050         , m_worker(m_model->createCompletionWorker())
0051     {
0052         Q_ASSERT(m_worker->parent() == nullptr); // Must be null, else we cannot change the thread affinity!
0053         m_worker->moveToThread(this);
0054         Q_ASSERT(m_worker->thread() == this);
0055     }
0056 
0057     ~CompletionWorkerThread() override
0058     {
0059         delete m_worker;
0060     }
0061 
0062     void run() override
0063     {
0064         //We connect directly, so we can do the pre-grouping within the background thread
0065         connect(m_worker, &CodeCompletionWorker::foundDeclarationsReal, m_model,
0066                 &CodeCompletionModel::foundDeclarations, Qt::QueuedConnection);
0067 
0068         connect(m_model, &CodeCompletionModel::completionsNeeded, m_worker,
0069                 QOverload<const DUChainPointer<KDevelop::DUContext>&, const Cursor&, View*>::of(&CodeCompletionWorker::computeCompletions),
0070                 Qt::QueuedConnection);
0071         connect(m_model, &CodeCompletionModel::doSpecialProcessingInBackground, m_worker,
0072                 &CodeCompletionWorker::doSpecialProcessing);
0073         exec();
0074     }
0075 
0076     CodeCompletionModel* m_model;
0077     CodeCompletionWorker* m_worker;
0078 };
0079 
0080 bool CodeCompletionModel::forceWaitForModel()
0081 {
0082     return m_forceWaitForModel;
0083 }
0084 
0085 void CodeCompletionModel::setForceWaitForModel(bool wait)
0086 {
0087     m_forceWaitForModel = wait;
0088 }
0089 
0090 CodeCompletionModel::CodeCompletionModel(QObject* parent)
0091     : KTextEditor::CodeCompletionModel(parent)
0092     , m_forceWaitForModel(false)
0093     , m_fullCompletion(true)
0094     , m_mutex(new QMutex)
0095     , m_thread(nullptr)
0096 {
0097     qRegisterMetaType<KTextEditor::Cursor>();
0098 }
0099 
0100 void CodeCompletionModel::initialize()
0101 {
0102     if (!m_thread) {
0103         m_thread = new CompletionWorkerThread(this);
0104 #ifdef SINGLE_THREADED_COMPLETION
0105         m_thread->m_worker = createCompletionWorker();
0106 #endif
0107         m_thread->start();
0108     }
0109 }
0110 
0111 CodeCompletionModel::~CodeCompletionModel()
0112 {
0113     if (m_thread->m_worker)
0114         m_thread->m_worker->abortCurrentCompletion();
0115     m_thread->quit();
0116     m_thread->wait();
0117 
0118     delete m_thread;
0119     delete m_mutex;
0120 }
0121 
0122 bool CodeCompletionModel::fullCompletion() const
0123 {
0124     return m_fullCompletion;
0125 }
0126 
0127 KDevelop::CodeCompletionWorker* CodeCompletionModel::worker() const
0128 {
0129     return m_thread->m_worker;
0130 }
0131 
0132 void CodeCompletionModel::clear()
0133 {
0134     beginResetModel();
0135     m_completionItems.clear();
0136     m_completionContext.reset();
0137     endResetModel();
0138 }
0139 
0140 void CodeCompletionModel::completionInvokedInternal(KTextEditor::View* view, const KTextEditor::Range& range,
0141                                                     InvocationType invocationType, const QUrl& url)
0142 {
0143     Q_ASSERT(m_thread == worker()->thread());
0144     Q_UNUSED(invocationType)
0145 
0146     DUChainReadLocker lock(DUChain::lock(), 400);
0147     if (!lock.locked()) {
0148         qCDebug(LANGUAGE) << "could not lock du-chain in time";
0149         return;
0150     }
0151 
0152     TopDUContext* top = DUChainUtils::standardContextForUrl(url);
0153     if (!top) {
0154         qCDebug(LANGUAGE) << "================== NO CONTEXT FOUND =======================";
0155         beginResetModel();
0156         m_completionItems.clear();
0157         endResetModel();
0158 
0159         qCDebug(LANGUAGE) << "Completion invoked for unknown context. Document:" << url << ", Known documents:" <<
0160             DUChain::self()->documents();
0161 
0162         return;
0163     }
0164 
0165     setCurrentTopContext(TopDUContextPointer(top));
0166 
0167     RangeInRevision rangeInRevision = top->transformToLocalRevision(KTextEditor::Range(range));
0168 
0169     qCDebug(LANGUAGE) << "completion invoked for context" << ( DUContext* )top;
0170 
0171     if (top->parsingEnvironmentFile() &&
0172         top->parsingEnvironmentFile()->modificationRevision() !=
0173         ModificationRevision::revisionForFile(IndexedString(url.toString()))) {
0174         qCDebug(LANGUAGE) << "Found context is not current.";
0175     }
0176 
0177     DUContextPointer thisContext;
0178     {
0179         qCDebug(LANGUAGE) << "apply specialization:" << range.start();
0180         thisContext = SpecializationStore::self().applySpecialization(top->findContextAt(rangeInRevision.start), top);
0181 
0182         if (thisContext) {
0183             qCDebug(LANGUAGE) << "after specialization:" << thisContext->localScopeIdentifier().toString() <<
0184                 thisContext->rangeInCurrentRevision();
0185         } else {
0186             thisContext = top;
0187         }
0188 
0189         qCDebug(LANGUAGE) << "context is set to" << thisContext.data();
0190     }
0191 
0192     lock.unlock();
0193 
0194     if (m_forceWaitForModel)
0195         emit waitForReset();
0196 
0197     emit completionsNeeded(thisContext, range.start(), view);
0198 }
0199 
0200 void CodeCompletionModel::completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range,
0201                                             InvocationType invocationType)
0202 {
0203     //If this triggers, initialize() has not been called after creation.
0204     Q_ASSERT(m_thread);
0205 
0206     KDevelop::ICompletionSettings::CompletionLevel level =
0207         KDevelop::ICore::self()->languageController()->completionSettings()->completionLevel();
0208     if (level == KDevelop::ICompletionSettings::AlwaysFull ||
0209         (invocationType != AutomaticInvocation && level == KDevelop::ICompletionSettings::MinimalWhenAutomatic))
0210         m_fullCompletion = true;
0211     else
0212         m_fullCompletion = false;
0213 
0214     //Only use grouping in full completion mode
0215     setHasGroups(m_fullCompletion);
0216 
0217     Q_UNUSED(invocationType)
0218 
0219     if (!worker()) {
0220         qCWarning(LANGUAGE) << "Completion invoked on a completion model which has no code completion worker assigned!";
0221     }
0222 
0223     beginResetModel();
0224     m_completionItems.clear();
0225     endResetModel();
0226 
0227     worker()->abortCurrentCompletion();
0228     worker()->setFullCompletion(m_fullCompletion);
0229 
0230     QUrl url = view->document()->url();
0231 
0232     completionInvokedInternal(view, range, invocationType, url);
0233 }
0234 
0235 void CodeCompletionModel::foundDeclarations(const QList<QExplicitlySharedDataPointer<CompletionTreeElement>>& items,
0236                                             const QExplicitlySharedDataPointer<CodeCompletionContext>& completionContext)
0237 {
0238     m_completionContext = completionContext;
0239 
0240     if (m_completionItems.isEmpty() && items.isEmpty()) {
0241         if (m_forceWaitForModel) {
0242             // TODO KF5: Check if this actually works
0243             beginResetModel();
0244             endResetModel(); //If we need to reset the model, reset it
0245         }
0246         return; //We don't need to reset, which is bad for target model
0247     }
0248 
0249     beginResetModel();
0250     m_completionItems = items;
0251     endResetModel();
0252 
0253     if (m_completionContext) {
0254         qCDebug(LANGUAGE) << "got completion-context with " << m_completionContext->ungroupedElements().size() <<
0255             "ungrouped elements";
0256     }
0257 }
0258 
0259 KTextEditor::CodeCompletionModelControllerInterface::MatchReaction CodeCompletionModel::matchingItem(const QModelIndex& /*matched*/)
0260 {
0261     return None;
0262 }
0263 
0264 void CodeCompletionModel::setCompletionContext(
0265     const QExplicitlySharedDataPointer<CodeCompletionContext>& completionContext)
0266 {
0267     QMutexLocker lock(m_mutex);
0268     m_completionContext = completionContext;
0269 
0270     if (m_completionContext) {
0271         qCDebug(LANGUAGE) << "got completion-context with " << m_completionContext->ungroupedElements().size() <<
0272             "ungrouped elements";
0273     }
0274 }
0275 
0276 QExplicitlySharedDataPointer<CodeCompletionContext> CodeCompletionModel::completionContext() const
0277 {
0278     QMutexLocker lock(m_mutex);
0279     return m_completionContext;
0280 }
0281 
0282 void CodeCompletionModel::executeCompletionItem(View* view, const KTextEditor::Range& word,
0283                                                 const QModelIndex& index) const
0284 {
0285     //We must not lock the duchain at this place, because the items might rely on that
0286     auto* element = static_cast<CompletionTreeElement*>(index.internalPointer());
0287     if (!element || !element->asItem())
0288         return;
0289 
0290     element->asItem()->execute(view, word);
0291 }
0292 
0293 QExplicitlySharedDataPointer<KDevelop::CompletionTreeElement> CodeCompletionModel::itemForIndex(const QModelIndex& index)
0294 const
0295 {
0296     auto* element = static_cast<CompletionTreeElement*>(index.internalPointer());
0297     return QExplicitlySharedDataPointer<KDevelop::CompletionTreeElement>(element);
0298 }
0299 
0300 QVariant CodeCompletionModel::data(const QModelIndex& index, int role) const
0301 {
0302     if (role == Qt::TextAlignmentRole && index.column() == 0) {
0303         return Qt::AlignRight;
0304     }
0305     auto element = static_cast<const CompletionTreeElement*>(index.internalPointer());
0306     if (!element)
0307         return QVariant();
0308 
0309     if (role == CodeCompletionModel::GroupRole) {
0310         if (element->asNode()) {
0311             return QVariant(element->asNode()->role);
0312         } else {
0313             qCDebug(LANGUAGE) << "Requested group-role from leaf tree element";
0314             return QVariant();
0315         }
0316     } else {
0317         if (element->asNode()) {
0318             if (role == CodeCompletionModel::InheritanceDepth) {
0319                 auto customGroupNode = dynamic_cast<const CompletionCustomGroupNode*>(element);
0320                 if (customGroupNode)
0321                     return QVariant(customGroupNode->inheritanceDepth);
0322             }
0323             if (role == element->asNode()->role) {
0324                 return element->asNode()->roleValue;
0325             } else {
0326                 return QVariant();
0327             }
0328         }
0329     }
0330 
0331     if (!element->asItem()) {
0332         qCWarning(LANGUAGE) << "Error in completion model";
0333         return QVariant();
0334     }
0335 
0336     //Navigation widget interaction is done here, the other stuff is done within the tree-elements
0337     switch (role) {
0338     case CodeCompletionModel::InheritanceDepth:
0339         return element->asItem()->inheritanceDepth();
0340     case CodeCompletionModel::ArgumentHintDepth:
0341         return element->asItem()->argumentHintDepth();
0342 
0343     case CodeCompletionModel::ItemSelected: {
0344         DeclarationPointer decl = element->asItem()->declaration();
0345         if (decl) {
0346             DUChain::self()->emitDeclarationSelected(decl);
0347         }
0348         break;
0349     }
0350     }
0351 
0352     //In minimal completion mode, hide all columns except the "name" one
0353     if (!m_fullCompletion && role == Qt::DisplayRole && index.column() != Name &&
0354         (element->asItem()->argumentHintDepth() == 0 || index.column() == Prefix))
0355         return QVariant();
0356 
0357     //In reduced completion mode, don't show information text with the selected items
0358     if (role == ItemSelected &&
0359         (!m_fullCompletion ||
0360          !ICore::self()->languageController()->completionSettings()->showMultiLineSelectionInformation()))
0361         return QVariant();
0362 
0363     return element->asItem()->data(index, role, this);
0364 }
0365 
0366 KDevelop::TopDUContextPointer CodeCompletionModel::currentTopContext() const
0367 {
0368     return m_currentTopContext;
0369 }
0370 
0371 void CodeCompletionModel::setCurrentTopContext(const KDevelop::TopDUContextPointer& topContext)
0372 {
0373     m_currentTopContext = topContext;
0374 }
0375 
0376 QModelIndex CodeCompletionModel::index(int row, int column, const QModelIndex& parent) const
0377 {
0378     if (parent.isValid()) {
0379         auto* element = static_cast<CompletionTreeElement*>(parent.internalPointer());
0380 
0381         CompletionTreeNode* node = element->asNode();
0382 
0383         if (!node) {
0384             qCDebug(LANGUAGE) << "Requested sub-index of leaf node";
0385             return QModelIndex();
0386         }
0387 
0388         if (row < 0 || row >= node->children.count() || column < 0 || column >= ColumnCount)
0389             return QModelIndex();
0390 
0391         return createIndex(row, column, node->children[row].data());
0392     } else {
0393         if (row < 0 || row >= m_completionItems.count() || column < 0 || column >= ColumnCount)
0394             return QModelIndex();
0395 
0396         return createIndex(row, column, const_cast<CompletionTreeElement*>(m_completionItems[row].data()));
0397     }
0398 }
0399 
0400 QModelIndex CodeCompletionModel::parent(const QModelIndex& index) const
0401 {
0402     if (rowCount() == 0)
0403         return QModelIndex();
0404 
0405     if (index.isValid()) {
0406         auto* element = static_cast<CompletionTreeElement*>(index.internalPointer());
0407 
0408         if (element->parent())
0409             return createIndex(element->rowInParent(), element->columnInParent(), element->parent());
0410     }
0411 
0412     return QModelIndex();
0413 }
0414 
0415 int CodeCompletionModel::rowCount(const QModelIndex& parent) const
0416 {
0417     if (parent.isValid()) {
0418         auto* element = static_cast<CompletionTreeElement*>(parent.internalPointer());
0419 
0420         CompletionTreeNode* node = element->asNode();
0421 
0422         if (!node)
0423             return 0;
0424 
0425         return node->children.count();
0426     } else {
0427         return m_completionItems.count();
0428     }
0429 }
0430 
0431 QString CodeCompletionModel::filterString(KTextEditor::View* view, const KTextEditor::Range& range,
0432                                           const KTextEditor::Cursor& position)
0433 {
0434     m_filterString = KTextEditor::CodeCompletionModelControllerInterface::filterString(view, range, position);
0435     return m_filterString;
0436 }
0437 }
0438 
0439 #include "moc_codecompletionmodel.cpp"
0440 #include "codecompletionmodel.moc"