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),
|
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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
@ -66,25 +102,6 @@ pub const Notification = struct {
|
|||||||
type: MessageType,
|
type: MessageType,
|
||||||
message: string,
|
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
|
/// 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 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,
|
||||||
|
Loading…
Reference in New Issue
Block a user