aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2026-07-18 11:15:17 +0200
committerGitHub <noreply@github.com>2026-07-18 11:15:17 +0200
commit68362d4cf0b20682d16647570cc324a770b687bc (patch)
treeea179f8c32379f11587c2951b82538f08ffb0cbc
parentdocs(changelog): stdin parse fix (diff)
downloadsl0ppij-68362d4cf0b20682d16647570cc324a770b687bc.tar.gz
sl0ppij-68362d4cf0b20682d16647570cc324a770b687bc.tar.xz
sl0ppij-68362d4cf0b20682d16647570cc324a770b687bc.zip
Stacked resize list fixes (#5379)
* fix: mouse scroll resize stack issue * regression test * cleanups
-rw-r--r--zellij-server/src/tab/mouse_handler.rs4
-rw-r--r--zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__ctrl_scroll_up_merging_stacks_preserves_all_panes.snap45
-rw-r--r--zellij-server/src/tab/unit/tab_integration_tests.rs251
3 files changed, 300 insertions, 0 deletions
diff --git a/zellij-server/src/tab/mouse_handler.rs b/zellij-server/src/tab/mouse_handler.rs
index 51ff2cca..ca2a0804 100644
--- a/zellij-server/src/tab/mouse_handler.rs
+++ b/zellij-server/src/tab/mouse_handler.rs
@@ -1679,8 +1679,10 @@ impl MouseHandler {
.get_active_pane_id(client_id)
.ok_or_else(|| anyhow!("Failed to find active pane"))?;
+ tab.dissolve_stack_lists_for_classic_mutation();
Self::resize_tiled_pane_with_stacked_resize(tab, active_pane_id, &strategy)
.with_context(err_context)?;
+ tab.tiled_panes.reapply_pane_frames();
tab.swap_layouts.set_is_tiled_damaged();
}
@@ -1712,8 +1714,10 @@ impl MouseHandler {
.get_active_pane_id(client_id)
.ok_or_else(|| anyhow!("Failed to find active pane"))?;
+ tab.dissolve_stack_lists_for_classic_mutation();
Self::resize_tiled_pane_with_stacked_resize(tab, active_pane_id, &strategy)
.with_context(err_context)?;
+ tab.tiled_panes.reapply_pane_frames();
tab.swap_layouts.set_is_tiled_damaged();
}
diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__ctrl_scroll_up_merging_stacks_preserves_all_panes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__ctrl_scroll_up_merging_stacks_preserves_all_panes.snap
new file mode 100644
index 00000000..e9214f5c
--- /dev/null
+++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__ctrl_scroll_up_merging_stacks_preserves_all_panes.snap
@@ -0,0 +1,45 @@
+---
+source: zellij-server/src/tab/./unit/tab_integration_tests.rs
+expression: snapshot
+---
+00 (C): > │ Pane #1 │
+01 (C): │ Pane #3 │
+02 (C): │ Pane #4 │
+03 (C):
+04 (C): ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
+05 (C): │ │
+06 (C): │ │
+07 (C): │ │
+08 (C): │ │
+09 (C): │ │
+10 (C): │ │
+11 (C): │ │
+12 (C): │ │
+13 (C): │ │
+14 (C): │ │
+15 (C): │ │
+16 (C): │ │
+17 (C): │ │
+18 (C): │ │
+19 (C): │ │
+20 (C): │ │
+21 (C): └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
+22 (C): │ Pane #2 │
+23 (C): │ Pane #5 │
+24 (C): > │ Pane #6 │
+25 (C):
+26 (C): ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
+27 (C): │ │
+28 (C): │ │
+29 (C): │ │
+30 (C): │ │
+31 (C): │ │
+32 (C): │ │
+33 (C): │ │
+34 (C): │ │
+35 (C): │ │
+36 (C): │ │
+37 (C): │ │
+38 (C): │ │
+39 (C): └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
+
diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs
index 46ab7950..e5de0b58 100644
--- a/zellij-server/src/tab/unit/tab_integration_tests.rs
+++ b/zellij-server/src/tab/unit/tab_integration_tests.rs
@@ -296,6 +296,105 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab {
tab
}
+fn create_new_tab_with_stacked_pane_list(
+ size: Size,
+ default_mode: ModeInfo,
+ stacked_pane_list: bool,
+ base_layout_and_ids: Option<(TiledPaneLayout, Vec<(u32, Option<RunCommand>)>)>,
+) -> Tab {
+ set_session_name("test".into());
+ let index = 0;
+ let position = 0;
+ let name = String::new();
+ let os_api = Box::new(FakeInputOutput::default());
+ let senders = ThreadSenders::default().silently_fail_on_send();
+ let max_panes = None;
+ let mode_info = default_mode;
+ let style = Style::default();
+ let draw_pane_frames = PaneFrameStyle::Full;
+ let auto_layout = true;
+ let client_id = 1;
+ let session_is_mirrored = true;
+ let mut connected_clients = HashMap::new();
+ connected_clients.insert(client_id, false);
+ let connected_clients = Rc::new(RefCell::new(connected_clients));
+ let character_cell_info = Rc::new(RefCell::new(None));
+ let stacked_resize = Rc::new(RefCell::new(true));
+ let stacked_pane_list = Rc::new(RefCell::new(stacked_pane_list));
+ let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
+ let copy_options = CopyOptions::default();
+ let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
+ let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
+ let current_group = Rc::new(RefCell::new(PaneGroups::new(ThreadSenders::default())));
+ let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
+ let debug = false;
+ let arrow_fonts = true;
+ let styled_underlines = true;
+ let osc8_hyperlinks = true;
+ let explicitly_disable_kitty_keyboard_protocol = false;
+ let advanced_mouse_actions = true;
+ let mouse_scroll_resize = true;
+ let web_sharing = WebSharing::Off;
+ let web_server_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
+ let web_server_port = 8080;
+ let mut tab = Tab::new(
+ index,
+ position,
+ name,
+ size,
+ character_cell_info,
+ stacked_resize,
+ stacked_pane_list,
+ sixel_image_store,
+ os_api,
+ senders,
+ max_panes,
+ style,
+ mode_info,
+ draw_pane_frames,
+ auto_layout,
+ connected_clients,
+ session_is_mirrored,
+ Some(client_id),
+ copy_options,
+ terminal_emulator_colors,
+ terminal_emulator_color_codes,
+ (vec![], vec![]),
+ PathBuf::from("my_default_shell"),
+ debug,
+ arrow_fonts,
+ styled_underlines,
+ osc8_hyperlinks,
+ explicitly_disable_kitty_keyboard_protocol,
+ None,
+ false,
+ web_sharing,
+ current_group,
+ currently_marking_pane_group,
+ advanced_mouse_actions,
+ mouse_scroll_resize,
+ true,
+ false,
+ false,
+ web_server_ip,
+ web_server_port,
+ 0,
+ );
+ let (base_layout, new_terminal_ids) =
+ base_layout_and_ids.unwrap_or_else(|| (TiledPaneLayout::default(), vec![(1, None)]));
+ tab.apply_layout(
+ base_layout,
+ vec![],
+ new_terminal_ids,
+ vec![],
+ HashMap::new(),
+ client_id,
+ None,
+ )
+ .unwrap();
+ tab
+}
+
fn create_new_tab_without_pane_frames(size: Size, default_mode: ModeInfo) -> Tab {
set_session_name("test".into());
let index = 0;
@@ -12630,6 +12729,158 @@ fn test_ctrl_scroll_down_does_not_resize_pane_when_mouse_scroll_resize_disabled(
assert_eq!(pane_geom_before, pane_geom_after);
}
+fn two_adjacent_stacks_layout() -> (TiledPaneLayout, Vec<(u32, Option<RunCommand>)>) {
+ let base_layout = r#"
+ layout {
+ pane split_direction="horizontal" {
+ pane stacked=true {
+ pane focus=true
+ pane
+ pane
+ }
+ pane stacked=true {
+ pane
+ pane
+ pane
+ }
+ }
+ }
+ "#;
+ let (base_layout, _base_floating_layout) =
+ Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
+ .unwrap()
+ .template
+ .unwrap();
+ let new_terminal_ids = vec![
+ (1, None),
+ (2, None),
+ (3, None),
+ (4, None),
+ (5, None),
+ (6, None),
+ ];
+ (base_layout, new_terminal_ids)
+}
+
+fn stack_member_pane_count(tab: &Tab) -> usize {
+ tab.get_all_pane_ids()
+ .into_iter()
+ .filter(|id| tab.pane_is_stack_list_member(id))
+ .count()
+}
+
+#[test]
+fn test_ctrl_scroll_up_merging_stacks_preserves_all_panes() {
+ let size = Size {
+ cols: 150,
+ rows: 40,
+ };
+ let client_id = 1;
+ let mut output = Output::default();
+ let mut tab = create_new_tab_with_stacked_pane_list(
+ size,
+ ModeInfo::default(),
+ true,
+ Some(two_adjacent_stacks_layout()),
+ );
+
+ tab.render(&mut output, None).unwrap();
+
+ let mut pane_ids_before = tab.get_all_pane_ids();
+ pane_ids_before.sort();
+ assert_eq!(
+ pane_ids_before.len(),
+ 6,
+ "all six panes exist before the merge"
+ );
+ assert_eq!(stack_member_pane_count(&tab), 6);
+
+ let active_pane_position = Position::new(5, 40);
+ tab.handle_mouse_event(
+ &MouseEvent::new_ctrl_scroll_up_event(active_pane_position),
+ client_id,
+ )
+ .unwrap();
+
+ let dissolved_into_grid = !tab.has_stack_lists();
+ assert!(
+ dissolved_into_grid,
+ "stack lists must be dissolved into in-grid stacks before the classic merge mutation"
+ );
+ let mut in_grid_ids = tab.get_static_and_floating_pane_ids();
+ in_grid_ids.sort();
+ assert_eq!(
+ in_grid_ids, pane_ids_before,
+ "all panes must be present in the grid after the merge, none orphaned"
+ );
+
+ let mut pane_ids_after = tab.get_all_pane_ids();
+ pane_ids_after.sort();
+ assert_eq!(
+ pane_ids_before, pane_ids_after,
+ "no panes are lost when merging stacks via ctrl+scroll resize"
+ );
+
+ let mut output = Output::default();
+ tab.render(&mut output, None).unwrap();
+ let mut pane_ids_final = tab.get_all_pane_ids();
+ pane_ids_final.sort();
+ assert_eq!(pane_ids_before, pane_ids_final);
+ let snapshot = take_snapshot(
+ output.serialize().unwrap().get(&client_id).unwrap(),
+ size.rows,
+ size.cols,
+ Palette::default(),
+ );
+ assert_snapshot!(snapshot);
+}
+
+#[test]
+fn test_ctrl_scroll_down_in_stack_dissolves_stack_lists_before_mutation() {
+ let size = Size {
+ cols: 150,
+ rows: 40,
+ };
+ let client_id = 1;
+ let mut output = Output::default();
+ let mut tab = create_new_tab_with_stacked_pane_list(
+ size,
+ ModeInfo::default(),
+ true,
+ Some(two_adjacent_stacks_layout()),
+ );
+
+ tab.render(&mut output, None).unwrap();
+
+ let mut pane_ids_before = tab.get_all_pane_ids();
+ pane_ids_before.sort();
+ assert_eq!(pane_ids_before.len(), 6);
+ assert_eq!(stack_member_pane_count(&tab), 6);
+
+ tab.handle_mouse_event(
+ &MouseEvent::new_ctrl_scroll_down_event(Position::new(5, 40)),
+ client_id,
+ )
+ .unwrap();
+
+ assert!(
+ !tab.has_stack_lists(),
+ "stack lists must be dissolved into in-grid stacks before the classic merge mutation"
+ );
+ let mut in_grid_ids = tab.get_static_and_floating_pane_ids();
+ in_grid_ids.sort();
+ assert_eq!(
+ in_grid_ids, pane_ids_before,
+ "all panes must be present in the grid after the resize, none orphaned"
+ );
+
+ let mut output = Output::default();
+ tab.render(&mut output, None).unwrap();
+ let mut pane_ids_final = tab.get_all_pane_ids();
+ pane_ids_final.sort();
+ assert_eq!(pane_ids_before, pane_ids_final);
+}
+
#[test]
fn resize_hint_text_tracks_mouse_scroll_resize_updates() {
let size = Size {