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 |
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 | |
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> |
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(Fmt: "Failed to decode {0} {1}: {2}" , Vals&: PayloadName, Vals&: PayloadKind, |
117 | Vals: 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(Fmt: "{0}" , Vals&: OS.str()); |
123 | // Report the error (e.g. to the client). |
124 | return llvm::make_error<LSPError>( |
125 | Args: llvm::formatv(Fmt: "failed to decode {0} {1}: {2}" , Vals&: PayloadName, Vals&: PayloadKind, |
126 | Vals: fmt_consume(Item: Root.getError())), |
127 | Args: ErrorCode::InvalidParams); |
128 | } |
129 | return std::move(Result); |
130 | } |
131 | |
132 | template <typename Param, typename Result, typename ThisT> |
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> |
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(Err: P.takeError()); |
153 | (This->*Handler)(*P); |
154 | }; |
155 | } |
156 | |
157 | template <typename Param, typename Result, typename ThisT> |
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; |
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, Params: JSON(R)); }; |
182 | } |
183 | }; |
184 | |
185 | inline LSPBinder::UntypedOutgoingNotification |
186 | LSPBinder::outgoingNotification(llvm::StringLiteral Method) { |
187 | return UntypedOutgoingNotification(Method, &Out); |
188 | } |
189 | |
190 | class LSPBinder::UntypedOutgoingMethod { |
191 | llvm::StringLiteral Method; |
192 | 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, Params: JSON(R), |
203 | // FIXME: why keep ctx alive but not restore it for the callback? |
204 | Reply: [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 |
216 | LSPBinder::outgoingMethod(llvm::StringLiteral Method) { |
217 | return UntypedOutgoingMethod(Method, &Out); |
218 | } |
219 | |
220 | } // namespace clangd |
221 | } // namespace clang |
222 | |
223 | #endif |
224 | |