clang-tools 18.0.0git
LSPClient.cpp
Go to the documentation of this file.
1//===-- LSPClient.cpp - Helper for ClangdLSPServer tests ------------------===//
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#include "LSPClient.h"
10#include "Protocol.h"
11#include "TestFS.h"
12#include "Transport.h"
13#include "support/Logger.h"
14#include "support/Threading.h"
15#include "llvm/ADT/STLExtras.h"
16#include "llvm/ADT/StringMap.h"
17#include "llvm/ADT/StringRef.h"
18#include "llvm/Support/Error.h"
19#include "llvm/Support/JSON.h"
20#include "llvm/Support/Path.h"
21#include "llvm/Support/raw_ostream.h"
22#include "gtest/gtest.h"
23#include <condition_variable>
24#include <cstddef>
25#include <cstdint>
26#include <deque>
27#include <functional>
28#include <memory>
29#include <mutex>
30#include <optional>
31#include <queue>
32#include <string>
33#include <utility>
34#include <vector>
35
36namespace clang {
37namespace clangd {
38
39llvm::Expected<llvm::json::Value> clang::clangd::LSPClient::CallResult::take() {
40 std::unique_lock<std::mutex> Lock(Mu);
41 static constexpr size_t TimeoutSecs = 60;
42 if (!clangd::wait(Lock, CV, timeoutSeconds(TimeoutSecs),
43 [this] { return Value.has_value(); })) {
44 ADD_FAILURE() << "No result from call after " << TimeoutSecs << " seconds!";
45 return llvm::json::Value(nullptr);
46 }
47 auto Res = std::move(*Value);
48 Value.reset();
49 return Res;
50}
51
53 auto ExpValue = take();
54 if (!ExpValue) {
55 ADD_FAILURE() << "takeValue(): " << llvm::toString(ExpValue.takeError());
56 return llvm::json::Value(nullptr);
57 }
58 return std::move(*ExpValue);
59}
60
61void LSPClient::CallResult::set(llvm::Expected<llvm::json::Value> V) {
62 std::lock_guard<std::mutex> Lock(Mu);
63 if (Value) {
64 ADD_FAILURE() << "Multiple replies";
65 llvm::consumeError(V.takeError());
66 return;
67 }
68 Value = std::move(V);
69 CV.notify_all();
70}
71
73 if (Value && !*Value) {
74 ADD_FAILURE() << llvm::toString(Value->takeError());
75 }
76}
77
78static void logBody(llvm::StringRef Method, llvm::json::Value V, bool Send) {
79 // We invert <<< and >>> as the combined log is from the server's viewpoint.
80 vlog("{0} {1}: {2:2}", Send ? "<<<" : ">>>", Method, V);
81}
82
84public:
85 std::pair<llvm::json::Value, CallResult *> addCallSlot() {
86 std::lock_guard<std::mutex> Lock(Mu);
87 unsigned ID = CallResults.size();
88 CallResults.emplace_back();
89 return {ID, &CallResults.back()};
90 }
91
92 // A null action causes the transport to shut down.
93 void enqueue(std::function<void(MessageHandler &)> Action) {
94 std::lock_guard<std::mutex> Lock(Mu);
95 Actions.push(std::move(Action));
96 CV.notify_all();
97 }
98
99 std::vector<llvm::json::Value> takeNotifications(llvm::StringRef Method) {
100 std::vector<llvm::json::Value> Result;
101 {
102 std::lock_guard<std::mutex> Lock(Mu);
103 std::swap(Result, Notifications[Method]);
104 }
105 return Result;
106 }
107
108private:
109 void reply(llvm::json::Value ID,
110 llvm::Expected<llvm::json::Value> V) override {
111 if (V) // Nothing additional to log for error.
112 logBody("reply", *V, /*Send=*/false);
113 std::lock_guard<std::mutex> Lock(Mu);
114 if (auto I = ID.getAsInteger()) {
115 if (*I >= 0 && *I < static_cast<int64_t>(CallResults.size())) {
116 CallResults[*I].set(std::move(V));
117 return;
118 }
119 }
120 ADD_FAILURE() << "Invalid reply to ID " << ID;
121 llvm::consumeError(std::move(V).takeError());
122 }
123
124 void notify(llvm::StringRef Method, llvm::json::Value V) override {
125 logBody(Method, V, /*Send=*/false);
126 std::lock_guard<std::mutex> Lock(Mu);
127 Notifications[Method].push_back(std::move(V));
128 }
129
130 void call(llvm::StringRef Method, llvm::json::Value Params,
131 llvm::json::Value ID) override {
132 logBody(Method, Params, /*Send=*/false);
133 ADD_FAILURE() << "Unexpected server->client call " << Method;
134 }
135
136 llvm::Error loop(MessageHandler &H) override {
137 std::unique_lock<std::mutex> Lock(Mu);
138 while (true) {
139 CV.wait(Lock, [&] { return !Actions.empty(); });
140 if (!Actions.front()) // Stop!
141 return llvm::Error::success();
142 auto Action = std::move(Actions.front());
143 Actions.pop();
144 Lock.unlock();
145 Action(H);
146 Lock.lock();
147 }
148 }
149
150 std::mutex Mu;
151 std::deque<CallResult> CallResults;
152 std::queue<std::function<void(Transport::MessageHandler &)>> Actions;
153 std::condition_variable CV;
154 llvm::StringMap<std::vector<llvm::json::Value>> Notifications;
155};
156
157LSPClient::LSPClient() : T(std::make_unique<TransportImpl>()) {}
158LSPClient::~LSPClient() = default;
159
161 llvm::json::Value Params) {
162 auto Slot = T->addCallSlot();
163 T->enqueue([ID(Slot.first), Method(Method.str()),
164 Params(std::move(Params))](Transport::MessageHandler &H) {
165 logBody(Method, Params, /*Send=*/true);
166 H.onCall(Method, std::move(Params), ID);
167 });
168 return *Slot.second;
169}
170
171void LSPClient::notify(llvm::StringRef Method, llvm::json::Value Params) {
172 T->enqueue([Method(Method.str()),
173 Params(std::move(Params))](Transport::MessageHandler &H) {
174 logBody(Method, Params, /*Send=*/true);
175 H.onNotify(Method, std::move(Params));
176 });
177}
178
179std::vector<llvm::json::Value>
181 return T->takeNotifications(Method);
182}
183
184void LSPClient::stop() { T->enqueue(nullptr); }
185
187
188using Obj = llvm::json::Object;
189
190llvm::json::Value LSPClient::uri(llvm::StringRef Path) {
191 std::string Storage;
192 if (!llvm::sys::path::is_absolute(Path))
193 Path = Storage = testPath(Path);
195}
196llvm::json::Value LSPClient::documentID(llvm::StringRef Path) {
197 return Obj{{"uri", uri(Path)}};
198}
199
200void LSPClient::didOpen(llvm::StringRef Path, llvm::StringRef Content) {
201 notify(
202 "textDocument/didOpen",
203 Obj{{"textDocument",
204 Obj{{"uri", uri(Path)}, {"text", Content}, {"languageId", "cpp"}}}});
205}
206void LSPClient::didChange(llvm::StringRef Path, llvm::StringRef Content) {
207 notify("textDocument/didChange",
208 Obj{{"textDocument", documentID(Path)},
209 {"contentChanges", llvm::json::Array{Obj{{"text", Content}}}}});
210}
211void LSPClient::didClose(llvm::StringRef Path) {
212 notify("textDocument/didClose", Obj{{"textDocument", documentID(Path)}});
213}
214
215void LSPClient::sync() { call("sync", nullptr).takeValue(); }
216
217std::optional<std::vector<llvm::json::Value>>
218LSPClient::diagnostics(llvm::StringRef Path) {
219 sync();
220 auto Notifications = takeNotifications("textDocument/publishDiagnostics");
221 for (const auto &Notification : llvm::reverse(Notifications)) {
222 if (const auto *PubDiagsParams = Notification.getAsObject()) {
223 auto U = PubDiagsParams->getString("uri");
224 auto *D = PubDiagsParams->getArray("diagnostics");
225 if (!U || !D) {
226 ADD_FAILURE() << "Bad PublishDiagnosticsParams: " << PubDiagsParams;
227 continue;
228 }
229 if (*U == uri(Path))
230 return std::vector<llvm::json::Value>(D->begin(), D->end());
231 }
232 }
233 return {};
234}
235
236} // namespace clangd
237} // namespace clang
FieldAction Action
llvm::Expected< llvm::json::Value > take()
Definition: LSPClient.cpp:39
std::vector< llvm::json::Value > takeNotifications(llvm::StringRef Method)
Definition: LSPClient.cpp:99
void enqueue(std::function< void(MessageHandler &)> Action)
Definition: LSPClient.cpp:93
std::pair< llvm::json::Value, CallResult * > addCallSlot()
Definition: LSPClient.cpp:85
CallResult & call(llvm::StringRef Method, llvm::json::Value Params)
Definition: LSPClient.cpp:160
std::vector< llvm::json::Value > takeNotifications(llvm::StringRef Method)
Definition: LSPClient.cpp:180
static llvm::json::Value uri(llvm::StringRef Path)
Definition: LSPClient.cpp:190
static llvm::json::Value documentID(llvm::StringRef Path)
Definition: LSPClient.cpp:196
void didClose(llvm::StringRef Path)
Definition: LSPClient.cpp:211
void notify(llvm::StringRef Method, llvm::json::Value Params)
Definition: LSPClient.cpp:171
void didOpen(llvm::StringRef Path, llvm::StringRef Content)
Definition: LSPClient.cpp:200
void didChange(llvm::StringRef Path, llvm::StringRef Content)
Definition: LSPClient.cpp:206
std::optional< std::vector< llvm::json::Value > > diagnostics(llvm::StringRef Path)
Definition: LSPClient.cpp:218
Transport & transport()
Definition: LSPClient.cpp:186
A threadsafe flag that is initially clear.
Definition: Threading.h:91
std::string Path
A typedef to represent a file path.
Definition: Path.h:26
llvm::json::Object Obj
Definition: LSPClient.cpp:188
void vlog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:72
static void logBody(llvm::StringRef Method, llvm::json::Value V, bool Send)
Definition: LSPClient.cpp:78
std::string testPath(PathRef File, llvm::sys::path::Style Style)
Definition: TestFS.cpp:93
llvm::json::Value toJSON(const FuzzyFindRequest &Request)
Definition: Index.cpp:45
void wait(std::unique_lock< std::mutex > &Lock, std::condition_variable &CV, Deadline D)
Wait once on CV for the specified duration.
Definition: Threading.cpp:121
Deadline timeoutSeconds(std::optional< double > Seconds)
Makes a deadline from a timeout in seconds. std::nullopt means wait forever.
Definition: Threading.cpp:113
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
Canonicalizes AbsPath via URI.
Definition: Protocol.cpp:46