From cdd6fee9d63f7f0e2cab7e4ca9844086d48ba845 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 23 Feb 2026 21:44:24 +0100 Subject: [PATCH] refactor: compare widgets via the .ptr memeber instead of via their addresses Widget is a handle type. Sort of a smart pointer. Comparing their addresses is brittle because it requires keeping Widget pointers stable. This is nonsense because Widget identity is actually determined by the actual widget object it points to. This big refactor elimits the requirement that Widget addresses remain stable to work properly with Widget.walk and Widget.get. --- src/tui/Menu.zig | 9 ++++++++- src/tui/Widget.zig | 24 +++++++++++------------ src/tui/WidgetList.zig | 6 +++--- src/tui/editor_gutter.zig | 2 +- src/tui/filelist_view.zig | 4 ++-- src/tui/home.zig | 4 ++-- src/tui/mainview.zig | 14 +++++++------- src/tui/status/tabs.zig | 8 ++++---- src/tui/tui.zig | 40 +++++++++++++++++++-------------------- 9 files changed, 59 insertions(+), 52 deletions(-) diff --git a/src/tui/Menu.zig b/src/tui/Menu.zig index 331c2933..d7f8bfed 100644 --- a/src/tui/Menu.zig +++ b/src/tui/Menu.zig @@ -186,7 +186,14 @@ pub fn State(ctx_type: type) type { } pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn) bool { - return self.menu.walk(walk_ctx, f, if (self.frame_widget) |*frame| frame else &self.container_widget); + for (self.menu.widgets.items) |*w| + if (w.widget.walk(walk_ctx, f)) + return true; + + return if (self.frame_widget) |frame| + frame.walk(walk_ctx, f) + else + self.container_widget.walk(walk_ctx, f); } pub fn count(self: *Self) usize { diff --git a/src/tui/Widget.zig b/src/tui/Widget.zig index e298fb52..6db55a94 100644 --- a/src/tui/Widget.zig +++ b/src/tui/Widget.zig @@ -21,7 +21,7 @@ vtable: *const VTable, const Self = @This(); -pub const WalkFn = *const fn (ctx: *anyopaque, w: *Self) bool; +pub const WalkFn = *const fn (ctx: *anyopaque, w: Self) bool; pub const Direction = enum { horizontal, vertical }; pub const Layout = union(enum) { @@ -51,8 +51,8 @@ pub const VTable = struct { layout: *const fn (ctx: *anyopaque) Layout, subscribe: *const fn (ctx: *anyopaque, h: EventHandler) error{NotSupported}!void, unsubscribe: *const fn (ctx: *anyopaque, h: EventHandler) error{NotSupported}!void, - get: *const fn (ctx: *const anyopaque, name_: []const u8) ?*const Self, - walk: *const fn (ctx: *anyopaque, walk_ctx: *anyopaque, f: WalkFn, self_widget: *Self) bool, + get: *const fn (ctx: *const anyopaque, name_: []const u8) ?Self, + walk: *const fn (ctx: *anyopaque, walk_ctx: *anyopaque, f: WalkFn) bool, focus: *const fn (ctx: *anyopaque) void, unfocus: *const fn (ctx: *anyopaque) void, hover: *const fn (ctx: *const anyopaque) bool, @@ -134,13 +134,13 @@ pub fn to(pimpl: anytype) Self { } }.unsubscribe, .get = struct { - pub fn get(ctx: *const anyopaque, name_: []const u8) ?*const Self { + pub fn get(ctx: *const anyopaque, name_: []const u8) ?Self { return if (comptime @hasDecl(child, "get")) child.get(@as(*const child, @ptrCast(@alignCast(ctx))), name_) else null; } }.get, .walk = struct { - pub fn walk(ctx: *anyopaque, walk_ctx: *anyopaque, f: WalkFn, self: *Self) bool { - return if (comptime @hasDecl(child, "walk")) child.walk(@as(*child, @ptrCast(@alignCast(ctx))), walk_ctx, f, self) else false; + pub fn walk(ctx: *anyopaque, walk_ctx: *anyopaque, f: WalkFn) bool { + return if (comptime @hasDecl(child, "walk")) child.walk(@as(*child, @ptrCast(@alignCast(ctx))), walk_ctx, f) else false; } }.walk, .focus = struct { @@ -225,16 +225,16 @@ pub fn unsubscribe(self: Self, h: EventHandler) !void { return self.vtable.unsubscribe(self.ptr, h); } -pub fn get(self: *const Self, name_: []const u8) ?*const Self { +pub fn get(self: *const Self, name_: []const u8) ?Self { var buf: [256]u8 = undefined; return if (std.mem.eql(u8, self.plane.name(&buf), name_)) - self + self.* else self.vtable.get(self.ptr, name_); } -pub fn walk(self: *Self, walk_ctx: *anyopaque, f: WalkFn) bool { - return if (self.vtable.walk(self.ptr, walk_ctx, f, self)) true else f(walk_ctx, self); +pub fn walk(self: *const Self, walk_ctx: *anyopaque, f: WalkFn) bool { + return if (self.vtable.walk(self.ptr, walk_ctx, f)) true else f(walk_ctx, self.*); } pub fn focus(self: *Self) void { @@ -300,12 +300,12 @@ pub fn empty(allocator: Allocator, parent: Plane, layout_: Layout) !Self { } }.unsubscribe, .get = struct { - pub fn get(_: *const anyopaque, _: []const u8) ?*const Self { + pub fn get(_: *const anyopaque, _: []const u8) ?Self { return null; } }.get, .walk = struct { - pub fn walk(_: *anyopaque, _: *anyopaque, _: WalkFn, _: *Self) bool { + pub fn walk(_: *anyopaque, _: *anyopaque, _: WalkFn) bool { return false; } }.walk, diff --git a/src/tui/WidgetList.zig b/src/tui/WidgetList.zig index 91c38aaa..69304845 100644 --- a/src/tui/WidgetList.zig +++ b/src/tui/WidgetList.zig @@ -115,7 +115,7 @@ pub fn addP(self: *Self, w_: Widget) !*Widget { return &w.widget; } -pub fn get(self: *const Self, name_: []const u8) ?*const Widget { +pub fn get(self: *const Self, name_: []const u8) ?Widget { for (self.widgets.items) |*w| if (w.widget.get(name_)) |p| return p; @@ -347,10 +347,10 @@ fn do_resize(self: *Self, padding: Widget.Style.Margin) void { } } -pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn, self_w: *Widget) bool { +pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn) bool { for (self.widgets.items) |*w| if (w.widget.walk(ctx, f)) return true; - return f(ctx, self_w); + return f(ctx, Widget.to(self)); } pub fn focus(self: *Self) void { diff --git a/src/tui/editor_gutter.zig b/src/tui/editor_gutter.zig index 4ef2f47c..7f2e2583 100644 --- a/src/tui/editor_gutter.zig +++ b/src/tui/editor_gutter.zig @@ -38,7 +38,7 @@ highlight: bool, symbols: bool, width: usize, editor: *ed.Editor, -editor_widget: ?*const Widget = null, +editor_widget: ?Widget = null, differ: diffz.AsyncDiffer, const Self = @This(); diff --git a/src/tui/filelist_view.zig b/src/tui/filelist_view.zig index 33a7cd4d..ff820e13 100644 --- a/src/tui/filelist_view.zig +++ b/src/tui/filelist_view.zig @@ -107,8 +107,8 @@ pub fn handle_resize(self: *Self, pos: Widget.Box) void { self.update_scrollbar(); } -pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn, w: *Widget) bool { - return self.menu.container_widget.walk(walk_ctx, f) or f(walk_ctx, w); +pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn) bool { + return self.menu.container_widget.walk(walk_ctx, f) or f(walk_ctx, Widget.to(self)); } pub fn add_item(self: *Self, entry_: Entry) !void { diff --git a/src/tui/home.zig b/src/tui/home.zig index 5fae6c0a..9cdb16f0 100644 --- a/src/tui/home.zig +++ b/src/tui/home.zig @@ -201,8 +201,8 @@ pub fn update(self: *Self) void { self.menu.update(); } -pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn, w: *Widget) bool { - return self.menu.walk(walk_ctx, f) or f(walk_ctx, w); +pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn) bool { + return self.menu.walk(walk_ctx, f) or f(walk_ctx, Widget.to(self)); } pub fn receive(_: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index 58fb5627..178f7e55 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -1732,8 +1732,8 @@ pub fn get_active_buffer(self: *Self) ?*Buffer { return if (self.get_active_editor()) |editor| editor.buffer orelse null else null; } -pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn, w: *Widget) bool { - return self.floating_views.walk(ctx, f) or self.widgets.walk(ctx, f, &self.widgets_widget) or f(ctx, w); +pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn) bool { + return self.floating_views.walk(ctx, f) or self.widgets.walk(ctx, f) or f(ctx, Widget.to(self)); } fn close_all_editors(self: *Self) !void { @@ -1754,12 +1754,12 @@ fn add_and_activate_view(self: *Self, widget: Widget) !void { _ = try self.widgets_widget.msg(.{"splits_updated"}); } -pub fn find_view_for_widget(self: *Self, w_: *const Widget) ?usize { +pub fn find_view_for_widget(self: *Self, w_: Widget) ?usize { const Ctx = struct { - w: *const Widget, - fn find(ctx_: *anyopaque, w: *Widget) bool { + w: Widget, + fn find(ctx_: *anyopaque, w: Widget) bool { const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_))); - return ctx.w == w; + return ctx.w.ptr == w.ptr; } }; var ctx: Ctx = .{ .w = w_ }; @@ -1768,7 +1768,7 @@ pub fn find_view_for_widget(self: *Self, w_: *const Widget) ?usize { return null; } -pub fn focus_view_by_widget(self: *Self, w: *const Widget) tui.FocusAction { +pub fn focus_view_by_widget(self: *Self, w: Widget) tui.FocusAction { const n = self.find_view_for_widget(w) orelse return .notfound; if (n >= self.views.widgets.items.len) return .notfound; if (n == self.active_view) return .same; diff --git a/src/tui/status/tabs.zig b/src/tui/status/tabs.zig index 2e3f320e..2d76eaf7 100644 --- a/src/tui/status/tabs.zig +++ b/src/tui/status/tabs.zig @@ -286,11 +286,11 @@ pub const TabBar = struct { self.plane = self.splits_list.plane; } - pub fn get(self: *const Self, name: []const u8) ?*const Widget { + pub fn get(self: *const Self, name: []const u8) ?Widget { return self.splits_list_widget.get(name); } - pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn, self_w: *Widget) bool { + pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn) bool { for (self.tabs) |*tab| { const clipped, _ = self.is_tab_clipped(tab); if (!clipped) @@ -300,14 +300,14 @@ pub const TabBar = struct { for (split.widgets.items) |*widget_state| if (widget_state.widget.dynamic_cast(drop_target.ButtonType)) |_| { if (widget_state.widget.walk(ctx, f)) return true; }; - return f(ctx, self_w); + return f(ctx, Widget.to(self)); } pub fn hover(self: *Self) bool { return self.splits_list_widget.hover(); } - fn update_tabs(self: *Self, drag_source: ?*Widget) !void { + fn update_tabs(self: *Self, drag_source: ?Widget) !bool { const buffers_changed = try self.update_tab_buffers(); const dragging = for (self.tabs) |*tab| { if (tab.widget.dynamic_cast(Tab.ButtonType)) |btn| diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 4ba86c09..408ff074 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -54,12 +54,12 @@ input_mode_outer_: ?Mode = null, input_listeners_: EventHandler.List, keyboard_focus: ?Widget = null, mini_mode_: ?MiniMode = null, -hover_focus: ?*Widget = null, +hover_focus: ?Widget = null, last_hover_x: c_int = -1, last_hover_y: c_int = -1, commands: Commands = undefined, logger: log.Logger, -drag_source: ?*Widget = null, +drag_source: ?Widget = null, drag_button: input.MouseType = 0, dark_theme: Widget.Theme, dark_parsed_theme: ?std.json.Parsed(Widget.Theme), @@ -787,12 +787,12 @@ fn handle_system_clipboard(self: *Self, text: []const u8) !void { return command.executeName("paste", command.fmt(.{text})); } -fn find_coord_widget(self: *Self, y: usize, x: usize) ?*Widget { +fn find_coord_widget(self: *Self, y: usize, x: usize) ?Widget { const Ctx = struct { - widget: ?*Widget = null, + widget: ?Widget = null, y: usize, x: usize, - fn find(ctx_: *anyopaque, w: *Widget) bool { + fn find(ctx_: *anyopaque, w: Widget) bool { const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_))); if (w.box().is_abs_coord_inside(ctx.y, ctx.x)) { ctx.widget = w; @@ -810,12 +810,12 @@ pub fn is_abs_coord_in_widget(w: *const Widget, y: usize, x: usize) bool { return w.box().is_abs_coord_inside(y, x); } -fn is_live_widget_ptr(self: *Self, w_: *Widget) bool { +fn is_live_widget_ptr(self: *Self, w_: Widget) bool { const Ctx = struct { - w: *Widget, - fn find(ctx_: *anyopaque, w: *Widget) bool { + w: Widget, + fn find(ctx_: *anyopaque, w: Widget) bool { const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_))); - return ctx.w == w; + return ctx.w.ptr == w.ptr; } }; var ctx: Ctx = .{ .w = w_ }; @@ -824,7 +824,7 @@ fn is_live_widget_ptr(self: *Self, w_: *Widget) bool { pub const FocusAction = enum { same, changed, notfound }; -pub fn set_focus_by_widget(w: *const Widget) FocusAction { +pub fn set_focus_by_widget(w: Widget) FocusAction { const mv = mainview() orelse return .notfound; return mv.focus_view_by_widget(w); } @@ -869,12 +869,12 @@ fn send_mouse_drag(self: *Self, y: c_int, x: c_int, from: tp.pid_ref, m: tp.mess if (self.drag_source) |w| _ = try w.send(from, m); } -fn update_hover(self: *Self, y: c_int, x: c_int) !?*Widget { +fn update_hover(self: *Self, y: c_int, x: c_int) !?Widget { self.last_hover_y = y; self.last_hover_x = x; if (y >= 0 and x >= 0) if (self.find_coord_widget(@intCast(y), @intCast(x))) |w| { - if (if (self.hover_focus) |h| h != w else true) { - tp.trace(tp.channel.debug, .{ "update_hover", if (self.hover_focus) |h| @intFromPtr(h) else 0, @intFromPtr(w) }); + if (if (self.hover_focus) |h| h.ptr != w.ptr else true) { + tp.trace(tp.channel.debug, .{ "update_hover", if (self.hover_focus) |h| @as(u64, @intFromPtr(h.ptr)) else 0, @as(u64, @intFromPtr(w.ptr)) }); if (self.hover_focus) |h| if (self.is_live_widget_ptr(h)) try send_hover_msg(h, false); self.hover_focus = w; @@ -889,19 +889,19 @@ fn update_hover(self: *Self, y: c_int, x: c_int) !?*Widget { fn clear_hover_focus(self: *Self, src: std.builtin.SourceLocation) tp.result { if (self.hover_focus) |h| if (self.is_live_widget_ptr(h)) try send_hover_msg(h, false); - tp.trace(tp.channel.debug, .{ "tui", "clear_hover_focus", if (self.hover_focus) |h| @intFromPtr(h) else 0, src.fn_name, src.file, src.line }); + tp.trace(tp.channel.debug, .{ "tui", "clear_hover_focus", if (self.hover_focus) |h| @intFromPtr(h.ptr) else 0, src.fn_name, src.file, src.line }); self.hover_focus = null; } -fn send_hover_msg(widget: *const Widget, hover: bool) tp.result { +fn send_hover_msg(widget: Widget, hover: bool) tp.result { var buf: [256]u8 = undefined; - tp.trace(tp.channel.debug, .{ "hover_msg", @intFromPtr(widget), hover }); + tp.trace(tp.channel.debug, .{ "hover_msg", @intFromPtr(widget.ptr), hover }); _ = try widget.send(tp.self_pid(), tp.message.fmtbuf(&buf, .{ "H", hover }) catch |e| return tp.exit_error(e, @errorReturnTrace())); } pub fn refresh_hover(src: std.builtin.SourceLocation) void { const self = current(); - tp.trace(tp.channel.debug, .{ "tui", "refresh_hover", if (self.hover_focus) |h| @intFromPtr(h) else 0, src.fn_name, src.file, src.line }); + tp.trace(tp.channel.debug, .{ "tui", "refresh_hover", if (self.hover_focus) |h| @intFromPtr(h.ptr) else 0, src.fn_name, src.file, src.line }); _ = self.update_hover(self.last_hover_y, self.last_hover_x) catch {}; } @@ -1906,18 +1906,18 @@ pub fn get_keybind_mode() ?Mode { return self.input_mode_ orelse self.delayed_init_input_mode; } -pub fn update_drag_source(drag_source: *Widget, btn: input.MouseType) void { +pub fn update_drag_source(drag_source: Widget, btn: input.MouseType) void { const self = current(); self.drag_source = drag_source; self.drag_button = btn; } -fn set_drag_source(self: *Self, drag_source: ?*Widget, btn: input.MouseType) void { +fn set_drag_source(self: *Self, drag_source: ?Widget, btn: input.MouseType) void { self.drag_source = drag_source; self.drag_button = btn; } -pub fn get_drag_source() struct { ?*Widget, input.MouseType } { +pub fn get_drag_source() struct { ?Widget, input.MouseType } { const self = current(); return .{ self.drag_source, self.drag_button }; }