From 79fc70427c9e554465a961ade399c6536224e0c4 Mon Sep 17 00:00:00 2001 From: Miguel Granero Date: Fri, 13 Feb 2026 13:26:12 +0100 Subject: [PATCH 1/8] feat: add project file explorer widget (palette) --- src/tui/mode/overlay/palette.zig | 2 +- .../overlay/project_file_tree_palette.zig | 285 ++++++++++++++++++ src/tui/tui.zig | 5 + 3 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 src/tui/mode/overlay/project_file_tree_palette.zig diff --git a/src/tui/mode/overlay/palette.zig b/src/tui/mode/overlay/palette.zig index 63a541b..74764a5 100644 --- a/src/tui/mode/overlay/palette.zig +++ b/src/tui/mode/overlay/palette.zig @@ -317,7 +317,7 @@ pub fn Create(options: type) type { self.inputbox.hint.print(self.inputbox.allocator, "{d}/{d}", .{ self.total_items, self.entries.items.len }) catch {}; } - fn start_query(self: *Self, n: usize) !void { + pub fn start_query(self: *Self, n: usize) !void { defer tui.reset_hover(@src()); defer self.update_count_hint(); self.items = 0; diff --git a/src/tui/mode/overlay/project_file_tree_palette.zig b/src/tui/mode/overlay/project_file_tree_palette.zig new file mode 100644 index 0000000..73fc2da --- /dev/null +++ b/src/tui/mode/overlay/project_file_tree_palette.zig @@ -0,0 +1,285 @@ +const std = @import("std"); +const cbor = @import("cbor"); +const tp = @import("thespian"); +const root = @import("soft_root").root; +const command = @import("command"); + +const tui = @import("../../tui.zig"); +pub const Type = @import("palette.zig").Create(@This()); +const module_name = @typeName(@This()); +const Widget = @import("../../Widget.zig"); + +pub const label = "File Explorer"; +pub const name = "File Explorer"; +pub const description = "Project file explorer"; +pub const icon = "🗂️ "; +pub const modal_dim = true; +pub const placement = .top_center; + +pub const NodeType = enum { + file, + folder, +}; + +pub const Node = struct { + name: []const u8, + type_: NodeType, + expanded: bool = false, + children: ?std.ArrayList(Node) = null, + parent: ?*Node = null, + path: []const u8, +}; + +pub const Entry = struct { + label: []const u8, + node: *Node, +}; + +fn createNodeFromPath(allocator: std.mem.Allocator, path: []const u8) !*Node { + const node = try allocator.create(Node); + errdefer allocator.destroy(node); + + const basename = std.fs.path.basename(path); + node.* = .{ + .name = try allocator.dupe(u8, basename), + .path = try allocator.dupe(u8, path), + .type_ = undefined, + .expanded = false, + .children = null, + .parent = null, + }; + + var dir = std.fs.cwd().openDir(path, .{}) catch |err| { + if (err == error.NotDir or err == error.FileNotFound) { + node.*.type_ = .file; + return node; + } + return err; + }; + defer dir.close(); + node.*.type_ = .folder; + return node; +} + +fn loadNodeChildren(allocator: std.mem.Allocator, node: *Node, recursive: bool) !void { + if (node.type_ != .folder) return; + if (node.children != null) return; + + var children: std.ArrayList(Node) = .empty; + errdefer children.deinit(allocator); + + var dir = try std.fs.cwd().openDir(node.path, .{ .iterate = true }); + defer dir.close(); + + var iter = dir.iterateAssumeFirstIteration(); + while (try iter.next()) |entry| { + const child_path = try std.fs.path.join(allocator, &[_][]const u8{ node.path, entry.name }); + errdefer allocator.free(child_path); + + var child_node = Node{ + .name = try allocator.dupe(u8, entry.name), + .path = child_path, + .type_ = if (entry.kind == .directory) .folder else .file, + .expanded = false, + .children = null, + .parent = node, + }; + + if (recursive) { + try loadNodeChildren(allocator, &child_node, recursive); + } + + try children.append(allocator, child_node); + } + + node.children = children; +} + +fn deinitNode(allocator: std.mem.Allocator, node: *Node) void { + allocator.free(node.name); + allocator.free(node.path); + if (node.children) |*children| { + for (children.items) |*child| { + deinitNode(allocator, child); + } + children.deinit(allocator); + } +} + +fn deinitRootNode(allocator: std.mem.Allocator, node: *Node) void { + deinitNode(allocator, node); + allocator.destroy(node); +} + +var root_node: ?*Node = null; + +pub fn load_entries(palette: *Type) !usize { + palette.entries.clearRetainingCapacity(); + + const project_path = tp.env.get().str("project"); + if (project_path.len == 0) { + return 0; + } + + if (root_node == null) { + root_node = try createNodeFromPath(palette.allocator, project_path); + try loadNodeChildren(palette.allocator, root_node.?, false); + root_node.?.expanded = true; + } + + try buildVisibleList(palette, root_node.?, 0); + + return palette.entries.items.len; +} + +fn buildVisibleList(palette: *Type, node: *Node, depth: usize) !void { + const node_label = try createNodeLabel(palette.allocator, node, depth); + try palette.entries.append(palette.allocator, .{ + .label = node_label, + .node = node, + }); + + if (node.type_ == .folder and node.expanded) { + if (node.children) |children| { + for (children.items) |*child| { + try buildVisibleList(palette, child, depth + 1); + } + } + } +} + +fn isNodeVisible(node: *const Node, root_ptr: *const Node) bool { + var current: ?*const Node = node; + while (current) |c| { + if (c == root_ptr) return true; + if (!c.expanded) return false; + current = c.parent; + } + return false; +} + +fn createNodeLabel(allocator: std.mem.Allocator, node: *const Node, depth: usize) ![]const u8 { + var buffer: std.ArrayList(u8) = .empty; + + // Add indentation + for (0..depth) |_| { + try buffer.append(allocator, ' '); + } + + // Add folder icon or file icon + if (node.type_ == .folder) { + if (node.expanded) { + try buffer.appendSlice(allocator, "🗁"); + } else { + try buffer.appendSlice(allocator, "🗀"); + } + } else { + // @TODO: Add here file icon + try buffer.append(allocator, '>'); + } + try buffer.append(allocator, ' '); + + try buffer.appendSlice(allocator, node.name); + return buffer.toOwnedSlice(allocator); +} + +pub fn updated(palette: *Type, button_: ?*Type.ButtonType) !void { + _ = palette; + _ = button_; +} + +pub fn on_render_menu(_: *Type, button: *Type.ButtonType, 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(); + } + var label_str: []const u8 = undefined; + var iter = button.opts.label; + if (!(cbor.matchString(&iter, &label_str) catch false)) return false; + button.plane.set_style(style_hint); + tui.render_pointer(&button.plane, selected); + button.plane.set_style(style_label); + _ = button.plane.print("{s} ", .{label_str}) catch {}; + button.plane.set_style(style_hint); + 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 + 2, theme) catch break; + } else break; + } + return false; +} + +fn select(menu: **Type.MenuType, button: *Type.ButtonType, _: Type.Pos) void { + const palette = menu.*.opts.ctx; + + var label_str: []const u8 = undefined; + var entry_idx: usize = undefined; + var iter = button.opts.label; + if (!(cbor.matchString(&iter, &label_str) catch false)) return; + if (!(cbor.matchValue(&iter, cbor.extract(&entry_idx)) catch false)) return; + + if (entry_idx >= palette.entries.items.len) return; + + const entry = palette.entries.items[entry_idx]; + const node = entry.node; + + if (node.type_ == .folder) { + if (!node.expanded and node.children == null) { + loadNodeChildren(palette.allocator, node, false) catch |e| { + palette.logger.err("loadNodeChildren", e); + return; + }; + } + node.expanded = !node.expanded; + _ = load_entries(palette) catch unreachable; + + palette.inputbox.text.shrinkRetainingCapacity(0); + palette.inputbox.cursor = tui.egc_chunk_width(palette.inputbox.text.items, 0, 8); + + const new_idx = for (palette.entries.items, 0..) |e, i| { + if (e.node == node) break i + 1; + } else 0; + + palette.initial_selected = new_idx; + palette.start_query(0) catch unreachable; + + tui.need_render(@src()); + } else { + tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| palette.logger.err(module_name, e); + tp.self_pid().send(.{ "cmd", "navigate", .{ .file = node.path } }) catch |e| palette.logger.err(module_name, e); + } +} + +pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !void { + var value: std.Io.Writer.Allocating = .init(palette.allocator); + defer value.deinit(); + const writer = &value.writer; + try cbor.writeValue(writer, entry.label); + const entry_idx = for (palette.entries.items, 0..) |existing_entry, idx| { + if (existing_entry.node == entry.node) break idx; + } else palette.entries.items.len; + try cbor.writeValue(writer, entry_idx); + try cbor.writeValue(writer, matches orelse &[_]usize{}); + try palette.menu.add_item_with_handler(value.written(), select); + palette.items += 1; +} + +pub fn clear_entries(palette: *Type) void { + palette.entries.clearRetainingCapacity(); +} + +pub fn deinit(palette: *Type) void { + if (root_node) |node| { + deinitRootNode(palette.allocator, node); + root_node = null; + } +} diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 30c70c9..7192590 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -1371,6 +1371,11 @@ const cmds = struct { } pub const open_command_palette_meta: Meta = .{ .description = "Command palette" }; + pub fn open_project_file_tree(self: *Self, _: Ctx) Result { + return self.enter_overlay_mode(@import("mode/overlay/project_file_tree_palette.zig").Type); + } + pub const open_project_file_tree_meta: Meta = .{ .description = "Project file tree explorer" }; + pub fn insert_command_name(self: *Self, _: Ctx) Result { return self.enter_overlay_mode(@import("mode/overlay/list_all_commands_palette.zig").Type); } From 04ee1257b89dce06d49c0cdb48daf12a1b976166 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Fri, 13 Feb 2026 22:58:29 +0100 Subject: [PATCH 2/8] refactor: use file_type_config.guess_file_type to get icons Also, normalize folders to use the same icons as in other places in flow. --- .../overlay/project_file_tree_palette.zig | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/tui/mode/overlay/project_file_tree_palette.zig b/src/tui/mode/overlay/project_file_tree_palette.zig index 73fc2da..b7ad7a2 100644 --- a/src/tui/mode/overlay/project_file_tree_palette.zig +++ b/src/tui/mode/overlay/project_file_tree_palette.zig @@ -3,6 +3,7 @@ const cbor = @import("cbor"); const tp = @import("thespian"); const root = @import("soft_root").root; const command = @import("command"); +const file_type_config = @import("file_type_config"); const tui = @import("../../tui.zig"); pub const Type = @import("palette.zig").Create(@This()); @@ -12,7 +13,7 @@ const Widget = @import("../../Widget.zig"); pub const label = "File Explorer"; pub const name = "File Explorer"; pub const description = "Project file explorer"; -pub const icon = "🗂️ "; +pub const icon = " "; pub const modal_dim = true; pub const placement = .top_center; @@ -169,13 +170,13 @@ fn createNodeLabel(allocator: std.mem.Allocator, node: *const Node, depth: usize // Add folder icon or file icon if (node.type_ == .folder) { if (node.expanded) { - try buffer.appendSlice(allocator, "🗁"); + try buffer.appendSlice(allocator, ""); } else { - try buffer.appendSlice(allocator, "🗀"); + try buffer.appendSlice(allocator, ""); } } else { - // @TODO: Add here file icon - try buffer.append(allocator, '>'); + _, const icon_, _ = guess_file_type(node.path); + try buffer.appendSlice(allocator, icon_); } try buffer.append(allocator, ' '); @@ -183,6 +184,29 @@ fn createNodeLabel(allocator: std.mem.Allocator, node: *const Node, depth: usize return buffer.toOwnedSlice(allocator); } +fn default_ft() struct { []const u8, []const u8, u24 } { + return .{ + file_type_config.default.name, + file_type_config.default.icon, + file_type_config.default.color, + }; +} + +pub fn guess_file_type(file_path: []const u8) struct { []const u8, []const u8, u24 } { + var buf: [1024]u8 = undefined; + const content: []const u8 = blk: { + const file = std.fs.cwd().openFile(file_path, .{ .mode = .read_only }) catch break :blk &.{}; + defer file.close(); + const size = file.read(&buf) catch break :blk &.{}; + break :blk buf[0..size]; + }; + return if (file_type_config.guess_file_type(file_path, content)) |ft| .{ + ft.name, + ft.icon orelse file_type_config.default.icon, + ft.color orelse file_type_config.default.color, + } else default_ft(); +} + pub fn updated(palette: *Type, button_: ?*Type.ButtonType) !void { _ = palette; _ = button_; From 3407efaa49de1e32aa34688227a1569c6a58bb67 Mon Sep 17 00:00:00 2001 From: Miguel Granero Date: Sat, 14 Feb 2026 00:34:47 +0100 Subject: [PATCH 3/8] feat: add colored icons to project file tree viewer --- .../overlay/project_file_tree_palette.zig | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/src/tui/mode/overlay/project_file_tree_palette.zig b/src/tui/mode/overlay/project_file_tree_palette.zig index b7ad7a2..0f19352 100644 --- a/src/tui/mode/overlay/project_file_tree_palette.zig +++ b/src/tui/mode/overlay/project_file_tree_palette.zig @@ -32,7 +32,10 @@ pub const Node = struct { }; pub const Entry = struct { - label: []const u8, + label: []const u8, // TODO: Just needed because of pallete.zig L:328 self.longest = @max(self.longest, entry.label.len) + indent: usize, + file_icon: []const u8, + file_color: u24, node: *Node, }; @@ -134,9 +137,12 @@ pub fn load_entries(palette: *Type) !usize { } fn buildVisibleList(palette: *Type, node: *Node, depth: usize) !void { - const node_label = try createNodeLabel(palette.allocator, node, depth); + const file_icon, const file_color = try getNodeIconAndColor(node); try palette.entries.append(palette.allocator, .{ - .label = node_label, + .label = node.name, + .indent = depth, + .file_icon = file_icon, + .file_color = file_color, .node = node, }); @@ -159,36 +165,24 @@ fn isNodeVisible(node: *const Node, root_ptr: *const Node) bool { return false; } -fn createNodeLabel(allocator: std.mem.Allocator, node: *const Node, depth: usize) ![]const u8 { - var buffer: std.ArrayList(u8) = .empty; - - // Add indentation - for (0..depth) |_| { - try buffer.append(allocator, ' '); - } +fn getNodeIconAndColor(node: *const Node) !struct { []const u8, u24 } { // Add folder icon or file icon if (node.type_ == .folder) { if (node.expanded) { - try buffer.appendSlice(allocator, ""); - } else { - try buffer.appendSlice(allocator, ""); - } - } else { - _, const icon_, _ = guess_file_type(node.path); - try buffer.appendSlice(allocator, icon_); + return .{ "", 0xAAAAAA }; + } else return .{ "", 0xAAAAAA }; } - try buffer.append(allocator, ' '); - try buffer.appendSlice(allocator, node.name); - return buffer.toOwnedSlice(allocator); + _, const icon_, const color_ = guess_file_type(node.path); + return .{ icon_, color_ }; } fn default_ft() struct { []const u8, []const u8, u24 } { return .{ file_type_config.default.name, file_type_config.default.icon, - file_type_config.default.color, + 0xAAAAAA, // At least with my theme the default color (black) was barely visible }; } @@ -225,10 +219,26 @@ pub fn on_render_menu(_: *Type, button: *Type.ButtonType, theme: *const Widget.T button.plane.home(); } var label_str: []const u8 = undefined; + var file_icon: []const u8 = undefined; + var indent: usize = 0; + var entry_index: usize = 0; + var icon_color: u24 = 0; + var iter = button.opts.label; if (!(cbor.matchString(&iter, &label_str) catch false)) return false; + if (!(cbor.matchInt(usize, &iter, &entry_index) catch false)) return false; + if (!(cbor.matchInt(usize, &iter, &indent) catch false)) return false; + if (!(cbor.matchString(&iter, &file_icon) catch false)) return false; + if (!(cbor.matchInt(u24, &iter, &icon_color) catch false)) return false; + + var icon_style = style_label; + icon_style.fg.?.color = icon_color; + button.plane.set_style(style_hint); tui.render_pointer(&button.plane, selected); + button.plane.set_style(icon_style); + for (0..indent) |_| _ = button.plane.print(" ", .{}) catch {}; + _ = button.plane.print("{s} ", .{file_icon}) catch {}; button.plane.set_style(style_label); _ = button.plane.print("{s} ", .{label_str}) catch {}; button.plane.set_style(style_hint); @@ -292,6 +302,9 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v if (existing_entry.node == entry.node) break idx; } else palette.entries.items.len; try cbor.writeValue(writer, entry_idx); + try cbor.writeValue(writer, entry.indent); + try cbor.writeValue(writer, entry.file_icon); + try cbor.writeValue(writer, entry.file_color); try cbor.writeValue(writer, matches orelse &[_]usize{}); try palette.menu.add_item_with_handler(value.written(), select); palette.items += 1; From 6a418db852c58ba781eb090a9be0fcd2c8d7ad62 Mon Sep 17 00:00:00 2001 From: Miguel Granero Date: Sat, 14 Feb 2026 00:43:24 +0100 Subject: [PATCH 4/8] feat: add default keybinding to open project tree palette --- 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 bb2d412..e7f195a 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -28,6 +28,7 @@ ["ctrl+w", "close_split"], ["ctrl+o", "open_file"], ["ctrl+e", "open_recent"], + ["ctrl+shift+o", "open_project_file_tree"], ["alt+o", "open_previous_file"], ["ctrl+shift+t", "restore_closed_tab"], ["ctrl+shift+f5", "reload_file"], From 106f4b6f860211cf87c2776c14f34b1f9e7f9c9c Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 14 Feb 2026 13:11:36 +0100 Subject: [PATCH 5/8] refactor: use a more common keybind for switch_buffers in flow mode --- src/keybind/builtin/flow.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index e7f195a..652b285 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -63,7 +63,7 @@ ["ctrl+page_up", "previous_tab"], ["ctrl+shift+page_down", "move_tab_next"], ["ctrl+shift+page_up", "move_tab_previous"], - ["ctrl+shift+e", "switch_buffers"], + ["ctrl+k e", "switch_buffers"], ["alt+shift+v", "clipboard_history"], ["ctrl+0", "reset_fontsize"], ["ctrl+plus", "adjust_fontsize", 1.0], From ab5240fd892615a7b660428d0f4074cfbbd44fff Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 14 Feb 2026 13:26:38 +0100 Subject: [PATCH 6/8] refactor: use commond file browser keybinds for open_project_file_tree --- src/keybind/builtin/emacs.json | 1 + src/keybind/builtin/flow.json | 2 +- src/keybind/builtin/helix.json | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/keybind/builtin/emacs.json b/src/keybind/builtin/emacs.json index c76dea2..d57d10e 100644 --- a/src/keybind/builtin/emacs.json +++ b/src/keybind/builtin/emacs.json @@ -6,6 +6,7 @@ ["ctrl+=", "adjust_fontsize", 1.0], ["ctrl+-", "adjust_fontsize", -1.0], ["ctrl+r", "find_file"], + ["ctrl+x d", "open_project_file_tree"], ["ctrl+h ctrl+a", "open_help"], ["ctrl+c ctrl+o", "open_recent_project"], ["ctrl+c g", "find_in_files"], diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 652b285..6453eda 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -28,7 +28,7 @@ ["ctrl+w", "close_split"], ["ctrl+o", "open_file"], ["ctrl+e", "open_recent"], - ["ctrl+shift+o", "open_project_file_tree"], + ["ctrl+shift+e", "open_project_file_tree"], ["alt+o", "open_previous_file"], ["ctrl+shift+t", "restore_closed_tab"], ["ctrl+shift+f5", "reload_file"], diff --git a/src/keybind/builtin/helix.json b/src/keybind/builtin/helix.json index c6223b9..56cc684 100644 --- a/src/keybind/builtin/helix.json +++ b/src/keybind/builtin/helix.json @@ -220,6 +220,7 @@ ["page_down", "move_scroll_page_down"], ["space F", "find_file"], + ["space e", "open_project_file_tree"], ["space S", "workspace_symbol_picker"], ["space D", "workspace_diagnostics_picker"], ["space P", "system_paste"], From eb54d96e872332fa4f7c8d014024a4c8ab3e7e1a Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 14 Feb 2026 13:27:03 +0100 Subject: [PATCH 7/8] fix: fallback to theme foreground color for icons in file tree --- src/tui/mode/overlay/project_file_tree_palette.zig | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/tui/mode/overlay/project_file_tree_palette.zig b/src/tui/mode/overlay/project_file_tree_palette.zig index 0f19352..c27b838 100644 --- a/src/tui/mode/overlay/project_file_tree_palette.zig +++ b/src/tui/mode/overlay/project_file_tree_palette.zig @@ -170,8 +170,8 @@ fn getNodeIconAndColor(node: *const Node) !struct { []const u8, u24 } { // Add folder icon or file icon if (node.type_ == .folder) { if (node.expanded) { - return .{ "", 0xAAAAAA }; - } else return .{ "", 0xAAAAAA }; + return .{ "", 0 }; + } else return .{ "", 0 }; } _, const icon_, const color_ = guess_file_type(node.path); @@ -182,7 +182,7 @@ fn default_ft() struct { []const u8, []const u8, u24 } { return .{ file_type_config.default.name, file_type_config.default.icon, - 0xAAAAAA, // At least with my theme the default color (black) was barely visible + 0, }; } @@ -231,6 +231,9 @@ pub fn on_render_menu(_: *Type, button: *Type.ButtonType, theme: *const Widget.T if (!(cbor.matchString(&iter, &file_icon) catch false)) return false; if (!(cbor.matchInt(u24, &iter, &icon_color) catch false)) return false; + if (icon_color == 0xFFFFFF or icon_color == 0x000000 or icon_color == 0x000001) + icon_color = style_label.fg.?.color; + var icon_style = style_label; icon_style.fg.?.color = icon_color; From a5b640c00ed9dbc626418f3a8d7353fe898577cc Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 14 Feb 2026 16:19:25 +0100 Subject: [PATCH 8/8] fix: use tui.render_file_icon in file tree This fixes match cell rendering and de-duplicates a bit of code. --- src/tui/mode/overlay/project_file_tree_palette.zig | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/tui/mode/overlay/project_file_tree_palette.zig b/src/tui/mode/overlay/project_file_tree_palette.zig index c27b838..25cc726 100644 --- a/src/tui/mode/overlay/project_file_tree_palette.zig +++ b/src/tui/mode/overlay/project_file_tree_palette.zig @@ -231,17 +231,12 @@ pub fn on_render_menu(_: *Type, button: *Type.ButtonType, theme: *const Widget.T if (!(cbor.matchString(&iter, &file_icon) catch false)) return false; if (!(cbor.matchInt(u24, &iter, &icon_color) catch false)) return false; - if (icon_color == 0xFFFFFF or icon_color == 0x000000 or icon_color == 0x000001) - icon_color = style_label.fg.?.color; - - var icon_style = style_label; - icon_style.fg.?.color = icon_color; - button.plane.set_style(style_hint); tui.render_pointer(&button.plane, selected); - button.plane.set_style(icon_style); for (0..indent) |_| _ = button.plane.print(" ", .{}) catch {}; - _ = button.plane.print("{s} ", .{file_icon}) catch {}; + + const icon_width = tui.render_file_icon(&button.plane, file_icon, icon_color); + button.plane.set_style(style_label); _ = button.plane.print("{s} ", .{label_str}) catch {}; button.plane.set_style(style_hint); @@ -249,7 +244,7 @@ pub fn on_render_menu(_: *Type, button: *Type.ButtonType, theme: *const Widget.T 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 + 2, theme) catch break; + tui.render_match_cell(&button.plane, 0, index + 2 + icon_width + indent, theme) catch break; } else break; } return false;