xref: /llvm-project/clang-tools-extra/clangd/LSPBinder.h (revision 7b83837af6f472e9c0ed0b96b78717d4a3c4dbfe)
1 //===--- LSPBinder.h - Tables of LSP handlers --------------------*- C++-*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_LSPBINDER_H
10 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_LSPBINDER_H
11 
12 #include "Protocol.h"
13 #include "support/Function.h"
14 #include "support/Logger.h"
15 #include "llvm/ADT/FunctionExtras.h"
16 #include "llvm/ADT/StringMap.h"
17 #include "llvm/Support/JSON.h"
18 
19 namespace clang {
20 namespace clangd {
21 
22 /// LSPBinder collects a table of functions that handle LSP calls.
23 ///
24 /// It translates a handler method's signature, e.g.
25 ///    void codeComplete(CompletionParams, Callback<CompletionList>)
26 /// into a wrapper with a generic signature:
27 ///    void(json::Value, Callback<json::Value>)
28 /// The wrapper takes care of parsing/serializing responses.
29 ///
30 /// Incoming calls can be methods, notifications, or commands - all are similar.
31 ///
32 /// FIXME: this should also take responsibility for wrapping *outgoing* calls,
33 /// replacing the typed ClangdLSPServer::call<> etc.
34 class LSPBinder {
35 public:
36   using JSON = llvm::json::Value;
37 
38   struct RawHandlers {
39     template <typename HandlerT>
40     using HandlerMap = llvm::StringMap<llvm::unique_function<HandlerT>>;
41 
42     HandlerMap<void(JSON)> NotificationHandlers;
43     HandlerMap<void(JSON, Callback<JSON>)> MethodHandlers;
44     HandlerMap<void(JSON, Callback<JSON>)> CommandHandlers;
45   };
46   class RawOutgoing {
47   public:
48     virtual ~RawOutgoing() = default;
49     virtual void callMethod(llvm::StringRef Method, JSON Params,
50                             Callback<JSON> Reply) = 0;
51     virtual void notify(llvm::StringRef Method, JSON Params) = 0;
52   };
53 
LSPBinder(RawHandlers & Raw,RawOutgoing & Out)54   LSPBinder(RawHandlers &Raw, RawOutgoing &Out) : Raw(Raw), Out(Out) {}
55 
56   /// Bind a handler for an LSP method.
57   /// e.g. Bind.method("peek", this, &ThisModule::peek);
58   /// Handler should be e.g. void peek(const PeekParams&, Callback<PeekResult>);
59   /// PeekParams must be JSON-parseable and PeekResult must be serializable.
60   template <typename Param, typename Result, typename ThisT>
61   void method(llvm::StringLiteral Method, ThisT *This,
62               void (ThisT::*Handler)(const Param &, Callback<Result>));
63 
64   /// Bind a handler for an LSP notification.
65   /// e.g. Bind.notification("poke", this, &ThisModule::poke);
66   /// Handler should be e.g. void poke(const PokeParams&);
67   /// PokeParams must be JSON-parseable.
68   template <typename Param, typename ThisT>
69   void notification(llvm::StringLiteral Method, ThisT *This,
70                     void (ThisT::*Handler)(const Param &));
71 
72   /// Bind a handler for an LSP command.
73   /// e.g. Bind.command("load", this, &ThisModule::load);
74   /// Handler should be e.g. void load(const LoadParams&, Callback<LoadResult>);
75   /// LoadParams must be JSON-parseable and LoadResult must be serializable.
76   template <typename Param, typename Result, typename ThisT>
77   void command(llvm::StringLiteral Command, ThisT *This,
78                void (ThisT::*Handler)(const Param &, Callback<Result>));
79 
80   template <typename P, typename R>
81   using OutgoingMethod = llvm::unique_function<void(const P &, Callback<R>)>;
82   /// UntypedOutgoingMethod is convertible to OutgoingMethod<P, R>.
83   class UntypedOutgoingMethod;
84   /// Bind a function object to be used for outgoing method calls.
85   /// e.g. OutgoingMethod<EParams, EResult> Edit = Bind.outgoingMethod("edit");
86   /// EParams must be JSON-serializable, EResult must be parseable.
87   UntypedOutgoingMethod outgoingMethod(llvm::StringLiteral Method);
88 
89   template <typename P>
90   using OutgoingNotification = llvm::unique_function<void(const P &)>;
91   /// UntypedOutgoingNotification is convertible to OutgoingNotification<T>.
92   class UntypedOutgoingNotification;
93   /// Bind a function object to be used for outgoing notifications.
94   /// e.g. OutgoingNotification<LogParams> Log = Bind.outgoingMethod("log");
95   /// LogParams must be JSON-serializable.
96   UntypedOutgoingNotification outgoingNotification(llvm::StringLiteral Method);
97 
98 private:
99   // FIXME: remove usage from ClangdLSPServer and make this private.
100   template <typename T>
101   static llvm::Expected<T> parse(const llvm::json::Value &Raw,
102                                  llvm::StringRef PayloadName,
103                                  llvm::StringRef PayloadKind);
104 
105   RawHandlers &Raw;
106   RawOutgoing &Out;
107 };
108 
109 template <typename T>
parse(const llvm::json::Value & Raw,llvm::StringRef PayloadName,llvm::StringRef PayloadKind)110 llvm::Expected<T> LSPBinder::parse(const llvm::json::Value &Raw,
111                                    llvm::StringRef PayloadName,
112                                    llvm::StringRef PayloadKind) {
113   T Result;
114   llvm::json::Path::Root Root;
115   if (!fromJSON(Raw, Result, Root)) {
116     elog("Failed to decode {0} {1}: {2}", PayloadName, PayloadKind,
117          Root.getError());
118     // Dump the relevant parts of the broken message.
119     std::string Context;
120     llvm::raw_string_ostream OS(Context);
121     Root.printErrorContext(Raw, OS);
122     vlog("{0}", OS.str());
123     // Report the error (e.g. to the client).
124     return llvm::make_error<LSPError>(
125         llvm::formatv("failed to decode {0} {1}: {2}", PayloadName, PayloadKind,
126                       fmt_consume(Root.getError())),
127         ErrorCode::InvalidParams);
128   }
129   return std::move(Result);
130 }
131 
132 template <typename Param, typename Result, typename ThisT>
method(llvm::StringLiteral Method,ThisT * This,void (ThisT::* Handler)(const Param &,Callback<Result>))133 void LSPBinder::method(llvm::StringLiteral Method, ThisT *This,
134                        void (ThisT::*Handler)(const Param &,
135                                               Callback<Result>)) {
136   Raw.MethodHandlers[Method] = [Method, Handler, This](JSON RawParams,
137                                                        Callback<JSON> Reply) {
138     auto P = LSPBinder::parse<Param>(RawParams, Method, "request");
139     if (!P)
140       return Reply(P.takeError());
141     (This->*Handler)(*P, std::move(Reply));
142   };
143 }
144 
145 template <typename Param, typename ThisT>
notification(llvm::StringLiteral Method,ThisT * This,void (ThisT::* Handler)(const Param &))146 void LSPBinder::notification(llvm::StringLiteral Method, ThisT *This,
147                              void (ThisT::*Handler)(const Param &)) {
148   Raw.NotificationHandlers[Method] = [Method, Handler, This](JSON RawParams) {
149     llvm::Expected<Param> P =
150         LSPBinder::parse<Param>(RawParams, Method, "request");
151     if (!P)
152       return llvm::consumeError(P.takeError());
153     (This->*Handler)(*P);
154   };
155 }
156 
157 template <typename Param, typename Result, typename ThisT>
command(llvm::StringLiteral Method,ThisT * This,void (ThisT::* Handler)(const Param &,Callback<Result>))158 void LSPBinder::command(llvm::StringLiteral Method, ThisT *This,
159                         void (ThisT::*Handler)(const Param &,
160                                                Callback<Result>)) {
161   Raw.CommandHandlers[Method] = [Method, Handler, This](JSON RawParams,
162                                                         Callback<JSON> Reply) {
163     auto P = LSPBinder::parse<Param>(RawParams, Method, "command");
164     if (!P)
165       return Reply(P.takeError());
166     (This->*Handler)(*P, std::move(Reply));
167   };
168 }
169 
170 class LSPBinder::UntypedOutgoingNotification {
171   llvm::StringLiteral Method;
172   RawOutgoing *Out;
UntypedOutgoingNotification(llvm::StringLiteral Method,RawOutgoing * Out)173   UntypedOutgoingNotification(llvm::StringLiteral Method, RawOutgoing *Out)
174       : Method(Method), Out(Out) {}
175   friend UntypedOutgoingNotification
176       LSPBinder::outgoingNotification(llvm::StringLiteral);
177 
178 public:
179   template <typename Request> operator OutgoingNotification<Request>() && {
180     return
181         [Method(Method), Out(Out)](Request R) { Out->notify(Method, JSON(R)); };
182   }
183 };
184 
185 inline LSPBinder::UntypedOutgoingNotification
outgoingNotification(llvm::StringLiteral Method)186 LSPBinder::outgoingNotification(llvm::StringLiteral Method) {
187   return UntypedOutgoingNotification(Method, &Out);
188 }
189 
190 class LSPBinder::UntypedOutgoingMethod {
191   llvm::StringLiteral Method;
192   RawOutgoing *Out;
UntypedOutgoingMethod(llvm::StringLiteral Method,RawOutgoing * Out)193   UntypedOutgoingMethod(llvm::StringLiteral Method, RawOutgoing *Out)
194       : Method(Method), Out(Out) {}
195   friend UntypedOutgoingMethod LSPBinder::outgoingMethod(llvm::StringLiteral);
196 
197 public:
198   template <typename Request, typename Response>
199   operator OutgoingMethod<Request, Response>() && {
200     return [Method(Method), Out(Out)](Request R, Callback<Response> Reply) {
201       Out->callMethod(
202           Method, JSON(R),
203           // FIXME: why keep ctx alive but not restore it for the callback?
204           [Reply(std::move(Reply)), Ctx(Context::current().clone()),
205            Method](llvm::Expected<JSON> RawRsp) mutable {
206             if (!RawRsp)
207               return Reply(RawRsp.takeError());
208             Reply(LSPBinder::parse<Response>(std::move(*RawRsp), Method,
209                                              "reply"));
210           });
211     };
212   }
213 };
214 
215 inline LSPBinder::UntypedOutgoingMethod
outgoingMethod(llvm::StringLiteral Method)216 LSPBinder::outgoingMethod(llvm::StringLiteral Method) {
217   return UntypedOutgoingMethod(Method, &Out);
218 }
219 
220 } // namespace clangd
221 } // namespace clang
222 
223 #endif
224