feat: send didChange, didSave and didClose events to language server

This commit is contained in:
CJ van den Berg 2024-04-13 02:50:02 +02:00
parent 1288021682
commit 5375e1449e
5 changed files with 191 additions and 2 deletions

View file

@ -143,8 +143,10 @@ pub fn build(b: *std.Build) void {
.{ .name = "log", .module = log_mod }, .{ .name = "log", .module = log_mod },
.{ .name = "cbor", .module = cbor_mod }, .{ .name = "cbor", .module = cbor_mod },
.{ .name = "thespian", .module = thespian_mod }, .{ .name = "thespian", .module = thespian_mod },
.{ .name = "Buffer", .module = Buffer_mod },
.{ .name = "tracy", .module = tracy_mod }, .{ .name = "tracy", .module = tracy_mod },
.{ .name = "syntax", .module = syntax_dep.module("syntax") }, .{ .name = "syntax", .module = syntax_dep.module("syntax") },
.{ .name = "dizzy", .module = dizzy_dep.module("dizzy") },
}, },
}); });

View file

@ -35,7 +35,11 @@ pub fn send_notification(self: Self, method: []const u8, m: anytype) tp.result {
var cb = std.ArrayList(u8).init(self.a); var cb = std.ArrayList(u8).init(self.a);
defer cb.deinit(); defer cb.deinit();
cbor.writeValue(cb.writer(), m) catch |e| return tp.exit_error(e); cbor.writeValue(cb.writer(), m) catch |e| return tp.exit_error(e);
return self.pid.send(.{ "NTFY", method, cb.items }); return self.send_notification_raw(method, cb.items);
}
pub fn send_notification_raw(self: Self, method: []const u8, cb: []const u8) tp.result {
return self.pid.send(.{ "NTFY", method, cb });
} }
pub fn close(self: *Self) void { pub fn close(self: *Self) void {

View file

@ -2,6 +2,8 @@ const std = @import("std");
const tp = @import("thespian"); const tp = @import("thespian");
const cbor = @import("cbor"); const cbor = @import("cbor");
const root = @import("root"); const root = @import("root");
const dizzy = @import("dizzy");
const Buffer = @import("Buffer");
const builtin = @import("builtin"); const builtin = @import("builtin");
const LSP = @import("LSP.zig"); const LSP = @import("LSP.zig");
@ -185,6 +187,129 @@ pub fn did_open(self: *Self, from: tp.pid_ref, file_path: []const u8, file_type:
}); });
} }
pub fn did_change(self: *Self, file_path: []const u8, version: usize, root_dst_addr: usize, root_src_addr: usize) !void {
const lsp = try self.get_file_lsp(file_path);
const uri = self.make_URI(file_path) catch |e| return tp.exit_error(e);
defer self.a.free(uri);
const root_dst: Buffer.Root = if (root_dst_addr == 0) return else @ptrFromInt(root_dst_addr);
const root_src: Buffer.Root = if (root_src_addr == 0) return else @ptrFromInt(root_src_addr);
var dizzy_edits = std.ArrayListUnmanaged(dizzy.Edit){};
var dst = std.ArrayList(u8).init(self.a);
var src = std.ArrayList(u8).init(self.a);
var scratch = std.ArrayListUnmanaged(u32){};
var edits_cb = std.ArrayList(u8).init(self.a);
const writer = edits_cb.writer();
defer {
edits_cb.deinit();
dst.deinit();
src.deinit();
scratch.deinit(self.a);
dizzy_edits.deinit(self.a);
}
try root_dst.store(dst.writer());
try root_src.store(src.writer());
const scratch_len = 4 * (dst.items.len + src.items.len) + 2;
try scratch.ensureTotalCapacity(self.a, scratch_len);
scratch.items.len = scratch_len;
try dizzy.PrimitiveSliceDiffer(u8).diff(self.a, &dizzy_edits, src.items, dst.items, scratch.items);
var lines_dst: usize = 0;
var pos_src: usize = 0;
var pos_dst: usize = 0;
var last_offset: usize = 0;
var edits_count: usize = 0;
for (dizzy_edits.items) |dizzy_edit| {
switch (dizzy_edit.kind) {
.equal => {
const dist = dizzy_edit.range.end - dizzy_edit.range.start;
pos_src += dist;
pos_dst += dist;
scan_char(src.items[dizzy_edit.range.start..dizzy_edit.range.end], &lines_dst, '\n', &last_offset);
},
.insert => {
const dist = dizzy_edit.range.end - dizzy_edit.range.start;
pos_src += 0;
pos_dst += dist;
const line_start_dst: usize = lines_dst;
scan_char(dst.items[dizzy_edit.range.start..dizzy_edit.range.end], &lines_dst, '\n', null);
try cbor.writeValue(writer, .{
.range = .{
.start = .{ .line = line_start_dst, .character = last_offset },
.end = .{ .line = line_start_dst, .character = last_offset },
},
.text = dst.items[dizzy_edit.range.start..dizzy_edit.range.end],
});
edits_count += 1;
},
.delete => {
const dist = dizzy_edit.range.end - dizzy_edit.range.start;
pos_src += dist;
pos_dst += 0;
var line_end_dst: usize = lines_dst;
var offset_end_dst: usize = last_offset;
scan_char(src.items[dizzy_edit.range.start..dizzy_edit.range.end], &line_end_dst, '\n', &offset_end_dst);
if (lines_dst == line_end_dst) offset_end_dst = last_offset + dist;
try cbor.writeValue(writer, .{
.range = .{
.start = .{ .line = lines_dst, .character = last_offset },
.end = .{ .line = line_end_dst, .character = offset_end_dst },
},
.text = "",
});
edits_count += 1;
},
}
}
var msg = std.ArrayList(u8).init(self.a);
defer msg.deinit();
const msg_writer = msg.writer();
try cbor.writeMapHeader(msg_writer, 2);
try cbor.writeValue(msg_writer, "textDocument");
try cbor.writeValue(msg_writer, .{ .uri = uri, .version = version });
try cbor.writeValue(msg_writer, "contentChanges");
try cbor.writeArrayHeader(msg_writer, edits_count);
_ = try msg_writer.write(edits_cb.items);
try lsp.send_notification_raw("textDocument/didChange", msg.items);
}
fn scan_char(chars: []const u8, lines: *usize, char: u8, last_offset: ?*usize) void {
var pos = chars;
while (pos.len > 0) {
if (pos[0] == char) {
if (last_offset) |off| off.* = pos.len - 1;
lines.* += 1;
}
pos = pos[1..];
}
}
pub fn did_save(self: *Self, file_path: []const u8) tp.result {
const lsp = try self.get_file_lsp(file_path);
const uri = self.make_URI(file_path) catch |e| return tp.exit_error(e);
defer self.a.free(uri);
try lsp.send_notification("textDocument/didSave", .{
.textDocument = .{ .uri = uri },
});
}
pub fn did_close(self: *Self, file_path: []const u8) tp.result {
const lsp = try self.get_file_lsp(file_path);
const uri = self.make_URI(file_path) catch |e| return tp.exit_error(e);
defer self.a.free(uri);
try lsp.send_notification("textDocument/didClose", .{
.textDocument = .{ .uri = uri },
});
}
pub fn goto_definition(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) !void { pub fn goto_definition(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) !void {
const lsp = try self.get_file_lsp(file_path); const lsp = try self.get_file_lsp(file_path);
const uri = self.make_URI(file_path) catch |e| return tp.exit_error(e); const uri = self.make_URI(file_path) catch |e| return tp.exit_error(e);

View file

@ -67,6 +67,27 @@ pub fn did_open(file_path: []const u8, file_type: *const FileType, version: usiz
return (try get()).pid.send(.{ "did_open", project, file_path, file_type.name, file_type.language_server, version, text_ptr, text.len }); return (try get()).pid.send(.{ "did_open", project, file_path, file_type.name, file_type.language_server, version, text_ptr, text.len });
} }
pub fn did_change(file_path: []const u8, version: usize, root_dst: usize, root_src: usize) tp.result {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "did_change", project, file_path, version, root_dst, root_src });
}
pub fn did_save(file_path: []const u8) tp.result {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "did_save", project, file_path });
}
pub fn did_close(file_path: []const u8) tp.result {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "did_close", project, file_path });
}
pub fn goto_definition(file_path: []const u8, row: usize, col: usize) tp.result { pub fn goto_definition(file_path: []const u8, row: usize, col: usize) tp.result {
const project = tp.env.get().str("project"); const project = tp.env.get().str("project");
if (project.len == 0) if (project.len == 0)
@ -146,6 +167,9 @@ const Process = struct {
var text_ptr: usize = 0; var text_ptr: usize = 0;
var text_len: usize = 0; var text_len: usize = 0;
var root_dst: usize = 0;
var root_src: usize = 0;
if (try m.match(.{ "walk_tree_entry", tp.extract(&project_directory), tp.extract(&path), tp.extract(&high), tp.extract(&low) })) { if (try m.match(.{ "walk_tree_entry", tp.extract(&project_directory), tp.extract(&path), tp.extract(&high), tp.extract(&low) })) {
const mtime = (@as(i128, @intCast(high)) << 64) | @as(i128, @intCast(low)); const mtime = (@as(i128, @intCast(high)) << 64) | @as(i128, @intCast(low));
if (self.projects.get(project_directory)) |project| if (self.projects.get(project_directory)) |project|
@ -173,6 +197,12 @@ const Process = struct {
} else if (try m.match(.{ "did_open", tp.extract(&project_directory), tp.extract(&path), tp.extract(&file_type), tp.extract_cbor(&language_server), tp.extract(&version), tp.extract(&text_ptr), tp.extract(&text_len) })) { } else if (try m.match(.{ "did_open", tp.extract(&project_directory), tp.extract(&path), tp.extract(&file_type), tp.extract_cbor(&language_server), tp.extract(&version), tp.extract(&text_ptr), tp.extract(&text_len) })) {
const text = if (text_len > 0) @as([*]const u8, @ptrFromInt(text_ptr))[0..text_len] else ""; const text = if (text_len > 0) @as([*]const u8, @ptrFromInt(text_ptr))[0..text_len] else "";
self.did_open(from, project_directory, path, file_type, language_server, version, text) catch |e| return from.forward_error(e); self.did_open(from, project_directory, path, file_type, language_server, version, text) catch |e| return from.forward_error(e);
} else if (try m.match(.{ "did_change", tp.extract(&project_directory), tp.extract(&path), tp.extract(&version), tp.extract(&root_dst), tp.extract(&root_src) })) {
self.did_change(project_directory, path, version, root_dst, root_src) catch |e| return from.forward_error(e);
} else if (try m.match(.{ "did_save", tp.extract(&project_directory), tp.extract(&path) })) {
self.did_save(project_directory, path) catch |e| return from.forward_error(e);
} else if (try m.match(.{ "did_close", tp.extract(&project_directory), tp.extract(&path) })) {
self.did_close(project_directory, path) catch |e| return from.forward_error(e);
} else if (try m.match(.{ "goto_definition", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) { } else if (try m.match(.{ "goto_definition", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) {
self.goto_definition(from, project_directory, path, row, col) catch |e| return from.forward_error(e); self.goto_definition(from, project_directory, path, row, col) catch |e| return from.forward_error(e);
} else if (try m.match(.{ "get_mru_position", tp.extract(&project_directory), tp.extract(&path) })) { } else if (try m.match(.{ "get_mru_position", tp.extract(&project_directory), tp.extract(&path) })) {
@ -221,6 +251,27 @@ const Process = struct {
return project.did_open(from, file_path, file_type, language_server, version, text); return project.did_open(from, file_path, file_type, language_server, version, text);
} }
fn did_change(self: *Process, project_directory: []const u8, file_path: []const u8, version: usize, root_dst: usize, root_src: usize) tp.result {
const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".did_change" });
defer frame.deinit();
const project = if (self.projects.get(project_directory)) |p| p else return tp.exit("No project");
return project.did_change(file_path, version, root_dst, root_src) catch |e| tp.exit_error(e);
}
fn did_save(self: *Process, project_directory: []const u8, file_path: []const u8) tp.result {
const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".did_save" });
defer frame.deinit();
const project = if (self.projects.get(project_directory)) |p| p else return tp.exit("No project");
return project.did_save(file_path);
}
fn did_close(self: *Process, project_directory: []const u8, file_path: []const u8) tp.result {
const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".did_close" });
defer frame.deinit();
const project = if (self.projects.get(project_directory)) |p| p else return tp.exit("No project");
return project.did_close(file_path);
}
fn goto_definition(self: *Process, from: tp.pid_ref, project_directory: []const u8, file_path: []const u8, row: usize, col: usize) tp.result { fn goto_definition(self: *Process, from: tp.pid_ref, project_directory: []const u8, file_path: []const u8, row: usize, col: usize) tp.result {
const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".goto_definition" }); const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".goto_definition" });
defer frame.deinit(); defer frame.deinit();

View file

@ -385,6 +385,8 @@ pub const Editor = struct {
self.plane.home(); self.plane.home();
self.plane.context().cursor_disable() catch {}; self.plane.context().cursor_disable() catch {};
_ = try self.handlers.msg(.{ "E", "close" }); _ = try self.handlers.msg(.{ "E", "close" });
if (self.syntax) |_| if (self.file_path) |file_path|
project_manager.did_close(file_path) catch {};
} }
fn save(self: *Self) !void { fn save(self: *Self) !void {
@ -973,8 +975,10 @@ pub const Editor = struct {
const dirty = if (self.buffer) |buf| buf.is_dirty() else false; const dirty = if (self.buffer) |buf| buf.is_dirty() else false;
const root: ?Buffer.Root = self.buf_root() catch null; const root: ?Buffer.Root = self.buf_root() catch null;
if (token_from(self.last.root) != token_from(root)) if (token_from(self.last.root) != token_from(root)) {
try self.send_editor_update(self.last.root, root); try self.send_editor_update(self.last.root, root);
self.lsp_version += 1;
}
if (self.last.dirty != dirty) if (self.last.dirty != dirty)
try self.send_editor_dirty(dirty); try self.send_editor_dirty(dirty);
@ -1069,6 +1073,7 @@ pub const Editor = struct {
fn send_editor_save(self: *const Self, file_path: []const u8) !void { fn send_editor_save(self: *const Self, file_path: []const u8) !void {
_ = try self.handlers.msg(.{ "E", "save", file_path }); _ = try self.handlers.msg(.{ "E", "save", file_path });
if (self.syntax) |_| project_manager.did_save(file_path) catch {};
} }
fn send_editor_dirty(self: *const Self, file_dirty: bool) !void { fn send_editor_dirty(self: *const Self, file_dirty: bool) !void {
@ -1081,6 +1086,8 @@ pub const Editor = struct {
fn send_editor_update(self: *const Self, old_root: ?Buffer.Root, new_root: ?Buffer.Root) !void { fn send_editor_update(self: *const Self, old_root: ?Buffer.Root, new_root: ?Buffer.Root) !void {
_ = try self.handlers.msg(.{ "E", "update", token_from(new_root), token_from(old_root) }); _ = try self.handlers.msg(.{ "E", "update", token_from(new_root), token_from(old_root) });
if (self.syntax) |_| if (self.file_path) |file_path|
project_manager.did_change(file_path, self.lsp_version, token_from(new_root), token_from(old_root)) catch {};
} }
fn clamp_abs(self: *Self, abs: bool) void { fn clamp_abs(self: *Self, abs: bool) void {