ClangCodeModel: Move "follow symbol" into its own class

Change-Id: Ic64c7275debaa59c524f349fd38460f47f826ecd
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2022-06-07 11:19:41 +02:00
parent 457fe4eb87
commit 49bb40f19e
6 changed files with 674 additions and 518 deletions

View File

@@ -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

View File

@@ -34,6 +34,8 @@ QtcPlugin {
"clangdast.h",
"clangdclient.cpp",
"clangdclient.h",
"clangdfollowsymbol.cpp",
"clangdfollowsymbol.h",
"clangdiagnostictooltipwidget.cpp",
"clangdiagnostictooltipwidget.h",
"clangdlocatorfilters.cpp",

View File

@@ -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<QString, Utils::Link>;
using SymbolDataList = QList<SymbolData>;
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<CppEditor::CppEditorWidget> editorWidget;
const DocumentUri uri;
const Utils::LinkHandler callback;
VirtualFunctionAssistProvider virtualFuncAssistProvider;
QList<MessageId> pendingSymbolInfoRequests;
QList<MessageId> pendingGotoImplRequests;
QList<MessageId> pendingGotoDefRequests;
const int docRevision;
const bool openInSplit;
Utils::Link defLink;
QList<Utils::Link> allLinks;
QHash<Utils::Link, Utils::Link> declDefMap;
Utils::optional<ClangdAstNode> cursorNode;
ClangdAstNode defLinkNode;
SymbolDataList symbolsToDisplay;
std::set<Utils::FilePath> 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<QString> getContainingFunctionName(const ClangdAstPath &astPath, const Range& range);
@@ -801,16 +692,13 @@ public:
void handleSemanticTokens(TextDocument *doc, const QList<ExpandedSemanticToken> &tokens,
int version, bool force);
enum class AstCallbackMode { SyncIfPossible, AlwaysAsync };
using TextDocOrFile = const Utils::variant<const TextDocument *, Utils::FilePath>;
using AstHandler = const std::function<void(const ClangdAstNode &ast, const MessageId &)>;
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<quint64, ReferencesData> runningFindUsages;
Utils::optional<FollowSymbolData> followSymbolData;
ClangdFollowSymbol *followSymbol = nullptr;
Utils::optional<SwitchDeclDefData> switchDeclDefData;
Utils::optional<LocalRefsData> localRefsData;
Utils::optional<QVersionNumber> 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<QList<SymbolDetails>>(&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 &params)
@@ -1541,6 +1415,54 @@ void ClangdClient::clearTasks(const Utils::FilePath &filePath)
d->issuePaneEntries[filePath].clear();
}
Utils::optional<bool> 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<QList<SymbolDetails>>(&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<Location> &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<QList<SymbolDetails>>(&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<Range> &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<GotoResult> &result = response.result()) {
QList<Utils::Link> newLinks;
if (const auto ploc = Utils::get_if<Location>(&*result))
newLinks = {ploc->toLink()};
if (const auto plloc = Utils::get_if<QList<Location>>(&*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<QList<SymbolDetails>>(&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<GotoResult> _result = response.result()) {
const GotoResult result = _result.value();
if (const auto ploc = Utils::get_if<Location>(&result)) {
newLink = ploc->toLink();
} else if (const auto plloc = Utils::get_if<QList<Location>>(&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<AssistProposalItemInterface *> 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("<base declaration>");
} 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<QList<CodeAction> > ClangdDiagnostic::codeActions() const
{
auto actions = optionalArray<LanguageServerProtocol::CodeAction>("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:

View File

@@ -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<bool> hasVirtualFunctionAt(TextEditor::TextDocument *doc, int revision,
const LanguageServerProtocol::Range &range);
using TextDocOrFile = Utils::variant<const TextEditor::TextDocument *, Utils::FilePath>;
using AstHandler = std::function<void(const ClangdAstNode &ast,
const LanguageServerProtocol::MessageId &)>;
enum class AstCallbackMode { SyncIfPossible, AlwaysAsync };
LanguageServerProtocol::MessageId getAndHandleAst(const TextDocOrFile &doc,
const AstHandler &astHandler,
AstCallbackMode callbackMode,
const LanguageServerProtocol::Range &range);
using SymbolInfoHandler = std::function<void(const QString &name, const QString &prefix,
const LanguageServerProtocol::MessageId &)>;
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;

View File

@@ -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 <cppeditor/cppeditorwidget.h>
#include <cppeditor/cppvirtualfunctionassistprovider.h>
#include <cppeditor/cppvirtualfunctionproposalitem.h>
#include <languageclient/languageclientsymbolsupport.h>
#include <languageserverprotocol/lsptypes.h>
#include <languageserverprotocol/jsonrpcmessages.h>
#include <texteditor/codeassist/iassistprocessor.h>
#include <texteditor/codeassist/iassistprovider.h>
#include <texteditor/textdocument.h>
#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:
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<ClangdFollowSymbol> 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<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),
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<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;
optional<ClangdAstNode> cursorNode;
ClangdAstNode defLinkNode;
SymbolDataList symbolsToDisplay;
std::set<FilePath> 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<AssistProposalItemInterface *> 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("<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);
}
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<GotoResult> &result = response.result()) {
QList<Link> newLinks;
if (const auto ploc = get_if<Location>(&*result))
newLinks = {ploc->toLink()};
if (const auto plloc = get_if<QList<Location>>(&*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<GotoResult> _result = response.result()) {
const GotoResult result = _result.value();
if (const auto ploc = get_if<Location>(&result)) {
newLink = ploc->toLink();
} else if (const auto plloc = get_if<QList<Location>>(&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

View File

@@ -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 <utils/link.h>
#include <QObject>
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