improve conformance to the lsp (#687)

This commit is contained in:
Techatrix 2022-09-30 06:04:27 +02:00 committed by GitHub
parent 3c4535a321
commit d030dd0993
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 147 additions and 55 deletions

View File

@ -33,7 +33,16 @@ document_store: DocumentStore = undefined,
builtin_completions: std.ArrayListUnmanaged(types.CompletionItem), builtin_completions: std.ArrayListUnmanaged(types.CompletionItem),
client_capabilities: ClientCapabilities = .{}, client_capabilities: ClientCapabilities = .{},
offset_encoding: offsets.Encoding = .utf16, offset_encoding: offsets.Encoding = .utf16,
keep_running: bool = true, status: enum {
/// the server has not received a `initialize` request
uninitialized,
/// the server has recieved a `initialize` request and is awaiting the `initialized` notification
initializing,
/// the server has been initialized and is ready to received requests
initialized,
/// the server has been shutdown and can't handle any more requests
shutdown,
},
// Code was based off of https://github.com/andersfr/zig-lsp/blob/master/server.zig // Code was based off of https://github.com/andersfr/zig-lsp/blob/master/server.zig
@ -87,6 +96,16 @@ fn send(writer: anytype, allocator: std.mem.Allocator, reqOrRes: anytype) !void
try writer.writeAll(arr.items); try writer.writeAll(arr.items);
} }
pub fn sendErrorResponse(writer: anytype, allocator: std.mem.Allocator, code: types.ErrorCodes, message: []const u8) !void {
try send(writer, allocator, .{
.@"error" = types.ResponseError{
.code = @enumToInt(code),
.message = message,
.data = .Null,
},
});
}
fn respondGeneric(writer: anytype, id: types.RequestId, response: []const u8) !void { fn respondGeneric(writer: anytype, id: types.RequestId, response: []const u8) !void {
var buffered_writer = std.io.bufferedWriter(writer); var buffered_writer = std.io.bufferedWriter(writer);
const buf_writer = buffered_writer.writer(); const buf_writer = buffered_writer.writer();
@ -105,7 +124,6 @@ fn respondGeneric(writer: anytype, id: types.RequestId, response: []const u8) !v
break :blk digits; break :blk digits;
}, },
.String => |str_val| str_val.len + 2, .String => |str_val| str_val.len + 2,
else => unreachable,
}; };
// Numbers of character that will be printed from this string: len - 1 brackets // Numbers of character that will be printed from this string: len - 1 brackets
@ -115,7 +133,6 @@ fn respondGeneric(writer: anytype, id: types.RequestId, response: []const u8) !v
switch (id) { switch (id) {
.Integer => |int| try buf_writer.print("{}", .{int}), .Integer => |int| try buf_writer.print("{}", .{int}),
.String => |str| try buf_writer.print("\"{s}\"", .{str}), .String => |str| try buf_writer.print("\"{s}\"", .{str}),
else => unreachable,
} }
try buf_writer.writeAll(response); try buf_writer.writeAll(response);
@ -1472,12 +1489,12 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req:
.id = id, .id = id,
.result = .{ .result = .{
.InitializeResult = .{ .InitializeResult = .{
.offsetEncoding = server.offset_encoding,
.serverInfo = .{ .serverInfo = .{
.name = "zls", .name = "zls",
.version = "0.1.0", .version = "0.1.0",
}, },
.capabilities = .{ .capabilities = .{
.positionEncoding = server.offset_encoding,
.signatureHelpProvider = .{ .signatureHelpProvider = .{
.triggerCharacters = &.{"("}, .triggerCharacters = &.{"("},
.retriggerCharacters = &.{","}, .retriggerCharacters = &.{","},
@ -1540,6 +1557,8 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req:
}, },
}); });
server.status = .initializing;
if (req.params.capabilities.workspace) |workspace| { if (req.params.capabilities.workspace) |workspace| {
server.client_capabilities.supports_configuration = workspace.configuration.value; server.client_capabilities.supports_configuration = workspace.configuration.value;
if (workspace.didChangeConfiguration != null and workspace.didChangeConfiguration.?.dynamicRegistration.value) { if (workspace.didChangeConfiguration != null and workspace.didChangeConfiguration.?.dynamicRegistration.value) {
@ -1547,7 +1566,7 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req:
} }
} }
log.info("zls initialized", .{}); log.info("zls initializing", .{});
log.info("{}", .{server.client_capabilities}); log.info("{}", .{server.client_capabilities});
log.info("Using offset encoding: {s}", .{std.meta.tagName(server.offset_encoding)}); log.info("Using offset encoding: {s}", .{std.meta.tagName(server.offset_encoding)});
@ -1566,6 +1585,47 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req:
} }
} }
fn initializedHandler(server: *Server, writer: anytype, id: types.RequestId) !void {
_ = id;
if (server.status != .initializing) {
std.log.warn("received a initialized notification but the server has not send a initialize request!", .{});
}
server.status = .initialized;
if (server.client_capabilities.supports_configuration)
try server.requestConfiguration(writer);
}
fn shutdownHandler(server: *Server, writer: anytype, id: types.RequestId) !void {
if (server.status != .initialized) {
return try sendErrorResponse(
writer,
server.arena.allocator(),
types.ErrorCodes.InvalidRequest,
"received a shutdown request but the server is not initialized!",
);
}
// Technically we should deinitialize first and send possible errors to the client
return try respondGeneric(writer, id, null_result_response);
}
fn exitHandler(server: *Server, writer: anytype, id: types.RequestId) noreturn {
_ = writer;
_ = id;
log.info("Server exiting...", .{});
// Technically we should deinitialize first and send possible errors to the client
const error_code: u8 = switch (server.status) {
.uninitialized, .shutdown => 0,
else => 1,
};
std.os.exit(error_code);
}
fn registerCapability(server: *Server, writer: anytype, method: []const u8) !void { fn registerCapability(server: *Server, writer: anytype, method: []const u8) !void {
// NOTE: stage1 moment occurs if we dont do it like this :( // NOTE: stage1 moment occurs if we dont do it like this :(
// long live stage2's not broken anon structs // long live stage2's not broken anon structs
@ -1618,21 +1678,6 @@ fn requestConfiguration(server: *Server, writer: anytype) !void {
}); });
} }
fn initializedHandler(server: *Server, writer: anytype, id: types.RequestId) !void {
_ = id;
if (server.client_capabilities.supports_configuration)
try server.requestConfiguration(writer);
}
fn shutdownHandler(server: *Server, writer: anytype, id: types.RequestId) !void {
log.info("Server closing...", .{});
server.keep_running = false;
// Technically we should deinitialize first and send possible errors to the client
try respondGeneric(writer, id, null_result_response);
}
fn openDocumentHandler(server: *Server, writer: anytype, id: types.RequestId, req: requests.OpenDocument) !void { fn openDocumentHandler(server: *Server, writer: anytype, id: types.RequestId, req: requests.OpenDocument) !void {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2266,7 +2311,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void
defer tree.deinit(); defer tree.deinit();
const id = if (tree.root.Object.get("id")) |id| switch (id) { const id = if (tree.root.Object.get("id")) |id| switch (id) {
.Integer => |int| types.RequestId{ .Integer = int }, .Integer => |int| types.RequestId{ .Integer = @intCast(i32, int) },
.String => |str| types.RequestId{ .String = str }, .String => |str| types.RequestId{ .String = str },
else => types.RequestId{ .Integer = 0 }, else => types.RequestId{ .Integer = 0 },
} else types.RequestId{ .Integer = 0 }; } else types.RequestId{ .Integer = 0 };
@ -2326,6 +2371,26 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void
const method = tree.root.Object.get("method").?.String; const method = tree.root.Object.get("method").?.String;
switch (server.status) {
.uninitialized => blk: {
if (std.mem.eql(u8, method, "initialize")) break :blk;
if (std.mem.eql(u8, method, "exit")) break :blk;
// ignore notifications
if (tree.root.Object.get("id") == null) break :blk;
return try sendErrorResponse(writer, server.arena.allocator(), .ServerNotInitialized, "server received a request before being initialized!");
},
.initializing => blk: {
if (std.mem.eql(u8, method, "initialized")) break :blk;
if (std.mem.eql(u8, method, "$/progress")) break :blk;
return try sendErrorResponse(writer, server.arena.allocator(), .InvalidRequest, "server received a request during initialization!");
},
.initialized => {},
.shutdown => return try sendErrorResponse(writer, server.arena.allocator(), .InvalidRequest, "server received a request after shutdown!"),
}
const start_time = std.time.milliTimestamp(); const start_time = std.time.milliTimestamp();
defer { defer {
// makes `zig build test` look nice // makes `zig build test` look nice
@ -2341,6 +2406,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void
.{"textDocument/willSave"}, .{"textDocument/willSave"},
.{ "initialize", requests.Initialize, initializeHandler }, .{ "initialize", requests.Initialize, initializeHandler },
.{ "shutdown", void, shutdownHandler }, .{ "shutdown", void, shutdownHandler },
.{ "exit", void, exitHandler },
.{ "textDocument/didOpen", requests.OpenDocument, openDocumentHandler }, .{ "textDocument/didOpen", requests.OpenDocument, openDocumentHandler },
.{ "textDocument/didChange", requests.ChangeDocument, changeDocumentHandler }, .{ "textDocument/didChange", requests.ChangeDocument, changeDocumentHandler },
.{ "textDocument/didSave", requests.SaveDocument, saveDocumentHandler }, .{ "textDocument/didSave", requests.SaveDocument, saveDocumentHandler },
@ -2465,6 +2531,7 @@ pub fn init(
.allocator = allocator, .allocator = allocator,
.document_store = document_store, .document_store = document_store,
.builtin_completions = builtin_completions, .builtin_completions = builtin_completions,
.status = .uninitialized,
}; };
} }

View File

@ -36,7 +36,7 @@ pub fn log(
fn loop(server: *Server) !void { fn loop(server: *Server) !void {
var reader = std.io.getStdIn().reader(); var reader = std.io.getStdIn().reader();
while (server.keep_running) { while (true) {
const headers = readRequestHeader(server.allocator, reader) catch |err| { const headers = readRequestHeader(server.allocator, reader) catch |err| {
logger.err("{s}; exiting!", .{@errorName(err)}); logger.err("{s}; exiting!", .{@errorName(err)});
return; return;

View File

@ -2,7 +2,7 @@ const std = @import("std");
const string = []const u8; const string = []const u8;
// LSP types // LSP types
// https://microsoft.github.io/language-server-protocol/specifications/specification-3-16/ // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/
pub const Position = struct { pub const Position = struct {
line: u32, line: u32,
@ -22,13 +22,7 @@ pub const Location = struct {
/// Id of a request /// Id of a request
pub const RequestId = union(enum) { pub const RequestId = union(enum) {
String: string, String: string,
Integer: i64, Integer: i32,
Float: f64,
};
/// Hover response
pub const Hover = struct {
contents: MarkupContent,
}; };
/// Params of a response (result) /// Params of a response (result)
@ -38,7 +32,7 @@ pub const ResponseParams = union(enum) {
Location: Location, Location: Location,
Hover: Hover, Hover: Hover,
DocumentSymbols: []DocumentSymbol, DocumentSymbols: []DocumentSymbol,
SemanticTokensFull: struct { data: []const u32 }, SemanticTokensFull: SemanticTokens,
InlayHint: []InlayHint, InlayHint: []InlayHint,
TextEdits: []TextEdit, TextEdits: []TextEdit,
Locations: []Location, Locations: []Location,
@ -51,9 +45,51 @@ pub const ResponseParams = union(enum) {
ApplyEdit: ApplyWorkspaceEditParams, ApplyEdit: ApplyWorkspaceEditParams,
}; };
/// JSONRPC notifications pub const Response = struct {
jsonrpc: string = "2.0",
id: RequestId,
result: ResponseParams,
};
pub const Request = struct {
jsonrpc: string = "2.0",
id: RequestId,
method: []const u8,
params: ?ResponseParams,
};
pub const ResponseError = struct {
code: i32,
message: string,
data: std.json.Value,
};
pub const ErrorCodes = enum(i32) {
// Defined by JSON-RPC
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
// JSON-RPC reserved error codes
ServerNotInitialized = -32002,
UnknownErrorCode = -3200,
// LSP reserved error codes
RequestFailed = -32803,
ServerCancelled = -32802,
ContentModified = -32801,
RequestCancelled = -32800,
};
pub const Notification = struct { pub const Notification = struct {
pub const Params = union(enum) { jsonrpc: string = "2.0",
method: string,
params: NotificationParams,
};
pub const NotificationParams = union(enum) {
LogMessage: struct { LogMessage: struct {
type: MessageType, type: MessageType,
message: string, message: string,
@ -68,25 +104,6 @@ pub const Notification = struct {
}, },
}; };
jsonrpc: string = "2.0",
method: string,
params: Params,
};
/// JSONRPC response
pub const Response = struct {
jsonrpc: string = "2.0",
id: RequestId,
result: ResponseParams,
};
pub const Request = struct {
jsonrpc: string = "2.0",
id: RequestId,
method: []const u8,
params: ?ResponseParams,
};
/// Type of a debug message /// Type of a debug message
pub const MessageType = enum(i64) { pub const MessageType = enum(i64) {
Error = 1, Error = 1,
@ -215,6 +232,14 @@ pub const InsertTextFormat = enum(i64) {
} }
}; };
pub const Hover = struct {
contents: MarkupContent,
};
pub const SemanticTokens = struct {
data: []const u32,
};
pub const CompletionItem = struct { pub const CompletionItem = struct {
pub const Kind = enum(i64) { pub const Kind = enum(i64) {
Text = 1, Text = 1,
@ -441,8 +466,8 @@ const TextDocumentSyncKind = enum(u32) {
// Only includes options we set in our initialize result. // Only includes options we set in our initialize result.
const InitializeResult = struct { const InitializeResult = struct {
offsetEncoding: PositionEncodingKind,
capabilities: struct { capabilities: struct {
positionEncoding: PositionEncodingKind,
signatureHelpProvider: struct { signatureHelpProvider: struct {
triggerCharacters: []const string, triggerCharacters: []const string,
retriggerCharacters: []const string, retriggerCharacters: []const string,