ClangCodeModel: Implement declaration/definition switch via clangd

Change-Id: I522a415d76fbc5332e5cc1fdfd2d7ab19cb9ed64
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2021-05-31 15:57:44 +02:00
parent de6c7696d2
commit 41dafc8132
11 changed files with 392 additions and 80 deletions

View File

@@ -230,7 +230,8 @@ public:
void print(int indent = 0) const
{
(qDebug().noquote() << QByteArray(indent, ' ')).quote() << role() << kind()
<< detail().value_or(QString()) << arcana().value_or(QString());
<< detail().value_or(QString()) << arcana().value_or(QString())
<< range();
for (const AstNode &c : children().value_or(QList<AstNode>()))
c.print(indent + 2);
}
@@ -512,6 +513,58 @@ public:
VirtualFunctionAssistProcessor *virtualFuncAssistProcessor = nullptr;
};
class SwitchDeclDefData {
public:
SwitchDeclDefData(quint64 id, TextEditor::TextDocument *doc, const QTextCursor &cursor,
CppTools::CppEditorWidgetInterface *editorWidget,
Utils::ProcessLinkCallback &&callback)
: id(id), document(doc), uri(DocumentUri::fromFilePath(doc->filePath())),
cursor(cursor), editorWidget(editorWidget), callback(std::move(callback)) {}
Utils::optional<AstNode> getFunctionNode() const
{
QTC_ASSERT(ast, return {});
const QList<AstNode> path = getAstPath(*ast, Range(cursor));
for (auto it = path.rbegin(); it != path.rend(); ++it) {
if (it->role() == "declaration"
&& (it->kind() == "CXXMethod" || it->kind() == "CXXConversion"
|| it->kind() == "CXXConstructor" || it->kind() == "CXXDestructor")) {
return *it;
}
}
return {};
}
QTextCursor cursorForFunctionName(const AstNode &functionNode) const
{
QTC_ASSERT(docSymbols, return {});
const auto symbolList = Utils::get_if<QList<DocumentSymbol>>(&*docSymbols);
if (!symbolList)
return {};
const Range &astRange = functionNode.range();
QList symbolsToCheck = *symbolList;
while (!symbolsToCheck.isEmpty()) {
const DocumentSymbol symbol = symbolsToCheck.takeFirst();
if (symbol.range() == astRange)
return symbol.selectionRange().start().toTextCursor(document->document());
if (symbol.range().contains(astRange))
symbolsToCheck << symbol.children().value_or(QList<DocumentSymbol>());
}
return {};
}
const quint64 id;
const QPointer<TextEditor::TextDocument> document;
const DocumentUri uri;
const QTextCursor cursor;
CppTools::CppEditorWidgetInterface * const editorWidget;
Utils::ProcessLinkCallback callback;
Utils::optional<DocumentSymbolsResult> docSymbols;
Utils::optional<AstNode> ast;
};
class ClangdClient::Private
{
@@ -535,12 +588,16 @@ public:
void handleDocumentInfoResults();
void closeTempDocuments();
void handleDeclDefSwitchReplies();
ClangdClient * const q;
QHash<quint64, ReferencesData> runningFindUsages;
Utils::optional<FollowSymbolData> followSymbolData;
Utils::optional<SwitchDeclDefData> switchDeclDefData;
Utils::optional<QVersionNumber> versionNumber;
quint64 nextFindUsagesKey = 0;
quint64 nextFollowSymbolId = 0;
quint64 nextSwitchDeclDefId = 0;
bool isFullyIndexed = false;
bool isTesting = false;
};
@@ -555,7 +612,6 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir)
setSupportedLanguage(langFilter);
LanguageServerProtocol::ClientCapabilities caps = Client::defaultClientCapabilities();
caps.clearExperimental();
caps.clearTextDocument();
setClientCapabilities(caps);
setLocatorsEnabled(false);
setProgressTitleForToken(indexingToken(), tr("Parsing C/C++ Files (clangd)"));
@@ -581,6 +637,15 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir)
QTC_CHECK(d->runningFindUsages.isEmpty());
});
connect(documentSymbolCache(), &DocumentSymbolCache::gotSymbols, this,
[this](const DocumentUri &uri, const DocumentSymbolsResult &symbols) {
if (!d->switchDeclDefData || d->switchDeclDefData->uri != uri)
return;
d->switchDeclDefData->docSymbols = symbols;
if (d->switchDeclDefData->ast)
d->handleDeclDefSwitchReplies();
});
start();
}
@@ -943,6 +1008,42 @@ void ClangdClient::followSymbol(
sendContent(astRequest);
}
void ClangdClient::switchDeclDef(TextEditor::TextDocument *document, const QTextCursor &cursor,
CppTools::CppEditorWidgetInterface *editorWidget,
Utils::ProcessLinkCallback &&callback)
{
QTC_ASSERT(documentOpen(document), openDocument(document));
qCDebug(clangdLog) << "switch decl/dev requested" << document->filePath()
<< cursor.blockNumber() << cursor.positionInBlock();
d->switchDeclDefData.emplace(++d->nextSwitchDeclDefId, document, cursor, editorWidget,
std::move(callback));
// Retrieve AST and document symbols.
AstParams astParams;
astParams.setTextDocument(TextDocumentIdentifier(d->switchDeclDefData->uri));
AstRequest astRequest(astParams);
astRequest.setResponseCallback([this, id = d->switchDeclDefData->id]
(const AstRequest::Response &response) {
qCDebug(clangdLog) << "received ast for decl/def switch";
if (!d->switchDeclDefData || d->switchDeclDefData->id != id
|| !d->switchDeclDefData->document)
return;
const auto result = response.result();
if (!result) {
d->switchDeclDefData.reset();
return;
}
d->switchDeclDefData->ast = *result;
if (d->switchDeclDefData->docSymbols)
d->handleDeclDefSwitchReplies();
});
sendContent(astRequest);
documentSymbolCache()->requestSymbols(d->switchDeclDefData->uri);
}
void ClangdClient::Private::handleGotoDefinitionResult()
{
QTC_ASSERT(followSymbolData->defLink.hasValidTarget(), return);
@@ -1144,6 +1245,35 @@ void ClangdClient::Private::handleDocumentInfoResults()
followSymbolData->virtualFuncAssistProcessor->finalize();
}
void ClangdClient::Private::handleDeclDefSwitchReplies()
{
if (!switchDeclDefData->document) {
switchDeclDefData.reset();
return;
}
// Find the function declaration or definition associated with the cursor.
// For instance, the cursor could be somwehere inside a function body or
// on a function return type, or ...
if (clangdLog().isDebugEnabled())
switchDeclDefData->ast->print(0);
const Utils::optional<AstNode> functionNode = switchDeclDefData->getFunctionNode();
if (!functionNode) {
switchDeclDefData.reset();
return;
}
// Unfortunately, the AST does not contain the location of the actual function name symbol,
// so we have to look for it in the document symbols.
const QTextCursor funcNameCursor = switchDeclDefData->cursorForFunctionName(*functionNode);
if (!funcNameCursor.isNull()) {
q->followSymbol(switchDeclDefData->document.data(), funcNameCursor,
switchDeclDefData->editorWidget, std::move(switchDeclDefData->callback),
true, false);
}
switchDeclDefData.reset();
}
void ClangdClient::VirtualFunctionAssistProcessor::cancel()
{
if (!m_data)

View File

@@ -61,6 +61,11 @@ public:
bool resolveTarget,
bool openInSplit);
void switchDeclDef(TextEditor::TextDocument *document,
const QTextCursor &cursor,
CppTools::CppEditorWidgetInterface *editorWidget,
Utils::ProcessLinkCallback &&callback);
void enableTesting();
signals:

View File

@@ -251,5 +251,23 @@ void ClangFollowSymbol::findLink(const CppTools::CursorInEditor &data,
m_watcher->setFuture(infoFuture);
}
void ClangFollowSymbol::switchDeclDef(const CppTools::CursorInEditor &data,
Utils::ProcessLinkCallback &&processLinkCallback,
const CPlusPlus::Snapshot &snapshot,
const CPlusPlus::Document::Ptr &documentFromSemanticInfo,
CppTools::SymbolFinder *symbolFinder)
{
ClangdClient * const client
= ClangModelManagerSupport::instance()->clientForFile(data.filePath());
if (client && client->isFullyIndexed() && client->versionNumber() >= QVersionNumber(13)) {
client->switchDeclDef(data.textDocument(), data.cursor(), data.editorWidget(),
std::move(processLinkCallback));
return;
}
CppTools::CppModelManager::builtinFollowSymbol().switchDeclDef(
data, std::move(processLinkCallback), snapshot, documentFromSemanticInfo,
symbolFinder);
}
} // namespace Internal
} // namespace ClangCodeModel

View File

@@ -43,9 +43,17 @@ public:
const CPlusPlus::Document::Ptr &documentFromSemanticInfo,
CppTools::SymbolFinder *symbolFinder,
bool inNextSplit) override;
void switchDeclDef(const CppTools::CursorInEditor &data,
Utils::ProcessLinkCallback &&processLinkCallback,
const CPlusPlus::Snapshot &snapshot,
const CPlusPlus::Document::Ptr &documentFromSemanticInfo,
CppTools::SymbolFinder *symbolFinder) override;
private:
using FutureSymbolWatcher = QFutureWatcher<CppTools::SymbolInfo>;
std::unique_ptr<FutureSymbolWatcher> m_watcher;
};
} // namespace Internal

