refactor: share file item menu rendering

This commit is contained in:
CJ van den Berg 2025-08-13 14:44:03 +02:00
parent f3296482d0
commit c50ab782ec
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
3 changed files with 70 additions and 106 deletions

View file

@ -12,8 +12,6 @@ const Widget = @import("../../Widget.zig");
pub const label = "Switch buffers"; pub const label = "Switch buffers";
pub const name = " buffer"; pub const name = " buffer";
pub const description = "buffer"; pub const description = "buffer";
const dirty_indicator = "";
const hidden_indicator = "-";
pub const Entry = struct { pub const Entry = struct {
label: []const u8, 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); const buffers = try buffer_manager.list_most_recently_used(palette.allocator);
defer palette.allocator.free(buffers); defer palette.allocator.free(buffers);
for (buffers) |buffer| { for (buffers) |buffer| {
const indicator = if (buffer.is_dirty()) const indicator = tui.get_buffer_state_indicator(buffer);
dirty_indicator
else if (buffer.is_hidden())
hidden_indicator
else
"";
(try palette.entries.addOne()).* = .{ (try palette.entries.addOne()).* = .{
.label = buffer.get_file_path(), .label = buffer.get_file_path(),
.icon = buffer.file_type_icon orelse "", .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 { pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
const style_base = theme.editor_widget; return tui.render_file_item_cbor(&button.plane, button.opts.label, button.active, selected, button.hover, theme);
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;
} }
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void { fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {

View file

@ -88,7 +88,7 @@ pub fn deinit(self: *Self) void {
} }
inline fn menu_width(self: *Self) usize { 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 { inline fn menu_pos_x(self: *Self) usize {
@ -102,54 +102,8 @@ inline fn max_menu_width() usize {
return @max(15, width - (width / 5)); 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 { fn on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool {
const style_base = theme.editor_widget; return tui.render_file_item_cbor(&button.plane, button.opts.label, button.active, selected, button.hover, theme);
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 prepare_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) Widget.Box { 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( fn add_item(
self: *Self, self: *Self,
file_name: []const u8, file_name: []const u8,
file_type: []const u8,
file_icon: []const u8, file_icon: []const u8,
file_color: u24, file_color: u24,
indicator: []const u8,
matches: ?[]const u8, matches: ?[]const u8,
) !void { ) !void {
var label = std.ArrayList(u8).init(self.allocator); var label = std.ArrayList(u8).init(self.allocator);
defer label.deinit(); defer label.deinit();
const writer = label.writer(); const writer = label.writer();
try cbor.writeValue(writer, file_name); try cbor.writeValue(writer, file_name);
try cbor.writeValue(writer, file_type);
try cbor.writeValue(writer, file_icon); try cbor.writeValue(writer, file_icon);
try cbor.writeValue(writer, file_color); 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); 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), tp.extract_cbor(&matches),
})) { })) {
if (self.need_reset) self.reset_results(); 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(); self.do_resize();
if (self.need_select_first) { if (self.need_select_first) {
self.menu.select_down(); self.menu.select_down();
@ -237,7 +192,8 @@ fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void
tp.extract(&file_color), tp.extract(&file_color),
})) { })) {
if (self.need_reset) self.reset_results(); 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(); self.do_resize();
if (self.need_select_first) { if (self.need_select_first) {
self.menu.select_down(); self.menu.select_down();

View file

@ -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 {}; 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 { pub fn render_file_icon(self: *renderer.Plane, icon: []const u8, color: u24) void {
if (!config().show_fileicons) return;
var cell = self.cell_init(); var cell = self.cell_init();
_ = self.at_cursor_cell(&cell) catch return; _ = self.at_cursor_cell(&cell) catch return;
if (!(color == 0xFFFFFF or color == 0x000000 or color == 0x000001)) { 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 {}; _ = 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 { fn get_or_create_theme_file(self: *Self, allocator: std.mem.Allocator) ![]const u8 {
const theme_name = self.theme_.name; const theme_name = self.theme_.name;
if (root.read_theme(allocator, theme_name)) |content| { if (root.read_theme(allocator, theme_name)) |content| {