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 @@ -107,6 +107,8 @@ void onChangeConfiguration(const DidChangeConfigurationParams &); void onSymbolInfo(const TextDocumentPositionParams &, Callback>); + void onSelectionRange(const SelectionRangeParams &, + 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 @@ -25,11 +25,14 @@ #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/JSON.h" #include "llvm/Support/Path.h" #include "llvm/Support/SHA1.h" #include "llvm/Support/ScopedPrinter.h" #include +#include #include +#include namespace clang { namespace clangd { @@ -127,6 +130,22 @@ llvm::to_string(InvalidFileCount - 1) + " others)"); } +// Converts a list of Ranges to a LinkedList of SelectionRange. +SelectionRange render(const std::vector &Ranges) { + if (Ranges.empty()) { + return {}; + } + SelectionRange Result; + Result.range = Ranges[0]; + auto *Next = &Result.parent; + for (size_t I = 1; I < Ranges.size(); ++I) { + *Next = std::make_unique(); + Next->getValue()->range = Ranges[I]; + Next = &Next->getValue()->parent; + } + return Result; +} + } // namespace // MessageHandler dispatches incoming LSP messages. @@ -536,6 +555,7 @@ {"documentHighlightProvider", true}, {"hoverProvider", true}, {"renameProvider", std::move(RenameProvider)}, + {"selectionRangeProvider", true}, {"documentSymbolProvider", true}, {"workspaceSymbolProvider", true}, {"referencesProvider", true}, @@ -1125,6 +1145,30 @@ std::move(Reply)); } +void ClangdLSPServer::onSelectionRange( + const SelectionRangeParams &Params, + Callback> Reply) { + if (Params.positions.size() != 1) { + elog("{0} positions provided to SelectionRange. Supports exactly one " + "position.", + Params.positions.size()); + return Reply(llvm::make_error( + "SelectionRange supports exactly one position", + ErrorCode::InvalidRequest)); + } + Server->semanticRanges( + Params.textDocument.uri.file(), Params.positions[0], + [Reply = std::move(Reply)]( + llvm::Expected> Ranges) mutable { + if (!Ranges) { + return Reply(Ranges.takeError()); + } + std::vector Result; + Result.emplace_back(render(std::move(*Ranges))); + return Reply(std::move(Result)); + }); +} + ClangdLSPServer::ClangdLSPServer( class Transport &Transp, const FileSystemProvider &FSProvider, const clangd::CodeCompleteOptions &CCOpts, @@ -1167,6 +1211,7 @@ MsgHandler->bind("textDocument/symbolInfo", &ClangdLSPServer::onSymbolInfo); MsgHandler->bind("textDocument/typeHierarchy", &ClangdLSPServer::onTypeHierarchy); MsgHandler->bind("typeHierarchy/resolve", &ClangdLSPServer::onResolveTypeHierarchy); + MsgHandler->bind("textDocument/selectionRange", &ClangdLSPServer::onSelectionRange); // clang-format on } 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 @@ -30,6 +30,7 @@ #include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" #include +#include #include #include @@ -416,6 +417,10 @@ /// textDocument.semanticHighlightingCapabilities.semanticHighlighting bool SemanticHighlighting = false; + /// Client supports semantic selection. + /// textDocument.selectionRange + bool SemanticSelection = false; + /// Supported encodings for LSP character offsets. (clangd extension). llvm::Optional> offsetEncoding; @@ -1222,6 +1227,31 @@ }; llvm::json::Value toJSON(const SemanticHighlightingParams &Highlighting); +struct SelectionRangeParams { + /// The text document. + TextDocumentIdentifier textDocument; + + /// The positions inside the text document. + std::vector positions; +}; +bool fromJSON(const llvm::json::Value &, SelectionRangeParams &); + +struct SelectionRange { + /** + * The [range](#Range) of this selection range. + */ + Range range; + /** + * The parent selection range containing this range. Therefore `parent.range` + * must contain `this.range`. + */ + llvm::Optional> parent; + + SelectionRange() = default; + SelectionRange(SelectionRange &&) = default; +}; +llvm::json::Value toJSON(const SelectionRange &); + } // 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 @@ -15,6 +15,7 @@ #include "URI.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/Hashing.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/ErrorHandling.h" @@ -335,6 +336,9 @@ if (auto RenameSupport = Rename->getBoolean("prepareSupport")) R.RenamePrepareSupport = *RenameSupport; } + if (auto *SemanticSelection = TextDocument->getObject("selectionRange")) { + R.SemanticSelection = true; + } } if (auto *Workspace = O->getObject("workspace")) { if (auto *Symbol = Workspace->getObject("symbol")) { @@ -1073,5 +1077,18 @@ }; } +bool fromJSON(const llvm::json::Value &Params, SelectionRangeParams &P) { + llvm::json::ObjectMapper O(Params); + return O && O.map("textDocument", P.textDocument) && + O.map("positions", P.positions); +} + +llvm::json::Value toJSON(const SelectionRange &Out) { + if (Out.parent.hasValue()) { + return llvm::json::Object{{"range", Out.range}, + {"parent", toJSON(*Out.parent.getValue())}}; + } + return llvm::json::Object{{"range", Out.range}}; +} } // namespace clangd } // namespace clang 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 @@ -33,6 +33,7 @@ # CHECK-NEXT: "hoverProvider": true, # CHECK-NEXT: "referencesProvider": true, # CHECK-NEXT: "renameProvider": true, +# CHECK-NEXT: "selectionRangeProvider": true, # CHECK-NEXT: "signatureHelpProvider": { # CHECK-NEXT: "triggerCharacters": [ # CHECK-NEXT: "(", diff --git a/clang-tools-extra/clangd/test/selection-range.test b/clang-tools-extra/clangd/test/selection-range.test new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/test/selection-range.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":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"void func() {\n}"}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/selectionRange","params":{"textDocument":{"uri":"test:///main.cpp"},"positions":[{"line":1,"character":0}]}} +# CHECK: "id": 1 +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "parent": { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 1, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 1, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 12, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT:} +--- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"}