View File

@@ -711,78 +711,15 @@ void CppEditorWidget::switchDeclarationDefinition(bool inNextSplit)
if (!d->m_modelManager)
return;
if (!d->m_lastSemanticInfo.doc)
return;
// Find function declaration or definition under cursor
Function *functionDefinitionSymbol = nullptr;
Symbol *functionDeclarationSymbol = nullptr;
Symbol *declarationSymbol = nullptr;
ASTPath astPathFinder(d->m_lastSemanticInfo.doc);
const QList<AST *> astPath = astPathFinder(textCursor());
for (AST *ast : astPath) {
if (FunctionDefinitionAST *functionDefinitionAST = ast->asFunctionDefinition()) {
if ((functionDefinitionSymbol = functionDefinitionAST->symbol))
break; // Function definition found!
} else if (SimpleDeclarationAST *simpleDeclaration = ast->asSimpleDeclaration()) {
if (List<Symbol *> *symbols = simpleDeclaration->symbols) {
if (Symbol *symbol = symbols->value) {
if (symbol->isDeclaration()) {
declarationSymbol = symbol;
if (symbol->type()->isFunctionType()) {
functionDeclarationSymbol = symbol;
break; // Function declaration found!
}
}
}
}
}
}
// Link to function definition/declaration
Utils::Link symbolLink;
if (functionDeclarationSymbol) {
Symbol *symbol = d->m_modelManager->symbolFinder()
->findMatchingDefinition(functionDeclarationSymbol, d->m_modelManager->snapshot());
if (symbol)
symbolLink = symbol->toLink();
} else if (declarationSymbol) {
Symbol *symbol = d->m_modelManager->symbolFinder()
->findMatchingVarDefinition(declarationSymbol, d->m_modelManager->snapshot());
if (symbol)
symbolLink = symbol->toLink();
} else if (functionDefinitionSymbol) {
const Snapshot snapshot = d->m_modelManager->snapshot();
LookupContext context(d->m_lastSemanticInfo.doc, snapshot);
ClassOrNamespace *binding = context.lookupType(functionDefinitionSymbol);
const QList<LookupItem> declarations
= context.lookup(functionDefinitionSymbol->name(),
functionDefinitionSymbol->enclosingScope());
QList<Symbol *> best;
foreach (const LookupItem &r, declarations) {
if (Symbol *decl = r.declaration()) {
if (Function *funTy = decl->type()->asFunctionType()) {
if (funTy->match(functionDefinitionSymbol)) {
if (decl != functionDefinitionSymbol && binding == r.binding())
best.prepend(decl);
else
best.append(decl);
}
}
}
}
if (best.isEmpty())
return;
symbolLink = best.first()->toLink();
}
// Open Editor at link position
if (symbolLink.hasValidTarget())
openLink(symbolLink, inNextSplit != alwaysOpenLinksInNextSplit());
const CursorInEditor cursor(textCursor(), textDocument()->filePath(), this, textDocument());
auto callback = [self = QPointer(this),
split = inNextSplit != alwaysOpenLinksInNextSplit()](const Link &link) {
if (self && link.hasValidTarget())
self->openLink(link, split);
};
followSymbolInterface().switchDeclDef(cursor, std::move(callback),
d->m_modelManager->snapshot(), d->m_lastSemanticInfo.doc,
d->m_modelManager->symbolFinder());
}
void CppEditorWidget::findLinkAt(const QTextCursor &cursor,

View File

@@ -297,14 +297,12 @@ F2TestCase::F2TestCase(CppEditorAction action,
QSKIP("fuzzy matching is not supposed to work with clangd"); // TODO: Implement fallback as we do with libclang
if (tag == "baseClassFunctionIntroducedByUsingDeclaration")
QSKIP("clangd points to the using declaration");
if (tag == "classDestructor")
if (tag == "classDestructor" || tag == "fromDestructorDefinitionSymbol"
|| tag == "fromDestructorBody") {
QSKIP("clangd wants the cursor before the ~ character");
}
if (curTestName == "test_FollowSymbolUnderCursor_classOperator_inOp")
QSKIP("clangd goes to operator name first");
if (tag == "fromFunctionBody" || tag == "fromReturnType"
|| tag == "conversionOperatorDecl2Def") {
QSKIP("TODO: explicit decl/def switch not yet supported with clangd");
}
}
// Write files to disk
@@ -415,7 +413,9 @@ F2TestCase::F2TestCase(CppEditorAction action,
break;
}
case SwitchBetweenMethodDeclarationDefinitionAction:
if (CppTools::codeModelSettings()->useClangd())
// Some test cases were erroneously added as decl/def, but they are really
// follow symbol functionality (in commit a0764603d0).
if (useClangd && tag.endsWith("Var"))
initialTestFile->m_editorWidget->openLinkUnderCursor();
else
CppEditorPlugin::instance()->switchDeclarationDefinition();
@@ -623,6 +623,126 @@ void CppEditorPlugin::test_SwitchMethodDeclarationDefinition_data()
"}\n" // Line 10
);
QTest::newRow("fromConstructorDeclarationSymbol") << _(
"class C\n"
"{\n"
"public:\n"
" C();\n"
" int @function();\n" // Line 5
"};\n"
) << _(
"#include \"file.h\"\n"
"\n"
"C::C()\n"
"{\n"
"}\n" // Line 5
"\n"
"int C::$function()\n"
"{\n"
" return 1 + 1;\n"
"}\n" // Line 10
);
QTest::newRow("fromConstructorDefinitionSymbol") << _(
"class C\n"
"{\n"
"public:\n"
" $C();\n"
" int function();\n"
"};\n"
) << _(
"#include \"file.h\"\n"
"\n"
"C::@C()\n"
"{\n"
"}\n"
"\n"
"int C::function()\n"
"{\n"
" return 1 + 1;\n"
"}\n"
);
QTest::newRow("fromConstructorBody") << _(
"class C\n"
"{\n"
"public:\n"
" $C();\n"
" int function();\n"
"};\n"
) << _(
"#include \"file.h\"\n"
"\n"
"C::C()\n"
"{@\n"
"}\n" // Line 5
"\n"
"int C::function()\n"
"{\n"
" return 1 + 1;\n"
"}\n" // Line 10
);
QTest::newRow("fromDestructorDeclarationSymbol") << _(
"class C\n"
"{\n"
"public:\n"
" @C();\n"
" int function();\n" // Line 5
"};\n"
) << _(
"#include \"file.h\"\n"
"\n"
"C::$C()\n"
"{\n"
"}\n" // Line 5
"\n"
"int C::function()\n"
"{\n"
" return 1 + 1;\n"
"}\n" // Line 10
);
QTest::newRow("fromDestructorDefinitionSymbol") << _(
"class C\n"
"{\n"
"public:\n"
" ~$C();\n"
" int function();\n"
"};\n"
) << _(
"#include \"file.h\"\n"
"\n"
"C::@~C()\n"
"{\n"
"}\n"
"\n"
"int C::function()\n"
"{\n"
" return 1 + 1;\n"
"}\n"
);
QTest::newRow("fromDestructorBody") << _(
"class C\n"
"{\n"
"public:\n"
" ~$C();\n"
" int function();\n"
"};\n"
) << _(
"#include \"file.h\"\n"
"\n"
"C::~C()\n"
"{@\n"
"}\n" // Line 5
"\n"
"int C::function()\n"
"{\n"
" return 1 + 1;\n"
"}\n" // Line 10
);
QTest::newRow("fromReturnType") << _(
"class C\n"
"{\n"

View File

@@ -796,6 +796,83 @@ void FollowSymbolUnderCursor::findLink(
processLinkCallback(Link());
}
void FollowSymbolUnderCursor::switchDeclDef(
const CursorInEditor &data,
Utils::ProcessLinkCallback &&processLinkCallback,
const CPlusPlus::Snapshot &snapshot,
const CPlusPlus::Document::Ptr &documentFromSemanticInfo,
SymbolFinder *symbolFinder)
{
if (!documentFromSemanticInfo) {
processLinkCallback({});
return;
}
// Find function declaration or definition under cursor
Function *functionDefinitionSymbol = nullptr;
Symbol *functionDeclarationSymbol = nullptr;
Symbol *declarationSymbol = nullptr;
ASTPath astPathFinder(documentFromSemanticInfo);
const QList<AST *> astPath = astPathFinder(data.cursor());
for (AST *ast : astPath) {
if (FunctionDefinitionAST *functionDefinitionAST = ast->asFunctionDefinition()) {
if ((functionDefinitionSymbol = functionDefinitionAST->symbol))
break; // Function definition found!
} else if (SimpleDeclarationAST *simpleDeclaration = ast->asSimpleDeclaration()) {
if (List<Symbol *> *symbols = simpleDeclaration->symbols) {
if (Symbol *symbol = symbols->value) {
if (symbol->isDeclaration()) {
declarationSymbol = symbol;
if (symbol->type()->isFunctionType()) {
functionDeclarationSymbol = symbol;
break; // Function declaration found!
}
}
}
}
}
}
// Link to function definition/declaration
Utils::Link symbolLink;
if (functionDeclarationSymbol) {
Symbol *symbol = symbolFinder->findMatchingDefinition(functionDeclarationSymbol, snapshot);
if (symbol)
symbolLink = symbol->toLink();
} else if (declarationSymbol) {
Symbol *symbol = symbolFinder->findMatchingVarDefinition(declarationSymbol, snapshot);
if (symbol)
symbolLink = symbol->toLink();
} else if (functionDefinitionSymbol) {
LookupContext context(documentFromSemanticInfo, snapshot);
ClassOrNamespace *binding = context.lookupType(functionDefinitionSymbol);
const QList<LookupItem> declarations
= context.lookup(functionDefinitionSymbol->name(),
functionDefinitionSymbol->enclosingScope());
QList<Symbol *> best;
foreach (const LookupItem &r, declarations) {
if (Symbol *decl = r.declaration()) {
if (Function *funTy = decl->type()->asFunctionType()) {
if (funTy->match(functionDefinitionSymbol)) {
if (decl != functionDefinitionSymbol && binding == r.binding())
best.prepend(decl);
else
best.append(decl);
}
}
}
}
if (best.isEmpty())
return;
symbolLink = best.first()->toLink();
}
processLinkCallback(symbolLink);
}
QSharedPointer<VirtualFunctionAssistProvider> FollowSymbolUnderCursor::virtualFunctionAssistProvider()
{
return m_virtualFunctionAssistProvider;

View File

@@ -44,6 +44,12 @@ public:
CppTools::SymbolFinder *symbolFinder,
bool inNextSplit) override;
void switchDeclDef(const CursorInEditor &data,
Utils::ProcessLinkCallback &&processLinkCallback,
const CPlusPlus::Snapshot &snapshot,
const CPlusPlus::Document::Ptr &documentFromSemanticInfo,
SymbolFinder *symbolFinder) override;
QSharedPointer<VirtualFunctionAssistProvider> virtualFunctionAssistProvider();
void setVirtualFunctionAssistProvider(
const QSharedPointer<VirtualFunctionAssistProvider> &provider);

View File

@@ -472,6 +472,11 @@ RefactoringEngineInterface *CppModelManager::builtinRefactoringEngine()
return instance()->d->m_refactoringEngines.value(RefactoringEngineType::BuiltIn);
}
FollowSymbolInterface &CppModelManager::builtinFollowSymbol()
{
return instance()->d->m_builtinModelManagerSupport->followSymbolInterface();
}
template<class FilterClass>
static void setFilter(std::unique_ptr<FilterClass> &filter,
std::unique_ptr<FilterClass> &&newFilter)

View File

@@ -218,6 +218,7 @@ public:
RefactoringEngineInterface *refactoringEngine);
static void removeRefactoringEngine(RefactoringEngineType type);
static RefactoringEngineInterface *builtinRefactoringEngine();
static FollowSymbolInterface &builtinFollowSymbol();
void setLocatorFilter(std::unique_ptr<Core::ILocatorFilter> &&filter);
void setClassesFilter(std::unique_ptr<Core::ILocatorFilter> &&filter);

View File

@@ -49,6 +49,11 @@ public:
const CPlusPlus::Document::Ptr &documentFromSemanticInfo,
SymbolFinder *symbolFinder,
bool inNextSplit) = 0;
virtual void switchDeclDef(const CursorInEditor &data,
Utils::ProcessLinkCallback &&processLinkCallback,
const CPlusPlus::Snapshot &snapshot,
const CPlusPlus::Document::Ptr &documentFromSemanticInfo,
SymbolFinder *symbolFinder) = 0;
};
} // namespace CppTools