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"