From 89dc5cce547c4c1db66068b1a8ee62f74cea68ea Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Fri, 26 Dec 2025 12:49:03 +0100 Subject: [PATCH 1/4] fix: do not log git cat-file errors --- src/git.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/git.zig b/src/git.zig index 0330026..aee9383 100644 --- a/src/git.zig +++ b/src/git.zig @@ -275,11 +275,11 @@ pub fn rev_parse(context_: usize, rev: []const u8, file_path: []const u8) Error! pub fn cat_file(context_: usize, object: []const u8) Error!void { const tag = @src().fn_name; - try git_err(context_, .{ "cat-file", "-p", object }, struct { + try git(context_, .{ "cat-file", "-p", object }, struct { fn result(context: usize, parent: tp.pid_ref, output: []const u8) void { parent.send(.{ module_name, context, tag, output }) catch {}; } - }.result, log_err, exit_null(tag)); + }.result, exit_null(tag)); } fn git_line_output(context_: usize, comptime tag: []const u8, cmd: anytype) Error!void { From 4f737e4019d637dc62da05f4953eac06bb43c9ab Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Fri, 26 Dec 2025 14:25:03 +0100 Subject: [PATCH 2/4] refactor: add lsp_info to mainview --- src/tui/lsp_info.zig | 93 ++++++++++++++++++++++++++++++++++++++++++++ src/tui/mainview.zig | 9 +++++ 2 files changed, 102 insertions(+) create mode 100644 src/tui/lsp_info.zig diff --git a/src/tui/lsp_info.zig b/src/tui/lsp_info.zig new file mode 100644 index 0000000..d44039a --- /dev/null +++ b/src/tui/lsp_info.zig @@ -0,0 +1,93 @@ +allocator: std.mem.Allocator, +table: Table, + +const Table = std.StringHashMapUnmanaged(Info); + +pub fn init(allocator: std.mem.Allocator) @This() { + return .{ + .allocator = allocator, + .table = .empty, + }; +} + +pub fn deinit(self: *@This()) void { + var iter = self.table.iterator(); + while (iter.next()) |item| { + for (item.value_ptr.trigger_characters.items) |char| + self.allocator.free(char); + item.value_ptr.trigger_characters.deinit(self.allocator); + self.allocator.free(item.key_ptr.*); + } + self.table.deinit(self.allocator); +} + +pub fn add_from_event(self: *@This(), cbor_buf: []const u8) error{ InvalidTriggersArray, OutOfMemory }!void { + var iter = cbor_buf; + var project: []const u8 = undefined; + var lsp_arg0: []const u8 = undefined; + var trigger_characters: []const u8 = undefined; + if (!(cbor.matchValue(&iter, .{ + cbor.any, + cbor.any, + cbor.extract(&project), + .{ cbor.extract(&lsp_arg0), cbor.more }, + cbor.extract_cbor(&trigger_characters), + }) catch return)) return; + const value = try self.add(lsp_arg0, &trigger_characters); + std.log.debug("{s} triggers: {any}", .{ lsp_arg0, value.trigger_characters.items }); +} + +pub fn add(self: *@This(), lsp_arg0: []const u8, iter: *[]const u8) error{ InvalidTriggersArray, OutOfMemory }!*Info { + const key = try self.allocator.dupe(u8, lsp_arg0); + errdefer self.allocator.free(key); + + const p = try self.table.getOrPut(self.allocator, key); + const value = p.value_ptr; + if (p.found_existing) { + for (value.trigger_characters.items) |char| + self.allocator.free(char); + value.trigger_characters.clearRetainingCapacity(); + } else { + value.* = .{}; + } + + var len = cbor.decodeArrayHeader(iter) catch return error.InvalidTriggersArray; + while (len > 0) : (len -= 1) { + var char: []const u8 = undefined; + if (!(cbor.matchValue(iter, cbor.extract(&char)) catch return error.InvalidTriggersArray)) return error.InvalidTriggersArray; + (try value.trigger_characters.addOne(self.allocator)).* = try self.allocator.dupe(u8, char); + } + return value; +} + +pub const Info = struct { + trigger_characters: std.ArrayList([]const u8) = .empty, +}; + +pub fn write_state(self: *@This(), writer: *std.Io.Writer) error{WriteFailed}!void { + try cbor.writeArrayHeader(writer, self.table.count()); + var iter = self.table.iterator(); + while (iter.next()) |item| { + try cbor.writeArrayHeader(writer, 2); + try cbor.writeValue(writer, item.key_ptr.*); + try cbor.writeArrayHeader(writer, item.value_ptr.trigger_characters.items.len); + for (item.value_ptr.trigger_characters.items) |char| try cbor.writeValue(writer, char); + } +} + +pub fn extract_state(self: *@This(), iter: *[]const u8) error{ InvalidTriggersArray, OutOfMemory }!void { + var lsp_arg0: []const u8 = undefined; + var trigger_characters: []const u8 = undefined; + var len = cbor.decodeArrayHeader(iter) catch return; + while (len > 0) : (len -= 1) { + if (cbor.matchValue(iter, .{ cbor.extract(&lsp_arg0), cbor.extract_cbor(&trigger_characters) }) catch false) { + const value = try self.add(lsp_arg0, &trigger_characters); + std.log.debug("restored {s} triggers: {any}", .{ lsp_arg0, value.trigger_characters.items }); + } else { + cbor.skipValue(iter) catch return error.InvalidTriggersArray; + } + } +} + +const std = @import("std"); +const cbor = @import("cbor"); diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index d0a3430..da49c62 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -27,6 +27,7 @@ const WidgetList = @import("WidgetList.zig"); const WidgetStack = @import("WidgetStack.zig"); const ed = @import("editor.zig"); const home = @import("home.zig"); +const LspInfo = @import("lsp_info.zig"); const logview = @import("logview.zig"); const filelist_view = @import("filelist_view.zig"); @@ -61,6 +62,7 @@ panel_height: ?usize = null, symbols: std.ArrayListUnmanaged(u8) = .empty, symbols_complete: bool = true, closing_project: bool = false, +lsp_info: LspInfo, const FileListType = enum { diagnostics, @@ -85,6 +87,7 @@ pub fn create(allocator: std.mem.Allocator) CreateError!Widget { .panes = undefined, .panes_widget = undefined, .buffer_manager = Buffer.Manager.init(allocator), + .lsp_info = .init(allocator), }; try self.commands.init(self); const w = Widget.to(self); @@ -126,6 +129,7 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { self.symbols.deinit(allocator); self.floating_views.deinit(); self.buffer_manager.deinit(); + self.lsp_info.deinit(); allocator.destroy(self); } @@ -1773,6 +1777,8 @@ pub fn write_state(self: *Self, writer: *std.Io.Writer) WriteStateError!void { if (self.widgets.get("tabs")) |tabs_widget| if (tabs_widget.dynamic_cast(@import("status/tabs.zig").TabBar)) |tabs| try tabs.write_state(writer); + + self.lsp_info.write_state(writer) catch return error.WriteFailed; } fn read_restore_info(self: *Self) !void { @@ -1850,6 +1856,9 @@ fn extract_state(self: *Self, iter: *[]const u8, mode: enum { no_project, with_p logger.print_err("mainview", "failed to restore tabs: {}", .{e}); logger.print("restored tabs ({d} bytes)", .{prev_len - iter.len}); + self.lsp_info.extract_state(iter) catch |e| + logger.print_err("mainview", "failed to restore LSP info: {}", .{e}); + const buffers = try self.buffer_manager.list_unordered(self.allocator); defer self.allocator.free(buffers); for (buffers) |buffer| if (!buffer.is_ephemeral()) From fe2ea1302445c8162832ea437423dd4b75eda330 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Fri, 26 Dec 2025 14:25:19 +0100 Subject: [PATCH 3/4] refactor: store reported lsp trigger characters to main view lsp info --- src/tui/tui.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 42685b4..416dc60 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -533,6 +533,9 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void { if (try m.match(.{ "PRJ", "vcs_content", tp.more })) return if (mainview()) |mv| mv.vcs_content_update(m); + if (try m.match(.{ "PRJ", "triggerCharacters", tp.more })) + return if (mainview()) |mv| mv.lsp_info.add_from_event(m.buf); + if (try m.match(.{ "PRJ", tp.more })) // drop late project manager query responses return; From 3dcdb64f83c63605d203465ac24fba8128304131 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Fri, 26 Dec 2025 16:02:25 +0100 Subject: [PATCH 4/4] feat: configure editor completion triggers from LSP info --- src/tui/editor.zig | 30 +++++++++++++++++++++++++----- src/tui/lsp_info.zig | 8 ++++---- src/tui/mainview.zig | 6 ++++++ src/tui/tui.zig | 2 +- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 81ebdad..4d5d2ab 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -542,9 +542,8 @@ pub const Editor = struct { tp.extract_cbor(&cursels_cbor), })) return error.RestoreStateMatch; - self.insert_triggers.deinit(self.allocator); + self.clear_event_triggers(); self.insert_triggers = .fromOwnedSlice(insert_triggers); - self.delete_triggers.deinit(self.allocator); self.delete_triggers = .fromOwnedSlice(delete_triggers); self.refresh_tab_width(); if (op == .open_file) @@ -614,8 +613,7 @@ pub const Editor = struct { for (self.diagnostics.items) |*d| d.deinit(self.allocator); self.diagnostics.deinit(self.allocator); self.completions.deinit(self.allocator); - self.insert_triggers.deinit(self.allocator); - self.delete_triggers.deinit(self.allocator); + self.clear_event_triggers(); if (self.syntax) |syn| syn.destroy(tui.query_cache()); self.cancel_all_tabstops(); self.cursels.deinit(self.allocator); @@ -6234,9 +6232,31 @@ pub const Editor = struct { }; } + fn clear_event_triggers(self: *Self) void { + self.insert_triggers.deinit(self.allocator); + self.delete_triggers.deinit(self.allocator); + self.insert_triggers = .empty; + self.delete_triggers = .empty; + } + + pub fn update_completion_triggers(self: *Self) void { + self.clear_event_triggers(); + if (self.file_type) |ft| if (ft.language_server) |ls| if (ls.len > 0) { + const lsp_arg0 = ls[0]; + const info = (tui.mainview() orelse return).lsp_info.table.get(lsp_arg0) orelse return; + self.add_completion_triggers(info.trigger_characters.items); + }; + } + fn add_default_symbol_triggers(self: *Self) void { + const chars: []const []const u8 = &.{"."}; + self.add_completion_triggers(chars); + } + + fn add_completion_triggers(self: *Self, triggers: []const []const u8) void { const id = command.get_name_id("completion"); - self.add_symbol_trigger('.', id, .insert) catch {}; + for (triggers) |char| if (char.len > 0) + self.add_symbol_trigger(char[0], id, .insert) catch {}; } pub fn add_symbol_trigger(self: *Self, char: u8, command_: command.ID, event: TriggerEvent) error{OutOfMemory}!void { diff --git a/src/tui/lsp_info.zig b/src/tui/lsp_info.zig index d44039a..6659804 100644 --- a/src/tui/lsp_info.zig +++ b/src/tui/lsp_info.zig @@ -3,6 +3,10 @@ table: Table, const Table = std.StringHashMapUnmanaged(Info); +pub const Info = struct { + trigger_characters: std.ArrayList([]const u8) = .empty, +}; + pub fn init(allocator: std.mem.Allocator) @This() { return .{ .allocator = allocator, @@ -60,10 +64,6 @@ pub fn add(self: *@This(), lsp_arg0: []const u8, iter: *[]const u8) error{ Inval return value; } -pub const Info = struct { - trigger_characters: std.ArrayList([]const u8) = .empty, -}; - pub fn write_state(self: *@This(), writer: *std.Io.Writer) error{WriteFailed}!void { try cbor.writeArrayHeader(writer, self.table.count()); var iter = self.table.iterator(); diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index da49c62..fbc94f7 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -1994,3 +1994,9 @@ pub fn vcs_content_update(self: *Self, m: tp.message) void { editor.vcs_content_update() catch {}; } } + +pub fn trigger_characters_update(self: *Self, m: tp.message) void { + self.lsp_info.add_from_event(m.buf) catch return; + const editor = self.get_active_editor() orelse return; + editor.update_completion_triggers(); +} diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 416dc60..d0e0a74 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -534,7 +534,7 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void { return if (mainview()) |mv| mv.vcs_content_update(m); if (try m.match(.{ "PRJ", "triggerCharacters", tp.more })) - return if (mainview()) |mv| mv.lsp_info.add_from_event(m.buf); + return if (mainview()) |mv| mv.trigger_characters_update(m); if (try m.match(.{ "PRJ", tp.more })) // drop late project manager query responses return;