//===--- 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_LSPSERVERSUPPORT_TRANSPORT_H_ #define LIB_MLIR_TOOLS_LSPSERVERSUPPORT_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 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 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 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 is a void function that accepts Expected. This is /// accepted by functions that logically return T. template using Callback = llvm::unique_function)>; /// An OutgoingNotification is a function used for outgoing notifications /// send to the client. template using OutgoingNotification = llvm::unique_function; /// 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 result); template static llvm::Expected 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( llvm::formatv("failed to decode {0} {1}: {2}", payloadName, payloadKind, fmt_consume(root.getError())), ErrorCode::InvalidParams); } template void method(llvm::StringLiteral method, ThisT *thisPtr, void (ThisT::*handler)(const Param &, Callback)) { methodHandlers[method] = [method, handler, thisPtr](llvm::json::Value rawParams, Callback reply) { llvm::Expected param = parse(rawParams, method, "request"); if (!param) return reply(param.takeError()); (thisPtr->*handler)(*param, std::move(reply)); }; } template void notification(llvm::StringLiteral method, ThisT *thisPtr, void (ThisT::*handler)(const Param &)) { notificationHandlers[method] = [method, handler, thisPtr](llvm::json::Value rawParams) { llvm::Expected param = parse(rawParams, method, "request"); if (!param) return llvm::consumeError(param.takeError()); (thisPtr->*handler)(*param); }; } /// Create an OutgoingNotification object used for the given method. template OutgoingNotification outgoingNotification(llvm::StringLiteral method) { return [&, method](const T ¶ms) { Logger::info("--> {0}", method); transport.notify(method, llvm::json::Value(params)); }; } private: template using HandlerMap = llvm::StringMap>; HandlerMap notificationHandlers; HandlerMap)> methodHandlers; JSONTransport &transport; }; } // namespace lsp } // namespace mlir #endif