[clangd] Add semantic selection to ClangdLSPServer.

Summary:
This adds semantic selection to the LSP Server.
Adds support for serialization of input request and the output reply.
Also adds regression tests for the feature.

Currently we do not support multi cursor.The LSP Server only accepts single position in the request as opposed to many position in the spec.

Spec:
https://github.com/microsoft/language-server-protocol/blob/dbaeumer/3.15/specification.md#textDocument_selectionRange

Reviewers: hokein

Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D67720

llvm-svn: 372753
This commit is contained in:
Utkarsh Saxena 2019-09-24 13:38:33 +00:00
parent 3a415c20ad
commit 55925da4c9
6 changed files with 123 additions and 0 deletions

View File

@ -22,14 +22,18 @@
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/iterator_range.h"
#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 <cstddef>
#include <memory>
#include <string>
#include <vector>
namespace clang {
namespace clangd {
@ -127,6 +131,21 @@ llvm::Error validateEdits(const DraftStore &DraftMgr, const Tweak::Effect &E) {
llvm::to_string(InvalidFileCount - 1) + " others)");
}
// Converts a list of Ranges to a LinkedList of SelectionRange.
SelectionRange render(const std::vector<Range> &Ranges) {
if (Ranges.empty())
return {};
SelectionRange Result;
Result.range = Ranges[0];
auto *Next = &Result.parent;
for (const auto &R : llvm::make_range(Ranges.begin() + 1, Ranges.end())) {
*Next = std::make_unique<SelectionRange>();
Next->get()->range = R;
Next = &Next->get()->parent;
}
return Result;
}
} // namespace
// MessageHandler dispatches incoming LSP messages.
@ -536,6 +555,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
{"documentHighlightProvider", true},
{"hoverProvider", true},
{"renameProvider", std::move(RenameProvider)},
{"selectionRangeProvider", true},
{"documentSymbolProvider", true},
{"workspaceSymbolProvider", true},
{"referencesProvider", true},
@ -1125,6 +1145,30 @@ void ClangdLSPServer::onSymbolInfo(const TextDocumentPositionParams &Params,
std::move(Reply));
}
void ClangdLSPServer::onSelectionRange(
const SelectionRangeParams &Params,
Callback<std::vector<SelectionRange>> Reply) {
if (Params.positions.size() != 1) {
elog("{0} positions provided to SelectionRange. Supports exactly one "
"position.",
Params.positions.size());
return Reply(llvm::make_error<LSPError>(
"SelectionRange supports exactly one position",
ErrorCode::InvalidRequest));
}
Server->semanticRanges(
Params.textDocument.uri.file(), Params.positions[0],
[Reply = std::move(Reply)](
llvm::Expected<std::vector<Range>> Ranges) mutable {
if (!Ranges) {
return Reply(Ranges.takeError());
}
std::vector<SelectionRange> 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 @@ ClangdLSPServer::ClangdLSPServer(
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
}

View File

@ -107,6 +107,8 @@ private:
void onChangeConfiguration(const DidChangeConfigurationParams &);
void onSymbolInfo(const TextDocumentPositionParams &,
Callback<std::vector<SymbolDetails>>);
void onSelectionRange(const SelectionRangeParams &,
Callback<std::vector<SelectionRange>>);
std::vector<Fix> getFixes(StringRef File, const clangd::Diagnostic &D);

View File

@ -1073,5 +1073,18 @@ llvm::json::Value toJSON(const SemanticHighlightingParams &Highlighting) {
};
}
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) {
return llvm::json::Object{{"range", Out.range},
{"parent", toJSON(*Out.parent)}};
}
return llvm::json::Object{{"range", Out.range}};
}
} // namespace clangd
} // namespace clang

View File

@ -30,6 +30,7 @@
#include "llvm/Support/JSON.h"
#include "llvm/Support/raw_ostream.h"
#include <bitset>
#include <memory>
#include <string>
#include <vector>
@ -1222,6 +1223,28 @@ struct SemanticHighlightingParams {
};
llvm::json::Value toJSON(const SemanticHighlightingParams &Highlighting);
struct SelectionRangeParams {
/// The text document.
TextDocumentIdentifier textDocument;
/// The positions inside the text document.
std::vector<Position> positions;
};
bool fromJSON(const llvm::json::Value &, SelectionRangeParams &);
struct SelectionRange {
/**
* The range of this selection range.
*/
Range range;
/**
* The parent selection range containing this range. Therefore `parent.range`
* must contain `this.range`.
*/
std::unique_ptr<SelectionRange> parent;
};
llvm::json::Value toJSON(const SelectionRange &);
} // namespace clangd
} // namespace clang

View File

@ -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: "(",

View File

@ -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"}