//===--- 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) /// into a wrapper with a generic signature: /// void(json::Value, Callback) /// 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 using HandlerMap = llvm::StringMap>; HandlerMap NotificationHandlers; HandlerMap)> MethodHandlers; HandlerMap)> CommandHandlers; }; class RawOutgoing { public: virtual ~RawOutgoing() = default; virtual void callMethod(llvm::StringRef Method, JSON Params, Callback Reply) = 0; virtual void notify(llvm::StringRef Method, JSON Params) = 0; }; LSPBinder(RawHandlers &Raw, RawOutgoing &Out) : Raw(Raw), Out(Out) {} /// 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); /// PeekParams must be JSON-parseable and PeekResult must be serializable. template void method(llvm::StringLiteral Method, ThisT *This, void (ThisT::*Handler)(const Param &, Callback)); /// 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 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); /// LoadParams must be JSON-parseable and LoadResult must be serializable. template void command(llvm::StringLiteral Command, ThisT *This, void (ThisT::*Handler)(const Param &, Callback)); template using OutgoingMethod = llvm::unique_function)>; /// UntypedOutgoingMethod is convertible to OutgoingMethod. class UntypedOutgoingMethod; /// Bind a function object to be used for outgoing method calls. /// e.g. OutgoingMethod Edit = Bind.outgoingMethod("edit"); /// EParams must be JSON-serializable, EResult must be parseable. UntypedOutgoingMethod outgoingMethod(llvm::StringLiteral Method); template using OutgoingNotification = llvm::unique_function; /// UntypedOutgoingNotification is convertible to OutgoingNotification. class UntypedOutgoingNotification; /// Bind a function object to be used for outgoing notifications. /// e.g. OutgoingNotification Log = Bind.outgoingMethod("log"); /// LogParams must be JSON-serializable. UntypedOutgoingNotification outgoingNotification(llvm::StringLiteral Method); private: // FIXME: remove usage from ClangdLSPServer and make this private. template static llvm::Expected parse(const llvm::json::Value &Raw, llvm::StringRef PayloadName, llvm::StringRef PayloadKind); RawHandlers &Raw; RawOutgoing &Out; }; template llvm::Expected 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( llvm::formatv("failed to decode {0} {1}: {2}", PayloadName, PayloadKind, fmt_consume(Root.getError())), ErrorCode::InvalidParams); } return std::move(Result); } template void LSPBinder::method(llvm::StringLiteral Method, ThisT *This, void (ThisT::*Handler)(const Param &, Callback)) { Raw.MethodHandlers[Method] = [Method, Handler, This](JSON RawParams, Callback Reply) { auto P = LSPBinder::parse(RawParams, Method, "request"); if (!P) return Reply(P.takeError()); (This->*Handler)(*P, std::move(Reply)); }; } template void LSPBinder::notification(llvm::StringLiteral Method, ThisT *This, void (ThisT::*Handler)(const Param &)) { Raw.NotificationHandlers[Method] = [Method, Handler, This](JSON RawParams) { llvm::Expected P = LSPBinder::parse(RawParams, Method, "request"); if (!P) return llvm::consumeError(P.takeError()); (This->*Handler)(*P); }; } template void LSPBinder::command(llvm::StringLiteral Method, ThisT *This, void (ThisT::*Handler)(const Param &, Callback)) { Raw.CommandHandlers[Method] = [Method, Handler, This](JSON RawParams, Callback Reply) { auto P = LSPBinder::parse(RawParams, Method, "command"); if (!P) return Reply(P.takeError()); (This->*Handler)(*P, std::move(Reply)); }; } class LSPBinder::UntypedOutgoingNotification { llvm::StringLiteral Method; RawOutgoing *Out; UntypedOutgoingNotification(llvm::StringLiteral Method, RawOutgoing *Out) : Method(Method), Out(Out) {} friend UntypedOutgoingNotification LSPBinder::outgoingNotification(llvm::StringLiteral); public: template operator OutgoingNotification() && { return [Method(Method), Out(Out)](Request R) { Out->notify(Method, JSON(R)); }; } }; inline LSPBinder::UntypedOutgoingNotification LSPBinder::outgoingNotification(llvm::StringLiteral Method) { return UntypedOutgoingNotification(Method, &Out); } class LSPBinder::UntypedOutgoingMethod { llvm::StringLiteral Method; RawOutgoing *Out; UntypedOutgoingMethod(llvm::StringLiteral Method, RawOutgoing *Out) : Method(Method), Out(Out) {} friend UntypedOutgoingMethod LSPBinder::outgoingMethod(llvm::StringLiteral); public: template operator OutgoingMethod() && { return [Method(Method), Out(Out)](Request R, Callback Reply) { Out->callMethod( Method, JSON(R), // FIXME: why keep ctx alive but not restore it for the callback? [Reply(std::move(Reply)), Ctx(Context::current().clone()), Method](llvm::Expected RawRsp) mutable { if (!RawRsp) return Reply(RawRsp.takeError()); Reply(LSPBinder::parse(std::move(*RawRsp), Method, "reply")); }); }; } }; inline LSPBinder::UntypedOutgoingMethod LSPBinder::outgoingMethod(llvm::StringLiteral Method) { return UntypedOutgoingMethod(Method, &Out); } } // namespace clangd } // namespace clang #endif