feat: process textDocument/publishDiagnostics notifications from language server
This commit is contained in:
parent
ef61903334
commit
cc607089df
4 changed files with 144 additions and 9 deletions
16
src/LSP.zig
16
src/LSP.zig
|
@ -13,8 +13,8 @@ const sp_tag = "child";
|
|||
const debug_lsp = true;
|
||||
pub const Error = error{ OutOfMemory, Exit };
|
||||
|
||||
pub fn open(a: std.mem.Allocator, cmd: tp.message) Error!Self {
|
||||
return .{ .a = a, .pid = try Process.create(a, cmd) };
|
||||
pub fn open(a: std.mem.Allocator, project: []const u8, cmd: tp.message) Error!Self {
|
||||
return .{ .a = a, .pid = try Process.create(a, project, cmd) };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
|
@ -59,6 +59,7 @@ const Process = struct {
|
|||
recv_buf: std.ArrayList(u8),
|
||||
parent: tp.pid,
|
||||
tag: [:0]const u8,
|
||||
project: [:0]const u8,
|
||||
sp_tag: [:0]const u8,
|
||||
log_file: ?std.fs.File = null,
|
||||
next_id: i32 = 0,
|
||||
|
@ -66,7 +67,7 @@ const Process = struct {
|
|||
|
||||
const Receiver = tp.Receiver(*Process);
|
||||
|
||||
pub fn create(a: std.mem.Allocator, cmd: tp.message) Error!tp.pid {
|
||||
pub fn create(a: std.mem.Allocator, project: []const u8, cmd: tp.message) Error!tp.pid {
|
||||
var tag: []const u8 = undefined;
|
||||
if (try cmd.match(.{tp.extract(&tag)})) {
|
||||
//
|
||||
|
@ -87,6 +88,7 @@ const Process = struct {
|
|||
.recv_buf = std.ArrayList(u8).init(a),
|
||||
.parent = tp.self_pid().clone(),
|
||||
.tag = try a.dupeZ(u8, tag),
|
||||
.project = try a.dupeZ(u8, project),
|
||||
.requests = std.AutoHashMap(i32, tp.pid).init(a),
|
||||
.sp_tag = try sp_tag_.toOwnedSliceSentinel(0),
|
||||
};
|
||||
|
@ -323,13 +325,15 @@ const Process = struct {
|
|||
var msg = std.ArrayList(u8).init(self.a);
|
||||
defer msg.deinit();
|
||||
const writer = msg.writer();
|
||||
try cbor.writeArrayHeader(writer, 6);
|
||||
try cbor.writeArrayHeader(writer, 7);
|
||||
try cbor.writeValue(writer, sp_tag);
|
||||
try cbor.writeValue(writer, self.project);
|
||||
try cbor.writeValue(writer, self.tag);
|
||||
try cbor.writeValue(writer, "request");
|
||||
try cbor.writeValue(writer, method);
|
||||
try cbor.writeValue(writer, id);
|
||||
if (params) |p| _ = try writer.write(p) else try cbor.writeValue(writer, null);
|
||||
try self.parent.send_raw(.{ .buf = msg.items });
|
||||
}
|
||||
|
||||
fn receive_lsp_response(self: *Process, id: i32, result: ?[]const u8, err: ?[]const u8) !void {
|
||||
|
@ -362,12 +366,14 @@ const Process = struct {
|
|||
var msg = std.ArrayList(u8).init(self.a);
|
||||
defer msg.deinit();
|
||||
const writer = msg.writer();
|
||||
try cbor.writeArrayHeader(writer, 5);
|
||||
try cbor.writeArrayHeader(writer, 6);
|
||||
try cbor.writeValue(writer, sp_tag);
|
||||
try cbor.writeValue(writer, self.project);
|
||||
try cbor.writeValue(writer, self.tag);
|
||||
try cbor.writeValue(writer, "notify");
|
||||
try cbor.writeValue(writer, method);
|
||||
if (params) |p| _ = try writer.write(p) else try cbor.writeValue(writer, null);
|
||||
try self.parent.send_raw(.{ .buf = msg.items });
|
||||
}
|
||||
|
||||
fn write_log(self: *Process, comptime format: []const u8, args: anytype) void {
|
||||
|
|
|
@ -87,7 +87,7 @@ pub fn restore_state(self: *Self, data: []const u8) !void {
|
|||
|
||||
fn get_lsp(self: *Self, language_server: []const u8) !LSP {
|
||||
if (self.language_servers.get(language_server)) |lsp| return lsp;
|
||||
const lsp = try LSP.open(self.a, .{ .buf = language_server });
|
||||
const lsp = try LSP.open(self.a, self.name, .{ .buf = language_server });
|
||||
try self.language_servers.put(try self.a.dupe(u8, language_server), lsp);
|
||||
const uri = try self.make_URI(null);
|
||||
defer self.a.free(uri);
|
||||
|
@ -410,7 +410,7 @@ pub fn goto_definition(self: *Self, from: tp.pid_ref, file_path: []const u8, row
|
|||
}
|
||||
}
|
||||
|
||||
fn navigate_to_location_link(self: *Self, from: tp.pid_ref, location_link: []const u8) !void {
|
||||
fn navigate_to_location_link(_: *Self, from: tp.pid_ref, location_link: []const u8) !void {
|
||||
var iter = location_link;
|
||||
var targetUri: ?[]const u8 = null;
|
||||
var targetRange: ?Range = null;
|
||||
|
@ -437,8 +437,8 @@ fn navigate_to_location_link(self: *Self, from: tp.pid_ref, location_link: []con
|
|||
}
|
||||
if (targetUri == null or targetRange == null) return error.InvalidMessageField;
|
||||
if (!std.mem.eql(u8, targetUri.?[0..7], "file://")) return error.InvalidTargetURI;
|
||||
const file_path = try std.Uri.unescapeString(self.a, targetUri.?[7..]);
|
||||
defer self.a.free(file_path);
|
||||
var file_path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
const file_path = std.Uri.percentDecodeBackwards(&file_path_buf, targetUri.?[7..]);
|
||||
if (targetSelectionRange) |sel| {
|
||||
try from.send(.{ "cmd", "navigate", .{
|
||||
.file = file_path,
|
||||
|
@ -462,6 +462,85 @@ fn navigate_to_location_link(self: *Self, from: tp.pid_ref, location_link: []con
|
|||
}
|
||||
}
|
||||
|
||||
pub fn publish_diagnostics(self: *Self, to: tp.pid_ref, params_cb: []const u8) !void {
|
||||
var uri: ?[]const u8 = null;
|
||||
var diagnostics: []const u8 = &.{};
|
||||
var iter = params_cb;
|
||||
var len = try cbor.decodeMapHeader(&iter);
|
||||
while (len > 0) : (len -= 1) {
|
||||
var field_name: []const u8 = undefined;
|
||||
if (!(try cbor.matchString(&iter, &field_name))) return error.InvalidMessage;
|
||||
if (std.mem.eql(u8, field_name, "uri")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&uri)))) return error.InvalidMessageField;
|
||||
} else if (std.mem.eql(u8, field_name, "diagnostics")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&diagnostics)))) return error.InvalidMessageField;
|
||||
} else {
|
||||
try cbor.skipValue(&iter);
|
||||
}
|
||||
}
|
||||
|
||||
if (uri == null) return error.InvalidMessageField;
|
||||
if (!std.mem.eql(u8, uri.?[0..7], "file://")) return error.InvalidURI;
|
||||
var file_path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
const file_path = std.Uri.percentDecodeBackwards(&file_path_buf, uri.?[7..]);
|
||||
|
||||
try self.send_clear_diagnostics(to, file_path);
|
||||
|
||||
iter = diagnostics;
|
||||
len = try cbor.decodeArrayHeader(&iter);
|
||||
while (len > 0) : (len -= 1) {
|
||||
var diagnostic: []const u8 = undefined;
|
||||
if (try cbor.matchValue(&iter, cbor.extract_cbor(&diagnostic))) {
|
||||
try self.send_diagnostic(to, file_path, diagnostic);
|
||||
} else return error.InvalidMessageField;
|
||||
}
|
||||
}
|
||||
|
||||
fn send_diagnostic(_: *Self, to: tp.pid_ref, file_path: []const u8, diagnostic: []const u8) !void {
|
||||
var source: ?[]const u8 = null;
|
||||
var code: ?[]const u8 = null;
|
||||
var message: ?[]const u8 = null;
|
||||
var severity: ?i64 = null;
|
||||
var range: ?Range = null;
|
||||
var iter = diagnostic;
|
||||
var len = try cbor.decodeMapHeader(&iter);
|
||||
while (len > 0) : (len -= 1) {
|
||||
var field_name: []const u8 = undefined;
|
||||
if (!(try cbor.matchString(&iter, &field_name))) return error.InvalidMessage;
|
||||
if (std.mem.eql(u8, field_name, "source") or std.mem.eql(u8, field_name, "uri")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&source)))) return error.InvalidMessageField;
|
||||
} else if (std.mem.eql(u8, field_name, "code")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&code)))) return error.InvalidMessageField;
|
||||
} else if (std.mem.eql(u8, field_name, "message")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&message)))) return error.InvalidMessageField;
|
||||
} else if (std.mem.eql(u8, field_name, "severity")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&severity)))) return error.InvalidMessageField;
|
||||
} else if (std.mem.eql(u8, field_name, "range")) {
|
||||
var range_: []const u8 = undefined;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&range_)))) return error.InvalidMessageField;
|
||||
range = try read_range(range_);
|
||||
} else {
|
||||
try cbor.skipValue(&iter);
|
||||
}
|
||||
}
|
||||
if (range == null) return error.InvalidMessageField;
|
||||
try to.send(.{ "cmd", "add_diagnostic", .{
|
||||
file_path,
|
||||
source,
|
||||
code,
|
||||
message,
|
||||
severity,
|
||||
range.?.start.line,
|
||||
range.?.start.character,
|
||||
range.?.end.line,
|
||||
range.?.end.character,
|
||||
} });
|
||||
}
|
||||
|
||||
fn send_clear_diagnostics(_: *Self, to: tp.pid_ref, file_path: []const u8) !void {
|
||||
try to.send(.{ "cmd", "clear_diagnostics", file_path });
|
||||
}
|
||||
|
||||
const Range = struct { start: Position, end: Position };
|
||||
fn read_range(range: []const u8) !Range {
|
||||
var iter = range;
|
||||
|
|
|
@ -158,6 +158,9 @@ const Process = struct {
|
|||
var query: []const u8 = undefined;
|
||||
var file_type: []const u8 = undefined;
|
||||
var language_server: []const u8 = undefined;
|
||||
var method: []const u8 = undefined;
|
||||
var id: i32 = 0;
|
||||
var params_cb: []const u8 = undefined;
|
||||
var high: i64 = 0;
|
||||
var low: i64 = 0;
|
||||
var max: usize = 0;
|
||||
|
@ -183,6 +186,10 @@ const Process = struct {
|
|||
self.loaded(project_directory) catch |e| return from.forward_error(e);
|
||||
} else if (try m.match(.{ "update_mru", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) {
|
||||
self.update_mru(project_directory, path, row, col) catch |e| return from.forward_error(e);
|
||||
} else if (try m.match(.{ "child", tp.extract(&project_directory), tp.extract(&language_server), "notify", tp.extract(&method), tp.extract_cbor(¶ms_cb) })) {
|
||||
self.dispatch_notify(project_directory, language_server, method, params_cb) catch |e| return self.logger.err("notify", e);
|
||||
} else if (try m.match(.{ "child", tp.extract(&project_directory), tp.extract(&language_server), "request", tp.extract(&method), tp.extract(&id), tp.extract_cbor(¶ms_cb) })) {
|
||||
self.dispatch_request(project_directory, language_server, method, id, params_cb) catch |e| return self.logger.err("notify", e);
|
||||
} else if (try m.match(.{ "open", tp.extract(&project_directory) })) {
|
||||
self.open(project_directory) catch |e| return from.forward_error(e);
|
||||
} else if (try m.match(.{ "request_recent_files", tp.extract(&project_directory), tp.extract(&max) })) {
|
||||
|
@ -298,6 +305,24 @@ const Process = struct {
|
|||
return project.update_mru(file_path, row, col) catch |e| tp.exit_error(e);
|
||||
}
|
||||
|
||||
fn dispatch_notify(self: *Process, project_directory: []const u8, language_server: []const u8, method: []const u8, params_cb: []const u8) tp.result {
|
||||
_ = language_server;
|
||||
const project = if (self.projects.get(project_directory)) |p| p else return tp.exit("No project");
|
||||
return if (std.mem.eql(u8, method, "textDocument/publishDiagnostics"))
|
||||
project.publish_diagnostics(self.parent.ref(), params_cb) catch |e| tp.exit_error(e)
|
||||
else
|
||||
tp.unexpected(.{ .buf = params_cb });
|
||||
}
|
||||
|
||||
fn dispatch_request(self: *Process, project_directory: []const u8, language_server: []const u8, method: []const u8, id: i32, params_cb: []const u8) tp.result {
|
||||
_ = self;
|
||||
_ = project_directory;
|
||||
_ = language_server;
|
||||
_ = method;
|
||||
_ = id;
|
||||
return tp.unexpected(.{ .buf = params_cb });
|
||||
}
|
||||
|
||||
fn persist_projects(self: *Process) void {
|
||||
var i = self.projects.iterator();
|
||||
while (i.next()) |p| self.persist_project(p.value_ptr.*) catch {};
|
||||
|
|
|
@ -3220,6 +3220,31 @@ pub const Editor = struct {
|
|||
return project_manager.goto_definition(file_path, primary.cursor.row, primary.cursor.col);
|
||||
}
|
||||
|
||||
pub fn clear_diagnostics(self: *Self, _: command.Context) tp.result {
|
||||
self.logger.print("diag: clear", .{});
|
||||
}
|
||||
|
||||
pub fn add_diagnostic(self: *Self, ctx: command.Context) tp.result {
|
||||
var file_path: []const u8 = undefined;
|
||||
var source: []const u8 = undefined;
|
||||
var code: []const u8 = undefined;
|
||||
var message: []const u8 = undefined;
|
||||
var severity: i32 = 0;
|
||||
var sel: Selection = .{};
|
||||
if (!try ctx.args.match(.{
|
||||
tp.extract(&file_path),
|
||||
tp.extract(&source),
|
||||
tp.extract(&code),
|
||||
tp.extract(&message),
|
||||
tp.extract(&severity),
|
||||
tp.extract(&sel.begin.row),
|
||||
tp.extract(&sel.begin.col),
|
||||
tp.extract(&sel.end.row),
|
||||
tp.extract(&sel.end.col),
|
||||
})) return tp.exit_error(error.InvalidArgument);
|
||||
self.logger.print("diag: {d} {s} {s} {s} {any}", .{ severity, source, code, message, sel });
|
||||
}
|
||||
|
||||
pub fn select(self: *Self, ctx: command.Context) tp.result {
|
||||
var sel: Selection = .{};
|
||||
if (!try ctx.args.match(.{ tp.extract(&sel.begin.row), tp.extract(&sel.begin.col), tp.extract(&sel.end.row), tp.extract(&sel.end.col) }))
|
||||
|
|
Loading…
Add table
Reference in a new issue