File indexing completed on 2024-05-12 04:39:08
0001 /* 0002 SPDX-FileCopyrightText: 2014 Milian Wolff <mail@milianw.de> 0003 0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "model.h" 0008 0009 #include "util/clangdebug.h" 0010 #include "context.h" 0011 #include "includepathcompletioncontext.h" 0012 0013 #include "duchain/parsesession.h" 0014 #include "duchain/clangindex.h" 0015 #include "duchain/duchainutils.h" 0016 0017 #include <language/codecompletion/codecompletionworker.h> 0018 #include <language/duchain/topducontext.h> 0019 #include <language/duchain/duchainutils.h> 0020 #include <language/duchain/duchainlock.h> 0021 #include <language/duchain/stringhelpers.h> 0022 0023 #include <KTextEditor/View> 0024 #include <KTextEditor/Document> 0025 0026 #include <QTimer> 0027 0028 using namespace KDevelop; 0029 0030 namespace { 0031 0032 bool includePathCompletionRequired(const QString& text) 0033 { 0034 const auto properties = IncludePathProperties::parseText(text); 0035 return properties.valid; 0036 } 0037 0038 QSharedPointer<CodeCompletionContext> createCompletionContext(const DUContextPointer& context, 0039 const ParseSessionData::Ptr& session, 0040 const QUrl& url, 0041 const KTextEditor::Cursor& position, 0042 const QString& text, 0043 const QString& followingText) 0044 { 0045 if (includePathCompletionRequired(text)) { 0046 return QSharedPointer<IncludePathCompletionContext>::create(context, session, url, position, text); 0047 } else { 0048 return QSharedPointer<ClangCodeCompletionContext>::create(context, session, url, position, text, followingText); 0049 } 0050 } 0051 0052 class ClangCodeCompletionWorker : public CodeCompletionWorker 0053 { 0054 Q_OBJECT 0055 public: 0056 ClangCodeCompletionWorker(ClangIndex* index, CodeCompletionModel* model) 0057 : CodeCompletionWorker(model) 0058 , m_index(index) 0059 {} 0060 ~ClangCodeCompletionWorker() override = default; 0061 0062 public Q_SLOTS: 0063 void completionRequested(const QUrl &url, const KTextEditor::Cursor& position, const QString& text, const QString& followingText) 0064 { 0065 // group requests and only handle the latest one 0066 m_url = url; 0067 m_position = position; 0068 m_text = text; 0069 m_followingText = followingText; 0070 0071 if (!m_timer) { 0072 // lazy-load the timer to initialize it in the background thread 0073 m_timer = new QTimer(this); 0074 m_timer->setInterval(0); 0075 m_timer->setSingleShot(true); 0076 connect(m_timer, &QTimer::timeout, this, &ClangCodeCompletionWorker::run); 0077 } 0078 m_timer->start(); 0079 } 0080 0081 private: 0082 void run() 0083 { 0084 aborting() = false; 0085 0086 DUChainReadLocker lock; 0087 if (aborting()) { 0088 failed(); 0089 return; 0090 } 0091 0092 auto top = DUChainUtils::standardContextForUrl(m_url); 0093 if (!top) { 0094 qCWarning(KDEV_CLANG) << "No context found for" << m_url; 0095 return; 0096 } 0097 0098 ParseSessionData::Ptr sessionData(ClangIntegration::DUChainUtils::findParseSessionData(top->url(), m_index->translationUnitForUrl(top->url()))); 0099 0100 if (!sessionData) { 0101 // TODO: trigger reparse and re-request code completion 0102 qCWarning(KDEV_CLANG) << "No parse session / AST attached to context for url" << m_url; 0103 return; 0104 } 0105 0106 if (aborting()) { 0107 failed(); 0108 return; 0109 } 0110 0111 // We hold DUChain lock, and ask for ParseSession, but TUDUChain indirectly holds ParseSession lock. 0112 lock.unlock(); 0113 0114 auto completionContext = ::createCompletionContext(DUContextPointer(top), sessionData, m_url, 0115 m_position, m_text, m_followingText); 0116 0117 lock.lock(); 0118 if (aborting()) { 0119 failed(); 0120 return; 0121 } 0122 0123 bool abort = false; 0124 // NOTE: cursor might be wrong here, but shouldn't matter much I hope... 0125 // when the document changed significantly, then the cache is off anyways and we don't get anything sensible 0126 // the position here is just a "optimization" to only search up to that position 0127 const auto& items = completionContext->completionItems(abort); 0128 0129 if (aborting()) { 0130 failed(); 0131 return; 0132 } 0133 0134 auto tree = computeGroups( items, {} ); 0135 0136 if (aborting()) { 0137 failed(); 0138 return; 0139 } 0140 0141 tree += completionContext->ungroupedElements(); 0142 0143 foundDeclarations( tree, {} ); 0144 } 0145 private: 0146 ClangIndex* m_index; 0147 QTimer* m_timer = nullptr; 0148 QUrl m_url; 0149 KTextEditor::Cursor m_position; 0150 QString m_text; 0151 QString m_followingText; 0152 }; 0153 } 0154 0155 ClangCodeCompletionModel::ClangCodeCompletionModel(ClangIndex* index, QObject* parent) 0156 : CodeCompletionModel(parent) 0157 , m_index(index) 0158 { 0159 qRegisterMetaType<KTextEditor::Cursor>(); 0160 } 0161 0162 ClangCodeCompletionModel::~ClangCodeCompletionModel() 0163 { 0164 0165 } 0166 0167 bool ClangCodeCompletionModel::shouldStartCompletion(KTextEditor::View* view, const QString& inserted, 0168 bool userInsertion, const KTextEditor::Cursor& position) 0169 { 0170 const QString noCompletionAfter = QStringLiteral(";{}]) "); 0171 0172 if (inserted.isEmpty() || consistsOfWhitespace(inserted)) { 0173 return false; 0174 } 0175 const auto lastChar = inserted.at(inserted.size() - 1); 0176 if (noCompletionAfter.contains(lastChar)) { 0177 return false; 0178 } 0179 const auto wordAtPosition = view->document()->wordAt(position); 0180 if (!wordAtPosition.isEmpty() && wordAtPosition.at(0).isDigit()) { 0181 return false; 0182 } 0183 // also show include path completion after dashes 0184 if (userInsertion && lastChar == QLatin1Char('-') && includePathCompletionRequired(view->document()->line(position.line()))) { 0185 return true; 0186 } 0187 if (userInsertion && inserted.endsWith(QLatin1String("::"))) { 0188 return true; 0189 } 0190 0191 return KDevelop::CodeCompletionModel::shouldStartCompletion(view, inserted, userInsertion, position); 0192 } 0193 0194 KTextEditor::Range ClangCodeCompletionModel::completionRange(KTextEditor::View* view, const KTextEditor::Cursor& position) 0195 { 0196 auto range = KDevelop::CodeCompletionModel::completionRange(view, position); 0197 const auto includeProperties = IncludePathProperties::parseText(view->document()->line(position.line()), position.column()); 0198 if (includeProperties.valid && includeProperties.inputFrom != -1) { 0199 // expand include path range to include e.g. dashes 0200 range.setStart({position.line(), includeProperties.inputFrom}); 0201 } 0202 return range; 0203 } 0204 0205 bool ClangCodeCompletionModel::shouldAbortCompletion(KTextEditor::View* view, const KTextEditor::Range& range, const QString& currentCompletion) 0206 { 0207 const auto shouldAbort = KDevelop::CodeCompletionModel::shouldAbortCompletion(view, range, currentCompletion); 0208 if (shouldAbort && includePathCompletionRequired(view->document()->line(range.end().line()))) { 0209 // don't abort include path completion which can contain dashes 0210 return false; 0211 } 0212 return shouldAbort; 0213 } 0214 0215 CodeCompletionWorker* ClangCodeCompletionModel::createCompletionWorker() 0216 { 0217 auto worker = new ClangCodeCompletionWorker(m_index, this); 0218 connect(this, &ClangCodeCompletionModel::requestCompletion, 0219 worker, &ClangCodeCompletionWorker::completionRequested); 0220 return worker; 0221 } 0222 0223 void ClangCodeCompletionModel::completionInvokedInternal(KTextEditor::View* view, const KTextEditor::Range& range, 0224 CodeCompletionModel::InvocationType /*invocationType*/, const QUrl &url) 0225 { 0226 auto text = view->document()->text({0, 0, range.start().line(), range.start().column()}); 0227 auto followingText = view->document()->text({{range.start().line(), range.start().column()}, view->document()->documentEnd()}); 0228 emit requestCompletion(url, KTextEditor::Cursor(range.start()), text, followingText); 0229 } 0230 0231 #include "model.moc" 0232 #include "moc_model.cpp"