diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 5b3caf5340d..7ac6d4e30b5 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -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())) 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 getFunctionNode() const + { + QTC_ASSERT(ast, return {}); + + const QList 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>(&*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()); + } + return {}; + } + + const quint64 id; + const QPointer document; + const DocumentUri uri; + const QTextCursor cursor; + CppTools::CppEditorWidgetInterface * const editorWidget; + Utils::ProcessLinkCallback callback; + Utils::optional docSymbols; + Utils::optional ast; +}; + class ClangdClient::Private { @@ -535,12 +588,16 @@ public: void handleDocumentInfoResults(); void closeTempDocuments(); + void handleDeclDefSwitchReplies(); + ClangdClient * const q; QHash runningFindUsages; Utils::optional followSymbolData; + Utils::optional switchDeclDefData; Utils::optional 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 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) diff --git a/src/plugins/clangcodemodel/clangdclient.h b/src/plugins/clangcodemodel/clangdclient.h index 18c128eb739..be1fb6ba120 100644 --- a/src/plugins/clangcodemodel/clangdclient.h +++ b/src/plugins/clangcodemodel/clangdclient.h @@ -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: diff --git a/src/plugins/clangcodemodel/clangfollowsymbol.cpp b/src/plugins/clangcodemodel/clangfollowsymbol.cpp index 79d48da901c..f6e08103a5a 100644 --- a/src/plugins/clangcodemodel/clangfollowsymbol.cpp +++ b/src/plugins/clangcodemodel/clangfollowsymbol.cpp @@ -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 diff --git a/src/plugins/clangcodemodel/clangfollowsymbol.h b/src/plugins/clangcodemodel/clangfollowsymbol.h index c76730fd3c3..077764f79e7 100644 --- a/src/plugins/clangcodemodel/clangfollowsymbol.h +++ b/src/plugins/clangcodemodel/clangfollowsymbol.h @@ -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; std::unique_ptr m_watcher; + }; } // namespace Internal diff --git a/src/plugins/cppeditor/cppeditorwidget.cpp b/src/plugins/cppeditor/cppeditorwidget.cpp index 7e48d545456..7083707b6d7 100644 --- a/src/plugins/cppeditor/cppeditorwidget.cpp +++ b/src/plugins/cppeditor/cppeditorwidget.cpp @@ -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 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 *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 declarations - = context.lookup(functionDefinitionSymbol->name(), - functionDefinitionSymbol->enclosingScope()); - - QList 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, diff --git a/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp b/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp index 401a7eb39fa..5eefae1e02c 100644 --- a/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp +++ b/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp @@ -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" diff --git a/src/plugins/cpptools/cppfollowsymbolundercursor.cpp b/src/plugins/cpptools/cppfollowsymbolundercursor.cpp index a9cfe89360f..4e4d0458740 100644 --- a/src/plugins/cpptools/cppfollowsymbolundercursor.cpp +++ b/src/plugins/cpptools/cppfollowsymbolundercursor.cpp @@ -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 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 *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 declarations + = context.lookup(functionDefinitionSymbol->name(), + functionDefinitionSymbol->enclosingScope()); + + QList 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 FollowSymbolUnderCursor::virtualFunctionAssistProvider() { return m_virtualFunctionAssistProvider; diff --git a/src/plugins/cpptools/cppfollowsymbolundercursor.h b/src/plugins/cpptools/cppfollowsymbolundercursor.h index a3bdf17736d..6406f70cf77 100644 --- a/src/plugins/cpptools/cppfollowsymbolundercursor.h +++ b/src/plugins/cpptools/cppfollowsymbolundercursor.h @@ -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(); void setVirtualFunctionAssistProvider( const QSharedPointer &provider); diff --git a/src/plugins/cpptools/cppmodelmanager.cpp b/src/plugins/cpptools/cppmodelmanager.cpp index aafa51a906a..3488571b12e 100644 --- a/src/plugins/cpptools/cppmodelmanager.cpp +++ b/src/plugins/cpptools/cppmodelmanager.cpp @@ -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 static void setFilter(std::unique_ptr &filter, std::unique_ptr &&newFilter) diff --git a/src/plugins/cpptools/cppmodelmanager.h b/src/plugins/cpptools/cppmodelmanager.h index 2677a4327d4..98991f9c6ce 100644 --- a/src/plugins/cpptools/cppmodelmanager.h +++ b/src/plugins/cpptools/cppmodelmanager.h @@ -218,6 +218,7 @@ public: RefactoringEngineInterface *refactoringEngine); static void removeRefactoringEngine(RefactoringEngineType type); static RefactoringEngineInterface *builtinRefactoringEngine(); + static FollowSymbolInterface &builtinFollowSymbol(); void setLocatorFilter(std::unique_ptr &&filter); void setClassesFilter(std::unique_ptr &&filter); diff --git a/src/plugins/cpptools/followsymbolinterface.h b/src/plugins/cpptools/followsymbolinterface.h index 380e4e39ae6..95c7189e869 100644 --- a/src/plugins/cpptools/followsymbolinterface.h +++ b/src/plugins/cpptools/followsymbolinterface.h @@ -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