From c50ab782ec122ae4d229d467776a892341cee2ae Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 13 Aug 2025 14:44:03 +0200 Subject: [PATCH] refactor: share file item menu rendering --- src/tui/mode/overlay/buffer_palette.zig | 54 +-------------------- src/tui/mode/overlay/open_recent.zig | 64 ++++--------------------- src/tui/tui.zig | 58 ++++++++++++++++++++++ 3 files changed, 70 insertions(+), 106 deletions(-) diff --git a/src/tui/mode/overlay/buffer_palette.zig b/src/tui/mode/overlay/buffer_palette.zig index 794ca16..2d73a39 100644 --- a/src/tui/mode/overlay/buffer_palette.zig +++ b/src/tui/mode/overlay/buffer_palette.zig @@ -12,8 +12,6 @@ const Widget = @import("../../Widget.zig"); pub const label = "Switch buffers"; pub const name = " buffer"; pub const description = "buffer"; -const dirty_indicator = ""; -const hidden_indicator = "-"; pub const Entry = struct { label: []const u8, @@ -27,12 +25,7 @@ pub fn load_entries(palette: *Type) !usize { const buffers = try buffer_manager.list_most_recently_used(palette.allocator); defer palette.allocator.free(buffers); for (buffers) |buffer| { - const indicator = if (buffer.is_dirty()) - dirty_indicator - else if (buffer.is_hidden()) - hidden_indicator - else - ""; + const indicator = tui.get_buffer_state_indicator(buffer); (try palette.entries.addOne()).* = .{ .label = buffer.get_file_path(), .icon = buffer.file_type_icon orelse "", @@ -61,50 +54,7 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v } pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool { - const style_base = theme.editor_widget; - const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget; - const style_hint = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_label; - button.plane.set_base_style(style_base); - button.plane.erase(); - button.plane.home(); - button.plane.set_style(style_label); - if (button.active or button.hover or selected) { - button.plane.fill(" "); - button.plane.home(); - } - - button.plane.set_style(style_hint); - const pointer = if (selected) "⏵" else " "; - _ = button.plane.print("{s}", .{pointer}) catch {}; - - var iter = button.opts.label; - var file_path_: []const u8 = undefined; - var icon: []const u8 = undefined; - var color: u24 = undefined; - if (!(cbor.matchString(&iter, &file_path_) catch false)) @panic("invalid buffer file path"); - if (!(cbor.matchString(&iter, &icon) catch false)) @panic("invalid buffer file type icon"); - if (!(cbor.matchInt(u24, &iter, &color) catch false)) @panic("invalid buffer file type color"); - if (tui.config().show_fileicons) { - tui.render_file_icon(&button.plane, icon, color); - _ = button.plane.print(" ", .{}) catch {}; - } - button.plane.set_style(style_label); - _ = button.plane.print(" {s} ", .{file_path_}) catch {}; - - var indicator: []const u8 = undefined; - if (!(cbor.matchString(&iter, &indicator) catch false)) - indicator = ""; - button.plane.set_style(style_hint); - _ = button.plane.print_aligned_right(0, "{s} ", .{indicator}) catch {}; - - var index: usize = 0; - var len = cbor.decodeArrayHeader(&iter) catch return false; - while (len > 0) : (len -= 1) { - if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) { - tui.render_match_cell(&button.plane, 0, index + 4, theme) catch break; - } else break; - } - return false; + return tui.render_file_item_cbor(&button.plane, button.opts.label, button.active, selected, button.hover, theme); } fn select(menu: **Type.MenuState, button: *Type.ButtonState) void { diff --git a/src/tui/mode/overlay/open_recent.zig b/src/tui/mode/overlay/open_recent.zig index 0582129..58675e4 100644 --- a/src/tui/mode/overlay/open_recent.zig +++ b/src/tui/mode/overlay/open_recent.zig @@ -88,7 +88,7 @@ pub fn deinit(self: *Self) void { } inline fn menu_width(self: *Self) usize { - return @max(@min(self.longest, max_menu_width()) + 5, inputbox_label.len + 2); + return @max(@min(self.longest + 1, max_menu_width()) + 5, inputbox_label.len + 2); } inline fn menu_pos_x(self: *Self) usize { @@ -102,54 +102,8 @@ inline fn max_menu_width() usize { return @max(15, width - (width / 5)); } -fn on_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool { - const style_base = theme.editor_widget; - const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget; - const style_keybind = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_base; - button.plane.set_base_style(style_base); - button.plane.erase(); - button.plane.home(); - button.plane.set_style(style_label); - if (button.active or button.hover or selected) { - button.plane.fill(" "); - button.plane.home(); - } - var file_path: []const u8 = undefined; - var file_type: []const u8 = undefined; - var file_icon: []const u8 = undefined; - var file_color: u24 = undefined; - var iter = button.opts.label; // label contains cbor, first the file name, then multiple match indexes - if (!(cbor.matchString(&iter, &file_path) catch false)) file_path = "#ERROR#"; - if (!(cbor.matchString(&iter, &file_type) catch false)) file_type = file_type_config.default.name; - if (!(cbor.matchString(&iter, &file_icon) catch false)) file_icon = file_type_config.default.icon; - if (!(cbor.matchInt(u24, &iter, &file_color) catch false)) file_icon = file_type_config.default.icon; - - button.plane.set_style(style_keybind); - const dirty = if (self.buffer_manager) |bm| if (bm.is_buffer_dirty(file_path)) "" else " " else " "; - const pointer = if (selected) "⏵" else dirty; - _ = button.plane.print("{s}", .{pointer}) catch {}; - - if (tui.config().show_fileicons) { - tui.render_file_icon(&button.plane, file_icon, file_color); - _ = button.plane.print(" ", .{}) catch {}; - } - - var buf: [std.fs.max_path_bytes]u8 = undefined; - var removed_prefix: usize = 0; - const max_len = max_menu_width() - 2; - button.plane.set_style(style_label); - _ = button.plane.print("{s} ", .{ - if (file_path.len > max_len) root.shorten_path(&buf, file_path, &removed_prefix, max_len) else file_path, - }) catch {}; - - var index: usize = 0; - var len = cbor.decodeArrayHeader(&iter) catch return false; - while (len > 0) : (len -= 1) { - if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) { - tui.render_match_cell(&button.plane, 0, index + 4, theme) catch break; - } else break; - } - return false; +fn on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool { + return tui.render_file_item_cbor(&button.plane, button.opts.label, button.active, selected, button.hover, theme); } fn prepare_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) Widget.Box { @@ -178,19 +132,19 @@ fn menu_action_open_file(menu: **Menu.State(*Self), button: *Button.State(*Menu. fn add_item( self: *Self, file_name: []const u8, - file_type: []const u8, file_icon: []const u8, file_color: u24, + indicator: []const u8, matches: ?[]const u8, ) !void { var label = std.ArrayList(u8).init(self.allocator); defer label.deinit(); const writer = label.writer(); try cbor.writeValue(writer, file_name); - try cbor.writeValue(writer, file_type); try cbor.writeValue(writer, file_icon); try cbor.writeValue(writer, file_color); - if (matches) |cb| _ = try writer.write(cb); + 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.items, menu_action_open_file); } @@ -220,7 +174,8 @@ fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void tp.extract_cbor(&matches), })) { if (self.need_reset) self.reset_results(); - try self.add_item(file_name, file_type, file_icon, file_color, matches); + const indicator = if (self.buffer_manager) |bm| tui.get_file_state_indicator(bm, file_name) else ""; + try self.add_item(file_name, file_icon, file_color, indicator, matches); self.do_resize(); if (self.need_select_first) { self.menu.select_down(); @@ -237,7 +192,8 @@ fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void tp.extract(&file_color), })) { if (self.need_reset) self.reset_results(); - try self.add_item(file_name, file_type, file_icon, file_color, null); + const indicator = if (self.buffer_manager) |bm| tui.get_file_state_indicator(bm, file_name) else ""; + try self.add_item(file_name, file_icon, file_color, indicator, null); self.do_resize(); if (self.need_select_first) { self.menu.select_down(); diff --git a/src/tui/tui.zig b/src/tui/tui.zig index f68201c..e699767 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -1428,7 +1428,19 @@ pub fn message(comptime fmt: anytype, args: anytype) void { tp.self_pid().send(.{ "message", std.fmt.bufPrint(&buf, fmt, args) catch @panic("too large") }) catch {}; } +const dirty_indicator = ""; +const hidden_indicator = "-"; + +pub fn get_file_state_indicator(buffer_manager: *const @import("Buffer").Manager, file_name: []const u8) []const u8 { + return if (buffer_manager.get_buffer_for_file(file_name)) |buffer| get_buffer_state_indicator(buffer) else ""; +} + +pub fn get_buffer_state_indicator(buffer: *const @import("Buffer")) []const u8 { + return if (buffer.is_dirty()) dirty_indicator else if (buffer.is_hidden()) hidden_indicator else ""; +} + pub fn render_file_icon(self: *renderer.Plane, icon: []const u8, color: u24) void { + if (!config().show_fileicons) return; var cell = self.cell_init(); _ = self.at_cursor_cell(&cell) catch return; if (!(color == 0xFFFFFF or color == 0x000000 or color == 0x000001)) { @@ -1448,6 +1460,52 @@ pub fn render_match_cell(self: *renderer.Plane, y: usize, x: usize, theme_: *con _ = self.putc(&cell) catch {}; } +pub fn render_file_item_cbor(self: *renderer.Plane, file_item_cbor: []const u8, active: bool, selected: bool, hover: bool, theme_: *const Widget.Theme) bool { + const style_base = theme_.editor_widget; + const style_label = if (active) theme_.editor_cursor else if (hover or selected) theme_.editor_selection else theme_.editor_widget; + const style_hint = if (find_scope_style(theme_, "entity.name")) |sty| sty.style else style_label; + self.set_base_style(style_base); + self.erase(); + self.home(); + self.set_style(style_label); + if (active or hover or selected) { + self.fill(" "); + self.home(); + } + + self.set_style(style_hint); + const pointer = if (selected) "⏵" else " "; + _ = self.print("{s}", .{pointer}) catch {}; + + var iter = file_item_cbor; + var file_path_: []const u8 = undefined; + var icon: []const u8 = undefined; + var color: u24 = undefined; + if (!(cbor.matchString(&iter, &file_path_) catch false)) @panic("invalid buffer file path"); + if (!(cbor.matchString(&iter, &icon) catch false)) @panic("invalid buffer file type icon"); + if (!(cbor.matchInt(u24, &iter, &color) catch false)) @panic("invalid buffer file type color"); + + render_file_icon(self, icon, color); + + self.set_style(style_label); + _ = self.print("{s} ", .{file_path_}) catch {}; + + var indicator: []const u8 = undefined; + if (!(cbor.matchString(&iter, &indicator) catch false)) + indicator = ""; + self.set_style(style_hint); + _ = self.print_aligned_right(0, "{s} ", .{indicator}) catch {}; + + var index: usize = 0; + var len = cbor.decodeArrayHeader(&iter) catch return false; + while (len > 0) : (len -= 1) { + if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) { + render_match_cell(self, 0, index + 4, theme_) catch break; + } else break; + } + return false; +} + fn get_or_create_theme_file(self: *Self, allocator: std.mem.Allocator) ![]const u8 { const theme_name = self.theme_.name; if (root.read_theme(allocator, theme_name)) |content| {