forked from OSchip/llvm-project
[clangd] Implement "textDocument/documentLink" protocol support
Summary: This adds an implementation for the "textDocument/documentLink" LSP request. It returns links for all `#include` directives to the resolved target files. Fixes https://github.com/clangd/clangd/issues/217. Reviewers: sammccall Reviewed By: sammccall Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D70872
This commit is contained in:
parent
9c8cfa09d7
commit
d6417f5584
|
@ -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<std::vector<DocumentLink>> 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<std::vector<DocumentLink>> 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
|
||||
}
|
||||
|
||||
|
|
|
@ -111,6 +111,8 @@ private:
|
|||
Callback<std::vector<SymbolDetails>>);
|
||||
void onSelectionRange(const SelectionRangeParams &,
|
||||
Callback<std::vector<SelectionRange>>);
|
||||
void onDocumentLink(const DocumentLinkParams &,
|
||||
Callback<std::vector<DocumentLink>>);
|
||||
|
||||
std::vector<Fix> getFixes(StringRef File, const clangd::Diagnostic &D);
|
||||
|
||||
|
|
|
@ -611,6 +611,17 @@ void ClangdServer::semanticRanges(PathRef File, Position Pos,
|
|||
WorkScheduler.runWithAST("SemanticRanges", File, std::move(Action));
|
||||
}
|
||||
|
||||
void ClangdServer::documentLinks(PathRef File,
|
||||
Callback<std::vector<DocumentLink>> CB) {
|
||||
auto Action =
|
||||
[CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {
|
||||
if (!InpAST)
|
||||
return CB(InpAST.takeError());
|
||||
CB(clangd::getDocumentLinks(InpAST->AST));
|
||||
};
|
||||
WorkScheduler.runWithAST("DocumentLinks", File, std::move(Action));
|
||||
}
|
||||
|
||||
std::vector<std::pair<Path, std::size_t>>
|
||||
ClangdServer::getUsedBytesPerFile() const {
|
||||
return WorkScheduler.getUsedBytesPerFile();
|
||||
|
|
|
@ -287,6 +287,9 @@ public:
|
|||
void semanticRanges(PathRef File, Position Pos,
|
||||
Callback<std::vector<Range>> CB);
|
||||
|
||||
/// Get all document links in a file.
|
||||
void documentLinks(PathRef File, Callback<std::vector<DocumentLink>> 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -166,6 +166,26 @@ llvm::Optional<Location> makeLocation(ASTContext &AST, SourceLocation TokLoc,
|
|||
|
||||
} // namespace
|
||||
|
||||
std::vector<DocumentLink> 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<DocumentLink> 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<LocatedSymbol> locateSymbolAt(ParsedAST &AST, Position Pos,
|
||||
const SymbolIndex *Index) {
|
||||
const auto &SM = AST.getSourceManager();
|
||||
|
|
|
@ -49,6 +49,9 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &, const LocatedSymbol &);
|
|||
std::vector<LocatedSymbol> locateSymbolAt(ParsedAST &AST, Position Pos,
|
||||
const SymbolIndex *Index = nullptr);
|
||||
|
||||
/// Get all document links
|
||||
std::vector<DocumentLink> getDocumentLinks(ParsedAST &AST);
|
||||
|
||||
/// Returns highlights for all usages of a symbol at \p Pos.
|
||||
std::vector<DocumentHighlight> findDocumentHighlights(ParsedAST &AST,
|
||||
Position Pos);
|
||||
|
|
|
@ -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 <stdint.h>\n#include <stddef.h>"}}}
|
||||
---
|
||||
{"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"}
|
|
@ -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": []
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue