116 changes: 116 additions & 0 deletions mlir/lib/Tools/mlir-lsp-server/lsp/Protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class URIForFile {
std::string uriStr;
};

/// Add support for JSON serialization.
llvm::json::Value toJSON(const URIForFile &value);
bool fromJSON(const llvm::json::Value &value, URIForFile &result,
llvm::json::Path path);
Expand All @@ -142,13 +143,17 @@ enum class TraceLevel {
Messages = 1,
Verbose = 2,
};

/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value, TraceLevel &result,
llvm::json::Path path);

struct InitializeParams {
/// The initial trace setting. If omitted trace is disabled ('off').
Optional<TraceLevel> trace;
};

/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value, InitializeParams &result,
llvm::json::Path path);

Expand Down Expand Up @@ -176,6 +181,8 @@ struct TextDocumentItem {
/// The content of the opened text document.
std::string text;
};

/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value, TextDocumentItem &result,
llvm::json::Path path);

Expand All @@ -187,6 +194,8 @@ struct TextDocumentIdentifier {
/// The text document's URI.
URIForFile uri;
};

/// Add support for JSON serialization.
llvm::json::Value toJSON(const TextDocumentIdentifier &value);
bool fromJSON(const llvm::json::Value &value, TextDocumentIdentifier &result,
llvm::json::Path path);
Expand Down Expand Up @@ -218,6 +227,8 @@ struct Position {
std::tie(rhs.line, rhs.character);
}
};

/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value, Position &result,
llvm::json::Path path);
llvm::json::Value toJSON(const Position &value);
Expand All @@ -228,6 +239,10 @@ raw_ostream &operator<<(raw_ostream &os, const Position &value);
//===----------------------------------------------------------------------===//

struct Range {
Range() = default;
Range(Position start, Position end) : start(start), end(end) {}
Range(Position loc) : Range(loc, loc) {}

/// The range's start position.
Position start;

Expand All @@ -249,6 +264,8 @@ struct Range {
return start <= range.start && range.end <= end;
}
};

/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value, Range &result,
llvm::json::Path path);
llvm::json::Value toJSON(const Range &value);
Expand All @@ -275,6 +292,8 @@ struct Location {
return std::tie(lhs.uri, lhs.range) < std::tie(rhs.uri, rhs.range);
}
};

/// Add support for JSON serialization.
llvm::json::Value toJSON(const Location &value);
raw_ostream &operator<<(raw_ostream &os, const Location &value);

Expand All @@ -289,6 +308,8 @@ struct TextDocumentPositionParams {
/// The position inside the text document.
Position position;
};

/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value,
TextDocumentPositionParams &result, llvm::json::Path path);

Expand All @@ -300,12 +321,16 @@ struct ReferenceContext {
/// Include the declaration of the current symbol.
bool includeDeclaration = false;
};

/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value, ReferenceContext &result,
llvm::json::Path path);

struct ReferenceParams : public TextDocumentPositionParams {
ReferenceContext context;
};

/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value, ReferenceParams &result,
llvm::json::Path path);

Expand All @@ -317,6 +342,8 @@ struct DidOpenTextDocumentParams {
/// The document that was opened.
TextDocumentItem textDocument;
};

/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value, DidOpenTextDocumentParams &result,
llvm::json::Path path);

Expand All @@ -328,6 +355,8 @@ struct DidCloseTextDocumentParams {
/// The document that was closed.
TextDocumentIdentifier textDocument;
};

/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value,
DidCloseTextDocumentParams &result, llvm::json::Path path);

Expand All @@ -345,6 +374,8 @@ struct TextDocumentContentChangeEvent {
/// The new text of the range/document.
std::string text;
};

/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value,
TextDocumentContentChangeEvent &result, llvm::json::Path path);

Expand All @@ -355,6 +386,8 @@ struct DidChangeTextDocumentParams {
/// The actual content changes.
std::vector<TextDocumentContentChangeEvent> contentChanges;
};

