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"