forked from OSchip/llvm-project
[clangd] Extract binding of typed->untyped LSP handlers to LSPBinder. NFC
The goal is to allow the LSP bindings of features to be defined outside the ClangdLSPServer class, turning it into less of a monolith. Differential Revision: https://reviews.llvm.org/D96544
This commit is contained in:
parent
34ea608a47
commit
5786f64a4e
|
@ -13,6 +13,7 @@
|
|||
#include "DraftStore.h"
|
||||
#include "DumpAST.h"
|
||||
#include "GlobalCompilationDatabase.h"
|
||||
#include "LSPBinder.h"
|
||||
#include "Protocol.h"
|
||||
#include "SemanticHighlighting.h"
|
||||
#include "SourceCode.h"
|
||||
|
@ -158,6 +159,8 @@ public:
|
|||
MessageHandler(ClangdLSPServer &Server) : Server(Server) {}
|
||||
|
||||
bool onNotify(llvm::StringRef Method, llvm::json::Value Params) override {
|
||||
trace::Span Tracer(Method, LSPLatency);
|
||||
SPAN_ATTACH(Tracer, "Params", Params);
|
||||
WithContext HandlerContext(handlerContext());
|
||||
log("<-- {0}", Method);
|
||||
if (Method == "exit")
|
||||
|
@ -166,12 +169,15 @@ public:
|
|||
elog("Notification {0} before initialization", Method);
|
||||
} else if (Method == "$/cancelRequest") {
|
||||
onCancel(std::move(Params));
|
||||
} else if (auto Handler = Notifications.lookup(Method)) {
|
||||
Handler(std::move(Params));
|
||||
Server.maybeExportMemoryProfile();
|
||||
Server.maybeCleanupMemory();
|
||||
} else {
|
||||
log("unhandled notification {0}", Method);
|
||||
auto Handler = Server.Handlers.NotificationHandlers.find(Method);
|
||||
if (Handler != Server.Handlers.NotificationHandlers.end()) {
|
||||
Handler->second(std::move(Params));
|
||||
Server.maybeExportMemoryProfile();
|
||||
Server.maybeCleanupMemory();
|
||||
} else {
|
||||
log("unhandled notification {0}", Method);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -189,11 +195,15 @@ public:
|
|||
elog("Call {0} before initialization.", Method);
|
||||
Reply(llvm::make_error<LSPError>("server not initialized",
|
||||
ErrorCode::ServerNotInitialized));
|
||||
} else if (auto Handler = Calls.lookup(Method))
|
||||
Handler(std::move(Params), std::move(Reply));
|
||||
else
|
||||
Reply(llvm::make_error<LSPError>("method not found",
|
||||
ErrorCode::MethodNotFound));
|
||||
} else {
|
||||
auto Handler = Server.Handlers.MethodHandlers.find(Method);
|
||||
if (Handler != Server.Handlers.MethodHandlers.end()) {
|
||||
Handler->second(std::move(Params), std::move(Reply));
|
||||
} else {
|
||||
Reply(llvm::make_error<LSPError>("method not found",
|
||||
ErrorCode::MethodNotFound));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -236,28 +246,6 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
// Bind an LSP method name to a call.
|
||||
template <typename Param, typename Result>
|
||||
void bind(const char *Method,
|
||||
void (ClangdLSPServer::*Handler)(const Param &, Callback<Result>)) {
|
||||
Calls[Method] = [Method, Handler, this](llvm::json::Value RawParams,
|
||||
ReplyOnce Reply) {
|
||||
auto P = parse<Param>(RawParams, Method, "request");
|
||||
if (!P)
|
||||
return Reply(P.takeError());
|
||||
(Server.*Handler)(*P, std::move(Reply));
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
void bind(const char *Method,
|
||||
void (ClangdLSPServer::*Handler)(Callback<Result>)) {
|
||||
Calls[Method] = [Handler, this](llvm::json::Value RawParams,
|
||||
ReplyOnce Reply) {
|
||||
(Server.*Handler)(std::move(Reply));
|
||||
};
|
||||
}
|
||||
|
||||
// Bind a reply callback to a request. The callback will be invoked when
|
||||
// clangd receives the reply from the LSP client.
|
||||
// Return a call id of the request.
|
||||
|
@ -286,27 +274,6 @@ public:
|
|||
return ID;
|
||||
}
|
||||
|
||||
// Bind an LSP method name to a notification.
|
||||
template <typename Param>
|
||||
void bind(const char *Method,
|
||||
void (ClangdLSPServer::*Handler)(const Param &)) {
|
||||
Notifications[Method] = [Method, Handler,
|
||||
this](llvm::json::Value RawParams) {
|
||||
llvm::Expected<Param> P = parse<Param>(RawParams, Method, "request");
|
||||
if (!P)
|
||||
return llvm::consumeError(P.takeError());
|
||||
trace::Span Tracer(Method, LSPLatency);
|
||||
SPAN_ATTACH(Tracer, "Params", RawParams);
|
||||
(Server.*Handler)(*P);
|
||||
};
|
||||
}
|
||||
|
||||
void bind(const char *Method, void (ClangdLSPServer::*Handler)()) {
|
||||
Notifications[Method] = [Handler, this](llvm::json::Value RawParams) {
|
||||
(Server.*Handler)();
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
// Function object to reply to an LSP call.
|
||||
// Each instance must be called exactly once, otherwise:
|
||||
|
@ -378,9 +345,6 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
llvm::StringMap<std::function<void(llvm::json::Value)>> Notifications;
|
||||
llvm::StringMap<std::function<void(llvm::json::Value, ReplyOnce)>> Calls;
|
||||
|
||||
// Method calls may be cancelled by ID, so keep track of their state.
|
||||
// This needs a mutex: handlers may finish on a different thread, and that's
|
||||
// when we clean up entries in the map.
|
||||
|
@ -449,14 +413,6 @@ private:
|
|||
};
|
||||
constexpr int ClangdLSPServer::MessageHandler::MaxReplayCallbacks;
|
||||
|
||||
template <>
|
||||
void ClangdLSPServer::MessageHandler::bind<NoParams>(
|
||||
const char *Method, void (ClangdLSPServer::*Handler)(const NoParams &)) {
|
||||
Notifications[Method] = [Handler, this](llvm::json::Value RawParams) {
|
||||
(Server.*Handler)(NoParams{});
|
||||
};
|
||||
}
|
||||
|
||||
// call(), notify(), and reply() wrap the Transport, adding logging and locking.
|
||||
void ClangdLSPServer::callRaw(StringRef Method, llvm::json::Value Params,
|
||||
Callback<llvm::json::Value> CB) {
|
||||
|
@ -586,7 +542,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
|
|||
CodeAction::INFO_KIND}}};
|
||||
|
||||
std::vector<llvm::StringRef> Commands;
|
||||
for (llvm::StringRef Command : CommandHandlers.keys())
|
||||
for (llvm::StringRef Command : Handlers.CommandHandlers.keys())
|
||||
Commands.push_back(Command);
|
||||
llvm::sort(Commands);
|
||||
|
||||
|
@ -665,7 +621,8 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
|
|||
|
||||
void ClangdLSPServer::onInitialized(const InitializedParams &Params) {}
|
||||
|
||||
void ClangdLSPServer::onShutdown(Callback<std::nullptr_t> Reply) {
|
||||
void ClangdLSPServer::onShutdown(const NoParams &,
|
||||
Callback<std::nullptr_t> Reply) {
|
||||
// Do essentially nothing, just say we're ready to exit.
|
||||
ShutdownRequestReceived = true;
|
||||
Reply(nullptr);
|
||||
|
@ -673,7 +630,7 @@ void ClangdLSPServer::onShutdown(Callback<std::nullptr_t> Reply) {
|
|||
|
||||
// sync is a clangd extension: it blocks until all background work completes.
|
||||
// It blocks the calling thread, so no messages are processed until it returns!
|
||||
void ClangdLSPServer::onSync(Callback<std::nullptr_t> Reply) {
|
||||
void ClangdLSPServer::onSync(const NoParams &, Callback<std::nullptr_t> Reply) {
|
||||
if (Server->blockUntilIdleForTest(/*TimeoutSeconds=*/60))
|
||||
Reply(nullptr);
|
||||
else
|
||||
|
@ -734,8 +691,8 @@ void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
|
|||
|
||||
void ClangdLSPServer::onCommand(const ExecuteCommandParams &Params,
|
||||
Callback<llvm::json::Value> Reply) {
|
||||
auto It = CommandHandlers.find(Params.command);
|
||||
if (It == CommandHandlers.end()) {
|
||||
auto It = Handlers.CommandHandlers.find(Params.command);
|
||||
if (It == Handlers.CommandHandlers.end()) {
|
||||
return Reply(llvm::make_error<LSPError>(
|
||||
llvm::formatv("Unsupported command \"{0}\".", Params.command).str(),
|
||||
ErrorCode::InvalidParams));
|
||||
|
@ -1462,7 +1419,8 @@ void ClangdLSPServer::onSemanticTokensDelta(
|
|||
});
|
||||
}
|
||||
|
||||
void ClangdLSPServer::onMemoryUsage(Callback<MemoryTree> Reply) {
|
||||
void ClangdLSPServer::onMemoryUsage(const NoParams &,
|
||||
Callback<MemoryTree> Reply) {
|
||||
llvm::BumpPtrAllocator DetailAlloc;
|
||||
MemoryTree MT(&DetailAlloc);
|
||||
profile(MT);
|
||||
|
@ -1493,50 +1451,51 @@ ClangdLSPServer::ClangdLSPServer(class Transport &Transp,
|
|||
}
|
||||
|
||||
// clang-format off
|
||||
MsgHandler->bind("initialize", &ClangdLSPServer::onInitialize);
|
||||
MsgHandler->bind("initialized", &ClangdLSPServer::onInitialized);
|
||||
MsgHandler->bind("shutdown", &ClangdLSPServer::onShutdown);
|
||||
MsgHandler->bind("sync", &ClangdLSPServer::onSync);
|
||||
MsgHandler->bind("textDocument/rangeFormatting", &ClangdLSPServer::onDocumentRangeFormatting);
|
||||
MsgHandler->bind("textDocument/onTypeFormatting", &ClangdLSPServer::onDocumentOnTypeFormatting);
|
||||
MsgHandler->bind("textDocument/formatting", &ClangdLSPServer::onDocumentFormatting);
|
||||
MsgHandler->bind("textDocument/codeAction", &ClangdLSPServer::onCodeAction);
|
||||
MsgHandler->bind("textDocument/completion", &ClangdLSPServer::onCompletion);
|
||||
MsgHandler->bind("textDocument/signatureHelp", &ClangdLSPServer::onSignatureHelp);
|
||||
MsgHandler->bind("textDocument/definition", &ClangdLSPServer::onGoToDefinition);
|
||||
MsgHandler->bind("textDocument/declaration", &ClangdLSPServer::onGoToDeclaration);
|
||||
MsgHandler->bind("textDocument/implementation", &ClangdLSPServer::onGoToImplementation);
|
||||
MsgHandler->bind("textDocument/references", &ClangdLSPServer::onReference);
|
||||
MsgHandler->bind("textDocument/switchSourceHeader", &ClangdLSPServer::onSwitchSourceHeader);
|
||||
MsgHandler->bind("textDocument/prepareRename", &ClangdLSPServer::onPrepareRename);
|
||||
MsgHandler->bind("textDocument/rename", &ClangdLSPServer::onRename);
|
||||
MsgHandler->bind("textDocument/hover", &ClangdLSPServer::onHover);
|
||||
MsgHandler->bind("textDocument/documentSymbol", &ClangdLSPServer::onDocumentSymbol);
|
||||
MsgHandler->bind("workspace/executeCommand", &ClangdLSPServer::onCommand);
|
||||
MsgHandler->bind("textDocument/documentHighlight", &ClangdLSPServer::onDocumentHighlight);
|
||||
MsgHandler->bind("workspace/symbol", &ClangdLSPServer::onWorkspaceSymbol);
|
||||
MsgHandler->bind("textDocument/ast", &ClangdLSPServer::onAST);
|
||||
MsgHandler->bind("textDocument/didOpen", &ClangdLSPServer::onDocumentDidOpen);
|
||||
MsgHandler->bind("textDocument/didClose", &ClangdLSPServer::onDocumentDidClose);
|
||||
MsgHandler->bind("textDocument/didChange", &ClangdLSPServer::onDocumentDidChange);
|
||||
MsgHandler->bind("textDocument/didSave", &ClangdLSPServer::onDocumentDidSave);
|
||||
MsgHandler->bind("workspace/didChangeWatchedFiles", &ClangdLSPServer::onFileEvent);
|
||||
MsgHandler->bind("workspace/didChangeConfiguration", &ClangdLSPServer::onChangeConfiguration);
|
||||
MsgHandler->bind("textDocument/symbolInfo", &ClangdLSPServer::onSymbolInfo);
|
||||
MsgHandler->bind("textDocument/typeHierarchy", &ClangdLSPServer::onTypeHierarchy);
|
||||
MsgHandler->bind("typeHierarchy/resolve", &ClangdLSPServer::onResolveTypeHierarchy);
|
||||
MsgHandler->bind("textDocument/prepareCallHierarchy", &ClangdLSPServer::onPrepareCallHierarchy);
|
||||
MsgHandler->bind("callHierarchy/incomingCalls", &ClangdLSPServer::onCallHierarchyIncomingCalls);
|
||||
MsgHandler->bind("callHierarchy/outgoingCalls", &ClangdLSPServer::onCallHierarchyOutgoingCalls);
|
||||
MsgHandler->bind("textDocument/selectionRange", &ClangdLSPServer::onSelectionRange);
|
||||
MsgHandler->bind("textDocument/documentLink", &ClangdLSPServer::onDocumentLink);
|
||||
MsgHandler->bind("textDocument/semanticTokens/full", &ClangdLSPServer::onSemanticTokens);
|
||||
MsgHandler->bind("textDocument/semanticTokens/full/delta", &ClangdLSPServer::onSemanticTokensDelta);
|
||||
MsgHandler->bind("$/memoryUsage", &ClangdLSPServer::onMemoryUsage);
|
||||
LSPBinder Bind(this->Handlers);
|
||||
Bind.method("initialize", this, &ClangdLSPServer::onInitialize);
|
||||
Bind.notification("initialized", this, &ClangdLSPServer::onInitialized);
|
||||
Bind.method("shutdown", this, &ClangdLSPServer::onShutdown);
|
||||
Bind.method("sync", this, &ClangdLSPServer::onSync);
|
||||
Bind.method("textDocument/rangeFormatting", this, &ClangdLSPServer::onDocumentRangeFormatting);
|
||||
Bind.method("textDocument/onTypeFormatting", this, &ClangdLSPServer::onDocumentOnTypeFormatting);
|
||||
Bind.method("textDocument/formatting", this, &ClangdLSPServer::onDocumentFormatting);
|
||||
Bind.method("textDocument/codeAction", this, &ClangdLSPServer::onCodeAction);
|
||||
Bind.method("textDocument/completion", this, &ClangdLSPServer::onCompletion);
|
||||
Bind.method("textDocument/signatureHelp", this, &ClangdLSPServer::onSignatureHelp);
|
||||
Bind.method("textDocument/definition", this, &ClangdLSPServer::onGoToDefinition);
|
||||
Bind.method("textDocument/declaration", this, &ClangdLSPServer::onGoToDeclaration);
|
||||
Bind.method("textDocument/implementation", this, &ClangdLSPServer::onGoToImplementation);
|
||||
Bind.method("textDocument/references", this, &ClangdLSPServer::onReference);
|
||||
Bind.method("textDocument/switchSourceHeader", this, &ClangdLSPServer::onSwitchSourceHeader);
|
||||
Bind.method("textDocument/prepareRename", this, &ClangdLSPServer::onPrepareRename);
|
||||
Bind.method("textDocument/rename", this, &ClangdLSPServer::onRename);
|
||||
Bind.method("textDocument/hover", this, &ClangdLSPServer::onHover);
|
||||
Bind.method("textDocument/documentSymbol", this, &ClangdLSPServer::onDocumentSymbol);
|
||||
Bind.method("workspace/executeCommand", this, &ClangdLSPServer::onCommand);
|
||||
Bind.method("textDocument/documentHighlight", this, &ClangdLSPServer::onDocumentHighlight);
|
||||
Bind.method("workspace/symbol", this, &ClangdLSPServer::onWorkspaceSymbol);
|
||||
Bind.method("textDocument/ast", this, &ClangdLSPServer::onAST);
|
||||
Bind.notification("textDocument/didOpen", this, &ClangdLSPServer::onDocumentDidOpen);
|
||||
Bind.notification("textDocument/didClose", this, &ClangdLSPServer::onDocumentDidClose);
|
||||
Bind.notification("textDocument/didChange", this, &ClangdLSPServer::onDocumentDidChange);
|
||||
Bind.notification("textDocument/didSave", this, &ClangdLSPServer::onDocumentDidSave);
|
||||
Bind.notification("workspace/didChangeWatchedFiles", this, &ClangdLSPServer::onFileEvent);
|
||||
Bind.notification("workspace/didChangeConfiguration", this, &ClangdLSPServer::onChangeConfiguration);
|
||||
Bind.method("textDocument/symbolInfo", this, &ClangdLSPServer::onSymbolInfo);
|
||||
Bind.method("textDocument/typeHierarchy", this, &ClangdLSPServer::onTypeHierarchy);
|
||||
Bind.method("typeHierarchy/resolve", this, &ClangdLSPServer::onResolveTypeHierarchy);
|
||||
Bind.method("textDocument/prepareCallHierarchy", this, &ClangdLSPServer::onPrepareCallHierarchy);
|
||||
Bind.method("callHierarchy/incomingCalls", this, &ClangdLSPServer::onCallHierarchyIncomingCalls);
|
||||
Bind.method("callHierarchy/outgoingCalls", this, &ClangdLSPServer::onCallHierarchyOutgoingCalls);
|
||||
Bind.method("textDocument/selectionRange", this, &ClangdLSPServer::onSelectionRange);
|
||||
Bind.method("textDocument/documentLink", this, &ClangdLSPServer::onDocumentLink);
|
||||
Bind.method("textDocument/semanticTokens/full", this, &ClangdLSPServer::onSemanticTokens);
|
||||
Bind.method("textDocument/semanticTokens/full/delta", this, &ClangdLSPServer::onSemanticTokensDelta);
|
||||
Bind.method("$/memoryUsage", this, &ClangdLSPServer::onMemoryUsage);
|
||||
if (Opts.FoldingRanges)
|
||||
MsgHandler->bind("textDocument/foldingRange", &ClangdLSPServer::onFoldingRange);
|
||||
bindCommand(APPLY_FIX_COMMAND, &ClangdLSPServer::onCommandApplyEdit);
|
||||
bindCommand(APPLY_TWEAK_COMMAND, &ClangdLSPServer::onCommandApplyTweak);
|
||||
Bind.method("textDocument/foldingRange", this, &ClangdLSPServer::onFoldingRange);
|
||||
Bind.command(APPLY_FIX_COMMAND, this, &ClangdLSPServer::onCommandApplyEdit);
|
||||
Bind.command(APPLY_TWEAK_COMMAND, this, &ClangdLSPServer::onCommandApplyTweak);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "Features.inc"
|
||||
#include "FindSymbols.h"
|
||||
#include "GlobalCompilationDatabase.h"
|
||||
#include "LSPBinder.h"
|
||||
#include "Protocol.h"
|
||||
#include "Transport.h"
|
||||
#include "support/Context.h"
|
||||
|
@ -90,8 +91,8 @@ private:
|
|||
// Calls have signature void(const Params&, Callback<Response>).
|
||||
void onInitialize(const InitializeParams &, Callback<llvm::json::Value>);
|
||||
void onInitialized(const InitializedParams &);
|
||||
void onShutdown(Callback<std::nullptr_t>);
|
||||
void onSync(Callback<std::nullptr_t>);
|
||||
void onShutdown(const NoParams &, Callback<std::nullptr_t>);
|
||||
void onSync(const NoParams &, Callback<std::nullptr_t>);
|
||||
void onDocumentDidOpen(const DidOpenTextDocumentParams &);
|
||||
void onDocumentDidChange(const DidChangeTextDocumentParams &);
|
||||
void onDocumentDidClose(const DidCloseTextDocumentParams &);
|
||||
|
@ -157,11 +158,7 @@ private:
|
|||
Callback<SemanticTokensOrDelta>);
|
||||
/// This is a clangd extension. Provides a json tree representing memory usage
|
||||
/// hierarchy.
|
||||
void onMemoryUsage(Callback<MemoryTree>);
|
||||
|
||||
llvm::StringMap<llvm::unique_function<void(const llvm::json::Value &,
|
||||
Callback<llvm::json::Value>)>>
|
||||
CommandHandlers;
|
||||
void onMemoryUsage(const NoParams &, Callback<MemoryTree>);
|
||||
void onCommand(const ExecuteCommandParams &, Callback<llvm::json::Value>);
|
||||
|
||||
/// Implement commands.
|
||||
|
@ -229,29 +226,6 @@ private:
|
|||
std::unique_ptr<MessageHandler> MsgHandler;
|
||||
std::mutex TranspWriter;
|
||||
|
||||
template <typename T>
|
||||
static Expected<T> parse(const llvm::json::Value &Raw,
|
||||
llvm::StringRef PayloadName,
|
||||
llvm::StringRef PayloadKind) {
|
||||
T Result;
|
||||
llvm::json::Path::Root Root;
|
||||
if (!fromJSON(Raw, Result, Root)) {
|
||||
elog("Failed to decode {0} {1}: {2}", PayloadName, PayloadKind,
|
||||
Root.getError());
|
||||
// Dump the relevant parts of the broken message.
|
||||
std::string Context;
|
||||
llvm::raw_string_ostream OS(Context);
|
||||
Root.printErrorContext(Raw, OS);
|
||||
vlog("{0}", OS.str());
|
||||
// Report the error (e.g. to the client).
|
||||
return llvm::make_error<LSPError>(
|
||||
llvm::formatv("failed to decode {0} {1}: {2}", PayloadName,
|
||||
PayloadKind, fmt_consume(Root.getError())),
|
||||
ErrorCode::InvalidParams);
|
||||
}
|
||||
return std::move(Result);
|
||||
}
|
||||
|
||||
template <typename Response>
|
||||
void call(StringRef Method, llvm::json::Value Params, Callback<Response> CB) {
|
||||
// Wrap the callback with LSP conversion and error-handling.
|
||||
|
@ -261,7 +235,7 @@ private:
|
|||
llvm::Expected<llvm::json::Value> RawResponse) mutable {
|
||||
if (!RawResponse)
|
||||
return CB(RawResponse.takeError());
|
||||
CB(parse<Response>(*RawResponse, Method, "response"));
|
||||
CB(LSPBinder::parse<Response>(*RawResponse, Method, "response"));
|
||||
};
|
||||
callRaw(Method, std::move(Params), std::move(HandleReply));
|
||||
}
|
||||
|
@ -274,19 +248,8 @@ private:
|
|||
Params.value = std::move(Value);
|
||||
notify("$/progress", Params);
|
||||
}
|
||||
template <typename Param, typename Result>
|
||||
void bindCommand(llvm::StringLiteral Method,
|
||||
void (ClangdLSPServer::*Handler)(const Param &,
|
||||
Callback<Result>)) {
|
||||
CommandHandlers[Method] = [Method, Handler,
|
||||
this](llvm::json::Value RawParams,
|
||||
Callback<Result> Reply) {
|
||||
auto P = parse<Param>(RawParams, Method, "command");
|
||||
if (!P)
|
||||
return Reply(P.takeError());
|
||||
(this->*Handler)(*P, std::move(Reply));
|
||||
};
|
||||
}
|
||||
|
||||
LSPBinder::RawHandlers Handlers;
|
||||
|
||||
const ThreadsafeFS &TFS;
|
||||
/// Options used for diagnostics.
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
//===--- LSPBinder.h - Tables of LSP handlers --------------------*- C++-*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_LSPBINDER_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_LSPBINDER_H
|
||||
|
||||
#include "Protocol.h"
|
||||
#include "support/Function.h"
|
||||
#include "support/Logger.h"
|
||||
#include "llvm/ADT/FunctionExtras.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
#include "llvm/Support/JSON.h"
|
||||
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
|
||||
/// LSPBinder collects a table of functions that handle LSP calls.
|
||||
///
|
||||
/// It translates a handler method's signature, e.g.
|
||||
/// void codeComplete(CompletionParams, Callback<CompletionList>)
|
||||
/// into a wrapper with a generic signature:
|
||||
/// void(json::Value, Callback<json::Value>)
|
||||
/// The wrapper takes care of parsing/serializing responses.
|
||||
///
|
||||
/// Incoming calls can be methods, notifications, or commands - all are similar.
|
||||
///
|
||||
/// FIXME: this should also take responsibility for wrapping *outgoing* calls,
|
||||
/// replacing the typed ClangdLSPServer::call<> etc.
|
||||
class LSPBinder {
|
||||
public:
|
||||
using JSON = llvm::json::Value;
|
||||
|
||||
struct RawHandlers {
|
||||
template <typename HandlerT>
|
||||
using HandlerMap = llvm::StringMap<llvm::unique_function<HandlerT>>;
|
||||
|
||||
HandlerMap<void(JSON)> NotificationHandlers;
|
||||
HandlerMap<void(JSON, Callback<JSON>)> MethodHandlers;
|
||||
HandlerMap<void(JSON, Callback<JSON>)> CommandHandlers;
|
||||
};
|
||||
|
||||
LSPBinder(RawHandlers &Raw) : Raw(Raw) {}
|
||||
|
||||
/// Bind a handler for an LSP method.
|
||||
/// e.g. Bind.method("peek", this, &ThisModule::peek);
|
||||
/// Handler should be e.g. void peek(const PeekParams&, Callback<PeekResult>);
|
||||
/// PeekParams must be JSON-parseable and PeekResult must be serializable.
|
||||
template <typename Param, typename Result, typename ThisT>
|
||||
void method(llvm::StringLiteral Method, ThisT *This,
|
||||
void (ThisT::*Handler)(const Param &, Callback<Result>));
|
||||
|
||||
/// Bind a handler for an LSP notification.
|
||||
/// e.g. Bind.notification("poke", this, &ThisModule::poke);
|
||||
/// Handler should be e.g. void poke(const PokeParams&);
|
||||
/// PokeParams must be JSON-parseable.
|
||||
template <typename Param, typename ThisT>
|
||||
void notification(llvm::StringLiteral Method, ThisT *This,
|
||||
void (ThisT::*Handler)(const Param &));
|
||||
|
||||
/// Bind a handler for an LSP command.
|
||||
/// e.g. Bind.command("load", this, &ThisModule::load);
|
||||
/// Handler should be e.g. void load(const LoadParams&, Callback<LoadResult>);
|
||||
/// LoadParams must be JSON-parseable and LoadResult must be serializable.
|
||||
template <typename Param, typename Result, typename ThisT>
|
||||
void command(llvm::StringLiteral Command, ThisT *This,
|
||||
void (ThisT::*Handler)(const Param &, Callback<Result>));
|
||||
|
||||
// FIXME: remove usage from ClangdLSPServer and make this private.
|
||||
template <typename T>
|
||||
static llvm::Expected<T> parse(const llvm::json::Value &Raw,
|
||||
llvm::StringRef PayloadName,
|
||||
llvm::StringRef PayloadKind);
|
||||
|
||||
private:
|
||||
RawHandlers &Raw;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
llvm::Expected<T> LSPBinder::parse(const llvm::json::Value &Raw,
|
||||
llvm::StringRef PayloadName,
|
||||
llvm::StringRef PayloadKind) {
|
||||
T Result;
|
||||
llvm::json::Path::Root Root;
|
||||
if (!fromJSON(Raw, Result, Root)) {
|
||||
elog("Failed to decode {0} {1}: {2}", PayloadName, PayloadKind,
|
||||
Root.getError());
|
||||
// Dump the relevant parts of the broken message.
|
||||
std::string Context;
|
||||
llvm::raw_string_ostream OS(Context);
|
||||
Root.printErrorContext(Raw, OS);
|
||||
vlog("{0}", OS.str());
|
||||
// Report the error (e.g. to the client).
|
||||
return llvm::make_error<LSPError>(
|
||||
llvm::formatv("failed to decode {0} {1}: {2}", PayloadName, PayloadKind,
|
||||
fmt_consume(Root.getError())),
|
||||
ErrorCode::InvalidParams);
|
||||
}
|
||||
return std::move(Result);
|
||||
}
|
||||
|
||||
template <typename Param, typename Result, typename ThisT>
|
||||
void LSPBinder::method(llvm::StringLiteral Method, ThisT *This,
|
||||
void (ThisT::*Handler)(const Param &,
|
||||
Callback<Result>)) {
|
||||
Raw.MethodHandlers[Method] = [Method, Handler, This](JSON RawParams,
|
||||
Callback<JSON> Reply) {
|
||||
auto P = LSPBinder::parse<Param>(RawParams, Method, "request");
|
||||
if (!P)
|
||||
return Reply(P.takeError());
|
||||
(This->*Handler)(*P, std::move(Reply));
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Param, typename ThisT>
|
||||
void LSPBinder::notification(llvm::StringLiteral Method, ThisT *This,
|
||||
void (ThisT::*Handler)(const Param &)) {
|
||||
Raw.NotificationHandlers[Method] = [Method, Handler, This](JSON RawParams) {
|
||||
llvm::Expected<Param> P =
|
||||
LSPBinder::parse<Param>(RawParams, Method, "request");
|
||||
if (!P)
|
||||
return llvm::consumeError(P.takeError());
|
||||
(This->*Handler)(*P);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Param, typename Result, typename ThisT>
|
||||
void LSPBinder::command(llvm::StringLiteral Method, ThisT *This,
|
||||
void (ThisT::*Handler)(const Param &,
|
||||
Callback<Result>)) {
|
||||
Raw.CommandHandlers[Method] = [Method, Handler, This](JSON RawParams,
|
||||
Callback<JSON> Reply) {
|
||||
auto P = LSPBinder::parse<Param>(RawParams, Method, "command");
|
||||
if (!P)
|
||||
return Reply(P.takeError());
|
||||
(This->*Handler)(*P, std::move(Reply));
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
||||
#endif
|
|
@ -70,6 +70,7 @@ add_unittest(ClangdUnitTests ClangdTests
|
|||
IndexTests.cpp
|
||||
JSONTransportTests.cpp
|
||||
LoggerTests.cpp
|
||||
LSPBinderTests.cpp
|
||||
LSPClient.cpp
|
||||
ModulesTests.cpp
|
||||
ParsedASTTests.cpp
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
//===-- LSPBinderTests.cpp ------------------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "LSPBinder.h"
|
||||
#include "llvm/Testing/Support/Error.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
namespace {
|
||||
|
||||
using testing::HasSubstr;
|
||||
using testing::UnorderedElementsAre;
|
||||
|
||||
// JSON-serializable type for testing.
|
||||
struct Foo {
|
||||
int X;
|
||||
};
|
||||
bool fromJSON(const llvm::json::Value &V, Foo &F, llvm::json::Path P) {
|
||||
return fromJSON(V, F.X, P.field("X"));
|
||||
}
|
||||
llvm::json::Value toJSON(const Foo &F) { return F.X; }
|
||||
|
||||
// Creates a Callback that writes its received value into an Optional<Expected>.
|
||||
template <typename T>
|
||||
llvm::unique_function<void(llvm::Expected<T>)>
|
||||
capture(llvm::Optional<llvm::Expected<T>> &Out) {
|
||||
Out.reset();
|
||||
return [&Out](llvm::Expected<T> V) { Out.emplace(std::move(V)); };
|
||||
}
|
||||
|
||||
TEST(LSPBinderTest, IncomingCalls) {
|
||||
LSPBinder::RawHandlers RawHandlers;
|
||||
LSPBinder Binder{RawHandlers};
|
||||
struct Handler {
|
||||
void plusOne(const Foo &Params, Callback<Foo> Reply) {
|
||||
Reply(Foo{Params.X + 1});
|
||||
}
|
||||
void fail(const Foo &Params, Callback<Foo> Reply) {
|
||||
Reply(error("X={0}", Params.X));
|
||||
}
|
||||
void notify(const Foo &Params) {
|
||||
LastNotify = Params.X;
|
||||
++NotifyCount;
|
||||
}
|
||||
int LastNotify = -1;
|
||||
int NotifyCount = 0;
|
||||
};
|
||||
|
||||
Handler H;
|
||||
Binder.method("plusOne", &H, &Handler::plusOne);
|
||||
Binder.method("fail", &H, &Handler::fail);
|
||||
Binder.notification("notify", &H, &Handler::notify);
|
||||
Binder.command("cmdPlusOne", &H, &Handler::plusOne);
|
||||
ASSERT_THAT(RawHandlers.MethodHandlers.keys(),
|
||||
UnorderedElementsAre("plusOne", "fail"));
|
||||
ASSERT_THAT(RawHandlers.NotificationHandlers.keys(),
|
||||
UnorderedElementsAre("notify"));
|
||||
ASSERT_THAT(RawHandlers.CommandHandlers.keys(),
|
||||
UnorderedElementsAre("cmdPlusOne"));
|
||||
llvm::Optional<llvm::Expected<llvm::json::Value>> Reply;
|
||||
|
||||
auto &RawPlusOne = RawHandlers.MethodHandlers["plusOne"];
|
||||
RawPlusOne(1, capture(Reply));
|
||||
ASSERT_TRUE(Reply.hasValue());
|
||||
EXPECT_THAT_EXPECTED(Reply.getValue(), llvm::HasValue(2));
|
||||
RawPlusOne("foo", capture(Reply));
|
||||
ASSERT_TRUE(Reply.hasValue());
|
||||
EXPECT_THAT_EXPECTED(
|
||||
Reply.getValue(),
|
||||
llvm::FailedWithMessage(
|
||||
HasSubstr("failed to decode plusOne request: expected integer")));
|
||||
|
||||
auto &RawFail = RawHandlers.MethodHandlers["fail"];
|
||||
RawFail(2, capture(Reply));
|
||||
ASSERT_TRUE(Reply.hasValue());
|
||||
EXPECT_THAT_EXPECTED(Reply.getValue(), llvm::FailedWithMessage("X=2"));
|
||||
|
||||
auto &RawNotify = RawHandlers.NotificationHandlers["notify"];
|
||||
RawNotify(42);
|
||||
EXPECT_EQ(H.LastNotify, 42);
|
||||
EXPECT_EQ(H.NotifyCount, 1);
|
||||
RawNotify("hi"); // invalid, will be logged
|
||||
EXPECT_EQ(H.LastNotify, 42);
|
||||
EXPECT_EQ(H.NotifyCount, 1);
|
||||
|
||||
auto &RawCmdPlusOne = RawHandlers.CommandHandlers["cmdPlusOne"];
|
||||
RawCmdPlusOne(1, capture(Reply));
|
||||
ASSERT_TRUE(Reply.hasValue());
|
||||
EXPECT_THAT_EXPECTED(Reply.getValue(), llvm::HasValue(2));
|
||||
}
|
||||
} // namespace
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
Loading…
Reference in New Issue