From 2d2d8a915b6a5ca9e535365d32db3394a5e1a1f9 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 3 Feb 2026 21:31:47 +0100 Subject: [PATCH 1/3] refactor: make tabs.find_next/previous_tab_buffer also return view --- src/tui/status/tabs.zig | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/tui/status/tabs.zig b/src/tui/status/tabs.zig index 65f1ce0..a15444a 100644 --- a/src/tui/status/tabs.zig +++ b/src/tui/status/tabs.zig @@ -422,45 +422,50 @@ pub const TabBar = struct { return last; } - fn find_next_tab_buffer(self: *Self) ?Buffer.Ref { + fn find_next_tab_buffer(self: *Self) struct { ?Buffer.Ref, usize } { var found_active: bool = false; for (self.widget_list.widgets.items) |*split_widget| if (split_widget.widget.dynamic_cast(WidgetList)) |split| for (split.widgets.items) |*widget_state| if (widget_state.widget.dynamic_cast(Tab.ButtonType)) |btn| { if (found_active) - return btn.opts.ctx.buffer_ref; + return .{ btn.opts.ctx.buffer_ref, btn.opts.ctx.view }; if (btn.opts.ctx.buffer_ref == self.active_focused_buffer_ref) found_active = true; }; - return null; + return .{ null, 0 }; } - fn find_previous_tab_buffer(self: *Self) ?Buffer.Ref { + fn find_previous_tab_buffer(self: *Self) struct { ?Buffer.Ref, usize } { var previous: ?Buffer.Ref = null; + var previous_view: usize = 0; for (self.widget_list.widgets.items) |*split_widget| if (split_widget.widget.dynamic_cast(WidgetList)) |split| for (split.widgets.items) |*widget_state| if (widget_state.widget.dynamic_cast(Tab.ButtonType)) |btn| { if (btn.opts.ctx.buffer_ref == self.active_focused_buffer_ref) - return previous; + return .{ previous, previous_view }; previous = btn.opts.ctx.buffer_ref; + previous_view = btn.opts.ctx.view; }; - return null; + return .{ null, 0 }; } fn select_next_tab(self: *Self) void { tp.trace(tp.channel.debug, .{"select_next_tab"}); - const buffer_ref = self.find_next_tab_buffer() orelse self.find_first_tab_buffer() orelse return; - navigate_to_buffer(buffer_ref); + const buffer_ref, _ = self.find_next_tab_buffer(); + if (buffer_ref) |ref| return navigate_to_buffer(ref); + if (self.find_first_tab_buffer()) |ref| return navigate_to_buffer(ref); } fn select_previous_tab(self: *Self) void { tp.trace(tp.channel.debug, .{"select_previous_tab"}); - const buffer_ref = self.find_previous_tab_buffer() orelse self.find_last_tab_buffer() orelse return; - navigate_to_buffer(buffer_ref); + const buffer_ref, _ = self.find_previous_tab_buffer(); + if (buffer_ref) |ref| return navigate_to_buffer(ref); + if (self.find_last_tab_buffer()) |ref| return navigate_to_buffer(ref); } fn move_tab_next(self: *Self) void { tp.trace(tp.channel.debug, .{"move_tab_next"}); const this_idx = self.find_buffer_tab(self.active_focused_buffer_ref orelse return) orelse return; - const other_buffer_ref = self.find_next_tab_buffer() orelse return self.move_tab_to_new_split(this_idx); + const other_buffer_ref_, _ = self.find_next_tab_buffer(); + const other_buffer_ref = other_buffer_ref_ orelse return self.move_tab_to_new_split(this_idx); const other_idx = self.find_buffer_tab(other_buffer_ref) orelse return; self.move_tab_to(other_idx, this_idx); } @@ -468,7 +473,8 @@ pub const TabBar = struct { fn move_tab_previous(self: *Self) void { tp.trace(tp.channel.debug, .{"move_tab_previous"}); const this_idx = self.find_buffer_tab(self.active_focused_buffer_ref orelse return) orelse return; - const other_buffer_ref = self.find_previous_tab_buffer() orelse return; + const other_buffer_ref_, _ = self.find_previous_tab_buffer(); + const other_buffer_ref = other_buffer_ref_ orelse return; const other_idx = self.find_buffer_tab(other_buffer_ref) orelse return; self.move_tab_to(other_idx, this_idx); } From cc2aabf7dd46f61c8dff5cd5b12631762a99dc80 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 4 Feb 2026 09:53:18 +0100 Subject: [PATCH 2/3] feat: allow moving tabs to empty splits --- src/tui/mainview.zig | 2 +- src/tui/status/tabs.zig | 42 ++++++++++++++++++++++++----------------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index 12a45fd..f444d10 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -1792,7 +1792,7 @@ fn remove_view(self: *Self, view: usize) void { const buffers = self.buffer_manager.list_unordered(self.allocator) catch @panic("OOM remove_view"); defer self.allocator.free(buffers); for (buffers) |buffer| if (buffer.get_last_view()) |buffer_view| - if (buffer_view >= view) + if (buffer_view > view) buffer.set_last_view(buffer_view - 1); } diff --git a/src/tui/status/tabs.zig b/src/tui/status/tabs.zig index a15444a..00fcd91 100644 --- a/src/tui/status/tabs.zig +++ b/src/tui/status/tabs.zig @@ -202,6 +202,7 @@ pub const TabBar = struct { } else if (try m.match(.{ "E", "close" })) { self.refresh_active_buffer(); } else if (try m.match(.{"splits_updated"})) { + self.refresh_active_buffer(); const drag_source, _ = tui.get_drag_source(); self.update_tab_widgets(drag_source) catch {}; } @@ -399,11 +400,11 @@ pub const TabBar = struct { return drop_target.create(self, view); } - fn find_buffer_tab(self: *Self, buffer_ref: Buffer.Ref) ?usize { + fn find_buffer_tab(self: *Self, buffer_ref: Buffer.Ref) struct { ?usize, usize } { for (self.tabs, 0..) |*tab, idx| if (tab.widget.dynamic_cast(Tab.ButtonType)) |btn| - if (btn.opts.ctx.buffer_ref == buffer_ref) return idx; - return null; + if (btn.opts.ctx.buffer_ref == buffer_ref) return .{ idx, btn.opts.ctx.view }; + return .{ null, 0 }; } fn find_first_tab_buffer(self: *Self) ?Buffer.Ref { @@ -463,20 +464,24 @@ pub const TabBar = struct { fn move_tab_next(self: *Self) void { tp.trace(tp.channel.debug, .{"move_tab_next"}); - const this_idx = self.find_buffer_tab(self.active_focused_buffer_ref orelse return) orelse return; - const other_buffer_ref_, _ = self.find_next_tab_buffer(); - const other_buffer_ref = other_buffer_ref_ orelse return self.move_tab_to_new_split(this_idx); - const other_idx = self.find_buffer_tab(other_buffer_ref) orelse return; - self.move_tab_to(other_idx, this_idx); + const this_idx_, const this_view = self.find_buffer_tab(self.active_focused_buffer_ref orelse return); + const this_idx = this_idx_ orelse return; + const other_buffer_ref_, const other_view = self.find_next_tab_buffer(); + const other_buffer_ref = other_buffer_ref_ orelse return self.move_tab_to_new_split(this_idx, this_view); + if (other_view -| this_view > 1) return self.move_tab_to_view(this_view + 1, this_idx); + const other_idx, _ = self.find_buffer_tab(other_buffer_ref); + if (other_idx) |idx| self.move_tab_to(idx, this_idx); } fn move_tab_previous(self: *Self) void { tp.trace(tp.channel.debug, .{"move_tab_previous"}); - const this_idx = self.find_buffer_tab(self.active_focused_buffer_ref orelse return) orelse return; - const other_buffer_ref_, _ = self.find_previous_tab_buffer(); + const this_idx_, const this_view = self.find_buffer_tab(self.active_focused_buffer_ref orelse return); + const this_idx = this_idx_ orelse return; + const other_buffer_ref_, const other_view = self.find_previous_tab_buffer(); const other_buffer_ref = other_buffer_ref_ orelse return; - const other_idx = self.find_buffer_tab(other_buffer_ref) orelse return; - self.move_tab_to(other_idx, this_idx); + if (this_view -| other_view > 1) return self.move_tab_to_view(this_view -| 1, this_idx); + const other_idx, _ = self.find_buffer_tab(other_buffer_ref); + if (other_idx) |idx| self.move_tab_to(idx, this_idx); } fn move_tab_to(self: *Self, dst_idx: usize, src_idx: usize) void { @@ -547,18 +552,21 @@ pub const TabBar = struct { navigate_to_buffer(src_tab.buffer_ref); } - fn move_tab_to_new_split(self: *Self, src_idx: usize) void { + fn move_tab_to_new_split(self: *Self, src_idx: usize, src_view: usize) void { const mv = tui.mainview() orelse return; - const src_tab = &self.tabs[src_idx]; var tabs_in_view: usize = 0; for (self.tabs) |*tab| if (tab.view) |view| { - if (view == src_tab.view) + if (view == src_view) tabs_in_view += 1; }; if (tabs_in_view > 1) { const view = mv.get_view_count(); - mv.create_home_split() catch return; - self.move_tab_to_view(view, src_idx); + if (view -| src_view > 1) { + self.move_tab_to_view(src_view + 1, src_idx); + } else { + mv.create_home_split() catch return; + self.move_tab_to_view(view, src_idx); + } } } From 752028944232c4e93d6fbd52114c4fd82e1d92b6 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 4 Feb 2026 09:54:07 +0100 Subject: [PATCH 3/3] fix: always update tab views when splits arrangement has changed closes #485 --- src/tui/mainview.zig | 2 +- src/tui/status/tabs.zig | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index f444d10..5cdf21c 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -1739,6 +1739,7 @@ fn add_and_activate_view(self: *Self, widget: Widget) !void { try self.views.add(widget); self.active_view = self.views.widgets.items.len - 1; if (self.views.get_at(self.active_view)) |view| view.focus(); + _ = try self.widgets_widget.msg(.{"splits_updated"}); } pub fn find_view_for_widget(self: *Self, w_: *const Widget) ?usize { @@ -1864,7 +1865,6 @@ fn create_home(self: *Self) !void { pub fn create_home_split(self: *Self) !void { tui.reset_drag_context(); try self.add_and_activate_view(try home.create(self.allocator, Widget.to(self))); - _ = try self.widgets_widget.msg(.{"splits_updated"}); tui.resize(); } diff --git a/src/tui/status/tabs.zig b/src/tui/status/tabs.zig index 00fcd91..b7bc505 100644 --- a/src/tui/status/tabs.zig +++ b/src/tui/status/tabs.zig @@ -281,6 +281,7 @@ pub const TabBar = struct { const mv = tui.mainview() orelse @panic("tabs no main view"); const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager"); var prev_widget_count: usize = 0; + for (self.widget_list.widgets.items) |*split_widgetstate| if (split_widgetstate.widget.dynamic_cast(WidgetList)) |split| { prev_widget_count += 1; for (split.widgets.items) |_| prev_widget_count += 1; @@ -296,6 +297,10 @@ pub const TabBar = struct { split.deinit(self.widget_list.allocator); }; + for (self.tabs) |*tab| if (buffer_manager.buffer_from_ref(tab.buffer_ref)) |buffer| { + tab.view = buffer.get_last_view() orelse 0; + }; + const views = mv.get_view_count(); var widget_count: usize = 0;