From 49bb40f19e71ab92f3318b29c2e929bdfb418178 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Tue, 7 Jun 2022 11:19:41 +0200 Subject: [PATCH] ClangCodeModel: Move "follow symbol" into its own class Change-Id: Ic64c7275debaa59c524f349fd38460f47f826ecd Reviewed-by: Reviewed-by: David Schulz --- src/plugins/clangcodemodel/CMakeLists.txt | 1 + src/plugins/clangcodemodel/clangcodemodel.qbs | 2 + src/plugins/clangcodemodel/clangdclient.cpp | 597 +++--------------- src/plugins/clangcodemodel/clangdclient.h | 22 +- .../clangcodemodel/clangdfollowsymbol.cpp | 505 +++++++++++++++ .../clangcodemodel/clangdfollowsymbol.h | 65 ++ 6 files changed, 674 insertions(+), 518 deletions(-) create mode 100644 src/plugins/clangcodemodel/clangdfollowsymbol.cpp create mode 100644 src/plugins/clangcodemodel/clangdfollowsymbol.h diff --git a/src/plugins/clangcodemodel/CMakeLists.txt b/src/plugins/clangcodemodel/CMakeLists.txt index acd6b4bbd28..896df4e18ce 100644 --- a/src/plugins/clangcodemodel/CMakeLists.txt +++ b/src/plugins/clangcodemodel/CMakeLists.txt @@ -17,6 +17,7 @@ add_qtc_plugin(ClangCodeModel clangconstants.h clangdast.cpp clangdast.h clangdclient.cpp clangdclient.h + clangdfollowsymbol.cpp clangdfollowsymbol.h clangdiagnostictooltipwidget.cpp clangdiagnostictooltipwidget.h clangdquickfixfactory.cpp clangdquickfixfactory.h clangdqpropertyhighlighter.cpp clangdqpropertyhighlighter.h diff --git a/src/plugins/clangcodemodel/clangcodemodel.qbs b/src/plugins/clangcodemodel/clangcodemodel.qbs index 5f4cc2c1b6e..19a0e7604a9 100644 --- a/src/plugins/clangcodemodel/clangcodemodel.qbs +++ b/src/plugins/clangcodemodel/clangcodemodel.qbs @@ -34,6 +34,8 @@ QtcPlugin { "clangdast.h", "clangdclient.cpp", "clangdclient.h", + "clangdfollowsymbol.cpp", + "clangdfollowsymbol.h", "clangdiagnostictooltipwidget.cpp", "clangdiagnostictooltipwidget.h", "clangdlocatorfilters.cpp", diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 7d5970c2915..580b4b5896c 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -28,6 +28,7 @@ #include "clangcompletioncontextanalyzer.h" #include "clangconstants.h" #include "clangdast.h" +#include "clangdfollowsymbol.h" #include "clangdlocatorfilters.h" #include "clangpreprocessorassistproposalitem.h" #include "clangtextmark.h" @@ -115,7 +116,7 @@ using namespace TextEditor; namespace ClangCodeModel { namespace Internal { -static Q_LOGGING_CATEGORY(clangdLog, "qtc.clangcodemodel.clangd", QtWarningMsg); +Q_LOGGING_CATEGORY(clangdLog, "qtc.clangcodemodel.clangd", QtWarningMsg); static Q_LOGGING_CATEGORY(clangdLogServer, "qtc.clangcodemodel.clangd.server", QtWarningMsg); static Q_LOGGING_CATEGORY(clangdLogAst, "qtc.clangcodemodel.clangd.ast", QtWarningMsg); static Q_LOGGING_CATEGORY(clangdLogCompletion, "qtc.clangcodemodel.clangd.completion", @@ -309,111 +310,6 @@ public: bool categorize = CppEditor::codeModelSettings()->categorizeFindReferences(); }; -using SymbolData = QPair; -using SymbolDataList = QList; - -class ClangdClient::VirtualFunctionAssistProcessor : public IAssistProcessor -{ -public: - VirtualFunctionAssistProcessor(ClangdClient::Private *data) : m_data(data) {} - - void cancel() override; - bool running() override { return m_data; } - - void update(); - void finalize(); - void resetData(bool resetFollowSymbolData); - -private: - IAssistProposal *perform(const AssistInterface *) override - { - return nullptr; - } - - IAssistProposal *immediateProposal(const AssistInterface *) override - { - return createProposal(false); - } - - IAssistProposal *immediateProposalImpl() const; - IAssistProposal *createProposal(bool final) const; - CppEditor::VirtualFunctionProposalItem *createEntry(const QString &name, - const Utils::Link &link) const; - - ClangdClient::Private *m_data = nullptr; -}; - -class ClangdClient::VirtualFunctionAssistProvider : public IAssistProvider -{ -public: - VirtualFunctionAssistProvider(ClangdClient::Private *data) : m_data(data) {} - -private: - RunType runType() const override { return Asynchronous; } - IAssistProcessor *createProcessor(const AssistInterface *) const override; - - ClangdClient::Private * const m_data; -}; - -class ClangdClient::FollowSymbolData { -public: - FollowSymbolData(ClangdClient *q, quint64 id, const QTextCursor &cursor, - CppEditor::CppEditorWidget *editorWidget, - const DocumentUri &uri, const Utils::LinkHandler &callback, - bool openInSplit) - : q(q), id(id), cursor(cursor), editorWidget(editorWidget), uri(uri), - callback(callback), virtualFuncAssistProvider(q->d), - docRevision(editorWidget ? editorWidget->textDocument()->document()->revision() : -1), - openInSplit(openInSplit) {} - - ~FollowSymbolData() - { - closeTempDocuments(); - if (virtualFuncAssistProcessor) - virtualFuncAssistProcessor->resetData(false); - for (const MessageId &id : qAsConst(pendingSymbolInfoRequests)) - q->cancelRequest(id); - for (const MessageId &id : qAsConst(pendingGotoImplRequests)) - q->cancelRequest(id); - for (const MessageId &id : qAsConst(pendingGotoDefRequests)) - q->cancelRequest(id); - } - - void closeTempDocuments() - { - for (const Utils::FilePath &fp : qAsConst(openedFiles)) { - if (!q->documentForFilePath(fp)) - q->closeExtraFile(fp); - } - openedFiles.clear(); - } - - bool defLinkIsAmbiguous() const; - - ClangdClient * const q; - const quint64 id; - const QTextCursor cursor; - const QPointer editorWidget; - const DocumentUri uri; - const Utils::LinkHandler callback; - VirtualFunctionAssistProvider virtualFuncAssistProvider; - QList pendingSymbolInfoRequests; - QList pendingGotoImplRequests; - QList pendingGotoDefRequests; - const int docRevision; - const bool openInSplit; - - Utils::Link defLink; - QList allLinks; - QHash declDefMap; - Utils::optional cursorNode; - ClangdAstNode defLinkNode; - SymbolDataList symbolsToDisplay; - std::set openedFiles; - VirtualFunctionAssistProcessor *virtualFuncAssistProcessor = nullptr; - bool finished = false; -}; - class SwitchDeclDefData { public: SwitchDeclDefData(quint64 id, TextDocument *doc, const QTextCursor &cursor, @@ -781,11 +677,6 @@ public: void reportAllSearchResultsAndFinish(ReferencesData &data); void finishSearch(const ReferencesData &refData, bool canceled); - void handleGotoDefinitionResult(); - void sendGotoImplementationRequest(const Utils::Link &link); - void handleGotoImplementationResult(const GotoImplementationRequest::Response &response); - void handleDocumentInfoResults(); - void handleDeclDefSwitchReplies(); Utils::optional getContainingFunctionName(const ClangdAstPath &astPath, const Range& range); @@ -801,16 +692,13 @@ public: void handleSemanticTokens(TextDocument *doc, const QList &tokens, int version, bool force); - enum class AstCallbackMode { SyncIfPossible, AlwaysAsync }; - using TextDocOrFile = const Utils::variant; - using AstHandler = const std::function; - MessageId getAndHandleAst(TextDocOrFile &doc, AstHandler &astHandler, + MessageId getAndHandleAst(const TextDocOrFile &doc, const AstHandler &astHandler, AstCallbackMode callbackMode, const Range &range = {}); ClangdClient * const q; const CppEditor::ClangdSettings::Data settings; QHash runningFindUsages; - Utils::optional followSymbolData; + ClangdFollowSymbol *followSymbol = nullptr; Utils::optional switchDeclDefData; Utils::optional localRefsData; Utils::optional versionNumber; @@ -1141,12 +1029,8 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir) ClangdClient::~ClangdClient() { - if (d->followSymbolData) { - d->followSymbolData->openedFiles.clear(); - d->followSymbolData->pendingSymbolInfoRequests.clear(); - d->followSymbolData->pendingGotoImplRequests.clear(); - d->followSymbolData->pendingGotoDefRequests.clear(); - } + if (d->followSymbol) + d->followSymbol->clear(); delete d; } @@ -1194,25 +1078,15 @@ void ClangdClient::findUsages(TextDocument *document, const QTextCursor &cursor, // Otherwise get the proper spelling of the search term from clang, so we can put it into the // search widget. - const TextDocumentIdentifier docId(DocumentUri::fromFilePath(document->filePath())); - const TextDocumentPositionParams params(docId, Range(adjustedCursor).start()); - SymbolInfoRequest symReq(params); - symReq.setResponseCallback([this, doc = QPointer(document), adjustedCursor, replacement, categorize] - (const SymbolInfoRequest::Response &response) { + const auto symbolInfoHandler = [this, doc = QPointer(document), adjustedCursor, replacement, categorize] + (const QString &, const QString &name, const MessageId &) { if (!doc) return; - const auto result = response.result(); - if (!result) + if (name.isEmpty()) return; - const auto list = Utils::get_if>(&result.value()); - if (!list || list->isEmpty()) - return; - const SymbolDetails &sd = list->first(); - if (sd.name().isEmpty()) - return; - d->findUsages(doc.data(), adjustedCursor, sd.name(), replacement, categorize); - }); - sendMessage(symReq); + d->findUsages(doc.data(), adjustedCursor, name, replacement, categorize); + }; + requestSymbolInfo(document->filePath(), Range(adjustedCursor).start(), symbolInfoHandler); } void ClangdClient::handleDiagnostics(const PublishDiagnosticsParams ¶ms) @@ -1541,6 +1415,54 @@ void ClangdClient::clearTasks(const Utils::FilePath &filePath) d->issuePaneEntries[filePath].clear(); } +Utils::optional ClangdClient::hasVirtualFunctionAt(TextDocument *doc, int revision, + const Range &range) +{ + const auto highlightingData = d->highlightingData.constFind(doc); + if (highlightingData == d->highlightingData.constEnd() + || highlightingData->virtualRanges.second != revision) { + return {}; + } + const auto matcher = [range](const Range &r) { return range.overlaps(r); }; + return Utils::contains(highlightingData->virtualRanges.first, matcher); +} + +MessageId ClangdClient::getAndHandleAst(const TextDocOrFile &doc, const AstHandler &astHandler, + AstCallbackMode callbackMode, const Range &range) +{ + return d->getAndHandleAst(doc, astHandler, callbackMode, range); +} + +MessageId ClangdClient::requestSymbolInfo(const Utils::FilePath &filePath, const Position &position, + const SymbolInfoHandler &handler) +{ + const TextDocumentIdentifier docId(DocumentUri::fromFilePath(filePath)); + const TextDocumentPositionParams params(docId, position); + SymbolInfoRequest symReq(params); + symReq.setResponseCallback([handler, reqId = symReq.id()] + (const SymbolInfoRequest::Response &response) { + const auto result = response.result(); + if (!result) { + handler({}, {}, reqId); + return; + } + + // According to the documentation, we should receive a single + // object here, but it's a list. No idea what it means if there's + // more than one entry. We choose the first one. + const auto list = Utils::get_if>(&result.value()); + if (!list || list->isEmpty()) { + handler({}, {}, reqId); + return; + } + + const SymbolDetails &sd = list->first(); + handler(sd.name(), sd.containerName(), reqId); + }); + sendMessage(symReq); + return symReq.id(); +} + void ClangdClient::Private::handleFindUsagesResult(quint64 key, const QList &locations) { const auto refData = runningFindUsages.find(key); @@ -1732,47 +1654,24 @@ void ClangdClient::followSymbol(TextDocument *document, ) { QTC_ASSERT(documentOpen(document), openDocument(document)); + + delete d->followSymbol; + d->followSymbol = nullptr; + const QTextCursor adjustedCursor = d->adjustedCursor(cursor, document); if (!resolveTarget) { - d->followSymbolData.reset(); symbolSupport().findLinkAt(document, adjustedCursor, callback, false); return; } qCDebug(clangdLog) << "follow symbol requested" << document->filePath() << adjustedCursor.blockNumber() << adjustedCursor.positionInBlock(); - d->followSymbolData.emplace(this, ++d->nextJobId, adjustedCursor, editorWidget, - DocumentUri::fromFilePath(document->filePath()), - callback, openInSplit); - - // Step 1: Follow the symbol via "Go to Definition". At the same time, request the - // AST node corresponding to the cursor position, so we can find out whether - // we have to look for overrides. - const auto gotoDefCallback = [this, id = d->followSymbolData->id](const Utils::Link &link) { - qCDebug(clangdLog) << "received go to definition response"; - if (!link.hasValidTarget()) { - d->followSymbolData.reset(); - return; - } - if (!d->followSymbolData || id != d->followSymbolData->id) - return; - d->followSymbolData->defLink = link; - if (d->followSymbolData->cursorNode) - d->handleGotoDefinitionResult(); - }; - symbolSupport().findLinkAt(document, adjustedCursor, std::move(gotoDefCallback), true); - - const auto astHandler = [this, id = d->followSymbolData->id] - (const ClangdAstNode &ast, const MessageId &) { - qCDebug(clangdLog) << "received ast response for cursor"; - if (!d->followSymbolData || d->followSymbolData->id != id) - return; - d->followSymbolData->cursorNode = ast; - if (d->followSymbolData->defLink.hasValidTarget()) - d->handleGotoDefinitionResult(); - }; - d->getAndHandleAst(document, astHandler, Private::AstCallbackMode::AlwaysAsync, - Range(adjustedCursor)); + d->followSymbol = new ClangdFollowSymbol(this, adjustedCursor, editorWidget, document, callback, + openInSplit); + connect(d->followSymbol, &ClangdFollowSymbol::done, this, [this] { + delete d->followSymbol; + d->followSymbol = nullptr; + }); } void ClangdClient::switchDeclDef(TextDocument *document, const QTextCursor &cursor, @@ -1801,7 +1700,7 @@ void ClangdClient::switchDeclDef(TextDocument *document, const QTextCursor &curs d->handleDeclDefSwitchReplies(); }; - d->getAndHandleAst(document, astHandler, Private::AstCallbackMode::SyncIfPossible); + d->getAndHandleAst(document, astHandler, AstCallbackMode::SyncIfPossible); documentSymbolCache()->requestSymbols(d->switchDeclDefData->uri, Schedule::Now); } @@ -1907,7 +1806,7 @@ void ClangdClient::findLocalUsages(TextDocument *document, const QTextCursor &cu }; qCDebug(clangdLog) << "sending ast request for link"; d->getAndHandleAst(d->localRefsData->document, astHandler, - Private::AstCallbackMode::SyncIfPossible); + AstCallbackMode::SyncIfPossible); }; symbolSupport().findLinkAt(document, cursor, std::move(gotoDefCallback), true); } @@ -1992,28 +1891,19 @@ void ClangdClient::gatherHelpItemForTooltip(const HoverRequest::Response &hoverR const bool isFunction = node.role() == "expression" && node.kind() == "DeclRef" && type.contains('('); if (isMemberFunction || isFunction) { - const TextDocumentPositionParams params(TextDocumentIdentifier(uri), range.start()); - SymbolInfoRequest symReq(params); - symReq.setResponseCallback([this, id, type, isFunction] - (const SymbolInfoRequest::Response &response) { + const auto symbolInfoHandler = [this, id, type, isFunction] + (const QString &name, const QString &prefix, const MessageId &) { qCDebug(clangdLog) << "handling symbol info reply"; - QString fqn; - if (const auto result = response.result()) { - if (const auto list = Utils::get_if>(&result.value())) { - if (!list->isEmpty()) { - const SymbolDetails &sd = list->first(); - fqn = sd.containerName() + sd.name(); - } - } - } + const QString fqn = prefix + name; // Unfortunately, the arcana string contains the signature only for // free functions, so we can't distinguish member function overloads. // But since HtmlDocExtractor::getFunctionDescription() is always called // with mainOverload = true, such information would get ignored anyway. - d->setHelpItemForTooltip(id, fqn, HelpItem::Function, isFunction ? type : "()"); - }); - sendMessage(symReq, SendDocUpdates::Ignore); + if (!fqn.isEmpty()) + d->setHelpItemForTooltip(id, fqn, HelpItem::Function, isFunction ? type : "()"); + }; + requestSymbolInfo(uri.toFilePath(), range.start(), symbolInfoHandler); return; } if ((node.role() == "expression" && node.kind() == "DeclRef") @@ -2078,7 +1968,7 @@ void ClangdClient::gatherHelpItemForTooltip(const HoverRequest::Response &hoverR } d->setHelpItemForTooltip(id); }; - d->getAndHandleAst(doc, astHandler, Private::AstCallbackMode::SyncIfPossible); + d->getAndHandleAst(doc, astHandler, AstCallbackMode::SyncIfPossible); } void ClangdClient::setVirtualRanges(const Utils::FilePath &filePath, const QList &ranges, @@ -2089,209 +1979,6 @@ void ClangdClient::setVirtualRanges(const Utils::FilePath &filePath, const QList d->highlightingData[doc].virtualRanges = {ranges, revision}; } -void ClangdClient::Private::handleGotoDefinitionResult() -{ - QTC_ASSERT(followSymbolData->defLink.hasValidTarget(), return); - - qCDebug(clangdLog) << "handling go to definition result"; - - // No dis-ambiguation necessary. Call back with the link and finish. - if (!followSymbolData->defLinkIsAmbiguous()) { - followSymbolData->callback(followSymbolData->defLink); - followSymbolData.reset(); - return; - } - - // Step 2: Get all possible overrides via "Go to Implementation". - // Note that we have to do this for all member function calls, because - // we cannot tell here whether the member function is virtual. - followSymbolData->allLinks << followSymbolData->defLink; - sendGotoImplementationRequest(followSymbolData->defLink); -} - -void ClangdClient::Private::sendGotoImplementationRequest(const Utils::Link &link) -{ - if (!q->documentForFilePath(link.targetFilePath) - && followSymbolData->openedFiles.insert(link.targetFilePath).second) { - q->openExtraFile(link.targetFilePath); - } - const Position position(link.targetLine - 1, link.targetColumn); - const TextDocumentIdentifier documentId(DocumentUri::fromFilePath(link.targetFilePath)); - GotoImplementationRequest req(TextDocumentPositionParams(documentId, position)); - req.setResponseCallback([this, id = followSymbolData->id, reqId = req.id()]( - const GotoImplementationRequest::Response &response) { - qCDebug(clangdLog) << "received go to implementation reply"; - if (!followSymbolData || id != followSymbolData->id) - return; - followSymbolData->pendingGotoImplRequests.removeOne(reqId); - handleGotoImplementationResult(response); - }); - q->sendMessage(req, SendDocUpdates::Ignore); - followSymbolData->pendingGotoImplRequests << req.id(); - qCDebug(clangdLog) << "sending go to implementation request" << link.targetLine; -} - -void ClangdClient::Private::handleGotoImplementationResult( - const GotoImplementationRequest::Response &response) -{ - if (const Utils::optional &result = response.result()) { - QList newLinks; - if (const auto ploc = Utils::get_if(&*result)) - newLinks = {ploc->toLink()}; - if (const auto plloc = Utils::get_if>(&*result)) - newLinks = Utils::transform(*plloc, &Location::toLink); - for (const Utils::Link &link : qAsConst(newLinks)) { - if (!followSymbolData->allLinks.contains(link)) { - followSymbolData->allLinks << link; - - // We must do this recursively, because clangd reports only the first - // level of overrides. - sendGotoImplementationRequest(link); - } - } - } - - // We didn't find any further candidates, so jump to the original definition link. - if (followSymbolData->allLinks.size() == 1 - && followSymbolData->pendingGotoImplRequests.isEmpty()) { - followSymbolData->callback(followSymbolData->allLinks.first()); - followSymbolData.reset(); - return; - } - - // As soon as we know that there is more than one candidate, we start the code assist - // procedure, to let the user know that things are happening. - if (followSymbolData->allLinks.size() > 1 && !followSymbolData->virtualFuncAssistProcessor - && followSymbolData->editorWidget) { - followSymbolData->editorWidget->invokeTextEditorWidgetAssist(FollowSymbol, - &followSymbolData->virtualFuncAssistProvider); - } - - if (!followSymbolData->pendingGotoImplRequests.isEmpty()) - return; - - // Step 3: We are done looking for overrides, and we found at least one. - // Make a symbol info request for each link to get the class names. - // Also get the AST for the base declaration, so we can find out whether it's - // pure virtual and mark it accordingly. - // In addition, we need to follow all override links, because for these, clangd - // gives us the declaration instead of the definition. - for (const Utils::Link &link : qAsConst(followSymbolData->allLinks)) { - if (!q->documentForFilePath(link.targetFilePath) - && followSymbolData->openedFiles.insert(link.targetFilePath).second) { - q->openExtraFile(link.targetFilePath); - } - const TextDocumentIdentifier doc(DocumentUri::fromFilePath(link.targetFilePath)); - const Position pos(link.targetLine - 1, link.targetColumn); - const TextDocumentPositionParams params(doc, pos); - SymbolInfoRequest symReq(params); - symReq.setResponseCallback([this, link, id = followSymbolData->id, reqId = symReq.id()]( - const SymbolInfoRequest::Response &response) { - qCDebug(clangdLog) << "handling symbol info reply" - << link.targetFilePath.toUserOutput() << link.targetLine; - if (!followSymbolData || id != followSymbolData->id) - return; - if (const auto result = response.result()) { - if (const auto list = Utils::get_if>(&result.value())) { - if (!list->isEmpty()) { - // According to the documentation, we should receive a single - // object here, but it's a list. No idea what it means if there's - // more than one entry. We choose the first one. - const SymbolDetails &sd = list->first(); - followSymbolData->symbolsToDisplay << qMakePair(sd.containerName() - + sd.name(), link); - } - } - } - followSymbolData->pendingSymbolInfoRequests.removeOne(reqId); - followSymbolData->virtualFuncAssistProcessor->update(); - if (followSymbolData->pendingSymbolInfoRequests.isEmpty() - && followSymbolData->pendingGotoDefRequests.isEmpty() - && followSymbolData->defLinkNode.isValid()) { - handleDocumentInfoResults(); - } - }); - followSymbolData->pendingSymbolInfoRequests << symReq.id(); - qCDebug(clangdLog) << "sending symbol info request"; - q->sendMessage(symReq, SendDocUpdates::Ignore); - - if (link == followSymbolData->defLink) - continue; - - GotoDefinitionRequest defReq(params); - defReq.setResponseCallback([this, link, id = followSymbolData->id, reqId = defReq.id()] - (const GotoDefinitionRequest::Response &response) { - qCDebug(clangdLog) << "handling additional go to definition reply for" - << link.targetFilePath << link.targetLine; - if (!followSymbolData || id != followSymbolData->id) - return; - Utils::Link newLink; - if (Utils::optional _result = response.result()) { - const GotoResult result = _result.value(); - if (const auto ploc = Utils::get_if(&result)) { - newLink = ploc->toLink(); - } else if (const auto plloc = Utils::get_if>(&result)) { - if (!plloc->isEmpty()) - newLink = plloc->value(0).toLink(); - } - } - qCDebug(clangdLog) << "def link is" << newLink.targetFilePath << newLink.targetLine; - followSymbolData->declDefMap.insert(link, newLink); - followSymbolData->pendingGotoDefRequests.removeOne(reqId); - if (followSymbolData->pendingSymbolInfoRequests.isEmpty() - && followSymbolData->pendingGotoDefRequests.isEmpty() - && followSymbolData->defLinkNode.isValid()) { - handleDocumentInfoResults(); - } - }); - followSymbolData->pendingGotoDefRequests << defReq.id(); - qCDebug(clangdLog) << "sending additional go to definition request" - << link.targetFilePath << link.targetLine; - q->sendMessage(defReq, SendDocUpdates::Ignore); - } - - const Utils::FilePath defLinkFilePath = followSymbolData->defLink.targetFilePath; - const TextDocument * const defLinkDoc = q->documentForFilePath(defLinkFilePath); - const auto defLinkDocVariant = defLinkDoc ? TextDocOrFile(defLinkDoc) - : TextDocOrFile(defLinkFilePath); - const Position defLinkPos(followSymbolData->defLink.targetLine - 1, - followSymbolData->defLink.targetColumn); - const auto astHandler = [this, id = followSymbolData->id] - (const ClangdAstNode &ast, const MessageId &) { - qCDebug(clangdLog) << "received ast response for def link"; - if (!followSymbolData || followSymbolData->id != id) - return; - followSymbolData->defLinkNode = ast; - if (followSymbolData->pendingSymbolInfoRequests.isEmpty() - && followSymbolData->pendingGotoDefRequests.isEmpty()) { - handleDocumentInfoResults(); - } - }; - getAndHandleAst(defLinkDocVariant, astHandler, AstCallbackMode::AlwaysAsync, - Range(defLinkPos, defLinkPos)); -} - -void ClangdClient::Private::handleDocumentInfoResults() -{ - followSymbolData->closeTempDocuments(); - - // If something went wrong, we just follow the original link. - if (followSymbolData->symbolsToDisplay.isEmpty()) { - followSymbolData->callback(followSymbolData->defLink); - followSymbolData.reset(); - return; - } - if (followSymbolData->symbolsToDisplay.size() == 1) { - followSymbolData->callback(followSymbolData->symbolsToDisplay.first().second); - followSymbolData.reset(); - return; - } - QTC_ASSERT(followSymbolData->virtualFuncAssistProcessor - && followSymbolData->virtualFuncAssistProcessor->running(), - followSymbolData.reset(); return); - followSymbolData->virtualFuncAssistProcessor->finalize(); -} - void ClangdClient::Private::handleDeclDefSwitchReplies() { if (!switchDeclDefData->document) { @@ -2559,104 +2246,6 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc, getAndHandleAst(doc, astHandler, AstCallbackMode::SyncIfPossible); } -void ClangdClient::VirtualFunctionAssistProcessor::cancel() -{ - resetData(true); -} - -void ClangdClient::VirtualFunctionAssistProcessor::update() -{ - if (!m_data->followSymbolData->editorWidget) - return; - setAsyncProposalAvailable(createProposal(false)); -} - -void ClangdClient::VirtualFunctionAssistProcessor::finalize() -{ - if (!m_data->followSymbolData->editorWidget) - return; - const auto proposal = createProposal(true); - if (m_data->followSymbolData->editorWidget->isInTestMode()) { - m_data->followSymbolData->symbolsToDisplay.clear(); - const auto immediateProposal = createProposal(false); - m_data->followSymbolData->editorWidget->setProposals(immediateProposal, proposal); - } else { - setAsyncProposalAvailable(proposal); - } - resetData(true); -} - -void ClangdClient::VirtualFunctionAssistProcessor::resetData(bool resetFollowSymbolData) -{ - if (!m_data) - return; - m_data->followSymbolData->virtualFuncAssistProcessor = nullptr; - if (resetFollowSymbolData) - m_data->followSymbolData.reset(); - m_data = nullptr; -} - -IAssistProposal *ClangdClient::VirtualFunctionAssistProcessor::createProposal(bool final) const -{ - QTC_ASSERT(m_data && m_data->followSymbolData, return nullptr); - - QList items; - bool needsBaseDeclEntry = !m_data->followSymbolData->defLinkNode.range() - .contains(Position(m_data->followSymbolData->cursor)); - for (const SymbolData &symbol : qAsConst(m_data->followSymbolData->symbolsToDisplay)) { - Utils::Link link = symbol.second; - if (m_data->followSymbolData->defLink == link) { - if (!needsBaseDeclEntry) - continue; - needsBaseDeclEntry = false; - } else { - const Utils::Link defLink = m_data->followSymbolData->declDefMap.value(symbol.second); - if (defLink.hasValidTarget()) - link = defLink; - } - items << createEntry(symbol.first, link); - } - if (needsBaseDeclEntry) - items << createEntry({}, m_data->followSymbolData->defLink); - if (!final) { - const auto infoItem = new CppEditor::VirtualFunctionProposalItem({}, false); - infoItem->setText(ClangdClient::tr("collecting overrides ...")); - infoItem->setOrder(-1); - items << infoItem; - } - - return new CppEditor::VirtualFunctionProposal( - m_data->followSymbolData->cursor.position(), - items, m_data->followSymbolData->openInSplit); -} - -CppEditor::VirtualFunctionProposalItem * -ClangdClient::VirtualFunctionAssistProcessor::createEntry(const QString &name, - const Utils::Link &link) const -{ - const auto item = new CppEditor::VirtualFunctionProposalItem( - link, m_data->followSymbolData->openInSplit); - QString text = name; - if (link == m_data->followSymbolData->defLink) { - item->setOrder(1000); // Ensure base declaration is on top. - if (text.isEmpty()) { - text = ClangdClient::tr(""); - } else if (m_data->followSymbolData->defLinkNode.isPureVirtualDeclaration() - || m_data->followSymbolData->defLinkNode.isPureVirtualDefinition()) { - text += " = 0"; - } - } - item->setText(text); - return item; -} - -IAssistProcessor *ClangdClient::VirtualFunctionAssistProvider::createProcessor( - const AssistInterface *) const -{ - return m_data->followSymbolData->virtualFuncAssistProcessor - = new VirtualFunctionAssistProcessor(m_data); -} - Utils::optional > ClangdDiagnostic::codeActions() const { auto actions = optionalArray("codeActions"); @@ -3009,32 +2598,6 @@ MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc, return requestAst(q, filePath, range, wrapperHandler); } -bool ClangdClient::FollowSymbolData::defLinkIsAmbiguous() const -{ - // Even if the call is to a virtual function, it might not be ambiguous: - // class A { virtual void f(); }; class B : public A { void f() override { A::f(); } }; - if (!cursorNode->mightBeAmbiguousVirtualCall() && !cursorNode->isPureVirtualDeclaration()) - return false; - - // If we have up-to-date highlighting info, we know whether we are dealing with - // a virtual call. - if (editorWidget) { - const auto highlightingData = - q->d->highlightingData.constFind(editorWidget->textDocument()); - if (highlightingData != q->d->highlightingData.constEnd() - && highlightingData->virtualRanges.second == docRevision) { - const auto matcher = [cursorRange = cursorNode->range()](const Range &r) { - return cursorRange.overlaps(r); - }; - return Utils::contains(highlightingData->virtualRanges.first, matcher); - } - } - - // Otherwise, we accept potentially doing more work than needed rather than not catching - // possible overrides. - return true; -} - class MemoryTree : public JsonObject { public: diff --git a/src/plugins/clangcodemodel/clangdclient.h b/src/plugins/clangcodemodel/clangdclient.h index b644749f469..13530b168f7 100644 --- a/src/plugins/clangcodemodel/clangdclient.h +++ b/src/plugins/clangcodemodel/clangdclient.h @@ -48,6 +48,9 @@ class IAssistProposal; namespace ClangCodeModel { namespace Internal { +class ClangdAstNode; + +Q_DECLARE_LOGGING_CATEGORY(clangdLog); void setupClangdConfigFile(); @@ -103,6 +106,24 @@ public: void switchIssuePaneEntries(const Utils::FilePath &filePath); void addTask(const ProjectExplorer::Task &task); void clearTasks(const Utils::FilePath &filePath); + Utils::optional hasVirtualFunctionAt(TextEditor::TextDocument *doc, int revision, + const LanguageServerProtocol::Range &range); + + using TextDocOrFile = Utils::variant; + using AstHandler = std::function; + enum class AstCallbackMode { SyncIfPossible, AlwaysAsync }; + LanguageServerProtocol::MessageId getAndHandleAst(const TextDocOrFile &doc, + const AstHandler &astHandler, + AstCallbackMode callbackMode, + const LanguageServerProtocol::Range &range); + + using SymbolInfoHandler = std::function; + LanguageServerProtocol::MessageId requestSymbolInfo( + const Utils::FilePath &filePath, + const LanguageServerProtocol::Position &position, + const SymbolInfoHandler &handler); signals: void indexingFinished(); @@ -125,7 +146,6 @@ private: LanguageClient::DiagnosticManager *createDiagnosticManager() override; class Private; - class FollowSymbolData; class VirtualFunctionAssistProcessor; class VirtualFunctionAssistProvider; class ClangdFunctionHintProcessor; diff --git a/src/plugins/clangcodemodel/clangdfollowsymbol.cpp b/src/plugins/clangcodemodel/clangdfollowsymbol.cpp new file mode 100644 index 00000000000..53ec97342f4 --- /dev/null +++ b/src/plugins/clangcodemodel/clangdfollowsymbol.cpp @@ -0,0 +1,505 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangdfollowsymbol.h" + +#include "clangdast.h" +#include "clangdclient.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace CppEditor; +using namespace LanguageServerProtocol; +using namespace TextEditor; +using namespace Utils; + +namespace ClangCodeModel::Internal { +using SymbolData = QPair; +using SymbolDataList = QList; + +class ClangdFollowSymbol::VirtualFunctionAssistProcessor : public IAssistProcessor +{ +public: + VirtualFunctionAssistProcessor(ClangdFollowSymbol *followSymbol) + : m_followSymbol(followSymbol) {} + + void cancel() override { resetData(true); } + bool running() override { return m_followSymbol; } + void update(); + void finalize(); + void resetData(bool resetFollowSymbolData); + +private: + IAssistProposal *perform(const AssistInterface *) override + { + return nullptr; + } + + IAssistProposal *immediateProposal(const AssistInterface *) override + { + return createProposal(false); + } + + IAssistProposal *immediateProposalImpl() const; + IAssistProposal *createProposal(bool final) const; + VirtualFunctionProposalItem *createEntry(const QString &name, const Link &link) const; + + QPointer m_followSymbol; +}; + +class ClangdFollowSymbol::VirtualFunctionAssistProvider : public IAssistProvider +{ +public: + VirtualFunctionAssistProvider(ClangdFollowSymbol *followSymbol) + : m_followSymbol(followSymbol) {} + +private: + RunType runType() const override { return Asynchronous; } + IAssistProcessor *createProcessor(const AssistInterface *) const override; + + const QPointer m_followSymbol; +}; + +class ClangdFollowSymbol::Private +{ +public: + Private(ClangdFollowSymbol *q, ClangdClient *client, const QTextCursor &cursor, + CppEditorWidget *editorWidget, const FilePath &filePath, const LinkHandler &callback, + bool openInSplit) + : q(q), client(client), cursor(cursor), editorWidget(editorWidget), + uri(DocumentUri::fromFilePath(filePath)), callback(callback), + virtualFuncAssistProvider(q), + docRevision(editorWidget ? editorWidget->textDocument()->document()->revision() : -1), + openInSplit(openInSplit) {} + + void handleGotoDefinitionResult(); + void sendGotoImplementationRequest(const Utils::Link &link); + void handleGotoImplementationResult(const GotoImplementationRequest::Response &response); + void handleDocumentInfoResults(); + void closeTempDocuments(); + bool addOpenFile(const FilePath &filePath); + bool defLinkIsAmbiguous() const; + + ClangdFollowSymbol * const q; + ClangdClient * const client; + const QTextCursor cursor; + const QPointer editorWidget; + const DocumentUri uri; + const LinkHandler callback; + VirtualFunctionAssistProvider virtualFuncAssistProvider; + QList pendingSymbolInfoRequests; + QList pendingGotoImplRequests; + QList pendingGotoDefRequests; + const int docRevision; + const bool openInSplit; + + Link defLink; + Links allLinks; + QHash declDefMap; + optional cursorNode; + ClangdAstNode defLinkNode; + SymbolDataList symbolsToDisplay; + std::set openedFiles; + VirtualFunctionAssistProcessor *virtualFuncAssistProcessor = nullptr; + bool finished = false; +}; + +ClangdFollowSymbol::ClangdFollowSymbol(ClangdClient *client, const QTextCursor &cursor, + CppEditorWidget *editorWidget, TextDocument *document, const LinkHandler &callback, + bool openInSplit) + : QObject(client), + d(new Private(this, client, cursor, editorWidget, document->filePath(), callback, + openInSplit)) +{ + // Step 1: Follow the symbol via "Go to Definition". At the same time, request the + // AST node corresponding to the cursor position, so we can find out whether + // we have to look for overrides. + const auto gotoDefCallback = [self = QPointer(this)](const Utils::Link &link) { + qCDebug(clangdLog) << "received go to definition response"; + if (!self) + return; + if (!link.hasValidTarget()) { + emit self->done(); + return; + } + self->d->defLink = link; + if (self->d->cursorNode) + self->d->handleGotoDefinitionResult(); + }; + client->symbolSupport().findLinkAt(document, cursor, std::move(gotoDefCallback), true); + + const auto astHandler = [self = QPointer(this)](const ClangdAstNode &ast, const MessageId &) { + qCDebug(clangdLog) << "received ast response for cursor"; + if (!self) + return; + self->d->cursorNode = ast; + if (self->d->defLink.hasValidTarget()) + self->d->handleGotoDefinitionResult(); + }; + client->getAndHandleAst(document, astHandler, ClangdClient::AstCallbackMode::AlwaysAsync, + Range(cursor)); +} + +ClangdFollowSymbol::~ClangdFollowSymbol() +{ + d->closeTempDocuments(); + if (d->virtualFuncAssistProcessor) + d->virtualFuncAssistProcessor->resetData(false); + for (const MessageId &id : qAsConst(d->pendingSymbolInfoRequests)) + d->client->cancelRequest(id); + for (const MessageId &id : qAsConst(d->pendingGotoImplRequests)) + d->client->cancelRequest(id); + for (const MessageId &id : qAsConst(d->pendingGotoDefRequests)) + d->client->cancelRequest(id); +} + +void ClangdFollowSymbol::clear() +{ + d->openedFiles.clear(); + d->pendingSymbolInfoRequests.clear(); + d->pendingGotoImplRequests.clear(); + d->pendingGotoDefRequests.clear(); +} + +bool ClangdFollowSymbol::Private::defLinkIsAmbiguous() const +{ + // Even if the call is to a virtual function, it might not be ambiguous: + // class A { virtual void f(); }; class B : public A { void f() override { A::f(); } }; + if (!cursorNode->mightBeAmbiguousVirtualCall() && !cursorNode->isPureVirtualDeclaration()) + return false; + + // If we have up-to-date highlighting info, we know whether we are dealing with + // a virtual call. + if (editorWidget) { + const auto result = client->hasVirtualFunctionAt(editorWidget->textDocument(), + docRevision, cursorNode->range()); + if (result.has_value()) + return *result; + } + + // Otherwise, we accept potentially doing more work than needed rather than not catching + // possible overrides. + return true; +} + +bool ClangdFollowSymbol::Private::addOpenFile(const FilePath &filePath) +{ + return openedFiles.insert(filePath).second; +} + +void ClangdFollowSymbol::Private::handleDocumentInfoResults() +{ + closeTempDocuments(); + + // If something went wrong, we just follow the original link. + if (symbolsToDisplay.isEmpty()) { + callback(defLink); + emit q->done(); + return; + } + + if (symbolsToDisplay.size() == 1) { + callback(symbolsToDisplay.first().second); + emit q->done(); + return; + } + + QTC_ASSERT(virtualFuncAssistProcessor && virtualFuncAssistProcessor->running(), + emit q->done(); return); + virtualFuncAssistProcessor->finalize(); +} + +void ClangdFollowSymbol::Private::sendGotoImplementationRequest(const Link &link) +{ + if (!client->documentForFilePath(link.targetFilePath) && addOpenFile(link.targetFilePath)) + client->openExtraFile(link.targetFilePath); + const Position position(link.targetLine - 1, link.targetColumn); + const TextDocumentIdentifier documentId(DocumentUri::fromFilePath(link.targetFilePath)); + GotoImplementationRequest req(TextDocumentPositionParams(documentId, position)); + req.setResponseCallback([sentinel = QPointer(q), this, reqId = req.id()] + (const GotoImplementationRequest::Response &response) { + qCDebug(clangdLog) << "received go to implementation reply"; + if (!sentinel) + return; + pendingGotoImplRequests.removeOne(reqId); + handleGotoImplementationResult(response); + }); + client->sendMessage(req, ClangdClient::SendDocUpdates::Ignore); + pendingGotoImplRequests << req.id(); + qCDebug(clangdLog) << "sending go to implementation request" << link.targetLine; +} + +void ClangdFollowSymbol::VirtualFunctionAssistProcessor::update() +{ + if (!m_followSymbol->d->editorWidget) + return; + setAsyncProposalAvailable(createProposal(false)); +} + +void ClangdFollowSymbol::VirtualFunctionAssistProcessor::finalize() +{ + if (!m_followSymbol->d->editorWidget) + return; + const auto proposal = createProposal(true); + if (m_followSymbol->d->editorWidget->isInTestMode()) { + m_followSymbol->d->symbolsToDisplay.clear(); + const auto immediateProposal = createProposal(false); + m_followSymbol->d->editorWidget->setProposals(immediateProposal, proposal); + } else { + setAsyncProposalAvailable(proposal); + } + resetData(true); +} + +void ClangdFollowSymbol::VirtualFunctionAssistProcessor::resetData(bool resetFollowSymbolData) +{ + if (!m_followSymbol) + return; + m_followSymbol->d->virtualFuncAssistProcessor = nullptr; + if (resetFollowSymbolData) + emit m_followSymbol->done(); + m_followSymbol = nullptr; +} + +IAssistProposal * +ClangdFollowSymbol::VirtualFunctionAssistProcessor::createProposal(bool final) const +{ + QTC_ASSERT(m_followSymbol, return nullptr); + + QList items; + bool needsBaseDeclEntry = !m_followSymbol->d->defLinkNode.range() + .contains(Position(m_followSymbol->d->cursor)); + for (const SymbolData &symbol : qAsConst(m_followSymbol->d->symbolsToDisplay)) { + Link link = symbol.second; + if (m_followSymbol->d->defLink == link) { + if (!needsBaseDeclEntry) + continue; + needsBaseDeclEntry = false; + } else { + const Link defLink = m_followSymbol->d->declDefMap.value(symbol.second); + if (defLink.hasValidTarget()) + link = defLink; + } + items << createEntry(symbol.first, link); + } + if (needsBaseDeclEntry) + items << createEntry({}, m_followSymbol->d->defLink); + if (!final) { + const auto infoItem = new VirtualFunctionProposalItem({}, false); + infoItem->setText(ClangdClient::tr("collecting overrides ...")); + infoItem->setOrder(-1); + items << infoItem; + } + + return new VirtualFunctionProposal(m_followSymbol->d->cursor.position(), items, + m_followSymbol->d->openInSplit); +} + +CppEditor::VirtualFunctionProposalItem * +ClangdFollowSymbol::VirtualFunctionAssistProcessor::createEntry(const QString &name, + const Link &link) const +{ + const auto item = new VirtualFunctionProposalItem(link, m_followSymbol->d->openInSplit); + QString text = name; + if (link == m_followSymbol->d->defLink) { + item->setOrder(1000); // Ensure base declaration is on top. + if (text.isEmpty()) { + text = ClangdClient::tr(""); + } else if (m_followSymbol->d->defLinkNode.isPureVirtualDeclaration() + || m_followSymbol->d->defLinkNode.isPureVirtualDefinition()) { + text += " = 0"; + } + } + item->setText(text); + return item; +} + +IAssistProcessor * +ClangdFollowSymbol::VirtualFunctionAssistProvider::createProcessor(const AssistInterface *) const +{ + return m_followSymbol->d->virtualFuncAssistProcessor + = new VirtualFunctionAssistProcessor(m_followSymbol); +} + +void ClangdFollowSymbol::Private::handleGotoDefinitionResult() +{ + QTC_ASSERT(defLink.hasValidTarget(), return); + + qCDebug(clangdLog) << "handling go to definition result"; + + // No dis-ambiguation necessary. Call back with the link and finish. + if (!defLinkIsAmbiguous()) { + callback(defLink); + emit q->done(); + return; + } + + // Step 2: Get all possible overrides via "Go to Implementation". + // Note that we have to do this for all member function calls, because + // we cannot tell here whether the member function is virtual. + allLinks << defLink; + sendGotoImplementationRequest(defLink); +} + +void ClangdFollowSymbol::Private::handleGotoImplementationResult( + const GotoImplementationRequest::Response &response) +{ + if (const optional &result = response.result()) { + QList newLinks; + if (const auto ploc = get_if(&*result)) + newLinks = {ploc->toLink()}; + if (const auto plloc = get_if>(&*result)) + newLinks = transform(*plloc, &Location::toLink); + for (const Link &link : qAsConst(newLinks)) { + if (!allLinks.contains(link)) { + allLinks << link; + + // We must do this recursively, because clangd reports only the first + // level of overrides. + sendGotoImplementationRequest(link); + } + } + } + + // We didn't find any further candidates, so jump to the original definition link. + if (allLinks.size() == 1 && pendingGotoImplRequests.isEmpty()) { + callback(allLinks.first()); + emit q->done(); + return; + } + + // As soon as we know that there is more than one candidate, we start the code assist + // procedure, to let the user know that things are happening. + if (allLinks.size() > 1 && !virtualFuncAssistProcessor && editorWidget) + editorWidget->invokeTextEditorWidgetAssist(FollowSymbol, &virtualFuncAssistProvider); + + if (!pendingGotoImplRequests.isEmpty()) + return; + + // Step 3: We are done looking for overrides, and we found at least one. + // Make a symbol info request for each link to get the class names. + // Also get the AST for the base declaration, so we can find out whether it's + // pure virtual and mark it accordingly. + // In addition, we need to follow all override links, because for these, clangd + // gives us the declaration instead of the definition. + for (const Link &link : qAsConst(allLinks)) { + if (!client->documentForFilePath(link.targetFilePath) && addOpenFile(link.targetFilePath)) + client->openExtraFile(link.targetFilePath); + const auto symbolInfoHandler = [sentinel = QPointer(q), this, link]( + const QString &name, const QString &prefix, const MessageId &reqId) { + qCDebug(clangdLog) << "handling symbol info reply" + << link.targetFilePath.toUserOutput() << link.targetLine; + if (!sentinel) + return; + if (!name.isEmpty()) + symbolsToDisplay << qMakePair(prefix + name, link); + pendingSymbolInfoRequests.removeOne(reqId); + virtualFuncAssistProcessor->update(); + if (pendingSymbolInfoRequests.isEmpty() && pendingGotoDefRequests.isEmpty() + && defLinkNode.isValid()) { + handleDocumentInfoResults(); + } + }; + const Position pos(link.targetLine - 1, link.targetColumn); + const MessageId reqId = client->requestSymbolInfo(link.targetFilePath, pos, + symbolInfoHandler); + pendingSymbolInfoRequests << reqId; + qCDebug(clangdLog) << "sending symbol info request"; + + if (link == defLink) + continue; + + const TextDocumentIdentifier doc(DocumentUri::fromFilePath(link.targetFilePath)); + const TextDocumentPositionParams params(doc, pos); + GotoDefinitionRequest defReq(params); + defReq.setResponseCallback([this, link, sentinel = QPointer(q), reqId = defReq.id()] + (const GotoDefinitionRequest::Response &response) { + qCDebug(clangdLog) << "handling additional go to definition reply for" + << link.targetFilePath << link.targetLine; + if (!sentinel) + return; + Link newLink; + if (optional _result = response.result()) { + const GotoResult result = _result.value(); + if (const auto ploc = get_if(&result)) { + newLink = ploc->toLink(); + } else if (const auto plloc = get_if>(&result)) { + if (!plloc->isEmpty()) + newLink = plloc->value(0).toLink(); + } + } + qCDebug(clangdLog) << "def link is" << newLink.targetFilePath << newLink.targetLine; + declDefMap.insert(link, newLink); + pendingGotoDefRequests.removeOne(reqId); + if (pendingSymbolInfoRequests.isEmpty() && pendingGotoDefRequests.isEmpty() + && defLinkNode.isValid()) { + handleDocumentInfoResults(); + } + }); + pendingGotoDefRequests << defReq.id(); + qCDebug(clangdLog) << "sending additional go to definition request" + << link.targetFilePath << link.targetLine; + client->sendMessage(defReq, ClangdClient::SendDocUpdates::Ignore); + } + + const FilePath defLinkFilePath = defLink.targetFilePath; + const TextDocument * const defLinkDoc = client->documentForFilePath(defLinkFilePath); + const auto defLinkDocVariant = defLinkDoc ? ClangdClient::TextDocOrFile(defLinkDoc) + : ClangdClient::TextDocOrFile(defLinkFilePath); + const Position defLinkPos(defLink.targetLine - 1, defLink.targetColumn); + const auto astHandler = [this, sentinel = QPointer(q)] + (const ClangdAstNode &ast, const MessageId &) { + qCDebug(clangdLog) << "received ast response for def link"; + if (!sentinel) + return; + defLinkNode = ast; + if (pendingSymbolInfoRequests.isEmpty() && pendingGotoDefRequests.isEmpty()) + handleDocumentInfoResults(); + }; + client->getAndHandleAst(defLinkDocVariant, astHandler, + ClangdClient::AstCallbackMode::AlwaysAsync, + Range(defLinkPos, defLinkPos)); +} + +void ClangdFollowSymbol::Private::closeTempDocuments() +{ + for (const FilePath &fp : qAsConst(openedFiles)) { + if (!client->documentForFilePath(fp)) + client->closeExtraFile(fp); + } + openedFiles.clear(); +} + +} // namespace ClangCodeModel::Internal diff --git a/src/plugins/clangcodemodel/clangdfollowsymbol.h b/src/plugins/clangcodemodel/clangdfollowsymbol.h new file mode 100644 index 00000000000..4512675d041 --- /dev/null +++ b/src/plugins/clangcodemodel/clangdfollowsymbol.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include + +namespace CppEditor { class CppEditorWidget; } +namespace TextEditor { class TextDocument; } + +QT_BEGIN_NAMESPACE +class QTextCursor; +QT_END_NAMESPACE + +namespace ClangCodeModel::Internal { +class ClangdAstNode; +class ClangdClient; + +class ClangdFollowSymbol : public QObject +{ + Q_OBJECT +public: + ClangdFollowSymbol(ClangdClient *client, const QTextCursor &cursor, + CppEditor::CppEditorWidget *editorWidget, + TextEditor::TextDocument *document, const Utils::LinkHandler &callback, + bool openInSplit); + ~ClangdFollowSymbol(); + void clear(); + +signals: + void done(); + +private: + class VirtualFunctionAssistProcessor; + class VirtualFunctionAssistProvider; + + class Private; + Private * const d; +}; + +} // namespace ClangCodeModel::Internal