/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value,
DidChangeTextDocumentParams &result, llvm::json::Path path);

Expand All @@ -374,6 +407,8 @@ struct MarkupContent {
MarkupKind kind = MarkupKind::PlainText;
std::string value;
};

/// Add support for JSON serialization.
llvm::json::Value toJSON(const MarkupContent &mc);

//===----------------------------------------------------------------------===//
Expand All @@ -391,8 +426,89 @@ struct Hover {
/// visualize a hover, e.g. by changing the background color.
Optional<Range> range;
};

/// Add support for JSON serialization.
llvm::json::Value toJSON(const Hover &hover);

//===----------------------------------------------------------------------===//
// DiagnosticRelatedInformation
//===----------------------------------------------------------------------===//

/// Represents a related message and source code location for a diagnostic.
/// This should be used to point to code locations that cause or related to a
/// diagnostics, e.g. when duplicating a symbol in a scope.
struct DiagnosticRelatedInformation {
DiagnosticRelatedInformation(Location location, std::string message)
: location(location), message(std::move(message)) {}

/// The location of this related diagnostic information.
Location location;
/// The message of this related diagnostic information.
std::string message;
};

/// Add support for JSON serialization.
llvm::json::Value toJSON(const DiagnosticRelatedInformation &info);

//===----------------------------------------------------------------------===//
// Diagnostic
//===----------------------------------------------------------------------===//

enum class DiagnosticSeverity {
/// It is up to the client to interpret diagnostics as error, warning, info or
/// hint.
Undetermined = 0,
Error = 1,
Warning = 2,
Information = 3,
Hint = 4
};

struct Diagnostic {
/// The source range where the message applies.
Range range;

/// The diagnostic's severity. Can be omitted. If omitted it is up to the
/// client to interpret diagnostics as error, warning, info or hint.
DiagnosticSeverity severity = DiagnosticSeverity::Undetermined;

/// A human-readable string describing the source of this diagnostic, e.g.
/// 'typescript' or 'super lint'.
std::string source;

/// The diagnostic's message.
std::string message;

/// An array of related diagnostic information, e.g. when symbol-names within
/// a scope collide all definitions can be marked via this property.
Optional<std::vector<DiagnosticRelatedInformation>> relatedInformation;

/// The diagnostic's category. Can be omitted.
/// An LSP extension that's used to send the name of the category over to the
/// client. The category typically describes the compilation stage during
/// which the issue was produced, e.g. "Semantic Issue" or "Parse Issue".
Optional<std::string> category;
};

/// Add support for JSON serialization.
llvm::json::Value toJSON(const Diagnostic &diag);

//===----------------------------------------------------------------------===//
// PublishDiagnosticsParams
//===----------------------------------------------------------------------===//

struct PublishDiagnosticsParams {
PublishDiagnosticsParams(URIForFile uri) : uri(uri) {}

/// The URI for which diagnostic information is reported.
URIForFile uri;
/// The list of reported diagnostics.
std::vector<Diagnostic> diagnostics;
};

/// Add support for JSON serialization.
llvm::json::Value toJSON(const PublishDiagnosticsParams &params);

} // namespace lsp
} // namespace mlir

Expand Down
24 changes: 24 additions & 0 deletions mlir/lib/Tools/mlir-lsp-server/lsp/Transport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,30 @@ using namespace mlir::lsp;
// Reply
//===----------------------------------------------------------------------===//

namespace {
/// Function object to reply to an LSP call.
/// Each instance must be called exactly once, otherwise:
/// - if there was no reply, an error reply is sent
/// - if there were multiple replies, only the first is sent
class Reply {
public:
Reply(const llvm::json::Value &id, StringRef method,
JSONTransport &transport);
Reply(Reply &&other);
Reply &operator=(Reply &&) = delete;
Reply(const Reply &) = delete;
Reply &operator=(const Reply &) = delete;

void operator()(llvm::Expected<llvm::json::Value> reply);

private:
StringRef method;
std::atomic<bool> replied = {false};
llvm::json::Value id;
JSONTransport *transport;
};
} // namespace

