diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -135,6 +135,14 @@ Callback>); void onResolveTypeHierarchy(const ResolveTypeHierarchyItemParams &, Callback>); + void onPrepareCallHierarchy(const CallHierarchyPrepareParams &, + Callback>); + void onCallHierarchyIncomingCalls( + const CallHierarchyIncomingCallsParams &, + Callback>); + void onCallHierarchyOutgoingCalls( + const CallHierarchyOutgoingCallsParams &, + Callback>); void onChangeConfiguration(const DidChangeConfigurationParams &); void onSymbolInfo(const TextDocumentPositionParams &, Callback>); diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -625,6 +625,7 @@ }}, {"typeHierarchyProvider", true}, {"memoryUsageProvider", true}, // clangd extension. + {"callHierarchyProvider", true}, }}}}; if (Opts.Encoding) Result["offsetEncoding"] = *Opts.Encoding; @@ -1224,6 +1225,26 @@ std::move(Reply)); } +void ClangdLSPServer::onPrepareCallHierarchy( + const CallHierarchyPrepareParams &Params, + Callback> Reply) { + Server->prepareCallHierarchy(Params.textDocument.uri.file(), Params.position, + std::move(Reply)); +} + +void ClangdLSPServer::onCallHierarchyIncomingCalls( + const CallHierarchyIncomingCallsParams &Params, + Callback> Reply) { + Server->incomingCalls(Params.item, std::move(Reply)); +} + +void ClangdLSPServer::onCallHierarchyOutgoingCalls( + const CallHierarchyOutgoingCallsParams &Params, + Callback> Reply) { + // FIXME: To be implemented. + Reply(std::vector{}); +} + void ClangdLSPServer::applyConfiguration( const ConfigurationSettings &Settings) { // Per-file update to the compilation database. @@ -1468,6 +1489,9 @@ MsgHandler->bind("textDocument/symbolInfo", &ClangdLSPServer::onSymbolInfo); MsgHandler->bind("textDocument/typeHierarchy", &ClangdLSPServer::onTypeHierarchy); MsgHandler->bind("typeHierarchy/resolve", &ClangdLSPServer::onResolveTypeHierarchy); + MsgHandler->bind("textDocument/prepareCallHierarchy", &ClangdLSPServer::onPrepareCallHierarchy); + MsgHandler->bind("callHierarchy/incomingCalls", &ClangdLSPServer::onCallHierarchyIncomingCalls); + MsgHandler->bind("callHierarchy/outgoingCalls", &ClangdLSPServer::onCallHierarchyOutgoingCalls); MsgHandler->bind("textDocument/selectionRange", &ClangdLSPServer::onSelectionRange); MsgHandler->bind("textDocument/documentLink", &ClangdLSPServer::onDocumentLink); MsgHandler->bind("textDocument/semanticTokens/full", &ClangdLSPServer::onSemanticTokens); diff --git a/clang-tools-extra/clangd/test/call-hierarchy.test b/clang-tools-extra/clangd/test/call-hierarchy.test new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/test/call-hierarchy.test @@ -0,0 +1,39 @@ +# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"languageId":"cpp","text":"void callee(int);\nvoid caller1() {\n callee(42);\n}\nvoid caller2() {\n caller1();\n caller1();\n}\nvoid caller3() {\n caller1();\n caller2();\n}\n","uri":"test:///main.cpp","version":1}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/prepareCallHierarchy","params":{"position":{"character":8,"line":0},"textDocument":{"uri":"test:///main.cpp"}}} +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "data": "{{.*}}", +# CHECK-NEXT: "kind": 12, +# CHECK-NEXT: "name": "callee", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 16, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "selectionRange": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 11, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 5, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" +# CHECK-NEXT: } +--- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} diff --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test --- a/clang-tools-extra/clangd/test/initialize-params.test +++ b/clang-tools-extra/clangd/test/initialize-params.test @@ -6,6 +6,7 @@ # CHECK-NEXT: "result": { # CHECK-NEXT: "capabilities": { # CHECK-NEXT: "astProvider": true, +# CHECK-NEXT: "callHierarchyProvider": true, # CHECK-NEXT: "codeActionProvider": true, # CHECK-NEXT: "completionProvider": { # CHECK-NEXT: "allCommitCharacters": [ diff --git a/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp b/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp --- a/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp +++ b/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp @@ -37,6 +37,8 @@ LSPTest() : LogSession(*this) { ClangdServer::Options &Base = Opts; Base = ClangdServer::optsForTest(); + // This is needed to we can test index-based operations like call hierarchy. + Base.BuildDynamicSymbolIndex = true; } LSPClient &start() { @@ -165,6 +167,33 @@ stop(); EXPECT_THAT(Tracer.takeMetric("lsp_latency", MethodName), testing::SizeIs(1)); } + +TEST_F(LSPTest, IncomingCalls) { + Annotations Code(R"cpp( + void calle^e(int); + void caller1() { + [[callee]](42); + } + )cpp"); + auto &Client = start(); + Client.didOpen("foo.cpp", Code.code()); + auto Items = Client + .call("textDocument/prepareCallHierarchy", + llvm::json::Object{ + {"textDocument", Client.documentID("foo.cpp")}, + {"position", Code.point()}}) + .takeValue(); + auto FirstItem = (*Items.getAsArray())[0]; + auto Calls = Client + .call("callHierarchy/incomingCalls", + llvm::json::Object{{"item", FirstItem}}) + .takeValue(); + auto FirstCall = *(*Calls.getAsArray())[0].getAsObject(); + EXPECT_EQ(FirstCall["fromRanges"], llvm::json::Value{Code.range()}); + auto From = *FirstCall["from"].getAsObject(); + EXPECT_EQ(From["name"], "caller1"); +} + } // namespace } // namespace clangd } // namespace clang