From 2f77dbb8450de18bc3c4bc22e233107b1db655de Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 6 Dec 2025 20:16:43 +0100 Subject: [PATCH 1/6] refactor: add save/restore functionality to open_recent --- src/tui/mode/overlay/open_recent.zig | 89 ++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 4 deletions(-) diff --git a/src/tui/mode/overlay/open_recent.zig b/src/tui/mode/overlay/open_recent.zig index 6de797d..6407f8a 100644 --- a/src/tui/mode/overlay/open_recent.zig +++ b/src/tui/mode/overlay/open_recent.zig @@ -40,12 +40,18 @@ buffer_manager: ?*BufferManager, split: enum { none, vertical } = .none, total_items: usize = 0, total_files_in_project: usize = 0, +quick_activate_enabled: bool = true, +restore_info: std.ArrayList([]const u8) = .empty, const inputbox_label = "Search files by name"; const MenuType = Menu.Options(*Self).MenuType; const ButtonType = MenuType.ButtonType; pub fn create(allocator: std.mem.Allocator) !tui.Mode { + return create_with_args(allocator, .{}); +} + +pub fn create_with_args(allocator: std.mem.Allocator, ctx: command.Context) !tui.Mode { const mv = tui.mainview() orelse return error.NotFound; const self = try allocator.create(Self); errdefer allocator.destroy(self); @@ -70,8 +76,15 @@ pub fn create(allocator: std.mem.Allocator) !tui.Mode { }; try self.commands.init(self); try tui.message_filters().add(MessageFilter.bind(self, receive_project_manager)); - self.query_pending = true; - try project_manager.request_recent_files(max_recent_files); + + if (ctx.args.buf.len != 0) { + try self.restore(ctx); + self.quick_activate_enabled = false; + } else { + self.query_pending = true; + try project_manager.request_recent_files(max_recent_files); + } + self.do_resize(); try mv.floating_views.add(self.modal.widget()); try mv.floating_views.add(self.menu.container_widget); @@ -84,6 +97,8 @@ pub fn create(allocator: std.mem.Allocator) !tui.Mode { } pub fn deinit(self: *Self) void { + self.save(); + self.clear_restore_info(); self.commands.deinit(); tui.message_filters().remove_ptr(self); if (tui.mainview()) |mv| { @@ -94,6 +109,57 @@ pub fn deinit(self: *Self) void { self.allocator.destroy(self); } +fn clear_restore_info(self: *Self) void { + for (self.restore_info.items) |item| self.allocator.free(item); + self.restore_info.clearRetainingCapacity(); +} + +fn save(self: *Self) void { + var data: std.Io.Writer.Allocating = .init(self.allocator); + defer data.deinit(); + const writer = &data.writer; + + cbor.writeArrayHeader(writer, 4) catch return; + cbor.writeValue(writer, self.inputbox.text.items) catch return; + cbor.writeValue(writer, self.longest) catch return; + cbor.writeValue(writer, self.menu.selected) catch return; + cbor.writeArrayHeader(writer, self.restore_info.items.len) catch return; + for (self.restore_info.items) |item| cbor.writeValue(writer, item) catch return; + + tui.set_last_palette(.open_recent, .{ .args = .{ .buf = data.written() } }); +} + +fn restore(self: *Self, ctx: command.Context) !void { + var iter = ctx.args.buf; + + if ((cbor.decodeArrayHeader(&iter) catch 0) != 4) return; + + var input_: []const u8 = undefined; + if (!(cbor.matchString(&iter, &input_) catch return)) return; + + self.inputbox.text.shrinkRetainingCapacity(0); + try self.inputbox.text.appendSlice(self.inputbox.allocator, input_); + self.inputbox.cursor = tui.egc_chunk_width(self.inputbox.text.items, 0, 8); + + if (!(cbor.matchValue(&iter, cbor.extract(&self.longest)) catch return)) return; + + var selected: ?usize = null; + if (!(cbor.matchValue(&iter, cbor.extract(&selected)) catch return)) return; + + var len = cbor.decodeArrayHeader(&iter) catch 0; + while (len > 0) : (len -= 0) { + var item: []const u8 = undefined; + if (!(cbor.matchString(&iter, &item) catch break)) break; + const data = try self.allocator.dupe(u8, item); + errdefer self.allocator.free(data); + try self.restore_item(data); + } + + self.do_resize(); + + if (selected) |*idx| while (idx.* > 0) : (idx.* -= 1) self.menu.select_down(); +} + inline fn menu_width(self: *Self) usize { return @max(@min(self.longest + 3, max_menu_width()) + 5, inputbox_label.len + 2); } @@ -129,6 +195,7 @@ fn do_resize(self: *Self) void { } fn menu_action_open_file(menu: **MenuType, button: *ButtonType, _: Widget.Pos) void { + menu.*.opts.ctx.save(); var file_path: []const u8 = undefined; var iter = button.opts.label; if (!(cbor.matchString(&iter, &file_path) catch false)) return; @@ -148,7 +215,6 @@ fn add_item( indicator: []const u8, matches: ?[]const u8, ) !void { - self.total_items += 1; var label: std.Io.Writer.Allocating = .init(self.allocator); defer label.deinit(); const writer = &label.writer; @@ -157,7 +223,18 @@ fn add_item( try cbor.writeValue(writer, file_color); try cbor.writeValue(writer, indicator); if (matches) |cb| _ = try writer.write(cb) else try cbor.writeValue(writer, &[_]usize{}); - try self.menu.add_item_with_handler(label.written(), menu_action_open_file); + const data = try label.toOwnedSlice(); + errdefer self.allocator.free(data); + try self.restore_item(data); +} + +fn restore_item( + self: *Self, + label: []const u8, +) error{OutOfMemory}!void { + self.total_items += 1; + try self.menu.add_item_with_handler(label, menu_action_open_file); + (try self.restore_info.addOne(self.allocator)).* = label; } fn receive_project_manager(self: *Self, _: tp.pid_ref, m: tp.message) MessageFilter.Error!bool { @@ -253,6 +330,7 @@ fn start_query(self: *Self) MessageFilter.Error!void { self.total_items = 0; if (self.query_pending) return; self.query_pending = true; + self.clear_restore_info(); try project_manager.query_recent_files(max_recent_files, self.inputbox.text.items); } @@ -370,11 +448,14 @@ const cmds = struct { pub const palette_menu_activate_alternate_meta: Meta = .{}; pub fn palette_menu_activate_quick(self: *Self, _: Ctx) Result { + if (!self.quick_activate_enabled) return; if (self.menu.selected orelse 0 > 0) self.menu.activate_selected(); + self.quick_activate_enabled = false; } pub const palette_menu_activate_quick_meta: Meta = .{}; pub fn palette_menu_cancel(self: *Self, _: Ctx) Result { + self.save(); try self.cmd("exit_overlay_mode", .{}); } pub const palette_menu_cancel_meta: Meta = .{}; From a7a7e313ceeb72dca4011df4f80bb23904b642cd Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 6 Dec 2025 20:17:45 +0100 Subject: [PATCH 2/6] refactor: add tui.set_last_palette --- src/tui/tui.zig | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 52d4bbb..c3d5e50 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -85,9 +85,19 @@ clipboard_current_group_number: usize = 0, color_scheme: enum { dark, light } = .dark, color_scheme_locked: bool = false, hint_mode: HintMode = .prefix, +last_palette: ?LastPalette = null, const HintMode = enum { none, prefix, all }; +const LastPalette = struct { + type_: PaletteType, + ctx: command.Context, +}; + +pub const PaletteType = enum { + open_recent, +}; + pub const ClipboardEntry = struct { text: []const u8 = &.{}, group: usize = 0, @@ -2234,3 +2244,15 @@ fn clipboard_send_to_system_internal(self: *Self, text: []const u8) void { self.rdr_.copy_to_system_clipboard(text); } } + +pub fn set_last_palette(type_: PaletteType, ctx: command.Context) void { + const self = current(); + if (self.last_palette) |old| { + self.allocator.free(old.ctx.args.buf); + self.last_palette = null; + } + self.last_palette = .{ + .type_ = type_, + .ctx = .{ .args = ctx.args.clone(self.allocator) catch return }, + }; +} From b34e0c521cecdabf07a458602d7bdde30f0cf72e Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 6 Dec 2025 20:18:05 +0100 Subject: [PATCH 3/6] feat: add last_palette command --- src/tui/tui.zig | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/tui/tui.zig b/src/tui/tui.zig index c3d5e50..f04f7d3 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -1187,6 +1187,17 @@ const cmds = struct { } pub const open_recent_meta: Meta = .{ .description = "Open recent" }; + pub fn last_palette(self: *Self, _: Ctx) Result { + const palette = self.last_palette orelse { + self.logger.print("no previously used palette", .{}); + return; + }; + switch (palette.type_) { + .open_recent => return self.enter_overlay_mode_with_args(@import("mode/overlay/open_recent.zig"), palette.ctx), + } + } + pub const last_palette_meta: Meta = .{ .description = "Open last used palette" }; + pub fn show_vcs_status(self: *Self, _: Ctx) Result { return self.enter_overlay_mode(@import("mode/overlay/vcs_status.zig")); } From 3967995e05f318f12e5ba69055d11b8365e79d02 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 6 Dec 2025 20:18:26 +0100 Subject: [PATCH 4/6] feat: bind last_palette to ctrl+, in flow mode --- src/keybind/builtin/flow.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 7ca1490..6daedd7 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -7,6 +7,7 @@ ["ctrl+e", "find_file"], ["ctrl+shift+n", "create_new_file"], ["ctrl+r", "open_recent_project"], + ["ctrl+,", "last_palette"], ["f1", "open_help"], ["ctrl+\\", "add_split"], ["ctrl+k w", "close_split"], From e8ec1a4598a35f3ba2654653c7fbc27c95e949a2 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 6 Dec 2025 20:23:15 +0100 Subject: [PATCH 5/6] feat: bind last_palette to `space '` in helix mode --- src/keybind/builtin/helix.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/keybind/builtin/helix.json b/src/keybind/builtin/helix.json index b604d4d..614945f 100644 --- a/src/keybind/builtin/helix.json +++ b/src/keybind/builtin/helix.json @@ -240,7 +240,7 @@ ["space s", "show_symbols"], ["space d", "show_diagnostics"], ["space a", "code_action"], - ["space '", "last_picker"], + ["space '", "last_palette"], ["space y", "copy"], ["space p", "system_paste_after"], ["space /", "find_in_files"], @@ -526,10 +526,10 @@ ["space f", "find_file"], ["space b", "switch_buffers"], ["space j", "jumplist_picker"], - ["space s", "symbol_picker"], + ["space s", "show_symbols"], ["space d", "show_diagnostics"], ["space a", "code_action"], - ["space '", "last_picker"], + ["space '", "last_palette"], ["space y", "copy"], ["space p", "system_paste_after"], ["space /", "find_in_files"], From 23ac73ec1ce4df5a1a65b18c117f8b8bd8bcf699 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 6 Dec 2025 20:29:30 +0100 Subject: [PATCH 6/6] fix: select position in restored open_recent palette --- src/tui/mode/overlay/open_recent.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tui/mode/overlay/open_recent.zig b/src/tui/mode/overlay/open_recent.zig index 6407f8a..73620b9 100644 --- a/src/tui/mode/overlay/open_recent.zig +++ b/src/tui/mode/overlay/open_recent.zig @@ -157,7 +157,10 @@ fn restore(self: *Self, ctx: command.Context) !void { self.do_resize(); - if (selected) |*idx| while (idx.* > 0) : (idx.* -= 1) self.menu.select_down(); + if (selected) |idx| { + var i = idx + 1; + while (i > 0) : (i -= 1) self.menu.select_down(); + } } inline fn menu_width(self: *Self) usize {