14d006520SSam McCall //===-- LSPClient.cpp - Helper for ClangdLSPServer tests ------------------===//
24d006520SSam McCall //
34d006520SSam McCall // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
44d006520SSam McCall // See https://llvm.org/LICENSE.txt for license information.
54d006520SSam McCall // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
64d006520SSam McCall //
74d006520SSam McCall //===----------------------------------------------------------------------===//
81229245dSSam McCall
94d006520SSam McCall #include "LSPClient.h"
101229245dSSam McCall #include "Protocol.h"
111229245dSSam McCall #include "TestFS.h"
121229245dSSam McCall #include "Transport.h"
134d006520SSam McCall #include "support/Logger.h"
14ad97ccf6SSam McCall #include "support/Threading.h"
158c215442SKadir Cetinkaya #include "llvm/ADT/STLExtras.h"
168c215442SKadir Cetinkaya #include "llvm/ADT/StringMap.h"
178c215442SKadir Cetinkaya #include "llvm/ADT/StringRef.h"
188c215442SKadir Cetinkaya #include "llvm/Support/Error.h"
198c215442SKadir Cetinkaya #include "llvm/Support/JSON.h"
201229245dSSam McCall #include "llvm/Support/Path.h"
211229245dSSam McCall #include "llvm/Support/raw_ostream.h"
224d006520SSam McCall #include "gtest/gtest.h"
234d006520SSam McCall #include <condition_variable>
248c215442SKadir Cetinkaya #include <cstddef>
258c215442SKadir Cetinkaya #include <cstdint>
268c215442SKadir Cetinkaya #include <deque>
278c215442SKadir Cetinkaya #include <functional>
288c215442SKadir Cetinkaya #include <memory>
298c215442SKadir Cetinkaya #include <mutex>
3071f55735SKazu Hirata #include <optional>
311229245dSSam McCall #include <queue>
328c215442SKadir Cetinkaya #include <string>
338c215442SKadir Cetinkaya #include <utility>
348c215442SKadir Cetinkaya #include <vector>
351229245dSSam McCall
361229245dSSam McCall namespace clang {
371229245dSSam McCall namespace clangd {
381229245dSSam McCall
take()391229245dSSam McCall llvm::Expected<llvm::json::Value> clang::clangd::LSPClient::CallResult::take() {
401229245dSSam McCall std::unique_lock<std::mutex> Lock(Mu);
418c215442SKadir Cetinkaya static constexpr size_t TimeoutSecs = 60;
428c215442SKadir Cetinkaya if (!clangd::wait(Lock, CV, timeoutSeconds(TimeoutSecs),
43b8df4093SKazu Hirata [this] { return Value.has_value(); })) {
448c215442SKadir Cetinkaya ADD_FAILURE() << "No result from call after " << TimeoutSecs << " seconds!";
451229245dSSam McCall return llvm::json::Value(nullptr);
461229245dSSam McCall }
47e64f99c5SKadir Cetinkaya auto Res = std::move(*Value);
48e64f99c5SKadir Cetinkaya Value.reset();
49e64f99c5SKadir Cetinkaya return Res;
501229245dSSam McCall }
511229245dSSam McCall
takeValue()521229245dSSam McCall llvm::json::Value LSPClient::CallResult::takeValue() {
531229245dSSam McCall auto ExpValue = take();
541229245dSSam McCall if (!ExpValue) {
551229245dSSam McCall ADD_FAILURE() << "takeValue(): " << llvm::toString(ExpValue.takeError());
561229245dSSam McCall return llvm::json::Value(nullptr);
571229245dSSam McCall }
581229245dSSam McCall return std::move(*ExpValue);
591229245dSSam McCall }
601229245dSSam McCall
set(llvm::Expected<llvm::json::Value> V)611229245dSSam McCall void LSPClient::CallResult::set(llvm::Expected<llvm::json::Value> V) {
621229245dSSam McCall std::lock_guard<std::mutex> Lock(Mu);
631229245dSSam McCall if (Value) {
641229245dSSam McCall ADD_FAILURE() << "Multiple replies";
651229245dSSam McCall llvm::consumeError(V.takeError());
661229245dSSam McCall return;
671229245dSSam McCall }
681229245dSSam McCall Value = std::move(V);
691229245dSSam McCall CV.notify_all();
701229245dSSam McCall }
711229245dSSam McCall
~CallResult()721229245dSSam McCall LSPClient::CallResult::~CallResult() {
731229245dSSam McCall if (Value && !*Value) {
741229245dSSam McCall ADD_FAILURE() << llvm::toString(Value->takeError());
751229245dSSam McCall }
761229245dSSam McCall }
771229245dSSam McCall
logBody(llvm::StringRef Method,llvm::json::Value V,bool Send)781229245dSSam McCall static void logBody(llvm::StringRef Method, llvm::json::Value V, bool Send) {
791229245dSSam McCall // We invert <<< and >>> as the combined log is from the server's viewpoint.
801229245dSSam McCall vlog("{0} {1}: {2:2}", Send ? "<<<" : ">>>", Method, V);
811229245dSSam McCall }
821229245dSSam McCall
831229245dSSam McCall class LSPClient::TransportImpl : public Transport {
841229245dSSam McCall public:
addCallSlot()851229245dSSam McCall std::pair<llvm::json::Value, CallResult *> addCallSlot() {
861229245dSSam McCall std::lock_guard<std::mutex> Lock(Mu);
871229245dSSam McCall unsigned ID = CallResults.size();
881229245dSSam McCall CallResults.emplace_back();
891229245dSSam McCall return {ID, &CallResults.back()};
901229245dSSam McCall }
911229245dSSam McCall
921229245dSSam McCall // A null action causes the transport to shut down.
enqueue(std::function<void (MessageHandler &)> Action)931229245dSSam McCall void enqueue(std::function<void(MessageHandler &)> Action) {
941229245dSSam McCall std::lock_guard<std::mutex> Lock(Mu);
951229245dSSam McCall Actions.push(std::move(Action));
961229245dSSam McCall CV.notify_all();
971229245dSSam McCall }
981229245dSSam McCall
takeNotifications(llvm::StringRef Method)991229245dSSam McCall std::vector<llvm::json::Value> takeNotifications(llvm::StringRef Method) {
1001229245dSSam McCall std::vector<llvm::json::Value> Result;
1011229245dSSam McCall {
1021229245dSSam McCall std::lock_guard<std::mutex> Lock(Mu);
1031229245dSSam McCall std::swap(Result, Notifications[Method]);
1041229245dSSam McCall }
1051229245dSSam McCall return Result;
1061229245dSSam McCall }
1071229245dSSam McCall
expectCall(llvm::StringRef Method)108*a45df473STom Praschan void expectCall(llvm::StringRef Method) {
109*a45df473STom Praschan std::lock_guard<std::mutex> Lock(Mu);
110*a45df473STom Praschan Calls[Method] = {};
111*a45df473STom Praschan }
112*a45df473STom Praschan
takeCallParams(llvm::StringRef Method)113*a45df473STom Praschan std::vector<llvm::json::Value> takeCallParams(llvm::StringRef Method) {
114*a45df473STom Praschan std::vector<llvm::json::Value> Result;
115*a45df473STom Praschan {
116*a45df473STom Praschan std::lock_guard<std::mutex> Lock(Mu);
117*a45df473STom Praschan std::swap(Result, Calls[Method]);
118*a45df473STom Praschan }
119*a45df473STom Praschan return Result;
120*a45df473STom Praschan }
121*a45df473STom Praschan
1221229245dSSam McCall private:
reply(llvm::json::Value ID,llvm::Expected<llvm::json::Value> V)1231229245dSSam McCall void reply(llvm::json::Value ID,
1241229245dSSam McCall llvm::Expected<llvm::json::Value> V) override {
1251229245dSSam McCall if (V) // Nothing additional to log for error.
1261229245dSSam McCall logBody("reply", *V, /*Send=*/false);
1271229245dSSam McCall std::lock_guard<std::mutex> Lock(Mu);
1281229245dSSam McCall if (auto I = ID.getAsInteger()) {
1291229245dSSam McCall if (*I >= 0 && *I < static_cast<int64_t>(CallResults.size())) {
1301229245dSSam McCall CallResults[*I].set(std::move(V));
1311229245dSSam McCall return;
1321229245dSSam McCall }
1331229245dSSam McCall }
1341229245dSSam McCall ADD_FAILURE() << "Invalid reply to ID " << ID;
1351229245dSSam McCall llvm::consumeError(std::move(V).takeError());
1361229245dSSam McCall }
1371229245dSSam McCall
notify(llvm::StringRef Method,llvm::json::Value V)1381229245dSSam McCall void notify(llvm::StringRef Method, llvm::json::Value V) override {
1391229245dSSam McCall logBody(Method, V, /*Send=*/false);
1401229245dSSam McCall std::lock_guard<std::mutex> Lock(Mu);
1411229245dSSam McCall Notifications[Method].push_back(std::move(V));
1421229245dSSam McCall }
1431229245dSSam McCall
call(llvm::StringRef Method,llvm::json::Value Params,llvm::json::Value ID)1441229245dSSam McCall void call(llvm::StringRef Method, llvm::json::Value Params,
1451229245dSSam McCall llvm::json::Value ID) override {
1461229245dSSam McCall logBody(Method, Params, /*Send=*/false);
147*a45df473STom Praschan std::lock_guard<std::mutex> Lock(Mu);
148*a45df473STom Praschan if (Calls.contains(Method)) {
149*a45df473STom Praschan Calls[Method].push_back(std::move(Params));
150*a45df473STom Praschan } else {
1511229245dSSam McCall ADD_FAILURE() << "Unexpected server->client call " << Method;
1521229245dSSam McCall }
153*a45df473STom Praschan }
1541229245dSSam McCall
loop(MessageHandler & H)1551229245dSSam McCall llvm::Error loop(MessageHandler &H) override {
1561229245dSSam McCall std::unique_lock<std::mutex> Lock(Mu);
1571229245dSSam McCall while (true) {
1581229245dSSam McCall CV.wait(Lock, [&] { return !Actions.empty(); });
1591229245dSSam McCall if (!Actions.front()) // Stop!
1601229245dSSam McCall return llvm::Error::success();
1611229245dSSam McCall auto Action = std::move(Actions.front());
1621229245dSSam McCall Actions.pop();
1631229245dSSam McCall Lock.unlock();
1641229245dSSam McCall Action(H);
1651229245dSSam McCall Lock.lock();
1661229245dSSam McCall }
1671229245dSSam McCall }
1681229245dSSam McCall
1691229245dSSam McCall std::mutex Mu;
1701229245dSSam McCall std::deque<CallResult> CallResults;
1711229245dSSam McCall std::queue<std::function<void(Transport::MessageHandler &)>> Actions;
1721229245dSSam McCall std::condition_variable CV;
1731229245dSSam McCall llvm::StringMap<std::vector<llvm::json::Value>> Notifications;
174*a45df473STom Praschan llvm::StringMap<std::vector<llvm::json::Value>> Calls;
1751229245dSSam McCall };
1761229245dSSam McCall
LSPClient()1771229245dSSam McCall LSPClient::LSPClient() : T(std::make_unique<TransportImpl>()) {}
1781229245dSSam McCall LSPClient::~LSPClient() = default;
1791229245dSSam McCall
call(llvm::StringRef Method,llvm::json::Value Params)1801229245dSSam McCall LSPClient::CallResult &LSPClient::call(llvm::StringRef Method,
1811229245dSSam McCall llvm::json::Value Params) {
1821229245dSSam McCall auto Slot = T->addCallSlot();
1831229245dSSam McCall T->enqueue([ID(Slot.first), Method(Method.str()),
1841229245dSSam McCall Params(std::move(Params))](Transport::MessageHandler &H) {
1851229245dSSam McCall logBody(Method, Params, /*Send=*/true);
1861229245dSSam McCall H.onCall(Method, std::move(Params), ID);
1871229245dSSam McCall });
1881229245dSSam McCall return *Slot.second;
1891229245dSSam McCall }
1901229245dSSam McCall
expectServerCall(llvm::StringRef Method)191*a45df473STom Praschan void LSPClient::expectServerCall(llvm::StringRef Method) {
192*a45df473STom Praschan T->expectCall(Method);
193*a45df473STom Praschan }
194*a45df473STom Praschan
notify(llvm::StringRef Method,llvm::json::Value Params)1951229245dSSam McCall void LSPClient::notify(llvm::StringRef Method, llvm::json::Value Params) {
1961229245dSSam McCall T->enqueue([Method(Method.str()),
1971229245dSSam McCall Params(std::move(Params))](Transport::MessageHandler &H) {
1981229245dSSam McCall logBody(Method, Params, /*Send=*/true);
1991229245dSSam McCall H.onNotify(Method, std::move(Params));
2001229245dSSam McCall });
2011229245dSSam McCall }
2021229245dSSam McCall
2031229245dSSam McCall std::vector<llvm::json::Value>
takeNotifications(llvm::StringRef Method)2041229245dSSam McCall LSPClient::takeNotifications(llvm::StringRef Method) {
2051229245dSSam McCall return T->takeNotifications(Method);
2061229245dSSam McCall }
2071229245dSSam McCall
208*a45df473STom Praschan std::vector<llvm::json::Value>
takeCallParams(llvm::StringRef Method)209*a45df473STom Praschan LSPClient::takeCallParams(llvm::StringRef Method) {
210*a45df473STom Praschan return T->takeCallParams(Method);
211*a45df473STom Praschan }
212*a45df473STom Praschan
stop()2131229245dSSam McCall void LSPClient::stop() { T->enqueue(nullptr); }
2141229245dSSam McCall
transport()2151229245dSSam McCall Transport &LSPClient::transport() { return *T; }
2161229245dSSam McCall
2171229245dSSam McCall using Obj = llvm::json::Object;
2181229245dSSam McCall
uri(llvm::StringRef Path)2191229245dSSam McCall llvm::json::Value LSPClient::uri(llvm::StringRef Path) {
2201229245dSSam McCall std::string Storage;
2211229245dSSam McCall if (!llvm::sys::path::is_absolute(Path))
2221229245dSSam McCall Path = Storage = testPath(Path);
2231229245dSSam McCall return toJSON(URIForFile::canonicalize(Path, Path));
2241229245dSSam McCall }
documentID(llvm::StringRef Path)2251229245dSSam McCall llvm::json::Value LSPClient::documentID(llvm::StringRef Path) {
2261229245dSSam McCall return Obj{{"uri", uri(Path)}};
2271229245dSSam McCall }
2281229245dSSam McCall
didOpen(llvm::StringRef Path,llvm::StringRef Content)2291229245dSSam McCall void LSPClient::didOpen(llvm::StringRef Path, llvm::StringRef Content) {
2301229245dSSam McCall notify(
2311229245dSSam McCall "textDocument/didOpen",
2321229245dSSam McCall Obj{{"textDocument",
2331229245dSSam McCall Obj{{"uri", uri(Path)}, {"text", Content}, {"languageId", "cpp"}}}});
2341229245dSSam McCall }
didChange(llvm::StringRef Path,llvm::StringRef Content)2351229245dSSam McCall void LSPClient::didChange(llvm::StringRef Path, llvm::StringRef Content) {
2361229245dSSam McCall notify("textDocument/didChange",
2371229245dSSam McCall Obj{{"textDocument", documentID(Path)},
2381229245dSSam McCall {"contentChanges", llvm::json::Array{Obj{{"text", Content}}}}});
2391229245dSSam McCall }
didClose(llvm::StringRef Path)2401229245dSSam McCall void LSPClient::didClose(llvm::StringRef Path) {
2411229245dSSam McCall notify("textDocument/didClose", Obj{{"textDocument", documentID(Path)}});
2421229245dSSam McCall }
2431229245dSSam McCall
sync()2441229245dSSam McCall void LSPClient::sync() { call("sync", nullptr).takeValue(); }
2451229245dSSam McCall
246f71ffd3bSKazu Hirata std::optional<std::vector<llvm::json::Value>>
diagnostics(llvm::StringRef Path)2471229245dSSam McCall LSPClient::diagnostics(llvm::StringRef Path) {
2481229245dSSam McCall sync();
2491229245dSSam McCall auto Notifications = takeNotifications("textDocument/publishDiagnostics");
2501229245dSSam McCall for (const auto &Notification : llvm::reverse(Notifications)) {
2511229245dSSam McCall if (const auto *PubDiagsParams = Notification.getAsObject()) {
2521229245dSSam McCall auto U = PubDiagsParams->getString("uri");
2531229245dSSam McCall auto *D = PubDiagsParams->getArray("diagnostics");
2541229245dSSam McCall if (!U || !D) {
2551229245dSSam McCall ADD_FAILURE() << "Bad PublishDiagnosticsParams: " << PubDiagsParams;
2561229245dSSam McCall continue;
2571229245dSSam McCall }
2581229245dSSam McCall if (*U == uri(Path))
2591229245dSSam McCall return std::vector<llvm::json::Value>(D->begin(), D->end());
2601229245dSSam McCall }
2611229245dSSam McCall }
2621229245dSSam McCall return {};
2631229245dSSam McCall }
2641229245dSSam McCall
2651229245dSSam McCall } // namespace clangd
2661229245dSSam McCall } // namespace clang
267