1 //===--- LSPBinder.h - Tables of LSP handlers --------------------*- C++-*-===//
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
7 //===----------------------------------------------------------------------===//
9 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_LSPBINDER_H
10 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_LSPBINDER_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"
22 /// LSPBinder collects a table of functions that handle LSP calls.
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.
30 /// Incoming calls can be methods, notifications, or commands - all are similar.
32 /// FIXME: this should also take responsibility for wrapping *outgoing* calls,
33 /// replacing the typed ClangdLSPServer::call<> etc.
36 using JSON
= llvm::json::Value
;
39 template <typename HandlerT
>
40 using HandlerMap
= llvm::StringMap
<llvm::unique_function
<HandlerT
>>;
42 HandlerMap
<void(JSON
)> NotificationHandlers
;
43 HandlerMap
<void(JSON
, Callback
<JSON
>)> MethodHandlers
;
44 HandlerMap
<void(JSON
, Callback
<JSON
>)> CommandHandlers
;
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;
54 LSPBinder(RawHandlers
&Raw
, RawOutgoing
&Out
) : Raw(Raw
), Out(Out
) {}
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
>));
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
&));
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
>));
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
);
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
);
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
);
109 template <typename T
>
110 llvm::Expected
<T
> LSPBinder::parse(const llvm::json::Value
&Raw
,
111 llvm::StringRef PayloadName
,
112 llvm::StringRef PayloadKind
) {
114 llvm::json::Path::Root Root
;
115 if (!fromJSON(Raw
, Result
, Root
)) {
116 elog("Failed to decode {0} {1}: {2}", PayloadName
, PayloadKind
,
118 // Dump the relevant parts of the broken message.
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
);
129 return std::move(Result
);
132 template <typename Param
, typename Result
, typename ThisT
>
133 void LSPBinder::method(llvm::StringLiteral Method
, ThisT
*This
,
134 void (ThisT::*Handler
)(const Param
&,
136 Raw
.MethodHandlers
[Method
] = [Method
, Handler
, This
](JSON RawParams
,
137 Callback
<JSON
> Reply
) {
138 auto P
= LSPBinder::parse
<Param
>(RawParams
, Method
, "request");
140 return Reply(P
.takeError());
141 (This
->*Handler
)(*P
, std::move(Reply
));
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");
152 return llvm::consumeError(P
.takeError());
153 (This
->*Handler
)(*P
);
157 template <typename Param
, typename Result
, typename ThisT
>
158 void LSPBinder::command(llvm::StringLiteral Method
, ThisT
*This
,
159 void (ThisT::*Handler
)(const Param
&,
161 Raw
.CommandHandlers
[Method
] = [Method
, Handler
, This
](JSON RawParams
,
162 Callback
<JSON
> Reply
) {
163 auto P
= LSPBinder::parse
<Param
>(RawParams
, Method
, "command");
165 return Reply(P
.takeError());
166 (This
->*Handler
)(*P
, std::move(Reply
));
170 class LSPBinder::UntypedOutgoingNotification
{
171 llvm::StringLiteral Method
;
173 UntypedOutgoingNotification(llvm::StringLiteral Method
, RawOutgoing
*Out
)
174 : Method(Method
), Out(Out
) {}
175 friend UntypedOutgoingNotification
176 LSPBinder::outgoingNotification(llvm::StringLiteral
);
179 template <typename Request
> operator OutgoingNotification
<Request
>() && {
181 [Method(Method
), Out(Out
)](Request R
) { Out
->notify(Method
, JSON(R
)); };
185 inline LSPBinder::UntypedOutgoingNotification
186 LSPBinder::outgoingNotification(llvm::StringLiteral Method
) {
187 return UntypedOutgoingNotification(Method
, &Out
);
190 class LSPBinder::UntypedOutgoingMethod
{
191 llvm::StringLiteral Method
;
193 UntypedOutgoingMethod(llvm::StringLiteral Method
, RawOutgoing
*Out
)
194 : Method(Method
), Out(Out
) {}
195 friend UntypedOutgoingMethod
LSPBinder::outgoingMethod(llvm::StringLiteral
);
198 template <typename Request
, typename Response
>
199 operator OutgoingMethod
<Request
, Response
>() && {
200 return [Method(Method
), Out(Out
)](Request R
, Callback
<Response
> Reply
) {
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 {
207 return Reply(RawRsp
.takeError());
208 Reply(LSPBinder::parse
<Response
>(std::move(*RawRsp
), Method
,
215 inline LSPBinder::UntypedOutgoingMethod
216 LSPBinder::outgoingMethod(llvm::StringLiteral Method
) {
217 return UntypedOutgoingMethod(Method
, &Out
);
220 } // namespace clangd