2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2022 The Qt Company Ltd.
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
2022-06-07 11:19:41 +02:00
|
|
|
|
|
|
|
|
#include "clangdfollowsymbol.h"
|
|
|
|
|
|
|
|
|
|
#include "clangdast.h"
|
|
|
|
|
#include "clangdclient.h"
|
|
|
|
|
|
|
|
|
|
#include <cppeditor/cppeditorwidget.h>
|
|
|
|
|
#include <cppeditor/cppvirtualfunctionassistprovider.h>
|
|
|
|
|
#include <cppeditor/cppvirtualfunctionproposalitem.h>
|
|
|
|
|
#include <languageclient/languageclientsymbolsupport.h>
|
|
|
|
|
#include <languageserverprotocol/lsptypes.h>
|
|
|
|
|
#include <languageserverprotocol/jsonrpcmessages.h>
|
2022-11-07 12:02:07 +01:00
|
|
|
#include <texteditor/codeassist/assistinterface.h>
|
2022-06-07 11:19:41 +02:00
|
|
|
#include <texteditor/codeassist/iassistprocessor.h>
|
|
|
|
|
#include <texteditor/codeassist/iassistprovider.h>
|
|
|
|
|
#include <texteditor/textdocument.h>
|
|
|
|
|
|
2022-06-16 15:21:34 +02:00
|
|
|
#include <QApplication>
|
2022-06-07 11:19:41 +02:00
|
|
|
#include <QPointer>
|
|
|
|
|
|
|
|
|
|
using namespace CppEditor;
|
|
|
|
|
using namespace LanguageServerProtocol;
|
|
|
|
|
using namespace TextEditor;
|
|
|
|
|
using namespace Utils;
|
|
|
|
|
|
|
|
|
|
namespace ClangCodeModel::Internal {
|
|
|
|
|
using SymbolData = QPair<QString, Link>;
|
|
|
|
|
using SymbolDataList = QList<SymbolData>;
|
|
|
|
|
|
|
|
|
|
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:
|
2022-11-15 14:19:06 +01:00
|
|
|
IAssistProposal *perform() override
|
2022-06-07 11:19:41 +02:00
|
|
|
{
|
|
|
|
|
return createProposal(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IAssistProposal *createProposal(bool final) const;
|
|
|
|
|
VirtualFunctionProposalItem *createEntry(const QString &name, const Link &link) const;
|
|
|
|
|
|
|
|
|
|
QPointer<ClangdFollowSymbol> m_followSymbol;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class ClangdFollowSymbol::VirtualFunctionAssistProvider : public IAssistProvider
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
VirtualFunctionAssistProvider(ClangdFollowSymbol *followSymbol)
|
|
|
|
|
: m_followSymbol(followSymbol) {}
|
|
|
|
|
|
|
|
|
|
private:
|
2022-11-15 14:19:06 +01:00
|
|
|
IAssistProcessor *createProcessor(const AssistInterface *interface) const override;
|
2022-06-07 11:19:41 +02:00
|
|
|
|
|
|
|
|
const QPointer<ClangdFollowSymbol> 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),
|
2022-12-15 07:23:55 +01:00
|
|
|
uri(client->hostPathToServerUri(filePath)), callback(callback),
|
2022-06-07 11:19:41 +02:00
|
|
|
virtualFuncAssistProvider(q),
|
|
|
|
|
docRevision(editorWidget ? editorWidget->textDocument()->document()->revision() : -1),
|
|
|
|
|
openInSplit(openInSplit) {}
|
|
|
|
|
|
2022-09-26 11:53:22 +02:00
|
|
|
void goToTypeDefinition();
|
2022-06-07 11:19:41 +02:00
|
|
|
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<CppEditor::CppEditorWidget> editorWidget;
|
|
|
|
|
const DocumentUri uri;
|
|
|
|
|
const LinkHandler callback;
|
|
|
|
|
VirtualFunctionAssistProvider virtualFuncAssistProvider;
|
|
|
|
|
QList<MessageId> pendingSymbolInfoRequests;
|
|
|
|
|
QList<MessageId> pendingGotoImplRequests;
|
|
|
|
|
QList<MessageId> pendingGotoDefRequests;
|
|
|
|
|
const int docRevision;
|
|
|
|
|
const bool openInSplit;
|
|
|
|
|
|
|
|
|
|
Link defLink;
|
|
|
|
|
Links allLinks;
|
|
|
|
|
QHash<Link, Link> declDefMap;
|
2022-08-26 10:30:00 +02:00
|
|
|
std::optional<ClangdAstNode> cursorNode;
|
2022-06-07 11:19:41 +02:00
|
|
|
ClangdAstNode defLinkNode;
|
|
|
|
|
SymbolDataList symbolsToDisplay;
|
|
|
|
|
std::set<FilePath> openedFiles;
|
|
|
|
|
VirtualFunctionAssistProcessor *virtualFuncAssistProcessor = nullptr;
|
2022-06-21 11:02:54 +02:00
|
|
|
QMetaObject::Connection focusChangedConnection;
|
2022-07-07 15:46:53 +02:00
|
|
|
bool done = false;
|
2022-06-07 11:19:41 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ClangdFollowSymbol::ClangdFollowSymbol(ClangdClient *client, const QTextCursor &cursor,
|
|
|
|
|
CppEditorWidget *editorWidget, TextDocument *document, const LinkHandler &callback,
|
2022-09-26 11:53:22 +02:00
|
|
|
FollowTo followTo, bool openInSplit)
|
2022-06-07 11:19:41 +02:00
|
|
|
: QObject(client),
|
|
|
|
|
d(new Private(this, client, cursor, editorWidget, document->filePath(), callback,
|
|
|
|
|
openInSplit))
|
|
|
|
|
{
|
2022-06-16 15:21:34 +02:00
|
|
|
// Abort if the user does something else with the document in the meantime.
|
2022-07-18 18:08:01 +02:00
|
|
|
connect(document, &TextDocument::contentsChanged, this, [this] { emitDone(); },
|
2022-06-16 15:21:34 +02:00
|
|
|
Qt::QueuedConnection);
|
|
|
|
|
if (editorWidget) {
|
|
|
|
|
connect(editorWidget, &CppEditorWidget::cursorPositionChanged,
|
2022-07-18 18:08:01 +02:00
|
|
|
this, [this] { emitDone(); }, Qt::QueuedConnection);
|
2022-06-16 15:21:34 +02:00
|
|
|
}
|
2022-06-21 11:02:54 +02:00
|
|
|
d->focusChangedConnection = connect(qApp, &QApplication::focusChanged,
|
2022-07-18 18:08:01 +02:00
|
|
|
this, [this] { emitDone(); }, Qt::QueuedConnection);
|
2022-06-16 15:21:34 +02:00
|
|
|
|
2022-09-26 11:53:22 +02:00
|
|
|
if (followTo == FollowTo::SymbolType) {
|
|
|
|
|
d->goToTypeDefinition();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-07 11:19:41 +02:00
|
|
|
// 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()) {
|
2022-07-07 15:46:53 +02:00
|
|
|
self->emitDone();
|
2022-06-07 11:19:41 +02:00
|
|
|
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);
|
2022-10-07 14:46:06 +02:00
|
|
|
for (const MessageId &id : std::as_const(d->pendingSymbolInfoRequests))
|
2022-06-07 11:19:41 +02:00
|
|
|
d->client->cancelRequest(id);
|
2022-10-07 14:46:06 +02:00
|
|
|
for (const MessageId &id : std::as_const(d->pendingGotoImplRequests))
|
2022-06-07 11:19:41 +02:00
|
|
|
d->client->cancelRequest(id);
|
2022-10-07 14:46:06 +02:00
|
|
|
for (const MessageId &id : std::as_const(d->pendingGotoDefRequests))
|
2022-06-07 11:19:41 +02:00
|
|
|
d->client->cancelRequest(id);
|
2022-07-18 18:46:30 +02:00
|
|
|
delete d;
|
2022-06-07 11:19:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangdFollowSymbol::clear()
|
|
|
|
|
{
|
|
|
|
|
d->openedFiles.clear();
|
|
|
|
|
d->pendingSymbolInfoRequests.clear();
|
|
|
|
|
d->pendingGotoImplRequests.clear();
|
|
|
|
|
d->pendingGotoDefRequests.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-18 18:08:01 +02:00
|
|
|
void ClangdFollowSymbol::emitDone(const Link &link)
|
2022-07-07 15:46:53 +02:00
|
|
|
{
|
|
|
|
|
if (d->done)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
d->done = true;
|
2022-07-18 18:08:01 +02:00
|
|
|
if (link.hasValidTarget())
|
|
|
|
|
d->callback(link);
|
2022-07-07 15:46:53 +02:00
|
|
|
emit done();
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-07 11:19:41 +02:00
|
|
|
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()) {
|
2022-07-18 18:08:01 +02:00
|
|
|
q->emitDone(defLink);
|
2022-06-07 11:19:41 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (symbolsToDisplay.size() == 1) {
|
2022-07-18 18:08:01 +02:00
|
|
|
q->emitDone(symbolsToDisplay.first().second);
|
2022-06-07 11:19:41 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTC_ASSERT(virtualFuncAssistProcessor && virtualFuncAssistProcessor->running(),
|
2022-07-07 15:46:53 +02:00
|
|
|
q->emitDone(); return);
|
2022-06-07 11:19:41 +02:00
|
|
|
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);
|
2022-12-15 07:23:55 +01:00
|
|
|
const TextDocumentIdentifier documentId(client->hostPathToServerUri(link.targetFilePath));
|
2022-06-07 11:19:41 +02:00
|
|
|
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)
|
2022-07-07 15:46:53 +02:00
|
|
|
m_followSymbol->emitDone();
|
2022-06-07 11:19:41 +02:00
|
|
|
m_followSymbol = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IAssistProposal *
|
|
|
|
|
ClangdFollowSymbol::VirtualFunctionAssistProcessor::createProposal(bool final) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_followSymbol, return nullptr);
|
|
|
|
|
|
|
|
|
|
QList<AssistProposalItemInterface *> items;
|
|
|
|
|
bool needsBaseDeclEntry = !m_followSymbol->d->defLinkNode.range()
|
|
|
|
|
.contains(Position(m_followSymbol->d->cursor));
|
2022-10-07 14:46:06 +02:00
|
|
|
for (const SymbolData &symbol : std::as_const(m_followSymbol->d->symbolsToDisplay)) {
|
2022-06-07 11:19:41 +02:00
|
|
|
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("<base declaration>");
|
|
|
|
|
} 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);
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-26 11:53:22 +02:00
|
|
|
void ClangdFollowSymbol::Private::goToTypeDefinition()
|
|
|
|
|
{
|
|
|
|
|
GotoTypeDefinitionRequest req(TextDocumentPositionParams(TextDocumentIdentifier{uri},
|
|
|
|
|
Position(cursor)));
|
|
|
|
|
req.setResponseCallback([sentinel = QPointer(q), this, reqId = req.id()]
|
|
|
|
|
(const GotoTypeDefinitionRequest::Response &response) {
|
|
|
|
|
qCDebug(clangdLog) << "received go to type definition reply";
|
|
|
|
|
if (!sentinel)
|
|
|
|
|
return;
|
|
|
|
|
Link link;
|
2022-12-15 07:23:55 +01:00
|
|
|
|
2022-09-26 11:53:22 +02:00
|
|
|
if (const std::optional<GotoResult> &result = response.result()) {
|
|
|
|
|
if (const auto ploc = std::get_if<Location>(&*result)) {
|
2022-12-15 07:23:55 +01:00
|
|
|
link = {ploc->toLink(client->hostPathMapper())};
|
2022-09-26 11:53:22 +02:00
|
|
|
} else if (const auto plloc = std::get_if<QList<Location>>(&*result)) {
|
|
|
|
|
if (!plloc->empty())
|
2022-12-15 07:23:55 +01:00
|
|
|
link = plloc->first().toLink(client->hostPathMapper());
|
2022-09-26 11:53:22 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
q->emitDone(link);
|
|
|
|
|
});
|
|
|
|
|
client->sendMessage(req, ClangdClient::SendDocUpdates::Ignore);
|
|
|
|
|
qCDebug(clangdLog) << "sending go to type definition request";
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-07 11:19:41 +02:00
|
|
|
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()) {
|
2022-07-18 18:08:01 +02:00
|
|
|
q->emitDone(defLink);
|
2022-06-07 11:19:41 +02:00
|
|
|
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)
|
|
|
|
|
{
|
2022-12-15 07:23:55 +01:00
|
|
|
auto transformLink = [mapper = client->hostPathMapper()](const Location &loc) {
|
|
|
|
|
return loc.toLink(mapper);
|
|
|
|
|
};
|
2022-08-26 10:30:00 +02:00
|
|
|
if (const std::optional<GotoResult> &result = response.result()) {
|
2022-06-07 11:19:41 +02:00
|
|
|
QList<Link> newLinks;
|
2022-08-19 14:47:59 +02:00
|
|
|
if (const auto ploc = std::get_if<Location>(&*result))
|
2022-12-15 07:23:55 +01:00
|
|
|
newLinks = {transformLink(*ploc)};
|
2022-08-19 14:47:59 +02:00
|
|
|
if (const auto plloc = std::get_if<QList<Location>>(&*result))
|
2022-12-15 07:23:55 +01:00
|
|
|
newLinks = transform(*plloc, transformLink);
|
2022-10-07 14:46:06 +02:00
|
|
|
for (const Link &link : std::as_const(newLinks)) {
|
2022-06-07 11:19:41 +02:00
|
|
|
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()) {
|
2022-07-18 18:08:01 +02:00
|
|
|
q->emitDone(allLinks.first());
|
2022-06-07 11:19:41 +02:00
|
|
|
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.
|
2022-06-21 11:02:54 +02:00
|
|
|
if (allLinks.size() > 1 && !virtualFuncAssistProcessor && editorWidget) {
|
|
|
|
|
QObject::disconnect(focusChangedConnection);
|
2022-06-07 11:19:41 +02:00
|
|
|
editorWidget->invokeTextEditorWidgetAssist(FollowSymbol, &virtualFuncAssistProvider);
|
2022-06-21 11:02:54 +02:00
|
|
|
}
|
2022-06-07 11:19:41 +02:00
|
|
|
|
|
|
|
|
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.
|
2022-10-07 14:46:06 +02:00
|
|
|
for (const Link &link : std::as_const(allLinks)) {
|
2022-06-07 11:19:41 +02:00
|
|
|
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())
|
2022-09-30 14:11:20 +02:00
|
|
|
symbolsToDisplay.push_back({prefix + name, link});
|
2022-06-07 11:19:41 +02:00
|
|
|
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;
|
|
|
|
|
|
2022-12-15 07:23:55 +01:00
|
|
|
const TextDocumentIdentifier doc(client->hostPathToServerUri(link.targetFilePath));
|
2022-06-07 11:19:41 +02:00
|
|
|
const TextDocumentPositionParams params(doc, pos);
|
|
|
|
|
GotoDefinitionRequest defReq(params);
|
2022-12-15 07:23:55 +01:00
|
|
|
defReq.setResponseCallback(
|
|
|
|
|
[this, link, transformLink, 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 (std::optional<GotoResult> _result = response.result()) {
|
|
|
|
|
const GotoResult result = _result.value();
|
|
|
|
|
if (const auto ploc = std::get_if<Location>(&result)) {
|
|
|
|
|
newLink = transformLink(*ploc);
|
|
|
|
|
} else if (const auto plloc = std::get_if<QList<Location>>(&result)) {
|
|
|
|
|
if (!plloc->isEmpty())
|
|
|
|
|
newLink = transformLink(plloc->value(0));
|
|
|
|
|
}
|
2022-06-07 11:19:41 +02:00
|
|
|
}
|
2022-12-15 07:23:55 +01:00
|
|
|
qCDebug(clangdLog) << "def link is" << newLink.targetFilePath << newLink.targetLine;
|
|
|
|
|
declDefMap.insert(link, newLink);
|
|
|
|
|
pendingGotoDefRequests.removeOne(reqId);
|
|
|
|
|
if (pendingSymbolInfoRequests.isEmpty() && pendingGotoDefRequests.isEmpty()
|
2022-06-07 11:19:41 +02:00
|
|
|
&& defLinkNode.isValid()) {
|
2022-12-15 07:23:55 +01:00
|
|
|
handleDocumentInfoResults();
|
|
|
|
|
}
|
|
|
|
|
});
|
2022-06-07 11:19:41 +02:00
|
|
|
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()
|
|
|
|
|
{
|
2022-10-07 14:46:06 +02:00
|
|
|
for (const FilePath &fp : std::as_const(openedFiles)) {
|
2022-06-07 11:19:41 +02:00
|
|
|
if (!client->documentForFilePath(fp))
|
|
|
|
|
client->closeExtraFile(fp);
|
|
|
|
|
}
|
|
|
|
|
openedFiles.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace ClangCodeModel::Internal
|