improve conformance to the lsp (#687)
This commit is contained in:
parent
3c4535a321
commit
d030dd0993
109
src/Server.zig
109
src/Server.zig
@ -33,7 +33,16 @@ document_store: DocumentStore = undefined,
|
||||
builtin_completions: std.ArrayListUnmanaged(types.CompletionItem),
|
||||
client_capabilities: ClientCapabilities = .{},
|
||||
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
|
||||
|
||||
@ -87,6 +96,16 @@ fn send(writer: anytype, allocator: std.mem.Allocator, reqOrRes: anytype) !void
|
||||
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 {
|
||||
var buffered_writer = std.io.bufferedWriter(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;
|
||||
},
|
||||
.String => |str_val| str_val.len + 2,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
// 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) {
|
||||
.Integer => |int| try buf_writer.print("{}", .{int}),
|
||||
.String => |str| try buf_writer.print("\"{s}\"", .{str}),
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
try buf_writer.writeAll(response);
|
||||
@ -1472,12 +1489,12 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req:
|
||||
.id = id,
|
||||
.result = .{
|
||||
.InitializeResult = .{
|
||||
.offsetEncoding = server.offset_encoding,
|
||||
.serverInfo = .{
|
||||
.name = "zls",
|
||||
.version = "0.1.0",
|
||||
},
|
||||
.capabilities = .{
|
||||
.positionEncoding = server.offset_encoding,
|
||||
.signatureHelpProvider = .{
|
||||
.triggerCharacters = &.{"("},
|
||||
.retriggerCharacters = &.{","},
|
||||
@ -1540,6 +1557,8 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req:
|
||||
},
|
||||
});
|
||||
|
||||
server.status = .initializing;
|
||||
|
||||
if (req.params.capabilities.workspace) |workspace| {
|
||||
server.client_capabilities.supports_configuration = workspace.configuration.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("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 {
|
||||
// NOTE: stage1 moment occurs if we dont do it like this :(
|
||||
// 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 {
|
||||
const tracy_zone = tracy.trace(@src());
|
||||
defer tracy_zone.end();
|
||||
@ -2266,7 +2311,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void
|
||||
defer tree.deinit();
|
||||
|
||||
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 },
|
||||
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;
|
||||
|
||||
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();
|
||||
defer {
|
||||
// makes `zig build test` look nice
|
||||
@ -2341,6 +2406,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void
|
||||
.{"textDocument/willSave"},
|
||||
.{ "initialize", requests.Initialize, initializeHandler },
|
||||
.{ "shutdown", void, shutdownHandler },
|
||||
.{ "exit", void, exitHandler },
|
||||
.{ "textDocument/didOpen", requests.OpenDocument, openDocumentHandler },
|
||||
.{ "textDocument/didChange", requests.ChangeDocument, changeDocumentHandler },
|
||||
.{ "textDocument/didSave", requests.SaveDocument, saveDocumentHandler },
|
||||
@ -2465,6 +2531,7 @@ pub fn init(
|
||||
.allocator = allocator,
|
||||
.document_store = document_store,
|
||||
.builtin_completions = builtin_completions,
|
||||
.status = .uninitialized,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ pub fn log(
|
||||
fn loop(server: *Server) !void {
|
||||
var reader = std.io.getStdIn().reader();
|
||||
|
||||
while (server.keep_running) {
|
||||
while (true) {
|
||||
const headers = readRequestHeader(server.allocator, reader) catch |err| {
|
||||
logger.err("{s}; exiting!", .{@errorName(err)});
|
||||
return;
|
||||
|
@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const string = []const u8;
|
||||
|
||||
// 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 {
|
||||
line: u32,
|
||||
@ -22,13 +22,7 @@ pub const Location = struct {
|
||||
/// Id of a request
|
||||
pub const RequestId = union(enum) {
|
||||
String: string,
|
||||
Integer: i64,
|
||||
Float: f64,
|
||||
};
|
||||
|
||||
/// Hover response
|
||||
pub const Hover = struct {
|
||||
contents: MarkupContent,
|
||||
Integer: i32,
|
||||
};
|
||||
|
||||
/// Params of a response (result)
|
||||
@ -38,7 +32,7 @@ pub const ResponseParams = union(enum) {
|
||||
Location: Location,
|
||||
Hover: Hover,
|
||||
DocumentSymbols: []DocumentSymbol,
|
||||
SemanticTokensFull: struct { data: []const u32 },
|
||||
SemanticTokensFull: SemanticTokens,
|
||||
InlayHint: []InlayHint,
|
||||
TextEdits: []TextEdit,
|
||||
Locations: []Location,
|
||||
@ -51,9 +45,51 @@ pub const ResponseParams = union(enum) {
|
||||
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 Params = union(enum) {
|
||||
jsonrpc: string = "2.0",
|
||||
method: string,
|
||||
params: NotificationParams,
|
||||
};
|
||||
|
||||
pub const NotificationParams = union(enum) {
|
||||
LogMessage: struct {
|
||||
type: MessageType,
|
||||
message: string,
|
||||
@ -66,25 +102,6 @@ pub const Notification = struct {
|
||||
type: MessageType,
|
||||
message: string,
|
||||
},
|
||||
};
|
||||
|
||||
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
|
||||
@ -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 Kind = enum(i64) {
|
||||
Text = 1,
|
||||
@ -441,8 +466,8 @@ const TextDocumentSyncKind = enum(u32) {
|
||||
|
||||
// Only includes options we set in our initialize result.
|
||||
const InitializeResult = struct {
|
||||
offsetEncoding: PositionEncodingKind,
|
||||
capabilities: struct {
|
||||
positionEncoding: PositionEncodingKind,
|
||||
signatureHelpProvider: struct {
|
||||
triggerCharacters: []const string,
|
||||
retriggerCharacters: []const string,
|
||||
|
Loading…
Reference in New Issue
Block a user