feat: send didChange, didSave and didClose events to language server
This commit is contained in:
parent
1288021682
commit
5375e1449e
5 changed files with 191 additions and 2 deletions
|
@ -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") },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
125
src/Project.zig
125
src/Project.zig
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Reference in a new issue