From 2e8fd58ba573565faffa797d7ba5fa15853192df Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sun, 31 Mar 2024 22:38:25 +0200 Subject: [PATCH] feat: WIP start work on goto_definition LSP command --- src/{lsp_process.zig => Lsp.zig} | 32 +++++------- src/Project.zig | 84 ++++++++++++++++++++++++++++++++ src/project_manager.zig | 77 +++++++---------------------- src/tui/editor.zig | 8 +++ src/tui/mode/input/flow.zig | 2 +- 5 files changed, 121 insertions(+), 82 deletions(-) rename src/{lsp_process.zig => Lsp.zig} (77%) create mode 100644 src/Project.zig diff --git a/src/lsp_process.zig b/src/Lsp.zig similarity index 77% rename from src/lsp_process.zig rename to src/Lsp.zig index c8fbda9..b29106d 100644 --- a/src/lsp_process.zig +++ b/src/Lsp.zig @@ -52,7 +52,7 @@ const Process = struct { .recv_buf = std.ArrayList(u8).init(a), .parent = tp.self_pid().clone(), .tag = try a.dupeZ(u8, tag), - .logger = log.logger(@typeName(Self)), + .logger = log.logger(module_name), }; return tp.spawn_link(self.a, self, Process.start, tag) catch |e| tp.exit_error(e); } @@ -72,7 +72,7 @@ const Process = struct { fn start(self: *Process) tp.result { _ = tp.set_trap(true); - self.sp = tp.subprocess.init(self.a, self.cmd, sp_tag, self.stdin_behavior) catch |e| return tp.exit_error(e); + self.sp = tp.subprocess.init(self.a, self.cmd, sp_tag, .Pipe) catch |e| return tp.exit_error(e); tp.receive(&self.receiver); } @@ -101,30 +101,20 @@ const Process = struct { fn handle_output(self: *Process, bytes: []u8) !void { try self.recv_buf.appendSlice(bytes); - @import("log").logger(module_name).print("{s}", .{bytes}) catch {}; + self.logger.print("{s}", .{bytes}); const message = try self.frame_message() orelse return; _ = message; } fn handle_terminated(self: *Process) !void { - const recv_buf = try self.recv_buf.toOwnedSlice(); - var it = std.mem.splitScalar(u8, recv_buf, '\n'); - while (it.next()) |json| { - if (json.len == 0) continue; - var msg_buf: [tp.max_message_size]u8 = undefined; - const msg: tp.message = .{ .buf = try cbor.fromJson(json, &msg_buf) }; - try self.dispatch(msg); - // var buf: [tp.max_message_size]u8 = undefined; - // @import("log").logger(module_name).print("json: {s}", .{try msg.to_json(&buf)}) catch {}; - } - @import("log").logger(module_name).print("done", .{}) catch {}; + self.logger.print("done", .{}); try self.parent.send(.{ self.tag, "done" }); } - fn frame_message(self: *Self) !?Message { - const end = std.mem.indexOf(u8, self.recv_buf, "\r\n\r\n") orelse return null; - const headers = try Headers.parse(self.recv_buf[0..end]); - const body = self.recv_buf[end + 2 ..]; + fn frame_message(self: *Process) !?Message { + const end = std.mem.indexOf(u8, self.recv_buf.items, "\r\n\r\n") orelse return null; + const headers = try Headers.parse(self.recv_buf.items[0..end]); + const body = self.recv_buf.items[end + 2 ..]; if (body.len < headers.content_length) return null; return .{ .body = body }; } @@ -153,14 +143,14 @@ const Headers = struct { else sep + 1; const value = buf[vstart..end]; - ret.parse_one(name, value); + try ret.parse_one(name, value); buf = if (end < buf.len - 2) buf[end + 2 ..] else return ret; } } - fn parse_one(self: *Headers, name: []const u8, value: []const u8) void { + fn parse_one(self: *Headers, name: []const u8, value: []const u8) !void { if (std.mem.eql(u8, "Content-Length", name)) { - self.content_length = std.fmt.parseInt(@TypeOf(self.content_length), value, 10); + self.content_length = try std.fmt.parseInt(@TypeOf(self.content_length), value, 10); } else if (std.mem.eql(u8, "Content-Type", name)) { self.content_type = value; } diff --git a/src/Project.zig b/src/Project.zig new file mode 100644 index 0000000..d9486ed --- /dev/null +++ b/src/Project.zig @@ -0,0 +1,84 @@ +const std = @import("std"); +const tp = @import("thespian"); + +const Lsp = @import("Lsp.zig"); + +a: std.mem.Allocator, +name: []const u8, +files: std.ArrayList(File), +open_time: i64, +lsp: ?Lsp = null, + +const Self = @This(); + +const File = struct { + path: []const u8, + mtime: i128, +}; + +pub fn init(a: std.mem.Allocator, name: []const u8) error{OutOfMemory}!Self { + return .{ + .a = a, + .name = try a.dupe(u8, name), + .files = std.ArrayList(File).init(a), + .open_time = std.time.milliTimestamp(), + }; +} + +pub fn deinit(self: *Self) void { + for (self.files.items) |file| self.a.free(file.path); + self.files.deinit(); + if (self.lsp) |*lsp| lsp.deinit(); + self.a.free(self.name); +} + +fn get_lsp(self: *Self) !Lsp { + if (self.lsp) |lsp| return lsp; + self.lsp = try Lsp.open(self.a, tp.message.fmt(.{"zls"}), "LSP"); + return self.lsp.?; +} + +pub fn add_file(self: *Self, path: []const u8, mtime: i128) error{OutOfMemory}!void { + (try self.files.addOne()).* = .{ .path = try self.a.dupe(u8, path), .mtime = mtime }; +} + +pub fn sort_files_by_mtime(self: *Self) void { + const less_fn = struct { + fn less_fn(_: void, lhs: File, rhs: File) bool { + return lhs.mtime > rhs.mtime; + } + }.less_fn; + std.mem.sort(File, self.files.items, {}, less_fn); +} + +pub fn request_recent_files(self: *Self, from: tp.pid_ref, max: usize) error{ OutOfMemory, Exit }!void { + defer from.send(.{ "PRJ", "recent_done", "" }) catch {}; + for (self.files.items, 0..) |file, i| { + try from.send(.{ "PRJ", "recent", file.path }); + if (i >= max) return; + } +} + +pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []const u8) error{ OutOfMemory, Exit }!usize { + var i: usize = 0; + defer from.send(.{ "PRJ", "recent_done", query }) catch {}; + for (self.files.items) |file| { + if (file.path.len < query.len) continue; + if (std.mem.indexOf(u8, file.path, query)) |_| { + try from.send(.{ "PRJ", "recent", file.path }); + i += 1; + if (i >= max) return i; + } + } + return i; +} + +pub fn goto_definition(self: *Self, from: tp.pid_ref, file_path: []const u8, file_type: []const u8, row: usize, col: usize) tp.result { + const lsp = self.get_lsp() catch |e| return tp.exit_error(e); + _ = from; + _ = file_path; + _ = file_type; + _ = row; + _ = col; + _ = lsp; +} diff --git a/src/project_manager.zig b/src/project_manager.zig index 21de377..7a5d37b 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -4,6 +4,8 @@ const cbor = @import("cbor"); const log = @import("log"); const tracy = @import("tracy"); +const Project = @import("Project.zig"); + pid: tp.pid_ref, const Self = @This(); @@ -55,6 +57,13 @@ pub fn query_recent_files(max: usize, query: []const u8) tp.result { return (try get()).pid.send(.{ "query_recent_files", project, max, query }); } +pub fn goto_definition(file_path: []const u8, file_type: []const u8, row: usize, col: usize) tp.result { + const project = tp.env.get().str("project"); + if (project.len == 0) + return tp.exit("No project"); + return (try get()).pid.send(.{ "goto_definition", project, file_path, file_type, row, col }); +} + const Process = struct { a: std.mem.Allocator, parent: tp.pid, @@ -101,9 +110,12 @@ const Process = struct { var project_directory: []const u8 = undefined; var path: []const u8 = undefined; var query: []const u8 = undefined; + var file_type: []const u8 = undefined; var high: i64 = 0; var low: i64 = 0; var max: usize = 0; + var row: usize = 0; + var col: usize = 0; 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)); @@ -126,6 +138,8 @@ const Process = struct { self.request_recent_files(from, project_directory, max) catch |e| return from.send_raw(tp.exit_message(e)); } else if (try m.match(.{ "query_recent_files", tp.extract(&project_directory), tp.extract(&max), tp.extract(&query) })) { self.query_recent_files(from, project_directory, max, query) catch |e| return from.send_raw(tp.exit_message(e)); + } else if (try m.match(.{ "goto_definition", tp.extract(&project_directory), tp.extract(&path), tp.extract(&file_type), tp.extract(&row), tp.extract(&col) })) { + self.goto_definition(from, project_directory, path, file_type, row, col) catch |e| return from.send_raw(tp.exit_message(e)); } else if (try m.match(.{"shutdown"})) { if (self.walker) |pid| pid.send(.{"stop"}) catch {}; try from.send(.{ "project_manager", "shutdown" }); @@ -161,67 +175,10 @@ const Process = struct { _ = matched; // self.logger.print("queried: {s} for {s} match {d} in {d} ms", .{ project_directory, query, matched, std.time.milliTimestamp() - start_time }); } -}; -const Project = struct { - a: std.mem.Allocator, - name: []const u8, - files: std.ArrayList(File), - open_time: i64, - - const File = struct { - path: []const u8, - mtime: i128, - }; - - fn init(a: std.mem.Allocator, name: []const u8) error{OutOfMemory}!Project { - return .{ - .a = a, - .name = try a.dupe(u8, name), - .files = std.ArrayList(File).init(a), - .open_time = std.time.milliTimestamp(), - }; - } - - fn deinit(self: *Project) void { - for (self.files.items) |file| self.a.free(file.path); - self.files.deinit(); - self.a.free(self.name); - } - - fn add_file(self: *Project, path: []const u8, mtime: i128) error{OutOfMemory}!void { - (try self.files.addOne()).* = .{ .path = try self.a.dupe(u8, path), .mtime = mtime }; - } - - fn sort_files_by_mtime(self: *Project) void { - const less_fn = struct { - fn less_fn(_: void, lhs: File, rhs: File) bool { - return lhs.mtime > rhs.mtime; - } - }.less_fn; - std.mem.sort(File, self.files.items, {}, less_fn); - } - - fn request_recent_files(self: *Project, from: tp.pid_ref, max: usize) error{ OutOfMemory, Exit }!void { - defer from.send(.{ "PRJ", "recent_done", "" }) catch {}; - for (self.files.items, 0..) |file, i| { - try from.send(.{ "PRJ", "recent", file.path }); - if (i >= max) return; - } - } - - fn query_recent_files(self: *Project, from: tp.pid_ref, max: usize, query: []const u8) error{ OutOfMemory, Exit }!usize { - var i: usize = 0; - defer from.send(.{ "PRJ", "recent_done", query }) catch {}; - for (self.files.items) |file| { - if (file.path.len < query.len) continue; - if (std.mem.indexOf(u8, file.path, query)) |_| { - try from.send(.{ "PRJ", "recent", file.path }); - i += 1; - if (i >= max) return i; - } - } - return i; + fn goto_definition(self: *Process, from: tp.pid_ref, project_directory: []const u8, file_path: []const u8, file_type: []const u8, row: usize, col: usize) tp.result { + const project = if (self.projects.get(project_directory)) |p| p else return tp.exit("No project"); + return project.goto_definition(from, file_path, file_type, row, col); } }; diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 8b5de80..b17fb77 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -9,6 +9,7 @@ const ripgrep = @import("ripgrep"); const tracy = @import("tracy"); const text_manip = @import("text_manip"); const syntax = @import("syntax"); +const project_manager = @import("project_manager"); const scrollbar_v = @import("scrollbar_v.zig"); const editor_gutter = @import("editor_gutter.zig"); @@ -3146,6 +3147,13 @@ pub const Editor = struct { try self.send_editor_jump_destination(); } + pub fn goto_definition(self: *Self, _: command.Context) tp.result { + const file_path = self.file_path orelse return; + const primary = self.get_primary(); + const file_type = (self.syntax orelse return).file_type.name; + return project_manager.goto_definition(file_path, file_type, primary.cursor.row, primary.cursor.col); + } + 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) })) diff --git a/src/tui/mode/input/flow.zig b/src/tui/mode/input/flow.zig index 3d84b0e..c9356d0 100644 --- a/src/tui/mode/input/flow.zig +++ b/src/tui/mode/input/flow.zig @@ -186,7 +186,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { key.F09 => self.cmd("theme_prev", .{}), key.F10 => self.cmd("theme_next", .{}), key.F11 => self.cmd("toggle_logview", .{}), - key.F12 => self.cmd("toggle_inputview", .{}), + key.F12 => self.cmd("goto_definition", .{}), key.F34 => self.cmd("toggle_whitespace", .{}), // C-F10 key.ESC => self.cmd("cancel", .{}), key.ENTER => self.cmd("smart_insert_line", .{}),