Reply::Reply(const llvm::json::Value &id, llvm::StringRef method,
JSONTransport &transport)
: id(id), transport(&transport) {}
Expand Down
133 changes: 60 additions & 73 deletions mlir/lib/Tools/mlir-lsp-server/lsp/Transport.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,61 @@

namespace mlir {
namespace lsp {
class JSONTransport;
class MessageHandler;

//===----------------------------------------------------------------------===//
// Reply
// JSONTransport
//===----------------------------------------------------------------------===//

/// Function object to reply to an LSP call.
/// Each instance must be called exactly once, otherwise:
/// - if there was no reply, an error reply is sent
/// - if there were multiple replies, only the first is sent
class Reply {
/// 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:
Reply(const llvm::json::Value &id, StringRef method,
JSONTransport &transport);
Reply(Reply &&other);
Reply &operator=(Reply &&) = delete;
Reply(const Reply &) = delete;
Reply &operator=(const Reply &) = delete;
JSONTransport(std::FILE *in, raw_ostream &out,
JSONStreamStyle style = JSONStreamStyle::Standard,
bool prettyOutput = false)
: in(in), out(out), style(style), prettyOutput(prettyOutput) {}

void operator()(llvm::Expected<llvm::json::Value> reply);
/// 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:
StringRef method;
std::atomic<bool> replied = {false};
llvm::json::Value id;
JSONTransport *transport;
/// 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;
};

//===----------------------------------------------------------------------===//
Expand All @@ -65,6 +94,11 @@ class Reply {
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:
Expand Down Expand Up @@ -119,6 +153,14 @@ class MessageHandler {
};
}

/// Create an OutgoingNotification object used for the given method.
template <typename T>
OutgoingNotification<T> outgoingNotification(llvm::StringLiteral method) {
return [&, method](const T &params) {
transport.notify(method, llvm::json::Value(params));
};
}

private:
template <typename HandlerT>
using HandlerMap = llvm::StringMap<llvm::unique_function<HandlerT>>;
Expand All @@ -130,61 +172,6 @@ class MessageHandler {
JSONTransport &transport;
};

//===----------------------------------------------------------------------===//
// 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;
};

} // namespace lsp
} // namespace mlir

Expand Down
35 changes: 35 additions & 0 deletions mlir/test/mlir-lsp-server/diagnostics.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// RUN: mlir-lsp-server -lit-test < %s | FileCheck -strict-whitespace %s
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"mlir","capabilities":{},"trace":"off"}}
// -----
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{
"uri":"test:///foo.mlir",
"languageId":"mlir",
"version":1,
"text":"func ()"
}}}
// CHECK: "method": "textDocument/publishDiagnostics",
// CHECK-NEXT: "params": {
// CHECK-NEXT: "diagnostics": [
// CHECK-NEXT: {
// CHECK-NEXT: "category": "Parse Error",
// CHECK-NEXT: "message": "custom op 'func' expected valid '@'-identifier for symbol name",
// CHECK-NEXT: "range": {
// CHECK-NEXT: "end": {
// CHECK-NEXT: "character": 6,
// CHECK-NEXT: "line": 0
// CHECK-NEXT: },
// CHECK-NEXT: "start": {
// CHECK-NEXT: "character": 6,
// CHECK-NEXT: "line": 0
// CHECK-NEXT: }
// CHECK-NEXT: },
// CHECK-NEXT: "severity": 1,
// CHECK-NEXT: "source": "mlir"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "uri": "test:///foo.mlir"
// CHECK-NEXT: }
// -----
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
// -----
{"jsonrpc":"2.0","method":"exit"}