|  | #include "LSPClient.h" | 
|  | #include "gtest/gtest.h" | 
|  | #include <condition_variable> | 
|  |  | 
|  | #include "Protocol.h" | 
|  | #include "TestFS.h" | 
|  | #include "Transport.h" | 
|  | #include "support/Threading.h" | 
|  | #include "llvm/Support/Path.h" | 
|  | #include "llvm/Support/raw_ostream.h" | 
|  | #include <queue> | 
|  |  | 
|  | namespace clang { | 
|  | namespace clangd { | 
|  |  | 
|  | llvm::Expected<llvm::json::Value> clang::clangd::LSPClient::CallResult::take() { | 
|  | std::unique_lock<std::mutex> Lock(Mu); | 
|  | if (!clangd::wait(Lock, CV, timeoutSeconds(10), | 
|  | [this] { return Value.hasValue(); })) { | 
|  | ADD_FAILURE() << "No result from call after 10 seconds!"; | 
|  | return llvm::json::Value(nullptr); | 
|  | } | 
|  | auto Res = std::move(*Value); | 
|  | Value.reset(); | 
|  | return Res; | 
|  | } | 
|  |  | 
|  | llvm::json::Value LSPClient::CallResult::takeValue() { | 
|  | auto ExpValue = take(); | 
|  | if (!ExpValue) { | 
|  | ADD_FAILURE() << "takeValue(): " << llvm::toString(ExpValue.takeError()); | 
|  | return llvm::json::Value(nullptr); | 
|  | } | 
|  | return std::move(*ExpValue); | 
|  | } | 
|  |  | 
|  | void LSPClient::CallResult::set(llvm::Expected<llvm::json::Value> V) { | 
|  | std::lock_guard<std::mutex> Lock(Mu); | 
|  | if (Value) { | 
|  | ADD_FAILURE() << "Multiple replies"; | 
|  | llvm::consumeError(V.takeError()); | 
|  | return; | 
|  | } | 
|  | Value = std::move(V); | 
|  | CV.notify_all(); | 
|  | } | 
|  |  | 
|  | LSPClient::CallResult::~CallResult() { | 
|  | if (Value && !*Value) { | 
|  | ADD_FAILURE() << llvm::toString(Value->takeError()); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void logBody(llvm::StringRef Method, llvm::json::Value V, bool Send) { | 
|  | // We invert <<< and >>> as the combined log is from the server's viewpoint. | 
|  | vlog("{0} {1}: {2:2}", Send ? "<<<" : ">>>", Method, V); | 
|  | } | 
|  |  | 
|  | class LSPClient::TransportImpl : public Transport { | 
|  | public: | 
|  | std::pair<llvm::json::Value, CallResult *> addCallSlot() { | 
|  | std::lock_guard<std::mutex> Lock(Mu); | 
|  | unsigned ID = CallResults.size(); | 
|  | CallResults.emplace_back(); | 
|  | return {ID, &CallResults.back()}; | 
|  | } | 
|  |  | 
|  | // A null action causes the transport to shut down. | 
|  | void enqueue(std::function<void(MessageHandler &)> Action) { | 
|  | std::lock_guard<std::mutex> Lock(Mu); | 
|  | Actions.push(std::move(Action)); | 
|  | CV.notify_all(); | 
|  | } | 
|  |  | 
|  | std::vector<llvm::json::Value> takeNotifications(llvm::StringRef Method) { | 
|  | std::vector<llvm::json::Value> Result; | 
|  | { | 
|  | std::lock_guard<std::mutex> Lock(Mu); | 
|  | std::swap(Result, Notifications[Method]); | 
|  | } | 
|  | return Result; | 
|  | } | 
|  |  | 
|  | private: | 
|  | void reply(llvm::json::Value ID, | 
|  | llvm::Expected<llvm::json::Value> V) override { | 
|  | if (V) // Nothing additional to log for error. | 
|  | logBody("reply", *V, /*Send=*/false); | 
|  | std::lock_guard<std::mutex> Lock(Mu); | 
|  | if (auto I = ID.getAsInteger()) { | 
|  | if (*I >= 0 && *I < static_cast<int64_t>(CallResults.size())) { | 
|  | CallResults[*I].set(std::move(V)); | 
|  | return; | 
|  | } | 
|  | } | 
|  | ADD_FAILURE() << "Invalid reply to ID " << ID; | 
|  | llvm::consumeError(std::move(V).takeError()); | 
|  | } | 
|  |  | 
|  | void notify(llvm::StringRef Method, llvm::json::Value V) override { | 
|  | logBody(Method, V, /*Send=*/false); | 
|  | std::lock_guard<std::mutex> Lock(Mu); | 
|  | Notifications[Method].push_back(std::move(V)); | 
|  | } | 
|  |  | 
|  | void call(llvm::StringRef Method, llvm::json::Value Params, | 
|  | llvm::json::Value ID) override { | 
|  | logBody(Method, Params, /*Send=*/false); | 
|  | ADD_FAILURE() << "Unexpected server->client call " << Method; | 
|  | } | 
|  |  | 
|  | llvm::Error loop(MessageHandler &H) override { | 
|  | std::unique_lock<std::mutex> Lock(Mu); | 
|  | while (true) { | 
|  | CV.wait(Lock, [&] { return !Actions.empty(); }); | 
|  | if (!Actions.front()) // Stop! | 
|  | return llvm::Error::success(); | 
|  | auto Action = std::move(Actions.front()); | 
|  | Actions.pop(); | 
|  | Lock.unlock(); | 
|  | Action(H); | 
|  | Lock.lock(); | 
|  | } | 
|  | } | 
|  |  | 
|  | std::mutex Mu; | 
|  | std::deque<CallResult> CallResults; | 
|  | std::queue<std::function<void(Transport::MessageHandler &)>> Actions; | 
|  | std::condition_variable CV; | 
|  | llvm::StringMap<std::vector<llvm::json::Value>> Notifications; | 
|  | }; | 
|  |  | 
|  | LSPClient::LSPClient() : T(std::make_unique<TransportImpl>()) {} | 
|  | LSPClient::~LSPClient() = default; | 
|  |  | 
|  | LSPClient::CallResult &LSPClient::call(llvm::StringRef Method, | 
|  | llvm::json::Value Params) { | 
|  | auto Slot = T->addCallSlot(); | 
|  | T->enqueue([ID(Slot.first), Method(Method.str()), | 
|  | Params(std::move(Params))](Transport::MessageHandler &H) { | 
|  | logBody(Method, Params, /*Send=*/true); | 
|  | H.onCall(Method, std::move(Params), ID); | 
|  | }); | 
|  | return *Slot.second; | 
|  | } | 
|  |  | 
|  | void LSPClient::notify(llvm::StringRef Method, llvm::json::Value Params) { | 
|  | T->enqueue([Method(Method.str()), | 
|  | Params(std::move(Params))](Transport::MessageHandler &H) { | 
|  | logBody(Method, Params, /*Send=*/true); | 
|  | H.onNotify(Method, std::move(Params)); | 
|  | }); | 
|  | } | 
|  |  | 
|  | std::vector<llvm::json::Value> | 
|  | LSPClient::takeNotifications(llvm::StringRef Method) { | 
|  | return T->takeNotifications(Method); | 
|  | } | 
|  |  | 
|  | void LSPClient::stop() { T->enqueue(nullptr); } | 
|  |  | 
|  | Transport &LSPClient::transport() { return *T; } | 
|  |  | 
|  | using Obj = llvm::json::Object; | 
|  |  | 
|  | llvm::json::Value LSPClient::uri(llvm::StringRef Path) { | 
|  | std::string Storage; | 
|  | if (!llvm::sys::path::is_absolute(Path)) | 
|  | Path = Storage = testPath(Path); | 
|  | return toJSON(URIForFile::canonicalize(Path, Path)); | 
|  | } | 
|  | llvm::json::Value LSPClient::documentID(llvm::StringRef Path) { | 
|  | return Obj{{"uri", uri(Path)}}; | 
|  | } | 
|  |  | 
|  | void LSPClient::didOpen(llvm::StringRef Path, llvm::StringRef Content) { | 
|  | notify( | 
|  | "textDocument/didOpen", | 
|  | Obj{{"textDocument", | 
|  | Obj{{"uri", uri(Path)}, {"text", Content}, {"languageId", "cpp"}}}}); | 
|  | } | 
|  | void LSPClient::didChange(llvm::StringRef Path, llvm::StringRef Content) { | 
|  | notify("textDocument/didChange", | 
|  | Obj{{"textDocument", documentID(Path)}, | 
|  | {"contentChanges", llvm::json::Array{Obj{{"text", Content}}}}}); | 
|  | } | 
|  | void LSPClient::didClose(llvm::StringRef Path) { | 
|  | notify("textDocument/didClose", Obj{{"textDocument", documentID(Path)}}); | 
|  | } | 
|  |  | 
|  | void LSPClient::sync() { call("sync", nullptr).takeValue(); } | 
|  |  | 
|  | llvm::Optional<std::vector<llvm::json::Value>> | 
|  | LSPClient::diagnostics(llvm::StringRef Path) { | 
|  | sync(); | 
|  | auto Notifications = takeNotifications("textDocument/publishDiagnostics"); | 
|  | for (const auto &Notification : llvm::reverse(Notifications)) { | 
|  | if (const auto *PubDiagsParams = Notification.getAsObject()) { | 
|  | auto U = PubDiagsParams->getString("uri"); | 
|  | auto *D = PubDiagsParams->getArray("diagnostics"); | 
|  | if (!U || !D) { | 
|  | ADD_FAILURE() << "Bad PublishDiagnosticsParams: " << PubDiagsParams; | 
|  | continue; | 
|  | } | 
|  | if (*U == uri(Path)) | 
|  | return std::vector<llvm::json::Value>(D->begin(), D->end()); | 
|  | } | 
|  | } | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | } // namespace clangd | 
|  | } // namespace clang |