From 996ec70e4200a87cbe04b8e6812fe0af73cb206f Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sun, 20 Jul 2025 18:41:12 +0200 Subject: [PATCH] feat: add LSP client support for error responses to requests --- src/LSP.zig | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/LSP.zig b/src/LSP.zig index 2afae60..58012a3 100644 --- a/src/LSP.zig +++ b/src/LSP.zig @@ -67,6 +67,45 @@ pub fn send_notification_raw(self: *const Self, method: []const u8, cb: []const self.pid.send(.{ "NTFY", method, cb }) catch return error.SendFailed; } +pub const ErrorCode = enum(i32) { + + // Defined by JSON-RPC + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + + // Defined by LSP + RequestFailed = -32803, + ServerCancelled = -32802, + ContentModified = -32801, + RequestCancelled = -32800, +}; + +pub fn send_response(allocator: std.mem.Allocator, to: tp.pid_ref, cbor_id: []const u8, result: anytype) (SendError || OutOfMemoryError)!void { + var cb = std.ArrayList(u8).init(allocator); + defer cb.deinit(); + const writer = cb.writer(); + try cbor.writeArrayHeader(writer, 3); + try cbor.writeValue(writer, "RSP"); + try writer.writeAll(cbor_id); + try cbor.writeValue(cb.writer(), result); + to.send_raw(.{ .buf = cb.items }) catch return error.SendFailed; +} + +pub fn send_error_response(allocator: std.mem.Allocator, to: tp.pid_ref, cbor_id: []const u8, code: ErrorCode, message: []const u8) (SendError || OutOfMemoryError)!void { + var cb = std.ArrayList(u8).init(allocator); + defer cb.deinit(); + const writer = cb.writer(); + try cbor.writeArrayHeader(writer, 4); + try cbor.writeValue(writer, "ERR"); + try writer.writeAll(cbor_id); + try cbor.writeValue(cb.writer(), code); + try cbor.writeValue(cb.writer(), message); + to.send_raw(.{ .buf = cb.items }) catch return error.SendFailed; +} + pub fn close(self: *Self) void { self.deinit(); } @@ -252,6 +291,8 @@ const Process = struct { var err: []const u8 = ""; var code: u32 = 0; var cbor_id: []const u8 = ""; + var error_code: ErrorCode = undefined; + var message: []const u8 = ""; if (try cbor.match(m.buf, .{ "REQ", "initialize", tp.extract(&bytes) })) { try self.send_request(from, "initialize", bytes); @@ -262,6 +303,8 @@ const Process = struct { } } else if (try cbor.match(m.buf, .{ "RSP", tp.extract_cbor(&cbor_id), tp.extract_cbor(&bytes) })) { try self.send_response(cbor_id, bytes); + } else if (try cbor.match(m.buf, .{ "ERR", tp.extract_cbor(&cbor_id), tp.extract(&error_code), tp.extract(&message) })) { + try self.send_error_response(cbor_id, error_code, message); } else if (try cbor.match(m.buf, .{ "NTFY", "initialized", tp.extract(&bytes) })) { self.state = .running; try self.send_notification("initialized", bytes); @@ -482,6 +525,39 @@ const Process = struct { self.write_log("### SEND response:\n{s}\n###\n", .{output.items}); } + fn send_error_response(self: *Process, cbor_id: []const u8, error_code: ErrorCode, message: []const u8) (error{Closed} || SendError || cbor.Error || cbor.JsonEncodeError)!void { + const sp = if (self.sp) |*sp| sp else return error.Closed; + + var msg = std.ArrayList(u8).init(self.allocator); + defer msg.deinit(); + const msg_writer = msg.writer(); + try cbor.writeMapHeader(msg_writer, 3); + try cbor.writeValue(msg_writer, "jsonrpc"); + try cbor.writeValue(msg_writer, "2.0"); + try cbor.writeValue(msg_writer, "id"); + try msg_writer.writeAll(cbor_id); + try cbor.writeValue(msg_writer, "error"); + try cbor.writeMapHeader(msg_writer, 2); + try cbor.writeValue(msg_writer, "code"); + try cbor.writeValue(msg_writer, @intFromEnum(error_code)); + try cbor.writeValue(msg_writer, "message"); + try cbor.writeValue(msg_writer, message); + + const json = try cbor.toJsonAlloc(self.allocator, msg.items); + defer self.allocator.free(json); + var output = std.ArrayList(u8).init(self.allocator); + defer output.deinit(); + const writer = output.writer(); + const terminator = "\r\n"; + const content_length = json.len + terminator.len; + try writer.print("Content-Length: {d}\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n", .{content_length}); + _ = try writer.write(json); + _ = try writer.write(terminator); + + sp.send(output.items) catch return error.SendFailed; + self.write_log("### SEND error response:\n{s}\n###\n", .{output.items}); + } + fn send_notification(self: *Process, method: []const u8, params_cb: []const u8) Error!void { const sp = if (self.sp) |*sp| sp else return error.Closed;