diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 8ee320b5fc71..cb1f02ff68b9 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -566,6 +566,10 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params, {"declarationProvider", true}, {"definitionProvider", true}, {"documentHighlightProvider", true}, + {"documentLinkProvider", + llvm::json::Object{ + {"resolveProvider", false}, + }}, {"hoverProvider", true}, {"renameProvider", std::move(RenameProvider)}, {"selectionRangeProvider", true}, @@ -1200,6 +1204,25 @@ void ClangdLSPServer::onSelectionRange( }); } +void ClangdLSPServer::onDocumentLink( + const DocumentLinkParams &Params, + Callback> Reply) { + + // TODO(forster): This currently resolves all targets eagerly. This is slow, + // because it blocks on the preamble/AST being built. We could respond to the + // request faster by using string matching or the lexer to find the includes + // and resolving the targets lazily. + Server->documentLinks( + Params.textDocument.uri.file(), + [Reply = std::move(Reply)]( + llvm::Expected> Links) mutable { + if (!Links) { + return Reply(Links.takeError()); + } + return Reply(std::move(Links)); + }); +} + ClangdLSPServer::ClangdLSPServer( class Transport &Transp, const FileSystemProvider &FSProvider, const clangd::CodeCompleteOptions &CCOpts, @@ -1243,6 +1266,7 @@ ClangdLSPServer::ClangdLSPServer( MsgHandler->bind("textDocument/typeHierarchy", &ClangdLSPServer::onTypeHierarchy); MsgHandler->bind("typeHierarchy/resolve", &ClangdLSPServer::onResolveTypeHierarchy); MsgHandler->bind("textDocument/selectionRange", &ClangdLSPServer::onSelectionRange); + MsgHandler->bind("textDocument/documentLink", &ClangdLSPServer::onDocumentLink); // clang-format on } diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h index f1ed317f6bad..9650b6a7dbb2 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -111,6 +111,8 @@ class ClangdLSPServer : private DiagnosticsConsumer { Callback>); void onSelectionRange(const SelectionRangeParams &, Callback>); + void onDocumentLink(const DocumentLinkParams &, + Callback>); std::vector getFixes(StringRef File, const clangd::Diagnostic &D); diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index e9e03dbc3742..a858680d4067 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -611,6 +611,17 @@ void ClangdServer::semanticRanges(PathRef File, Position Pos, WorkScheduler.runWithAST("SemanticRanges", File, std::move(Action)); } +void ClangdServer::documentLinks(PathRef File, + Callback> CB) { + auto Action = + [CB = std::move(CB)](llvm::Expected InpAST) mutable { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::getDocumentLinks(InpAST->AST)); + }; + WorkScheduler.runWithAST("DocumentLinks", File, std::move(Action)); +} + std::vector> ClangdServer::getUsedBytesPerFile() const { return WorkScheduler.getUsedBytesPerFile(); diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index 499340808765..4574d6c35034 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -287,6 +287,9 @@ class ClangdServer { void semanticRanges(PathRef File, Position Pos, Callback> CB); + /// Get all document links in a file. + void documentLinks(PathRef File, Callback> CB); + /// Returns estimated memory usage for each of the currently open files. /// The order of results is unspecified. /// Overall memory usage of clangd may be significantly more than reported diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index 25826bd5a11d..29ceb1da5456 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -1087,5 +1087,18 @@ llvm::json::Value toJSON(const SelectionRange &Out) { } return llvm::json::Object{{"range", Out.range}}; } + +bool fromJSON(const llvm::json::Value &Params, DocumentLinkParams &R) { + llvm::json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument); +} + +llvm::json::Value toJSON(const DocumentLink &DocumentLink) { + return llvm::json::Object{ + {"range", DocumentLink.range}, + {"target", DocumentLink.target}, + }; +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index f110292b091b..1ccfa587bf87 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -1250,6 +1250,39 @@ struct SelectionRange { }; llvm::json::Value toJSON(const SelectionRange &); +/// Parameters for the document link request. +struct DocumentLinkParams { + /// The document to provide document links for. + TextDocumentIdentifier textDocument; +}; +bool fromJSON(const llvm::json::Value &, DocumentLinkParams &); + +/// A range in a text document that links to an internal or external resource, +/// like another text document or a web site. +struct DocumentLink { + /// The range this link applies to. + Range range; + + /// The uri this link points to. If missing a resolve request is sent later. + URIForFile target; + + // TODO(forster): The following optional fields defined by the language + // server protocol are unsupported: + // + // data?: any - A data entry field that is preserved on a document link + // between a DocumentLinkRequest and a + // DocumentLinkResolveRequest. + + friend bool operator==(const DocumentLink &LHS, const DocumentLink &RHS) { + return LHS.range == RHS.range && LHS.target == RHS.target; + } + + friend bool operator!=(const DocumentLink &LHS, const DocumentLink &RHS) { + return !(LHS == RHS); + } +}; +llvm::json::Value toJSON(const DocumentLink &DocumentLink); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index 3ad20522226b..d9a54ad971c4 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -166,6 +166,26 @@ llvm::Optional makeLocation(ASTContext &AST, SourceLocation TokLoc, } // namespace +std::vector getDocumentLinks(ParsedAST &AST) { + const auto &SM = AST.getSourceManager(); + auto MainFilePath = + getCanonicalPath(SM.getFileEntryForID(SM.getMainFileID()), SM); + if (!MainFilePath) { + elog("Failed to get a path for the main file, so no links"); + return {}; + } + + std::vector Result; + for (auto &Inc : AST.getIncludeStructure().MainFileIncludes) { + if (!Inc.Resolved.empty()) { + Result.push_back(DocumentLink( + {Inc.R, URIForFile::canonicalize(Inc.Resolved, *MainFilePath)})); + } + } + + return Result; +} + std::vector locateSymbolAt(ParsedAST &AST, Position Pos, const SymbolIndex *Index) { const auto &SM = AST.getSourceManager(); diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h index dc9e4f23f1ad..bc8deb1e3df7 100644 --- a/clang-tools-extra/clangd/XRefs.h +++ b/clang-tools-extra/clangd/XRefs.h @@ -49,6 +49,9 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &, const LocatedSymbol &); std::vector locateSymbolAt(ParsedAST &AST, Position Pos, const SymbolIndex *Index = nullptr); +/// Get all document links +std::vector getDocumentLinks(ParsedAST &AST); + /// Returns highlights for all usages of a symbol at \p Pos. std::vector findDocumentHighlights(ParsedAST &AST, Position Pos); diff --git a/clang-tools-extra/clangd/test/document-link.test b/clang-tools-extra/clangd/test/document-link.test new file mode 100644 index 000000000000..7802fe784568 --- /dev/null +++ b/clang-tools-extra/clangd/test/document-link.test @@ -0,0 +1,42 @@ +# 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":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"#include \n#include "}}} +--- +{"jsonrpc":"2.0","id":2,"method":"textDocument/documentLink","params":{"textDocument":{"uri":"test:///main.cpp"}}} +# CHECK: "id": 2, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 19, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 9, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "target": "file://{{.*}}/stdint.h" +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 19, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 9, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "target": "file://{{.*}}/stddef.h" +# CHECK-NEXT: } +# CHECK-NEXT: ] +# 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 index 8a853b7c6223..e5ec9fb3d7ac 100644 --- a/clang-tools-extra/clangd/test/initialize-params.test +++ b/clang-tools-extra/clangd/test/initialize-params.test @@ -18,6 +18,9 @@ # CHECK-NEXT: "definitionProvider": true, # CHECK-NEXT: "documentFormattingProvider": true, # CHECK-NEXT: "documentHighlightProvider": true, +# CHECK-NEXT: "documentLinkProvider": { +# CHECK-NEXT: "resolveProvider": false +# CHECK-NEXT: } # CHECK-NEXT: "documentOnTypeFormattingProvider": { # CHECK-NEXT: "firstTriggerCharacter": "\n", # CHECK-NEXT: "moreTriggerCharacter": [] diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp index e009c1c3ab20..058f205bb3a6 100644 --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -1048,6 +1048,27 @@ TEST(GetNonLocalDeclRefs, All) { } } +TEST(DocumentLinks, All) { + Annotations MainCpp(R"cpp( + #include $foo[["foo.h"]] + int end_of_preamble = 0; + #include $bar[["bar.h"]] + )cpp"); + + TestTU TU; + TU.Code = MainCpp.code(); + TU.AdditionalFiles = {{"foo.h", ""}, {"bar.h", ""}}; + auto AST = TU.build(); + + EXPECT_THAT( + clangd::getDocumentLinks(AST), + ElementsAre( + DocumentLink({MainCpp.range("foo"), + URIForFile::canonicalize(testPath("foo.h"), "")}), + DocumentLink({MainCpp.range("bar"), + URIForFile::canonicalize(testPath("bar.h"), "")}))); +} + } // namespace } // namespace clangd } // namespace clang