| //===--- Transport.h - Sending and Receiving LSP messages -------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // The language server protocol is usually implemented by writing messages as |
| // JSON-RPC over the stdin/stdout of a subprocess. This file contains a JSON |
| // transport interface that handles this communication. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef LIB_MLIR_TOOLS_MLIRLSPSERVER_LSP_TRANSPORT_H_ |
| #define LIB_MLIR_TOOLS_MLIRLSPSERVER_LSP_TRANSPORT_H_ |
| |
| #include "Logging.h" |
| #include "Protocol.h" |
| #include "mlir/Support/LLVM.h" |
| #include "mlir/Support/LogicalResult.h" |
| #include "llvm/ADT/FunctionExtras.h" |
| #include "llvm/ADT/StringMap.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Support/FormatAdapters.h" |
| #include "llvm/Support/JSON.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include <atomic> |
| |
| namespace mlir { |
| namespace lsp { |
| class MessageHandler; |
| |
| //===----------------------------------------------------------------------===// |
| // JSONTransport |
| //===----------------------------------------------------------------------===// |
| |
| /// The encoding style of the JSON-RPC messages (both input and output). |
| enum JSONStreamStyle { |
| /// Encoding per the LSP specification, with mandatory Content-Length header. |
| Standard, |
| /// Messages are delimited by a '// -----' line. Comment lines start with //. |
| Delimited |
| }; |
| |
| /// A transport class that performs the JSON-RPC communication with the LSP |
| /// client. |
| class JSONTransport { |
| public: |
| JSONTransport(std::FILE *in, raw_ostream &out, |
| JSONStreamStyle style = JSONStreamStyle::Standard, |
| bool prettyOutput = false) |
| : in(in), out(out), style(style), prettyOutput(prettyOutput) {} |
| |
| /// The following methods are used to send a message to the LSP client. |
| void notify(StringRef method, llvm::json::Value params); |
| void call(StringRef method, llvm::json::Value params, llvm::json::Value id); |
| void reply(llvm::json::Value id, llvm::Expected<llvm::json::Value> result); |
| |
| /// Start executing the JSON-RPC transport. |
| llvm::Error run(MessageHandler &handler); |
| |
| private: |
| /// Dispatches the given incoming json message to the message handler. |
| bool handleMessage(llvm::json::Value msg, MessageHandler &handler); |
| /// Writes the given message to the output stream. |
| void sendMessage(llvm::json::Value msg); |
| |
| /// Read in a message from the input stream. |
| LogicalResult readMessage(std::string &json) { |
| return style == JSONStreamStyle::Delimited ? readDelimitedMessage(json) |
| : readStandardMessage(json); |
| } |
| LogicalResult readDelimitedMessage(std::string &json); |
| LogicalResult readStandardMessage(std::string &json); |
| |
| /// An output buffer used when building output messages. |
| SmallVector<char, 0> outputBuffer; |
| /// The input file stream. |
| std::FILE *in; |
| /// The output file stream. |
| raw_ostream &out; |
| /// The JSON stream style to use. |
| JSONStreamStyle style; |
| /// If the output JSON should be formatted for easier readability. |
| bool prettyOutput; |
| }; |
| |
| //===----------------------------------------------------------------------===// |
| // MessageHandler |
| //===----------------------------------------------------------------------===// |
| |
| /// A Callback<T> is a void function that accepts Expected<T>. This is |
| /// accepted by functions that logically return T. |
| template <typename T> |
| using Callback = llvm::unique_function<void(llvm::Expected<T>)>; |
| |
| /// An OutgoingNotification<T> is a function used for outgoing notifications |
| /// send to the client. |
| template <typename T> |
| using OutgoingNotification = llvm::unique_function<void(const T &)>; |
| |
| /// A handler used to process the incoming transport messages. |
| class MessageHandler { |
| public: |
| MessageHandler(JSONTransport &transport) : transport(transport) {} |
| |
| bool onNotify(StringRef method, llvm::json::Value value); |
| bool onCall(StringRef method, llvm::json::Value params, llvm::json::Value id); |
| bool onReply(llvm::json::Value id, llvm::Expected<llvm::json::Value> result); |
| |
| template <typename T> |
| static llvm::Expected<T> parse(const llvm::json::Value &raw, |
| StringRef payloadName, StringRef payloadKind) { |
| T result; |
| llvm::json::Path::Root root; |
| if (fromJSON(raw, result, root)) |
| return std::move(result); |
| |
| // Dump the relevant parts of the broken message. |
| std::string context; |
| llvm::raw_string_ostream os(context); |
| root.printErrorContext(raw, os); |
| |
| // Report the error (e.g. to the client). |
| return llvm::make_error<LSPError>( |
| llvm::formatv("failed to decode {0} {1}: {2}", payloadName, payloadKind, |
| fmt_consume(root.getError())), |
| ErrorCode::InvalidParams); |
| } |
| |
| template <typename Param, typename Result, typename ThisT> |
| void method(llvm::StringLiteral method, ThisT *thisPtr, |
| void (ThisT::*handler)(const Param &, Callback<Result>)) { |
| methodHandlers[method] = [method, handler, |
| thisPtr](llvm::json::Value rawParams, |
| Callback<llvm::json::Value> reply) { |
| llvm::Expected<Param> param = parse<Param>(rawParams, method, "request"); |
| if (!param) |
| return reply(param.takeError()); |
| (thisPtr->*handler)(*param, std::move(reply)); |
| }; |
| } |
| |
| template <typename Param, typename ThisT> |
| void notification(llvm::StringLiteral method, ThisT *thisPtr, |
| void (ThisT::*handler)(const Param &)) { |
| notificationHandlers[method] = [method, handler, |
| thisPtr](llvm::json::Value rawParams) { |
| llvm::Expected<Param> param = parse<Param>(rawParams, method, "request"); |
| if (!param) |
| return llvm::consumeError(param.takeError()); |
| (thisPtr->*handler)(*param); |
| }; |
| } |
| |
| /// Create an OutgoingNotification object used for the given method. |
| template <typename T> |
| OutgoingNotification<T> outgoingNotification(llvm::StringLiteral method) { |
| return [&, method](const T ¶ms) { |
| Logger::info("--> {0}", method); |
| transport.notify(method, llvm::json::Value(params)); |
| }; |
| } |
| |
| private: |
| template <typename HandlerT> |
| using HandlerMap = llvm::StringMap<llvm::unique_function<HandlerT>>; |
| |
| HandlerMap<void(llvm::json::Value)> notificationHandlers; |
| HandlerMap<void(llvm::json::Value, Callback<llvm::json::Value>)> |
| methodHandlers; |
| |
| JSONTransport &transport; |
| }; |
| |
| } // namespace lsp |
| } // namespace mlir |
| |
| #endif |