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 @@ -111,6 +111,8 @@ 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/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -563,6 +563,10 @@ {"declarationProvider", true}, {"definitionProvider", true}, {"documentHighlightProvider", true}, + {"documentLinkProvider", + llvm::json::Object{ + {"resolveProvider", false}, + }}, {"hoverProvider", true}, {"renameProvider", std::move(RenameProvider)}, {"selectionRangeProvider", true}, @@ -1197,6 +1201,20 @@ }); } +void ClangdLSPServer::onDocumentLink( + const DocumentLinkParams &Params, + Callback> Reply) { + 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, @@ -1240,10 +1258,12 @@ 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 } -ClangdLSPServer::~ClangdLSPServer() { IsBeingDestroyed = true; +ClangdLSPServer::~ClangdLSPServer() { + IsBeingDestroyed = true; // Explicitly destroy ClangdServer first, blocking on threads it owns. // This ensures they don't access any other members. Server.reset(); diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -287,6 +287,9 @@ 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/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -611,6 +611,17 @@ 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/Protocol.h b/clang-tools-extra/clangd/Protocol.h --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -1250,6 +1250,33 @@ }; 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. +/// +/// TODO(forster): For now only file URIs are supported. +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. +}; +llvm::json::Value toJSON(const DocumentLink &DocumentLink); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -1087,5 +1087,18 @@ } 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/XRefs.h b/clang-tools-extra/clangd/XRefs.h --- a/clang-tools-extra/clangd/XRefs.h +++ b/clang-tools-extra/clangd/XRefs.h @@ -49,6 +49,9 @@ 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/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -166,6 +166,26 @@ } // 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.emplace_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/test/document-link.test b/clang-tools-extra/clangd/test/document-link.test new file mode 100644 --- /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://{{.*}}/iostream" +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 17, +# 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://{{.*}}/string" +# 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 --- 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": []