forked from qt-creator/qt-creator
ClangCodeModel: Cache clangd's AST
Since we use the AST in several contexts, it can easily happen that we need to consult it more than once for the same document revision. Therefore, it makes sense to cache the AST to prevent redundant requests for potentially large amounts of data. Change-Id: I33100e0052ee21bb46e91848d3d2e8a0a96bb860 Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
@@ -89,32 +89,6 @@ static Q_LOGGING_CATEGORY(clangdLogAst, "qtc.clangcodemodel.clangd.ast", QtWarni
|
|||||||
static Q_LOGGING_CATEGORY(clangdLogHighlight, "qtc.clangcodemodel.clangd.highlight", QtWarningMsg);
|
static Q_LOGGING_CATEGORY(clangdLogHighlight, "qtc.clangcodemodel.clangd.highlight", QtWarningMsg);
|
||||||
static QString indexingToken() { return "backgroundIndexProgress"; }
|
static QString indexingToken() { return "backgroundIndexProgress"; }
|
||||||
|
|
||||||
class AstParams : public JsonObject
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AstParams() {}
|
|
||||||
AstParams(const TextDocumentIdentifier &document, const Range &range = {})
|
|
||||||
{
|
|
||||||
setTextDocument(document);
|
|
||||||
if (range.isValid())
|
|
||||||
setRange(range);
|
|
||||||
}
|
|
||||||
|
|
||||||
using JsonObject::JsonObject;
|
|
||||||
|
|
||||||
// The open file to inspect.
|
|
||||||
TextDocumentIdentifier textDocument() const
|
|
||||||
{ return typedValue<TextDocumentIdentifier>(textDocumentKey); }
|
|
||||||
void setTextDocument(const TextDocumentIdentifier &id) { insert(textDocumentKey, id); }
|
|
||||||
|
|
||||||
// The region of the source code whose AST is fetched. The highest-level node that entirely
|
|
||||||
// contains the range is returned.
|
|
||||||
Utils::optional<Range> range() const { return optionalValue<Range>(rangeKey); }
|
|
||||||
void setRange(const Range &range) { insert(rangeKey, range); }
|
|
||||||
|
|
||||||
bool isValid() const override { return contains(textDocumentKey); }
|
|
||||||
};
|
|
||||||
|
|
||||||
class AstNode : public JsonObject
|
class AstNode : public JsonObject
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -329,6 +303,14 @@ static QList<AstNode> getAstPath(const AstNode &root, const Range &range)
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static AstNode getAstNode(const AstNode &root, const Range &range)
|
||||||
|
{
|
||||||
|
const QList<AstNode> path = getAstPath(root, range);
|
||||||
|
if (!path.isEmpty())
|
||||||
|
return path.last();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
static Usage::Type getUsageType(const QList<AstNode> &path)
|
static Usage::Type getUsageType(const QList<AstNode> &path)
|
||||||
{
|
{
|
||||||
bool potentialWrite = false;
|
bool potentialWrite = false;
|
||||||
@@ -401,13 +383,6 @@ static Usage::Type getUsageType(const QList<AstNode> &path)
|
|||||||
return Usage::Type::Other;
|
return Usage::Type::Other;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AstRequest : public Request<AstNode, std::nullptr_t, AstParams>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using Request::Request;
|
|
||||||
explicit AstRequest(const AstParams ¶ms) : Request("textDocument/ast", params) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class SymbolDetails : public JsonObject
|
class SymbolDetails : public JsonObject
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -713,6 +688,58 @@ private:
|
|||||||
const unsigned m_completionOperator;
|
const unsigned m_completionOperator;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static qint64 getRevision(const TextEditor::TextDocument *doc)
|
||||||
|
{
|
||||||
|
return doc->document()->revision();
|
||||||
|
}
|
||||||
|
static qint64 getRevision(const Utils::FilePath &fp)
|
||||||
|
{
|
||||||
|
return fp.lastModified().toMSecsSinceEpoch();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename DocType, typename DataType> class VersionedDocData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VersionedDocData(const DocType &doc, const DataType &data) :
|
||||||
|
revision(getRevision(doc)), data(data) {}
|
||||||
|
|
||||||
|
const qint64 revision;
|
||||||
|
const DataType data;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename DocType, typename DataType> class VersionedDataCache
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void insert(const DocType &doc, const DataType &data)
|
||||||
|
{
|
||||||
|
m_data.emplace(std::make_pair(doc, VersionedDocData(doc, data)));
|
||||||
|
}
|
||||||
|
void remove(const DocType &doc) { m_data.erase(doc); }
|
||||||
|
Utils::optional<VersionedDocData<DocType, DataType>> take(const DocType &doc)
|
||||||
|
{
|
||||||
|
const auto it = m_data.find(doc);
|
||||||
|
if (it == m_data.end())
|
||||||
|
return {};
|
||||||
|
const auto data = it->second;
|
||||||
|
m_data.erase(it);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
Utils::optional<DataType> get(const DocType &doc)
|
||||||
|
{
|
||||||
|
const auto it = m_data.find(doc);
|
||||||
|
if (it == m_data.end())
|
||||||
|
return {};
|
||||||
|
if (it->second.revision != getRevision(doc)) {
|
||||||
|
m_data.erase(it);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return it->second.data;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::unordered_map<DocType, VersionedDocData<DocType, DataType>> m_data;
|
||||||
|
};
|
||||||
|
|
||||||
class ClangdClient::Private
|
class ClangdClient::Private
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -749,6 +776,12 @@ public:
|
|||||||
void handleSemanticTokens(TextEditor::TextDocument *doc,
|
void handleSemanticTokens(TextEditor::TextDocument *doc,
|
||||||
const QList<ExpandedSemanticToken> &tokens);
|
const QList<ExpandedSemanticToken> &tokens);
|
||||||
|
|
||||||
|
enum class AstCallbackMode { SyncIfPossible, AlwaysAsync };
|
||||||
|
using TextDocOrFile = const Utils::variant<const TextEditor::TextDocument *, Utils::FilePath>;
|
||||||
|
using AstHandler = const std::function<void(const AstNode &ast, const MessageId &)>;
|
||||||
|
MessageId getAndHandleAst(TextDocOrFile &doc, AstHandler &astHandler,
|
||||||
|
AstCallbackMode callbackMode);
|
||||||
|
|
||||||
ClangdClient * const q;
|
ClangdClient * const q;
|
||||||
const CppEditor::ClangdSettings::Data settings;
|
const CppEditor::ClangdSettings::Data settings;
|
||||||
QHash<quint64, ReferencesData> runningFindUsages;
|
QHash<quint64, ReferencesData> runningFindUsages;
|
||||||
@@ -757,6 +790,8 @@ public:
|
|||||||
Utils::optional<LocalRefsData> localRefsData;
|
Utils::optional<LocalRefsData> localRefsData;
|
||||||
Utils::optional<QVersionNumber> versionNumber;
|
Utils::optional<QVersionNumber> versionNumber;
|
||||||
std::unordered_map<TextEditor::TextDocument *, CppEditor::SemanticHighlighter> highlighters;
|
std::unordered_map<TextEditor::TextDocument *, CppEditor::SemanticHighlighter> highlighters;
|
||||||
|
VersionedDataCache<const TextEditor::TextDocument *, AstNode> astCache;
|
||||||
|
VersionedDataCache<Utils::FilePath, AstNode> externalAstCache;
|
||||||
quint64 nextJobId = 0;
|
quint64 nextJobId = 0;
|
||||||
bool isFullyIndexed = false;
|
bool isFullyIndexed = false;
|
||||||
bool isTesting = false;
|
bool isTesting = false;
|
||||||
@@ -1048,9 +1083,19 @@ void ClangdClient::handleDiagnostics(const PublishDiagnosticsParams ¶ms)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ClangdClient::handleDocumentOpened(TextEditor::TextDocument *doc)
|
||||||
|
{
|
||||||
|
const auto data = d->externalAstCache.take(doc->filePath());
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
if (data->revision == getRevision(doc->filePath()))
|
||||||
|
d->astCache.insert(doc, data->data);
|
||||||
|
}
|
||||||
|
|
||||||
void ClangdClient::handleDocumentClosed(TextEditor::TextDocument *doc)
|
void ClangdClient::handleDocumentClosed(TextEditor::TextDocument *doc)
|
||||||
{
|
{
|
||||||
d->highlighters.erase(doc);
|
d->highlighters.erase(doc);
|
||||||
|
d->astCache.remove(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVersionNumber ClangdClient::versionNumber() const
|
QVersionNumber ClangdClient::versionNumber() const
|
||||||
@@ -1199,27 +1244,22 @@ void ClangdClient::Private::handleFindUsagesResult(quint64 key, const QList<Loca
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (auto it = refData->fileData.begin(); it != refData->fileData.end(); ++it) {
|
for (auto it = refData->fileData.begin(); it != refData->fileData.end(); ++it) {
|
||||||
const bool extraOpen = !q->documentForFilePath(it.key().toFilePath());
|
const TextEditor::TextDocument * const doc = q->documentForFilePath(it.key().toFilePath());
|
||||||
if (extraOpen)
|
if (!doc)
|
||||||
q->openExtraFile(it.key().toFilePath(), it->fileContent);
|
q->openExtraFile(it.key().toFilePath(), it->fileContent);
|
||||||
it->fileContent.clear();
|
it->fileContent.clear();
|
||||||
|
const auto docVariant = doc ? TextDocOrFile(doc) : TextDocOrFile(it.key().toFilePath());
|
||||||
AstParams params;
|
const auto astHandler = [this, key, loc = it.key()](const AstNode &ast,
|
||||||
params.setTextDocument(TextDocumentIdentifier(it.key()));
|
const MessageId &reqId) {
|
||||||
AstRequest request(params);
|
qCDebug(clangdLog) << "AST for" << loc.toFilePath();
|
||||||
request.setResponseCallback([this, key, loc = it.key(), request]
|
|
||||||
(AstRequest::Response response) {
|
|
||||||
qCDebug(clangdLog) << "AST response for" << loc.toFilePath();
|
|
||||||
const auto refData = runningFindUsages.find(key);
|
const auto refData = runningFindUsages.find(key);
|
||||||
if (refData == runningFindUsages.end())
|
if (refData == runningFindUsages.end())
|
||||||
return;
|
return;
|
||||||
if (!refData->search || refData->canceled)
|
if (!refData->search || refData->canceled)
|
||||||
return;
|
return;
|
||||||
ReferencesFileData &data = refData->fileData[loc];
|
ReferencesFileData &data = refData->fileData[loc];
|
||||||
const auto result = response.result();
|
data.ast = ast;
|
||||||
if (result)
|
refData->pendingAstRequests.removeOne(reqId);
|
||||||
data.ast = *result;
|
|
||||||
refData->pendingAstRequests.removeOne(request.id());
|
|
||||||
qCDebug(clangdLog) << refData->pendingAstRequests.size()
|
qCDebug(clangdLog) << refData->pendingAstRequests.size()
|
||||||
<< "AST requests still pending";
|
<< "AST requests still pending";
|
||||||
addSearchResultsForFile(*refData, loc.toFilePath(), data);
|
addSearchResultsForFile(*refData, loc.toFilePath(), data);
|
||||||
@@ -1228,12 +1268,11 @@ void ClangdClient::Private::handleFindUsagesResult(quint64 key, const QList<Loca
|
|||||||
qDebug(clangdLog) << "retrieved all ASTs";
|
qDebug(clangdLog) << "retrieved all ASTs";
|
||||||
finishSearch(*refData, false);
|
finishSearch(*refData, false);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
qCDebug(clangdLog) << "requesting AST for" << it.key().toFilePath();
|
const MessageId reqId = getAndHandleAst(docVariant, astHandler,
|
||||||
refData->pendingAstRequests << request.id();
|
AstCallbackMode::AlwaysAsync);
|
||||||
q->sendContent(request, SendDocUpdates::Ignore);
|
refData->pendingAstRequests << reqId;
|
||||||
|
if (!doc)
|
||||||
if (extraOpen)
|
|
||||||
q->closeExtraFile(it.key().toFilePath());
|
q->closeExtraFile(it.key().toFilePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1373,22 +1412,16 @@ void ClangdClient::followSymbol(TextEditor::TextDocument *document,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AstRequest astRequest(AstParams(TextDocumentIdentifier(d->followSymbolData->uri),
|
const auto astHandler = [this, id = d->followSymbolData->id, range = Range(cursor)]
|
||||||
Range(cursor)));
|
(const AstNode &ast, const MessageId &) {
|
||||||
astRequest.setResponseCallback([this, id = d->followSymbolData->id](
|
|
||||||
const AstRequest::Response &response) {
|
|
||||||
qCDebug(clangdLog) << "received ast response for cursor";
|
qCDebug(clangdLog) << "received ast response for cursor";
|
||||||
if (!d->followSymbolData || d->followSymbolData->id != id)
|
if (!d->followSymbolData || d->followSymbolData->id != id)
|
||||||
return;
|
return;
|
||||||
const auto result = response.result();
|
d->followSymbolData->cursorNode = getAstNode(ast, range);
|
||||||
if (result)
|
|
||||||
d->followSymbolData->cursorNode = *result;
|
|
||||||
else
|
|
||||||
d->followSymbolData->cursorNode.emplace(AstNode());
|
|
||||||
if (d->followSymbolData->defLink.hasValidTarget())
|
if (d->followSymbolData->defLink.hasValidTarget())
|
||||||
d->handleGotoDefinitionResult();
|
d->handleGotoDefinitionResult();
|
||||||
});
|
};
|
||||||
sendContent(astRequest, SendDocUpdates::Ignore);
|
d->getAndHandleAst(document, astHandler, Private::AstCallbackMode::SyncIfPossible);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClangdClient::switchDeclDef(TextEditor::TextDocument *document, const QTextCursor &cursor,
|
void ClangdClient::switchDeclDef(TextEditor::TextDocument *document, const QTextCursor &cursor,
|
||||||
@@ -1403,28 +1436,23 @@ void ClangdClient::switchDeclDef(TextEditor::TextDocument *document, const QText
|
|||||||
std::move(callback));
|
std::move(callback));
|
||||||
|
|
||||||
// Retrieve AST and document symbols.
|
// Retrieve AST and document symbols.
|
||||||
AstParams astParams;
|
const auto astHandler = [this, id = d->switchDeclDefData->id](const AstNode &ast,
|
||||||
astParams.setTextDocument(TextDocumentIdentifier(d->switchDeclDefData->uri));
|
const MessageId &) {
|
||||||
AstRequest astRequest(astParams);
|
|
||||||
astRequest.setResponseCallback([this, id = d->switchDeclDefData->id]
|
|
||||||
(const AstRequest::Response &response) {
|
|
||||||
qCDebug(clangdLog) << "received ast for decl/def switch";
|
qCDebug(clangdLog) << "received ast for decl/def switch";
|
||||||
if (!d->switchDeclDefData || d->switchDeclDefData->id != id
|
if (!d->switchDeclDefData || d->switchDeclDefData->id != id
|
||||||
|| !d->switchDeclDefData->document)
|
|| !d->switchDeclDefData->document)
|
||||||
return;
|
return;
|
||||||
const auto result = response.result();
|
if (!ast.isValid()) {
|
||||||
if (!result) {
|
|
||||||
d->switchDeclDefData.reset();
|
d->switchDeclDefData.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
d->switchDeclDefData->ast = *result;
|
d->switchDeclDefData->ast = ast;
|
||||||
if (d->switchDeclDefData->docSymbols)
|
if (d->switchDeclDefData->docSymbols)
|
||||||
d->handleDeclDefSwitchReplies();
|
d->handleDeclDefSwitchReplies();
|
||||||
|
|
||||||
});
|
};
|
||||||
sendContent(astRequest, SendDocUpdates::Ignore);
|
d->getAndHandleAst(document, astHandler, Private::AstCallbackMode::SyncIfPossible);
|
||||||
documentSymbolCache()->requestSymbols(d->switchDeclDefData->uri, Schedule::Now);
|
documentSymbolCache()->requestSymbols(d->switchDeclDefData->uri, Schedule::Now);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClangdClient::findLocalUsages(TextEditor::TextDocument *document, const QTextCursor &cursor,
|
void ClangdClient::findLocalUsages(TextEditor::TextDocument *document, const QTextCursor &cursor,
|
||||||
@@ -1454,19 +1482,17 @@ void ClangdClient::findLocalUsages(TextEditor::TextDocument *document, const QTe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Get AST and check whether it's a local variable.
|
// Step 2: Get AST and check whether it's a local variable.
|
||||||
AstRequest astRequest(AstParams(TextDocumentIdentifier(d->localRefsData->uri)));
|
const auto astHandler = [this, link, id](const AstNode &ast, const MessageId &) {
|
||||||
astRequest.setResponseCallback([this, link, id](const AstRequest::Response &response) {
|
|
||||||
qCDebug(clangdLog) << "received ast response";
|
qCDebug(clangdLog) << "received ast response";
|
||||||
if (!d->localRefsData || id != d->localRefsData->id)
|
if (!d->localRefsData || id != d->localRefsData->id)
|
||||||
return;
|
return;
|
||||||
const auto result = response.result();
|
if (!ast.isValid() || !d->localRefsData->document) {
|
||||||
if (!result || !d->localRefsData->document) {
|
|
||||||
d->localRefsData.reset();
|
d->localRefsData.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Position linkPos(link.targetLine - 1, link.targetColumn);
|
const Position linkPos(link.targetLine - 1, link.targetColumn);
|
||||||
const QList<AstNode> astPath = getAstPath(*result, Range(linkPos, linkPos));
|
const QList<AstNode> astPath = getAstPath(ast, Range(linkPos, linkPos));
|
||||||
bool isVar = false;
|
bool isVar = false;
|
||||||
for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) {
|
for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) {
|
||||||
if (it->role() == "declaration" && it->kind() == "Function") {
|
if (it->role() == "declaration" && it->kind() == "Function") {
|
||||||
@@ -1507,9 +1533,10 @@ void ClangdClient::findLocalUsages(TextEditor::TextDocument *document, const QTe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
d->localRefsData.reset();
|
d->localRefsData.reset();
|
||||||
});
|
};
|
||||||
qCDebug(clangdLog) << "sending ast request for link";
|
qCDebug(clangdLog) << "sending ast request for link";
|
||||||
sendContent(astRequest, SendDocUpdates::Ignore);
|
d->getAndHandleAst(d->localRefsData->document, astHandler,
|
||||||
|
Private::AstCallbackMode::SyncIfPossible);
|
||||||
};
|
};
|
||||||
symbolSupport().findLinkAt(document, cursor, std::move(gotoDefCallback), true);
|
symbolSupport().findLinkAt(document, cursor, std::move(gotoDefCallback), true);
|
||||||
}
|
}
|
||||||
@@ -1551,10 +1578,10 @@ void ClangdClient::gatherHelpItemForTooltip(const HoverRequest::Response &hoverR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AstRequest req((AstParams(TextDocumentIdentifier(uri))));
|
const TextEditor::TextDocument * const doc = documentForFilePath(uri.toFilePath());
|
||||||
req.setResponseCallback([this, uri, hoverResponse](const AstRequest::Response &response) {
|
QTC_ASSERT(doc, return);
|
||||||
|
const auto astHandler = [this, uri, hoverResponse](const AstNode &ast, const MessageId &) {
|
||||||
const MessageId id = hoverResponse.id();
|
const MessageId id = hoverResponse.id();
|
||||||
const AstNode ast = response.result().value_or(AstNode());
|
|
||||||
const Range range = hoverResponse.result()->range().value_or(Range());
|
const Range range = hoverResponse.result()->range().value_or(Range());
|
||||||
const QList<AstNode> path = getAstPath(ast, range);
|
const QList<AstNode> path = getAstPath(ast, range);
|
||||||
if (path.isEmpty()) {
|
if (path.isEmpty()) {
|
||||||
@@ -1672,8 +1699,8 @@ void ClangdClient::gatherHelpItemForTooltip(const HoverRequest::Response &hoverR
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
d->setHelpItemForTooltip(id);
|
d->setHelpItemForTooltip(id);
|
||||||
});
|
};
|
||||||
sendContent(req, SendDocUpdates::Ignore);
|
d->getAndHandleAst(doc, astHandler, Private::AstCallbackMode::SyncIfPossible);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClangdClient::Private::handleGotoDefinitionResult()
|
void ClangdClient::Private::handleGotoDefinitionResult()
|
||||||
@@ -1838,27 +1865,24 @@ void ClangdClient::Private::handleGotoImplementationResult(
|
|||||||
q->sendContent(defReq, SendDocUpdates::Ignore);
|
q->sendContent(defReq, SendDocUpdates::Ignore);
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocumentUri defLinkUri
|
const Utils::FilePath defLinkFilePath = followSymbolData->defLink.targetFilePath;
|
||||||
= DocumentUri::fromFilePath(followSymbolData->defLink.targetFilePath);
|
const TextEditor::TextDocument * const defLinkDoc = q->documentForFilePath(defLinkFilePath);
|
||||||
|
const auto defLinkDocVariant = defLinkDoc ? TextDocOrFile(defLinkDoc)
|
||||||
|
: TextDocOrFile(defLinkFilePath);
|
||||||
const Position defLinkPos(followSymbolData->defLink.targetLine - 1,
|
const Position defLinkPos(followSymbolData->defLink.targetLine - 1,
|
||||||
followSymbolData->defLink.targetColumn);
|
followSymbolData->defLink.targetColumn);
|
||||||
AstRequest astRequest(AstParams(TextDocumentIdentifier(defLinkUri),
|
const auto astHandler = [this, range = Range(defLinkPos, defLinkPos), id = followSymbolData->id]
|
||||||
Range(defLinkPos, defLinkPos)));
|
(const AstNode &ast, const MessageId &) {
|
||||||
astRequest.setResponseCallback([this, id = followSymbolData->id](
|
|
||||||
const AstRequest::Response &response) {
|
|
||||||
qCDebug(clangdLog) << "received ast response for def link";
|
qCDebug(clangdLog) << "received ast response for def link";
|
||||||
if (!followSymbolData || followSymbolData->id != id)
|
if (!followSymbolData || followSymbolData->id != id)
|
||||||
return;
|
return;
|
||||||
const auto result = response.result();
|
followSymbolData->defLinkNode = getAstNode(ast, range);
|
||||||
if (result)
|
|
||||||
followSymbolData->defLinkNode = *result;
|
|
||||||
if (followSymbolData->pendingSymbolInfoRequests.isEmpty()
|
if (followSymbolData->pendingSymbolInfoRequests.isEmpty()
|
||||||
&& followSymbolData->pendingGotoDefRequests.isEmpty()) {
|
&& followSymbolData->pendingGotoDefRequests.isEmpty()) {
|
||||||
handleDocumentInfoResults();
|
handleDocumentInfoResults();
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
qCDebug(clangdLog) << "sending ast request for def link";
|
getAndHandleAst(defLinkDocVariant, astHandler, AstCallbackMode::SyncIfPossible);
|
||||||
q->sendContent(astRequest, SendDocUpdates::Ignore);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClangdClient::Private::handleDocumentInfoResults()
|
void ClangdClient::Private::handleDocumentInfoResults()
|
||||||
@@ -2603,23 +2627,18 @@ void ClangdClient::Private::handleSemanticTokens(TextEditor::TextDocument *doc,
|
|||||||
qCDebug(clangdLogHighlight()) << '\t' << t.line << t.column << t.length << t.type
|
qCDebug(clangdLogHighlight()) << '\t' << t.line << t.column << t.length << t.type
|
||||||
<< t.modifiers;
|
<< t.modifiers;
|
||||||
|
|
||||||
// TODO: Cache ASTs
|
const auto astHandler = [this, tokens, doc](const AstNode &ast, const MessageId &) {
|
||||||
AstParams params(TextDocumentIdentifier(DocumentUri::fromFilePath(doc->filePath())));
|
|
||||||
AstRequest astReq(params);
|
|
||||||
astReq.setResponseCallback([this, tokens, doc](const AstRequest::Response &response) {
|
|
||||||
if (!q->documentOpen(doc))
|
if (!q->documentOpen(doc))
|
||||||
return;
|
return;
|
||||||
const Utils::optional<AstNode> ast = response.result();
|
if (clangdLogAst().isDebugEnabled())
|
||||||
if (ast && clangdLogAst().isDebugEnabled())
|
ast.print();
|
||||||
ast->print();
|
|
||||||
|
|
||||||
IEditor * const editor = Utils::findOrDefault(EditorManager::visibleEditors(),
|
IEditor * const editor = Utils::findOrDefault(EditorManager::visibleEditors(),
|
||||||
[doc](const IEditor *editor) { return editor->document() == doc; });
|
[doc](const IEditor *editor) { return editor->document() == doc; });
|
||||||
const auto editorWidget = TextEditor::TextEditorWidget::fromEditor(editor);
|
const auto editorWidget = TextEditor::TextEditorWidget::fromEditor(editor);
|
||||||
const auto runner = [tokens, text = doc->document()->toPlainText(),
|
const auto runner = [tokens, text = doc->document()->toPlainText(), ast,
|
||||||
theAst = ast ? *ast : AstNode(), w = QPointer(editorWidget),
|
w = QPointer(editorWidget), rev = doc->document()->revision()] {
|
||||||
rev = doc->document()->revision()] {
|
return Utils::runAsync(semanticHighlighter, tokens, text, ast, w, rev);
|
||||||
return Utils::runAsync(semanticHighlighter, tokens, text, theAst, w, rev);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isTesting) {
|
if (isTesting) {
|
||||||
@@ -2641,8 +2660,8 @@ void ClangdClient::Private::handleSemanticTokens(TextEditor::TextDocument *doc,
|
|||||||
}
|
}
|
||||||
it->second.setHighlightingRunner(runner);
|
it->second.setHighlightingRunner(runner);
|
||||||
it->second.run();
|
it->second.run();
|
||||||
});
|
};
|
||||||
q->sendContent(astReq, SendDocUpdates::Ignore);
|
getAndHandleAst(doc, astHandler, AstCallbackMode::SyncIfPossible);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClangdClient::VirtualFunctionAssistProcessor::cancel()
|
void ClangdClient::VirtualFunctionAssistProcessor::cancel()
|
||||||
@@ -2949,6 +2968,85 @@ void ClangdClient::ClangdCompletionAssistProcessor::applyCompletionItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc,
|
||||||
|
const AstHandler &astHandler,
|
||||||
|
AstCallbackMode callbackMode)
|
||||||
|
{
|
||||||
|
const auto textDocPtr = std::get_if<const TextEditor::TextDocument *>(&doc);
|
||||||
|
const TextEditor::TextDocument * const textDoc = textDocPtr ? *textDocPtr : nullptr;
|
||||||
|
const Utils::FilePath filePath = textDoc ? textDoc->filePath() : std::get<Utils::FilePath>(doc);
|
||||||
|
|
||||||
|
// If the document's AST is in the cache and is up to date, call the handler.
|
||||||
|
if (const auto ast = textDoc ? astCache.get(textDoc) : externalAstCache.get(filePath)) {
|
||||||
|
qCDebug(clangdLog) << "using AST from cache";
|
||||||
|
switch (callbackMode) {
|
||||||
|
case AstCallbackMode::SyncIfPossible:
|
||||||
|
astHandler(*ast, {});
|
||||||
|
break;
|
||||||
|
case AstCallbackMode::AlwaysAsync:
|
||||||
|
QMetaObject::invokeMethod(q, [ast, astHandler] { astHandler(*ast, {}); },
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise retrieve the AST from clangd.
|
||||||
|
|
||||||
|
class AstParams : public JsonObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AstParams() {}
|
||||||
|
AstParams(const TextDocumentIdentifier &document, const Range &range = {})
|
||||||
|
{
|
||||||
|
setTextDocument(document);
|
||||||
|
if (range.isValid())
|
||||||
|
setRange(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
using JsonObject::JsonObject;
|
||||||
|
|
||||||
|
// The open file to inspect.
|
||||||
|
TextDocumentIdentifier textDocument() const
|
||||||
|
{ return typedValue<TextDocumentIdentifier>(textDocumentKey); }
|
||||||
|
void setTextDocument(const TextDocumentIdentifier &id) { insert(textDocumentKey, id); }
|
||||||
|
|
||||||
|
// The region of the source code whose AST is fetched. The highest-level node that entirely
|
||||||
|
// contains the range is returned.
|
||||||
|
Utils::optional<Range> range() const { return optionalValue<Range>(rangeKey); }
|
||||||
|
void setRange(const Range &range) { insert(rangeKey, range); }
|
||||||
|
|
||||||
|
bool isValid() const override { return contains(textDocumentKey); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class AstRequest : public Request<AstNode, std::nullptr_t, AstParams>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Request::Request;
|
||||||
|
explicit AstRequest(const AstParams ¶ms) : Request("textDocument/ast", params) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
AstRequest request(AstParams(TextDocumentIdentifier(DocumentUri::fromFilePath(filePath))));
|
||||||
|
request.setResponseCallback([this, filePath, guardedTextDoc = QPointer(textDoc), astHandler,
|
||||||
|
docRev = textDoc ? getRevision(textDoc) : -1,
|
||||||
|
fileRev = getRevision(filePath), reqId = request.id()]
|
||||||
|
(AstRequest::Response response) {
|
||||||
|
qCDebug(clangdLog) << "retrieved AST from clangd";
|
||||||
|
const auto result = response.result();
|
||||||
|
const AstNode ast = result ? *result : AstNode();
|
||||||
|
if (guardedTextDoc) {
|
||||||
|
if (docRev == getRevision(guardedTextDoc))
|
||||||
|
astCache.insert(guardedTextDoc, ast);
|
||||||
|
} else if (fileRev == getRevision(filePath) && !q->documentForFilePath(filePath)) {
|
||||||
|
externalAstCache.insert(filePath, ast);
|
||||||
|
}
|
||||||
|
astHandler(ast, reqId);
|
||||||
|
});
|
||||||
|
qCDebug(clangdLog) << "requesting AST for" << filePath;
|
||||||
|
q->sendContent(request, SendDocUpdates::Ignore);
|
||||||
|
return request.id();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
} // namespace ClangCodeModel
|
} // namespace ClangCodeModel
|
||||||
|
|
||||||
|
@@ -91,6 +91,7 @@ signals:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void handleDiagnostics(const LanguageServerProtocol::PublishDiagnosticsParams ¶ms) override;
|
void handleDiagnostics(const LanguageServerProtocol::PublishDiagnosticsParams ¶ms) override;
|
||||||
|
void handleDocumentOpened(TextEditor::TextDocument *doc) override;
|
||||||
void handleDocumentClosed(TextEditor::TextDocument *doc) override;
|
void handleDocumentClosed(TextEditor::TextDocument *doc) override;
|
||||||
|
|
||||||
class Private;
|
class Private;
|
||||||
|
@@ -394,6 +394,7 @@ void Client::openDocument(TextEditor::TextDocument *document)
|
|||||||
m_documentVersions[filePath] = 0;
|
m_documentVersions[filePath] = 0;
|
||||||
item.setVersion(m_documentVersions[filePath]);
|
item.setVersion(m_documentVersions[filePath]);
|
||||||
sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item)));
|
sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item)));
|
||||||
|
handleDocumentOpened(document);
|
||||||
|
|
||||||
const Client *currentClient = LanguageClientManager::clientForDocument(document);
|
const Client *currentClient = LanguageClientManager::clientForDocument(document);
|
||||||
if (currentClient == this) {
|
if (currentClient == this) {
|
||||||
|
@@ -238,6 +238,7 @@ private:
|
|||||||
void rehighlight();
|
void rehighlight();
|
||||||
|
|
||||||
virtual void handleDocumentClosed(TextEditor::TextDocument *) {}
|
virtual void handleDocumentClosed(TextEditor::TextDocument *) {}
|
||||||
|
virtual void handleDocumentOpened(TextEditor::TextDocument *) {}
|
||||||
|
|
||||||
using ContentHandler = std::function<void(const QByteArray &, QTextCodec *, QString &,
|
using ContentHandler = std::function<void(const QByteArray &, QTextCodec *, QString &,
|
||||||
LanguageServerProtocol::ResponseHandlers,
|
LanguageServerProtocol::ResponseHandlers,
|
||||||
|
Reference in New Issue
Block a user