diff options
| author | Aram Drevekenin <aram@poor.dev> | 2025-12-23 13:41:56 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-23 13:41:56 +0100 |
| commit | 3fe48a972c55537502128779116d38d8f8aedb7e (patch) | |
| tree | 350386442db2a46e1d8ba3d9d4dd0c7d53b34c13 | |
| parent | Read only web tokens (#4551) (diff) | |
| download | sl0ppij-3fe48a972c55537502128779116d38d8f8aedb7e.tar.gz sl0ppij-3fe48a972c55537502128779116d38d8f8aedb7e.tar.xz sl0ppij-3fe48a972c55537502128779116d38d8f8aedb7e.zip | |
Feature: allow overriding a layout for a currently running tab (change layout at runtime) (#4566)
* add tests for LayoutApplier
* initial untested implementation of override functions
* add override-layout cli action with stub in screen
* filter run instructions to ignore
* implement override-layout roundtrip through plugin and pty threads
* initial full-cycle working override implementation
* fix some bugs (eg. actually start new terminals)
* fix some bugs with focus after override
* fix floating pane matching bug in layout applier
* formatting
* use first tab, error on more than 1 tab
* fix some floating_panes ordering and shell issues
* get new tab name to work
* respect swap layouts
* allow specifying tab properties on main layout node if we just have one tab
* add tests for new override methods
* start work on retaining panes, hard-coded things, kind of working but broken in many places
* fixed most tiled pane override bugs
* reset viewport before reapplying
* revert constraint system changes
* retain-existing-terminal-panes flag
* add retain terminal panes tests
* add retain plugin panes flag
* add keybind
* add retain plugin panes tests
* add override-layout e2e test
* refactor: layout_applier
* style: rustfmt
* some cleanups
* fix cli flags
* no crashy
* formatting and artifacts
* moar cleanups
* fix: respect expanded in stack when overriding
* docs(changelog): add PR
112 files changed, 11726 insertions, 177 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index acc3317f..17577560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * feat: improve cli interface (add detach, switch-session and more) (https://github.com/zellij-org/zellij/pull/4492) * feat: command sequences, conditionally blocking CLI commands, new plugin APIs (https://github.com/zellij-org/zellij/pull/4546) * feat: read-only web authentication-tokens (https://github.com/zellij-org/zellij/pull/4551) +* feat: allow overriding layouts at runtime (https://github.com/zellij-org/zellij/pull/4566) ## [0.43.1] - 2025-08-08 * fix: pane rename backspace regression (https://github.com/zellij-org/zellij/pull/4346) diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index eab81039..f7d9ea0d 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -2646,6 +2646,7 @@ pub fn pin_floating_panes() { let last_snapshot = account_for_races_in_snapshot(last_snapshot); assert_snapshot!(last_snapshot); } + #[test] #[ignore] pub fn watcher_client_functionality() { @@ -2849,3 +2850,51 @@ pub fn watcher_client_functionality() { assert_snapshot!(main_client_snapshot); assert_snapshot!(watcher_snapshot); } + +#[test] +#[ignore] +pub fn override_layout_from_default_to_compact() { + let fake_win_size = Size { + cols: 120, + rows: 24, + }; + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + + let mut runner = RemoteRunner::new(fake_win_size) + .add_step(Step { + name: "Wait for default layout to load", + instruction: |remote_terminal: RemoteTerminal| -> bool { + remote_terminal.status_bar_appears() + }, + }) + .add_step(Step { + name: "Override to compact layout", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + remote_terminal.run_zellij_action("override-layout compact"); + std::thread::sleep(std::time::Duration::from_millis(500)); + remote_terminal.send_key(&ENTER); + true + }, + }); + + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { + name: "Wait for compact layout to appear", + instruction: |remote_terminal: RemoteTerminal| -> bool { + remote_terminal.snapshot_contains("NORMAL") + }, + }); + + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; + + let last_snapshot = account_for_races_in_snapshot(last_snapshot); + assert_snapshot!(last_snapshot); +} diff --git a/src/tests/e2e/remote_runner.rs b/src/tests/e2e/remote_runner.rs index 377869f9..d4663ac5 100644 --- a/src/tests/e2e/remote_runner.rs +++ b/src/tests/e2e/remote_runner.rs @@ -419,6 +419,19 @@ impl RemoteTerminal { channel.flush().unwrap(); std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN } + pub fn run_zellij_action(&mut self, action_and_arguments: &str) { + let mut channel = self.channel.lock().unwrap(); + channel + .write_all( + format!( + "{} action {}", + ZELLIJ_EXECUTABLE_LOCATION, action_and_arguments + ) + .as_bytes(), + ) + .unwrap(); + channel.flush().unwrap(); + } pub fn send_command_through_the_cli(&mut self, command: &str) { let mut channel = self.channel.lock().unwrap(); channel diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__override_layout_from_default_to_compact.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__override_layout_from_default_to_compact.snap new file mode 100644 index 00000000..48724fb5 --- /dev/null +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__override_layout_from_default_to_compact.snap @@ -0,0 +1,28 @@ +--- +source: src/tests/e2e/cases.rs +expression: last_snapshot +--- +┌ Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│$ /usr/src/zellij/x86_64-unknown-linux-musl/release/zellij action override-layout compact │ +│$ │ +│$ █ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + Zellij (e2e-test) NORMAL Tab #1 diff --git a/zellij-client/src/web_client/server_listener.rs b/zellij-client/src/web_client/server_listener.rs index bd4979d1..daad1233 100644 --- a/zellij-client/src/web_client/server_listener.rs +++ b/zellij-client/src/web_client/server_listener.rs @@ -15,7 +15,7 @@ use zellij_utils::{ data::Style, input::{config::Config, options::Options}, ipc::{ClientToServerMsg, ExitReason, ServerToClientMsg}, - sessions::{generate_unique_session_name, session_exists}, + sessions::generate_unique_session_name, setup::Setup, }; diff --git a/zellij-client/src/web_client/session_management.rs b/zellij-client/src/web_client/session_management.rs index e42009ff..d3518b53 100644 --- a/zellij-client/src/web_client/session_management.rs +++ b/zellij-client/src/web_client/session_management.rs @@ -141,54 +141,3 @@ pub fn create_ipc_pipe(session_name: &str) -> PathBuf { }; zellij_ipc_pipe } - -fn ipc_pipe_and_first_message_for_existing_session( - session_name: &str, - client_attributes: ClientAttributes, - config_file_path: Option<PathBuf>, - mut config_opts: Options, - is_read_only: bool, -) -> (ClientToServerMsg, PathBuf) { - let zellij_ipc_pipe: PathBuf = { - let mut sock_dir = zellij_utils::consts::ZELLIJ_SOCK_DIR.clone(); - fs::create_dir_all(&sock_dir).unwrap(); - zellij_utils::shared::set_permissions(&sock_dir, 0o700).unwrap(); - sock_dir.push(session_name); - sock_dir - }; - - config_opts.web_server = Some(true); - config_opts.web_sharing = Some(WebSharing::On); - - let is_web_client = true; - let first_message = if is_read_only { - // read only clients attach as watchers - ClientToServerMsg::AttachWatcherClient { - terminal_size: client_attributes.size, - is_web_client, - } - } else { - let cli_assets = CliAssets { - config_file_path, - config_dir: None, - should_ignore_config: false, - configuration_options: Some(config_opts), - layout: None, - terminal_window_size: client_attributes.size, - data_dir: None, - is_debug: false, - max_panes: None, - force_run_layout_commands: false, - cwd: None, - }; - - ClientToServerMsg::AttachClient { - cli_assets, - tab_position_to_focus: None, - pane_to_focus: None, - is_web_client, - } - }; - - (first_message, zellij_ipc_pipe) -} diff --git a/zellij-client/src/web_client/types.rs b/zellij-client/src/web_client/types.rs index 2994653a..7079973c 100644 --- a/zellij-client/src/web_client/types.rs +++ b/zellij-client/src/web_client/types.rs @@ -9,9 +9,8 @@ use crate::os_input_output::ClientOsApi; use crate::web_client::session_management::spawn_new_session; use std::path::PathBuf; use zellij_utils::{ - data::LayoutInfo, input::{config::Config, options::Options}, - ipc::{ClientAttributes, ClientToServerMsg}, + ipc::ClientToServerMsg, }; pub trait ClientOsApiFactory: Send + Sync + std::fmt::Debug { diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs index d347dcd0..a52c78e7 100644 --- a/zellij-server/src/panes/floating_panes/mod.rs +++ b/zellij-server/src/panes/floating_panes/mod.rs @@ -264,6 +264,18 @@ impl FloatingPanes { ); floating_pane_grid.find_room_for_new_pane() } + pub fn move_client_focus_to_existing_panes(&mut self) { + let existing_pane_ids: Vec<PaneId> = self.panes.keys().copied().collect(); + let nonexisting_panes_that_are_focused = self + .active_panes + .values() + .filter(|pane_id| !existing_pane_ids.contains(pane_id)) + .copied() + .collect::<Vec<_>>(); + for pane_id in nonexisting_panes_that_are_focused { + self.move_clients_out_of_pane(pane_id); + } + } pub fn position_floating_pane_layout( &mut self, floating_pane_layout: &FloatingPaneLayout, diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs index 4df68db4..a658f801 100644 --- a/zellij-server/src/panes/tiled_panes/mod.rs +++ b/zellij-server/src/panes/tiled_panes/mod.rs @@ -178,6 +178,13 @@ impl TiledPanes { let should_relayout = true; self.add_pane(pane_id, pane, should_relayout, client_id); } + pub fn set_pane_logical_position(&mut self, pane_id: PaneId, logical_position: usize) { + if let Some(pane) = self.panes.get_mut(&pane_id) { + let mut position_and_size = pane.position_and_size(); + position_and_size.logical_position = Some(logical_position); + pane.set_geom(position_and_size); + } + } pub fn insert_pane_without_relayout( &mut self, pane_id: PaneId, @@ -1220,8 +1227,12 @@ impl TiledPanes { let display_area = self.display_area.borrow(); new_screen_size.rows != display_area.rows || new_screen_size.cols != display_area.cols } + pub fn force_resize(&mut self) { + let display_area = self.display_area.borrow().clone(); + self.resize(display_area); + } + pub fn resize(&mut self, new_screen_size: Size) { - // this is blocked out to appease the borrow checker { if self.display_area_changed(new_screen_size) { self.clear_tombstones(); @@ -1235,41 +1246,61 @@ impl TiledPanes { *display_area, *viewport, ); - match pane_grid.layout(SplitDirection::Horizontal, cols) { - Ok(_) => { - let column_difference = cols as isize - display_area.cols as isize; - // FIXME: Should the viewport be an Offset? - viewport.cols = (viewport.cols as isize + column_difference) as usize; - display_area.cols = cols; - }, - Err(e) => match e.downcast_ref::<ZellijError>() { - Some(ZellijError::PaneSizeUnchanged) => {}, // ignore unchanged layout - _ => { - // display area still changed, even if we had an error + + let resize_horizontally = |pane_grid: &mut TiledPaneGrid, + display_area: &mut Size, + viewport: &mut Viewport, + cols: usize| + -> bool { + match pane_grid.layout(SplitDirection::Horizontal, cols) { + Ok(_) => { + let column_difference = cols as isize - display_area.cols as isize; + viewport.cols = (viewport.cols as isize + column_difference) as usize; display_area.cols = cols; - Err::<(), _>(anyError::msg(e)) - .context("failed to resize tab horizontally") - .non_fatal(); + true }, - }, + Err(e) => match e.downcast_ref::<ZellijError>() { + Some(ZellijError::PaneSizeUnchanged) => true, + _ => false, + }, + } }; - match pane_grid.layout(SplitDirection::Vertical, rows) { - Ok(_) => { - let row_difference = rows as isize - display_area.rows as isize; - viewport.rows = (viewport.rows as isize + row_difference) as usize; - display_area.rows = rows; - }, - Err(e) => match e.downcast_ref::<ZellijError>() { - Some(ZellijError::PaneSizeUnchanged) => {}, // ignore unchanged layout - _ => { - // display area still changed, even if we had an error + + let resize_vertically = |pane_grid: &mut TiledPaneGrid, + display_area: &mut Size, + viewport: &mut Viewport, + rows: usize| + -> bool { + match pane_grid.layout(SplitDirection::Vertical, rows) { + Ok(_) => { + let row_difference = rows as isize - display_area.rows as isize; + viewport.rows = (viewport.rows as isize + row_difference) as usize; display_area.rows = rows; - Err::<(), _>(anyError::msg(e)) - .context("failed to resize tab vertically") - .non_fatal(); + true }, - }, + Err(e) => match e.downcast_ref::<ZellijError>() { + Some(ZellijError::PaneSizeUnchanged) => true, + _ => false, + }, + } }; + + let successfully_resized_horizontally = + resize_horizontally(&mut pane_grid, &mut display_area, &mut viewport, cols); + if successfully_resized_horizontally { + resize_vertically(&mut pane_grid, &mut display_area, &mut viewport, rows); + } else { + log::warn!("Failed to resize horizontally, attempting to first resize vertically"); + let successfully_resized_vertically = + resize_vertically(&mut pane_grid, &mut display_area, &mut viewport, rows); + if successfully_resized_vertically { + resize_horizontally(&mut pane_grid, &mut display_area, &mut viewport, cols); + } else { + log::error!("Failed to resize vertically, will not attempt again."); + } + } + display_area.rows = rows; + display_area.cols = cols; } self.set_pane_frames(self.draw_pane_frames); } @@ -2555,6 +2586,7 @@ impl TiledPanes { self.active_panes.focus_all_panes(&mut self.panes); } pub fn drain(&mut self) -> BTreeMap<PaneId, Box<dyn Pane>> { + self.unset_fullscreen(); match self.panes.iter().next().map(|(pid, _p)| *pid) { Some(first_pid) => self.panes.split_off(&first_pid), None => BTreeMap::new(), @@ -2566,6 +2598,18 @@ impl TiledPanes { pub fn set_active_panes(&mut self, active_panes: ActivePanes) { self.active_panes = active_panes; } + pub fn move_client_focus_to_existing_panes(&mut self) { + let existing_pane_ids: Vec<PaneId> = self.panes.keys().copied().collect(); + let nonexisting_panes_that_are_focused = self + .active_panes + .values() + .filter(|pane_id| !existing_pane_ids.contains(pane_id)) + .copied() + .collect::<Vec<_>>(); + for pane_id in nonexisting_panes_that_are_focused { + self.move_clients_out_of_pane(pane_id); + } + } fn move_clients_between_panes(&mut self, from_pane_id: PaneId, to_pane_id: PaneId) { let clients_in_pane: Vec<ClientId> = self .active_panes diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs index 24a1e811..a8f60b30 100644 --- a/zellij-server/src/plugins/mod.rs +++ b/zellij-server/src/plugins/mod.rs @@ -39,7 +39,10 @@ use zellij_utils::{ actions::Action, command::TerminalAction, keybinds::Keybinds, - layout::{FloatingPaneLayout, Layout, Run, RunPlugin, RunPluginOrAlias, TiledPaneLayout}, + layout::{ + FloatingPaneLayout, Layout, Run, RunPlugin, RunPluginOrAlias, SwapFloatingLayout, + SwapTiledLayout, TiledPaneLayout, + }, plugins::PluginAliases, }, ipc::ClientAttributes, @@ -94,6 +97,19 @@ pub enum PluginInstruction { (ClientId, bool), // bool -> is_web_client Option<NotificationEnd>, // completion signal ), + OverrideLayout( + Option<PathBuf>, // cwd + Option<TerminalAction>, // default_shell + TiledPaneLayout, + Vec<FloatingPaneLayout>, + Option<Vec<SwapTiledLayout>>, + Option<Vec<SwapFloatingLayout>>, + bool, // retain_existing_terminal_panes + bool, // retain_existing_plugin_panes + usize, // tab_index + ClientId, + Option<NotificationEnd>, + ), ApplyCachedEvents { plugin_ids: Vec<PluginId>, done_receiving_permissions: bool, @@ -201,6 +217,7 @@ impl From<&PluginInstruction> for PluginContext { PluginInstruction::AddClient(_) => PluginContext::AddClient, PluginInstruction::RemoveClient(_) => PluginContext::RemoveClient, PluginInstruction::NewTab(..) => PluginContext::NewTab, + PluginInstruction::OverrideLayout(..) => PluginContext::OverrideLayout, PluginInstruction::ApplyCachedEvents { .. } => PluginContext::ApplyCachedEvents, PluginInstruction::ApplyCachedWorkerMessages(..) => { PluginContext::ApplyCachedWorkerMessages @@ -538,18 +555,24 @@ pub(crate) fn plugin_thread_main( .get_initial_cwd() .or_else(|| cwd.clone()); let skip_cache = false; - let (plugin_id, _client_id) = wasm_bridge.load_plugin( + match wasm_bridge.load_plugin( &run_plugin, Some(tab_index), size, cwd, skip_cache, Some(client_id), - )?; - plugin_ids - .entry(run_plugin_or_alias.clone()) - .or_default() - .push(plugin_id); + ) { + Ok((plugin_id, _client_id)) => { + plugin_ids + .entry(run_plugin_or_alias.clone()) + .or_default() + .push(plugin_id); + }, + Err(e) => { + log::error!("Failed to load plugin: {}", e); + }, + } } } drop(bus.senders.send_to_pty(PtyInstruction::NewTab( @@ -566,6 +589,96 @@ pub(crate) fn plugin_thread_main( completion_tx, ))); }, + PluginInstruction::OverrideLayout( + cwd, + default_shell, + mut tiled_layout, + mut floating_layouts, + swap_tiled_layouts, + swap_floating_layouts, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + tab_index, + client_id, + completion_tx, + ) => { + // Prefer connected clients to avoid opening plugins in background for CLI clients + let client_id = if wasm_bridge.client_is_connected(&client_id) { + client_id + } else if let Some(first_client_id) = wasm_bridge.get_first_client_id() { + first_client_id + } else { + client_id + }; + + let mut plugin_ids: HashMap<RunPluginOrAlias, Vec<PluginId>> = HashMap::new(); + + // Populate plugin aliases in layouts + tiled_layout.populate_plugin_aliases_in_layout(&plugin_aliases); + floating_layouts.iter_mut().for_each(|f| { + f.run + .as_mut() + .map(|f| f.populate_run_plugin_if_needed(&plugin_aliases)); + }); + + // Extract run instructions from tiled layout + let extracted_run_instructions = tiled_layout.extract_run_instructions(); + + // Extract run instructions from floating layouts (excluding already_running) + let extracted_floating_plugins: Vec<Option<Run>> = floating_layouts + .iter() + .filter(|f| !f.already_running) + .map(|f| f.run.clone()) + .collect(); + + // Combine all run instructions + let mut all_run_instructions = extracted_run_instructions; + all_run_instructions.extend(extracted_floating_plugins); + + // Load plugins for all Run::Plugin instructions + let size = Size::default(); + for run_instruction in all_run_instructions { + if let Some(Run::Plugin(run_plugin_or_alias)) = run_instruction { + let run_plugin = run_plugin_or_alias.get_run_plugin(); + let cwd = run_plugin_or_alias.get_initial_cwd(); + let skip_cache = false; + match wasm_bridge.load_plugin( + &run_plugin, + Some(tab_index), + size, + cwd, + skip_cache, + Some(client_id), + ) { + Ok((plugin_id, _client_id)) => { + plugin_ids + .entry(run_plugin_or_alias.clone()) + .or_default() + .push(plugin_id); + }, + Err(e) => { + log::error!("Failed to load plugin: {}", e); + }, + } + } + } + + // Send to pty thread + drop(bus.senders.send_to_pty(PtyInstruction::OverrideLayout( + cwd, + default_shell, + tiled_layout, + floating_layouts, + swap_tiled_layouts, + swap_floating_layouts, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + tab_index, + plugin_ids, + client_id, + completion_tx, + ))); + }, PluginInstruction::ApplyCachedEvents { plugin_ids, done_receiving_permissions, diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index fee6054e..544830af 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -25,7 +25,10 @@ use zellij_utils::{ errors::{ContextType, PtyContext}, input::{ command::{OpenFilePayload, RunCommand, TerminalAction}, - layout::{FloatingPaneLayout, Layout, Run, RunPluginOrAlias, TiledPaneLayout}, + layout::{ + FloatingPaneLayout, Layout, Run, RunPluginOrAlias, SwapFloatingLayout, SwapTiledLayout, + TiledPaneLayout, + }, }, pane_size::Size, session_serialization, @@ -75,6 +78,20 @@ pub enum PtyInstruction { (ClientId, bool), // bool -> is_web_client Option<NotificationEnd>, // completion signal ), // the String is the tab name + OverrideLayout( + Option<PathBuf>, // CWD + Option<TerminalAction>, // Default Shell + TiledPaneLayout, + Vec<FloatingPaneLayout>, + Option<Vec<SwapTiledLayout>>, + Option<Vec<SwapFloatingLayout>>, + bool, // retain_existing_terminal_panes + bool, // retain_existing_plugin_panes + usize, // tab_index + HashMap<RunPluginOrAlias, Vec<u32>>, // plugin_ids + ClientId, + Option<NotificationEnd>, + ), ClosePane(PaneId, Option<NotificationEnd>), CloseTab(Vec<PaneId>), ReRunCommandInPane(PaneId, RunCommand, Option<NotificationEnd>), @@ -137,6 +154,7 @@ impl From<&PtyInstruction> for PtyContext { PtyInstruction::ClosePane(..) => PtyContext::ClosePane, PtyInstruction::CloseTab(_) => PtyContext::CloseTab, PtyInstruction::NewTab(..) => PtyContext::NewTab, + PtyInstruction::OverrideLayout(..) => PtyContext::OverrideLayout, PtyInstruction::ReRunCommandInPane(..) => PtyContext::ReRunCommandInPane, PtyInstruction::DropToShellInPane { .. } => PtyContext::DropToShellInPane, PtyInstruction::SpawnInPlaceTerminal(..) => PtyContext::SpawnInPlaceTerminal, @@ -475,6 +493,38 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> { ) .with_context(err_context)?; }, + PtyInstruction::OverrideLayout( + cwd, + default_shell, + tiled_layout, + floating_panes_layout, + swap_tiled_layouts, + swap_floating_layouts, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + tab_index, + plugin_ids, + client_id, + completion_tx, + ) => { + let err_context = || "failed to override layout"; + + pty.spawn_terminals_for_layout_override( + cwd, + tiled_layout, + floating_panes_layout, + default_shell, + plugin_ids, + tab_index, + client_id, + swap_tiled_layouts, + swap_floating_layouts, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + completion_tx, + ) + .with_context(err_context)?; + }, PtyInstruction::ClosePane(id, _completion_tx) => { pty.close_pane(id) .and_then(|_| { @@ -1200,6 +1250,187 @@ impl Pty { } Ok(()) } + pub fn spawn_terminals_for_layout_override( + &mut self, + cwd: Option<PathBuf>, + layout: TiledPaneLayout, + floating_panes_layout: Vec<FloatingPaneLayout>, + default_shell: Option<TerminalAction>, + plugin_ids: HashMap<RunPluginOrAlias, Vec<u32>>, + tab_index: usize, + client_id: ClientId, + swap_tiled_layouts: Option<Vec<SwapTiledLayout>>, + swap_floating_layouts: Option<Vec<SwapFloatingLayout>>, + retain_existing_terminal_panes: bool, + retain_existing_plugin_panes: bool, + completion_tx: Option<NotificationEnd>, + ) -> Result<()> { + let err_context = || format!("failed to spawn terminals for layout for"); + + let mut default_shell = + default_shell.unwrap_or_else(|| self.get_default_terminal(cwd, None)); + self.fill_cwd(&mut default_shell, client_id); + + let extracted_run_instructions = layout.extract_run_instructions(); + let extracted_floating_run_instructions = floating_panes_layout + .iter() + .filter(|f| !f.already_running) + .map(|f| f.run.clone()); + let mut new_pane_pids: Vec<(u32, bool, Option<RunCommand>, Result<RawFd>)> = vec![]; // (terminal_id, + // starts_held, + // run_command, + // file_descriptor) + + let mut new_floating_panes_pids: Vec<(u32, bool, Option<RunCommand>, Result<RawFd>)> = + vec![]; // same + // as + // new_pane_pids + + let mut originating_plugins_to_inform = vec![]; + + for run_instruction in extracted_run_instructions { + let originating_plugin = run_instruction.as_ref().and_then(|r| { + if let Run::Command(run_command) = r { + run_command.originating_plugin.clone() + } else { + None + } + }); + let mut terminal_id = None; + if let Some(new_pane_data) = + self.apply_run_instruction(run_instruction, default_shell.clone())? + { + terminal_id = Some(new_pane_data.0); + new_pane_pids.push(new_pane_data); + } + if let (Some(originating_plugin), Some(terminal_id)) = (originating_plugin, terminal_id) + { + originating_plugins_to_inform.push((terminal_id, originating_plugin)); + } + } + for run_instruction in extracted_floating_run_instructions { + let originating_plugin = run_instruction.as_ref().and_then(|r| { + if let Run::Command(run_command) = r { + run_command.originating_plugin.clone() + } else { + None + } + }); + let mut terminal_id = None; + if let Some(new_pane_data) = + self.apply_run_instruction(run_instruction, default_shell.clone())? + { + terminal_id = Some(new_pane_data.0); + new_floating_panes_pids.push(new_pane_data); + } + if let (Some(originating_plugin), Some(terminal_id)) = (originating_plugin, terminal_id) + { + originating_plugins_to_inform.push((terminal_id, originating_plugin)); + } + } + + // Option<RunCommand> should only be Some if the pane starts held + let new_tab_pane_ids: Vec<(u32, Option<RunCommand>)> = new_pane_pids + .iter() + .map(|(terminal_id, starts_held, run_command, _)| { + if *starts_held { + (*terminal_id, run_command.clone()) + } else { + (*terminal_id, None) + } + }) + .collect(); + let new_tab_floating_pane_ids: Vec<(u32, Option<RunCommand>)> = new_floating_panes_pids + .iter() + .map(|(terminal_id, starts_held, run_command, _)| { + if *starts_held { + (*terminal_id, run_command.clone()) + } else { + (*terminal_id, None) + } + }) + .collect(); + + self.bus + .senders + .send_to_screen(ScreenInstruction::OverrideLayoutComplete( + layout, + floating_panes_layout, + swap_tiled_layouts, + swap_floating_layouts, + new_tab_pane_ids, + new_tab_floating_pane_ids, + plugin_ids, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + tab_index, + client_id, + completion_tx, + )) + .with_context(err_context)?; + + let mut terminals_to_start = vec![]; + + terminals_to_start.append(&mut new_pane_pids); + terminals_to_start.append(&mut new_floating_panes_pids); + for (terminal_id, starts_held, run_command, pid_primary) in terminals_to_start { + if starts_held { + // we do not run a command or start listening for bytes on held panes + continue; + } + match pid_primary { + Ok(pid_primary) => { + let terminal_bytes = task::spawn({ + let senders = self.bus.senders.clone(); + let os_input = self + .bus + .os_input + .as_ref() + .with_context(err_context)? + .clone(); + let debug_to_file = self.debug_to_file; + async move { + TerminalBytes::new( + pid_primary, + senders, + os_input, + debug_to_file, + terminal_id, + ) + .listen() + .await + .context("failed to spawn terminals for layout") + .fatal(); + } + }); + self.task_handles.insert(terminal_id, terminal_bytes); + }, + _ => match run_command { + Some(run_command) => { + if run_command.hold_on_close { + send_command_not_found_to_screen( + self.bus.senders.clone(), + terminal_id, + run_command.clone(), + ) + .with_context(err_context)?; + } else { + self.close_pane(PaneId::Terminal(terminal_id)) + .with_context(err_context)?; + } + }, + None => { + self.close_pane(PaneId::Terminal(terminal_id)) + .with_context(err_context)?; + }, + }, + } + } + for (terminal_id, originating_plugin) in originating_plugins_to_inform { + self.inform_originating_plugin_of_open(terminal_id, originating_plugin); + } + Ok(()) + } fn inform_originating_plugin_of_open( &mut self, terminal_id: u32, @@ -1552,7 +1783,6 @@ impl Pty { &mut self, session_layout_metadata: &mut SessionLayoutMetadata, ) { - log::info!("populate_session_layout_metadata"); let terminal_ids = session_layout_metadata.all_terminal_ids(); let mut terminal_ids_to_commands: HashMap<u32, Vec<String>> = HashMap::new(); let mut terminal_ids_to_cwds: HashMap<u32, PathBuf> = HashMap::new(); diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 57f64d1a..57b499e1 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -28,7 +28,7 @@ use zellij_utils::{ command::TerminalAction, get_mode_info, keybinds::Keybinds, - layout::Layout, + layout::{Layout, TiledPaneLayout}, }, ipc::{ ClientAttributes, ClientToServerMsg, ExitReason, IpcReceiverWithContext, ServerToClientMsg, @@ -1114,6 +1114,39 @@ pub(crate) fn route_action( )) .with_context(err_context)?; }, + Action::OverrideLayout { + tiled_layout, + floating_layouts, + swap_tiled_layouts, + swap_floating_layouts, + tab_name, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + } => { + // Extract required layout fields, use defaults if None + let cwd = None; + let shell = default_shell.clone(); + let tiled = tiled_layout.unwrap_or_else(|| TiledPaneLayout::default()); + let floating = floating_layouts; + let swap_tiled = swap_tiled_layouts; + let swap_floating = swap_floating_layouts; + + senders + .send_to_screen(ScreenInstruction::OverrideLayout( + cwd, + shell, + tab_name, + tiled, + floating, + swap_tiled, + swap_floating, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + client_id, + Some(NotificationEnd::new(completion_tx)), + )) + .with_context(err_context)?; + }, Action::QueryTabNames => { senders .send_to_screen(ScreenInstruction::QueryTabNames( diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index c8998831..a93ad27e 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -295,6 +295,33 @@ pub enum ScreenInstruction { ClearPaneFrameColorOverride(Vec<PaneId>), PreviousSwapLayout(ClientId, Option<NotificationEnd>), NextSwapLayout(ClientId, Option<NotificationEnd>), + OverrideLayout( + Option<PathBuf>, // cwd + Option<TerminalAction>, // default_shell + Option<String>, // new name for tab + TiledPaneLayout, + Vec<FloatingPaneLayout>, + Option<Vec<SwapTiledLayout>>, + Option<Vec<SwapFloatingLayout>>, + bool, // retain_existing_terminal_panes + bool, // retain_existing_plugin_panes + ClientId, + Option<NotificationEnd>, + ), + OverrideLayoutComplete( + TiledPaneLayout, + Vec<FloatingPaneLayout>, + Option<Vec<SwapTiledLayout>>, + Option<Vec<SwapFloatingLayout>>, + Vec<(u32, HoldForCommand)>, // new terminal pids + Vec<(u32, HoldForCommand)>, // new floating pane pids + HashMap<RunPluginOrAlias, Vec<u32>>, + bool, // retain_existing_terminal_panes + bool, // retain_existing_plugin_panes + usize, // tab_index + ClientId, + Option<NotificationEnd>, + ), QueryTabNames(ClientId, Option<NotificationEnd>), NewTiledPluginPane( RunPluginOrAlias, @@ -610,6 +637,8 @@ impl From<&ScreenInstruction> for ScreenContext { }, ScreenInstruction::PreviousSwapLayout(..) => ScreenContext::PreviousSwapLayout, ScreenInstruction::NextSwapLayout(..) => ScreenContext::NextSwapLayout, + ScreenInstruction::OverrideLayout(..) => ScreenContext::OverrideLayout, + ScreenInstruction::OverrideLayoutComplete(..) => ScreenContext::OverrideLayoutComplete, ScreenInstruction::QueryTabNames(..) => ScreenContext::QueryTabNames, ScreenInstruction::NewTiledPluginPane(..) => ScreenContext::NewTiledPluginPane, ScreenInstruction::NewFloatingPluginPane(..) => ScreenContext::NewFloatingPluginPane, @@ -3548,6 +3577,47 @@ fn get_default_editor() -> Option<PathBuf> { None } +fn find_already_running_panes( + tiled_layout: &TiledPaneLayout, + floating_layouts: &[FloatingPaneLayout], + active_tab: &Tab, +) -> (Vec<Option<Run>>, Vec<usize>) { + let mut layout_tiled_instructions = tiled_layout.extract_run_instructions(); + let running_tiled_instructions: Vec<Option<Run>> = active_tab + .get_tiled_panes() + .map(|(_, pane)| pane.invoked_with().clone()) + .collect(); + + let mut tiled_to_ignore = Vec::new(); + for running_instr in running_tiled_instructions { + if let Some(pos) = layout_tiled_instructions + .iter() + .position(|layout_instr| layout_instr == &running_instr) + { + layout_tiled_instructions.remove(pos); + tiled_to_ignore.push(running_instr); + } + } + + let mut running_floating_instructions: Vec<Option<Run>> = active_tab + .get_floating_panes() + .map(|(_, pane)| pane.invoked_with().clone()) + .collect(); + + let mut floating_indices = Vec::new(); + for (idx, floating_layout) in floating_layouts.iter().enumerate() { + if let Some(pos) = running_floating_instructions + .iter() + .position(|instr| instr == &floating_layout.run) + { + running_floating_instructions.remove(pos); + floating_indices.push(idx); + } + } + + (tiled_to_ignore, floating_indices) +} + // The box is here in order to make the // NewClient enum smaller #[allow(clippy::boxed_local)] @@ -5120,6 +5190,97 @@ pub(crate) fn screen_thread_main( screen.render(None)?; screen.log_and_report_session_state()?; }, + ScreenInstruction::OverrideLayout( + cwd, + default_shell, + mut new_tab_name, + mut tiled_layout, + mut floating_layouts, + swap_tiled_layouts, + swap_floating_layouts, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + client_id, + _completion_tx, + ) => { + let active_tab = match screen.get_active_tab_mut(client_id) { + Ok(tab) => tab, + Err(_) => { + log::error!( + "OverrideLayout: No active tab for client_id {:?}", + client_id + ); + continue; + }, + }; + if let Some(name) = new_tab_name.take() { + active_tab.name = name; + } + + let (tiled_to_ignore, floating_indices) = + find_already_running_panes(&tiled_layout, &floating_layouts, active_tab); + + for run_instruction in tiled_to_ignore { + tiled_layout.ignore_run_instruction(run_instruction); + } + + for idx in floating_indices { + floating_layouts + .get_mut(idx) + .map(|f| f.already_running = true); + } + + let tab_index = active_tab.index; + screen + .bus + .senders + .send_to_plugin(PluginInstruction::OverrideLayout( + cwd, + default_shell, + tiled_layout, + floating_layouts, + swap_tiled_layouts, + swap_floating_layouts, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + tab_index, + client_id, + _completion_tx, + ))?; + }, + ScreenInstruction::OverrideLayoutComplete( + tiled_layout, + floating_layouts, + new_swap_tiled_layouts, + new_swap_floating_layouts, + new_terminal_pids, + new_floating_pane_pids, + plugin_ids, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + tab_index, + client_id, + completion_tx, + ) => { + screen.tabs.get_mut(&tab_index).map(|t| { + t.override_layout( + tiled_layout, + floating_layouts, + new_swap_tiled_layouts, + new_swap_floating_layouts, + new_terminal_pids, + new_floating_pane_pids, + plugin_ids, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + client_id, + None, + ) + }); + screen.log_and_report_session_state()?; + let _ = screen.render(None); + drop(completion_tx); // action ends here, notify the action initiator + }, ScreenInstruction::QueryTabNames(client_id, completion_tx) => { let tab_names = screen .get_tabs_mut() diff --git a/zellij-server/src/tab/layout_applier.rs b/zellij-server/src/tab/layout_applier.rs index 559dc5db..a0f30da8 100644 --- a/zellij-server/src/tab/layout_applier.rs +++ b/zellij-server/src/tab/layout_applier.rs @@ -122,6 +122,38 @@ impl<'a> LayoutApplier<'a> { let should_show_floating_panes = layout_has_floating_panes && !hide_floating_panes; return Ok(should_show_floating_panes); } + pub fn override_layout( + &mut self, + tiled_panes_layout: TiledPaneLayout, + floating_panes_layout: Vec<FloatingPaneLayout>, + new_terminal_ids: Vec<(u32, HoldForCommand)>, + new_floating_terminal_ids: Vec<(u32, HoldForCommand)>, + mut new_plugin_ids: HashMap<RunPluginOrAlias, Vec<u32>>, + retain_existing_terminal_panes: bool, + retain_existing_plugin_panes: bool, + client_id: ClientId, + ) -> Result<bool> { + // true => should_show_floating_panes + let hide_floating_panes = tiled_panes_layout.hide_floating_panes; + self.override_tiled_panes_layout_for_existing_panes( + &tiled_panes_layout, + new_terminal_ids, + &mut new_plugin_ids, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + client_id, + )?; + + let layout_has_floating_panes = self.override_floating_panes_layout_for_existing_panes( + &floating_panes_layout, + new_floating_terminal_ids, + &mut new_plugin_ids, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + )?; + let should_show_floating_panes = layout_has_floating_panes && !hide_floating_panes; + return Ok(should_show_floating_panes); + } pub fn apply_tiled_panes_layout_to_existing_panes( &mut self, layout: &TiledPaneLayout, @@ -181,16 +213,144 @@ impl<'a> LayoutApplier<'a> { // add the rest of the panes where tiled_panes finds room for them (eg. if the layout had // less panes than we've got in our state) let remaining_pane_ids: Vec<PaneId> = existing_tab_state.pane_ids(); - pane_applier.handle_remaining_tiled_pane_ids(remaining_pane_ids, existing_tab_state); + pane_applier.handle_remaining_tiled_pane_ids( + remaining_pane_ids, + existing_tab_state, + None, + None, + ); pane_applier.finalize_tiled_state(); LayoutApplier::offset_viewport( self.viewport.clone(), + self.display_area.clone(), self.tiled_panes, self.draw_pane_frames, ); Ok(()) } + pub fn override_tiled_panes_layout_for_existing_panes( + &mut self, + tiled_panes_layout: &TiledPaneLayout, + mut new_terminal_ids: Vec<(u32, HoldForCommand)>, + mut new_plugin_ids: &mut HashMap<RunPluginOrAlias, Vec<u32>>, + retain_existing_terminal_panes: bool, + retain_existing_plugin_panes: bool, + client_id: ClientId, + ) -> Result<()> { + let positions_in_layout = self.flatten_layout(tiled_panes_layout, false)?; + + let mut existing_tab_state = ExistingTabState::new(self.tiled_panes.drain()); + + let mut pane_applier = PaneApplier::new( + &mut self.tiled_panes, + &mut self.floating_panes, + &self.senders, + &self.character_cell_size, + ); + let mut positions_left_without_exact_matches = vec![]; + + // look for exact matches (eg. panes that expect a specific command or plugin to run in them) + let mut last_logical_position = None; + for (layout, position_and_size) in positions_in_layout { + match existing_tab_state.find_and_extract_exact_pane_with_same_run(&layout.run) { + Some(pane) => { + last_logical_position = position_and_size.logical_position; + pane_applier.apply_position_and_size_to_tiled_pane( + pane, + position_and_size, + layout, + ); + }, + None => { + last_logical_position = position_and_size.logical_position; + positions_left_without_exact_matches.push((layout, position_and_size)); + }, + } + } + + let remaining_pane_ids: Vec<PaneId> = existing_tab_state.pane_ids(); + self.close_non_retained_panes( + &mut existing_tab_state, + &remaining_pane_ids, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + ); + + let (focus_pane_id, pane_ids_expanded_in_stack) = self.position_new_panes( + &mut new_terminal_ids, + &mut new_plugin_ids, + &mut positions_left_without_exact_matches, + )?; + + // we do this because we have to add the remaining tiled pane ids ONLY AFTER positioning + // the new panes, otherwise the layout might get borked + let mut pane_applier = PaneApplier::new( + &mut self.tiled_panes, + &mut self.floating_panes, + &self.senders, + &self.character_cell_size, + ); + if retain_existing_terminal_panes || retain_existing_plugin_panes { + pane_applier.handle_remaining_tiled_pane_ids( + remaining_pane_ids, + existing_tab_state, + last_logical_position, + Some(client_id), + ); + } + + pane_applier.finalize_tiled_state(); + + if let Some(pane_id) = focus_pane_id { + *self.focus_pane_id = Some(pane_id); + self.tiled_panes.focus_pane(pane_id, client_id); + } + + // in case focused panes were closed by the override + self.tiled_panes.move_client_focus_to_existing_panes(); + + for pane_id in &pane_ids_expanded_in_stack { + self.tiled_panes.expand_pane_in_stack(*pane_id); + } + + LayoutApplier::offset_viewport( + self.viewport.clone(), + self.display_area.clone(), + self.tiled_panes, + self.draw_pane_frames, + ); + Ok(()) + } + fn close_non_retained_panes( + &self, + existing_tab_state: &mut ExistingTabState, + remaining_pane_ids: &[PaneId], + retain_existing_terminal_panes: bool, + retain_existing_plugin_panes: bool, + ) { + for pane_id in remaining_pane_ids { + if retain_existing_terminal_panes && matches!(pane_id, PaneId::Terminal(_)) { + continue; + } + if retain_existing_plugin_panes && matches!(pane_id, PaneId::Plugin(_)) { + continue; + } + existing_tab_state.remove_pane(pane_id); + match pane_id { + PaneId::Terminal(_) => { + let _ = self + .senders + .send_to_pty(PtyInstruction::ClosePane(*pane_id, None)); + }, + PaneId::Plugin(plugin_id) => { + let _ = self + .senders + .send_to_plugin(PluginInstruction::Unload(*plugin_id)); + }, + } + } + } fn flatten_layout( &self, layout: &TiledPaneLayout, @@ -243,7 +403,7 @@ impl<'a> LayoutApplier<'a> { &layout.run_instructions_to_ignore, &mut positions_in_layout, ); - let focus_pane_id = self.position_new_panes( + let (focus_pane_id, pane_ids_expanded_in_stack) = self.position_new_panes( &mut new_terminal_ids, &mut new_plugin_ids, &mut positions_in_layout, @@ -252,6 +412,11 @@ impl<'a> LayoutApplier<'a> { run_instructions_without_a_location, &mut new_terminal_ids, ); + + for pane_id in &pane_ids_expanded_in_stack { + self.tiled_panes.expand_pane_in_stack(*pane_id); + } + self.adjust_viewport().with_context(err_context)?; self.set_focused_tiled_pane(focus_pane_id, client_id); Ok(()) @@ -288,10 +453,12 @@ impl<'a> LayoutApplier<'a> { new_terminal_ids: &mut Vec<(u32, HoldForCommand)>, new_plugin_ids: &mut HashMap<RunPluginOrAlias, Vec<u32>>, positions_in_layout: &mut Vec<(TiledPaneLayout, PaneGeom)>, - ) -> Result<Option<PaneId>> { + ) -> Result<(Option<PaneId>, Vec<PaneId>)> { + // returns: optional pane id to focus, pane ids + // expanded in stack // here we open new panes for each run instruction in the layout with the details // we got from the plugin thread and pty thread - // let positions_and_size = positions_in_layout.iter(); + let mut pane_ids_expanded_in_stack = vec![]; let mut focus_pane_id: Option<PaneId> = None; let mut set_focus_pane_id = |layout: &TiledPaneLayout, pane_id: PaneId| { if layout.focus.unwrap_or(false) && focus_pane_id.is_none() { @@ -302,15 +469,21 @@ impl<'a> LayoutApplier<'a> { if let Some(Run::Plugin(run)) = layout.run.clone() { let pid = self.new_tiled_plugin_pane(run, new_plugin_ids, &position_and_size, &layout)?; + if layout.is_expanded_in_stack { + pane_ids_expanded_in_stack.push(PaneId::Terminal(pid)); + } set_focus_pane_id(&layout, PaneId::Plugin(pid)); } else if !new_terminal_ids.is_empty() { // there are still panes left to fill, use the pids we received in this method let (pid, hold_for_command) = new_terminal_ids.remove(0); self.new_terminal_pane(pid, &hold_for_command, &position_and_size, &layout)?; + if layout.is_expanded_in_stack { + pane_ids_expanded_in_stack.push(PaneId::Terminal(pid)); + } set_focus_pane_id(&layout, PaneId::Terminal(pid)); } } - Ok(focus_pane_id) + Ok((focus_pane_id, pane_ids_expanded_in_stack)) } fn handle_run_instructions_without_a_location( &mut self, @@ -715,6 +888,112 @@ impl<'a> LayoutApplier<'a> { Ok(false) } } + pub fn override_floating_panes_layout_for_existing_panes( + &mut self, + floating_panes_layout: &Vec<FloatingPaneLayout>, + new_terminal_ids: Vec<(u32, HoldForCommand)>, + mut new_plugin_ids: &mut HashMap<RunPluginOrAlias, Vec<u32>>, + retain_existing_terminal_panes: bool, + retain_existing_plugin_panes: bool, + ) -> Result<bool> { + let layout_has_floating_panes = !floating_panes_layout.is_empty(); + let mut positions_in_layout = floating_panes_layout.clone(); + let mut logical_position = 0; + for floating_pane_layout in positions_in_layout.iter_mut() { + floating_pane_layout.logical_position = Some(logical_position); + logical_position += 1; + } + let mut existing_tab_state = ExistingTabState::new(self.floating_panes.drain()); + let mut positions_left = vec![]; + + let mut pane_applier = PaneApplier::new( + &mut self.tiled_panes, + &mut self.floating_panes, + &self.senders, + &self.character_cell_size, + ); + + // find already running exact matches + for floating_pane_layout in positions_in_layout { + match existing_tab_state + .find_and_extract_exact_pane_with_same_run(&floating_pane_layout.run) + { + Some(pane) => { + pane_applier + .apply_floating_panes_layout_to_floating_pane(pane, floating_pane_layout)?; + }, + None => { + positions_left.push(floating_pane_layout); + }, + } + } + + let remaining_pane_ids: Vec<PaneId> = existing_tab_state.pane_ids(); + self.close_non_retained_panes( + &mut existing_tab_state, + &remaining_pane_ids, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + ); + + // open new panes + let mut focused_floating_pane = None; + let mut new_floating_terminal_ids = new_terminal_ids.iter(); + for floating_pane_layout in positions_left { + let position_and_size = self + .floating_panes + .position_floating_pane_layout(&floating_pane_layout)?; + let pid_to_focus = if let Some(Run::Plugin(run)) = floating_pane_layout.run.clone() { + self.new_floating_plugin_pane( + run, + &mut new_plugin_ids, + position_and_size, + &floating_pane_layout, + )? + } else if let Some((pid, hold_for_command)) = new_floating_terminal_ids.next() { + self.new_floating_terminal_pane( + pid, + hold_for_command, + position_and_size, + &floating_pane_layout, + )? + } else { + None + }; + if let Some(pid_to_focus) = pid_to_focus { + focused_floating_pane = Some(pid_to_focus); + } + } + + // we do this because we have to add the remaining tiled pane ids ONLY AFTER positioning + // the new panes, otherwise the layout might get borked + let mut pane_applier = PaneApplier::new( + &mut self.tiled_panes, + &mut self.floating_panes, + &self.senders, + &self.character_cell_size, + ); + pane_applier.handle_remaining_floating_pane_ids(existing_tab_state, logical_position); + + if let Some(focused_floating_pane) = focused_floating_pane { + self.floating_panes + .focus_pane_for_all_clients(focused_floating_pane); + } else { + // we do this in case there is no explicitly focused pane in the new layout, so as to + // make sure any panes that are already focused have a higher z_index than the ones + // added above + self.floating_panes.reapply_pane_focus(); + } + + // in case focused panes were closed by the override + self.floating_panes.move_client_focus_to_existing_panes(); + + if layout_has_floating_panes { + Ok(true) + } else { + Ok(false) + } + } fn resize_whole_tab(&mut self, new_screen_size: Size) -> Result<()> { let err_context = || { format!( @@ -732,9 +1011,15 @@ impl<'a> LayoutApplier<'a> { } pub fn offset_viewport( viewport: Rc<RefCell<Viewport>>, + display_area: Rc<RefCell<Size>>, tiled_panes: &mut TiledPanes, draw_pane_frames: bool, ) { + { + // reset viewport before reapplying offset + let mut viewport = viewport.borrow_mut(); + *viewport = (*display_area.borrow()).into(); + } let boundary_geoms = tiled_panes.non_selectable_pane_geoms_inside_viewport(); { // curly braces here is so that we free viewport immediately when we're done @@ -784,6 +1069,7 @@ impl<'a> LayoutApplier<'a> { self.resize_whole_tab(display_area).context(err_context)?; LayoutApplier::offset_viewport( self.viewport.clone(), + self.display_area.clone(), self.tiled_panes, self.draw_pane_frames, ); @@ -843,6 +1129,20 @@ impl ExistingTabState { } None } + pub fn find_and_extract_exact_pane_with_same_run( + &mut self, + run: &Option<Run>, + ) -> Option<Box<dyn Pane>> { + let candidates = self.pane_candidates(); + let pane_id = candidates.iter().find_map(|(_pid, p)| { + if p.invoked_with() == run { + Some(p.pid()) + } else { + None + } + }); + pane_id.and_then(|p| self.existing_panes.remove(&p)) + } pub fn find_and_extract_pane_with_same_logical_position( &mut self, logical_position: Option<usize>, @@ -1015,10 +1315,16 @@ impl<'a> PaneApplier<'a> { &mut self, remaining_pane_ids: Vec<PaneId>, mut existing_tab_state: ExistingTabState, + mut last_logical_position: Option<usize>, + client_id: Option<ClientId>, ) { for pane_id in remaining_pane_ids { if let Some(pane) = existing_tab_state.remove_pane(&pane_id) { - self.tiled_panes.insert_pane(pane.pid(), pane, None); + self.tiled_panes.insert_pane(pane.pid(), pane, client_id); + if let Some(l) = last_logical_position.take() { + self.tiled_panes.set_pane_logical_position(pane_id, l); + last_logical_position = Some(l + 1); + } } } } diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 7e441a0e..696a0d2f 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -859,6 +859,102 @@ impl Tab { // we should still be able to properly recover from this with a useful error // message though log::error!("Failed to apply layout: {}", e); + self.tiled_panes.reapply_pane_frames(); + self.is_pending = false; + self.apply_buffered_instructions().non_fatal(); + }, + } + Ok(()) + } + pub fn override_layout( + &mut self, + layout: TiledPaneLayout, + floating_panes_layout: Vec<FloatingPaneLayout>, + mut new_swap_tiled_layouts: Option<Vec<SwapTiledLayout>>, + mut new_swap_floating_layouts: Option<Vec<SwapFloatingLayout>>, + new_terminal_ids: Vec<(u32, HoldForCommand)>, + new_floating_terminal_ids: Vec<(u32, HoldForCommand)>, + new_plugin_ids: HashMap<RunPluginOrAlias, Vec<u32>>, + retain_existing_terminal_panes: bool, + retain_existing_plugin_panes: bool, + client_id: ClientId, + blocking_terminal: Option<(u32, NotificationEnd)>, + ) -> Result<()> { + let new_swap_tiled_layouts = new_swap_tiled_layouts.take().unwrap_or_else(|| vec![]); + let new_swap_floating_layouts = new_swap_floating_layouts.take().unwrap_or_else(|| vec![]); + self.swap_layouts + .set_swap_tiled_layouts(new_swap_tiled_layouts); + self.swap_layouts + .set_swap_floating_layouts(new_swap_floating_layouts); + match LayoutApplier::new( + &self.viewport, + &self.senders, + &self.sixel_image_store, + &self.link_handler, + &self.terminal_emulator_colors, + &self.terminal_emulator_color_codes, + &self.character_cell_size, + &self.connected_clients_in_app, + &self.style, + &self.display_area, + &mut self.tiled_panes, + &mut self.floating_panes, + self.draw_pane_frames, + &mut self.focus_pane_id, + &self.os_api, + self.debug, + self.arrow_fonts, + self.styled_underlines, + self.explicitly_disable_kitty_keyboard_protocol, + blocking_terminal, + ) + .override_layout( + layout, + floating_panes_layout, + new_terminal_ids, + new_floating_terminal_ids, + new_plugin_ids, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + client_id, + ) { + Ok(should_show_floating_panes) => { + if should_show_floating_panes && !self.floating_panes.panes_are_visible() { + self.toggle_floating_panes(Some(client_id), None, None) + .non_fatal(); + } else if !should_show_floating_panes && self.floating_panes.panes_are_visible() { + self.toggle_floating_panes(Some(client_id), None, None) + .non_fatal(); + } + + // this is essentially another pass of the layout applier + // we do this because the layout applier does not know about swap layouts, and in + // this case we might have had to re-add existing panes that were not in the + // overridden layout (eg. if we had more panes than were in the layout). In such a + // case, we would like to make sure these extra panes fit the current swap layout + self.swap_layouts.set_is_tiled_damaged(); + self.swap_layouts.set_is_floating_damaged(); + let _ = self.relayout_tiled_panes(false); + let _ = self.relayout_floating_panes(false); + + self.tiled_panes.reapply_pane_frames(); + self.is_pending = false; + + LayoutApplier::offset_viewport( + self.viewport.clone(), + self.display_area.clone(), + &mut self.tiled_panes, + self.draw_pane_frames, + ); + + self.apply_buffered_instructions().non_fatal(); + }, + Err(e) => { + // TODO: this should only happen due to an erroneous layout created by user + // configuration that was somehow not caught in our KDL layout parser + // we should still be able to properly recover from this with a useful error + // message though + log::error!("Failed to apply layout: {}", e); }, } Ok(()) @@ -1269,6 +1365,18 @@ impl Tab { self.set_force_render(); Ok(()) } + fn normalize_invoked_with_for_default_shell(&self, invoked_with: Option<Run>) -> Option<Run> { + let default_shell_run_command = Run::Command(RunCommand { + command: self.default_shell.clone(), + use_terminal_title: true, + ..Default::default() + }); + if invoked_with == Some(default_shell_run_command) { + None + } else { + invoked_with + } + } pub fn new_pane( &mut self, pid: PaneId, @@ -1280,6 +1388,7 @@ impl Tab { client_id: Option<ClientId>, blocking_notification: Option<NotificationEnd>, ) -> Result<()> { + let invoked_with = self.normalize_invoked_with_for_default_shell(invoked_with); match new_pane_placement { NewPanePlacement::NoPreference => self.new_no_preference_pane( pid, @@ -3409,6 +3518,7 @@ impl Tab { // outside the viewport (a ui-pane, eg. the status-bar and tab-bar) and need to adjust for it LayoutApplier::offset_viewport( self.viewport.clone(), + self.display_area.clone(), &mut self.tiled_panes, self.draw_pane_frames, ); @@ -5745,3 +5855,7 @@ mod tab_tests; #[cfg(test)] #[path = "./unit/tab_integration_tests.rs"] mod tab_integration_tests; + +#[cfg(test)] +#[path = "./unit/layout_applier_tests.rs"] +mod layout_applier_tests; diff --git a/zellij-server/src/tab/swap_layouts.rs b/zellij-server/src/tab/swap_layouts.rs index 8ee9709d..af4e5590 100644 --- a/zellij-server/src/tab/swap_layouts.rs +++ b/zellij-server/src/tab/swap_layouts.rs @@ -55,6 +55,17 @@ impl SwapLayouts { self.current_tiled_layout_position = 0; self.current_floating_layout_position = 0; } + pub fn set_swap_tiled_layouts(&mut self, new_swap_tiled_layouts: Vec<SwapTiledLayout>) { + self.swap_tiled_layouts = new_swap_tiled_layouts; + self.current_tiled_layout_position = 0; + } + pub fn set_swap_floating_layouts( + &mut self, + new_swap_floating_layouts: Vec<SwapFloatingLayout>, + ) { + self.swap_floating_layouts = new_swap_floating_layouts; + self.current_floating_layout_position = 0; + } pub fn set_is_floating_damaged(&mut self) { self.is_floating_damaged = true; } diff --git a/zellij-server/src/tab/unit/layout_applier_tests.rs b/zellij-server/src/tab/unit/layout_applier_tests.rs new file mode 100644 index 00000000..4ccf6f22 --- /dev/null +++ b/zellij-server/src/tab/unit/layout_applier_tests.rs @@ -0,0 +1,7100 @@ +use crate::panes::sixel::SixelImageStore; +use crate::panes::{FloatingPanes, TiledPanes}; +use crate::panes::{LinkHandler, PaneId}; +use crate::plugins::PluginInstruction; +use crate::pty::PtyInstruction; +use crate::tab::layout_applier::LayoutApplier; +use crate::{ + os_input_output::{AsyncReader, Pid, ServerOsApi}, + thread_bus::ThreadSenders, + ClientId, +}; +use insta::assert_snapshot; +use std::cell::RefCell; +use std::collections::{HashMap, HashSet}; +use std::fmt::Write; +use std::os::unix::io::RawFd; +use std::path::PathBuf; +use std::rc::Rc; + +use interprocess::local_socket::LocalSocketStream; +use zellij_utils::{ + channels::{self, ChannelWithContext, Receiver, SenderWithContext}, + data::{ModeInfo, Palette, Style}, + errors::prelude::*, + input::command::{RunCommand, TerminalAction}, + input::layout::RunPluginOrAlias, + input::layout::{FloatingPaneLayout, Layout, Run, TiledPaneLayout}, + ipc::{ClientToServerMsg, IpcReceiverWithContext, ServerToClientMsg}, + pane_size::{Size, SizeInPixels, Viewport}, +}; + +#[derive(Clone)] +struct FakeInputOutput {} + +impl ServerOsApi for FakeInputOutput { + fn set_terminal_size_using_terminal_id( + &self, + _id: u32, + _cols: u16, + _rows: u16, + _width_in_pixels: Option<u16>, + _height_in_pixels: Option<u16>, + ) -> Result<()> { + Ok(()) + } + + fn spawn_terminal( + &self, + _file_to_open: TerminalAction, + _quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, + _default_editor: Option<PathBuf>, + ) -> Result<(u32, RawFd, RawFd)> { + unimplemented!() + } + + fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result<usize> { + unimplemented!() + } + + fn async_file_reader(&self, _fd: RawFd) -> Box<dyn AsyncReader> { + unimplemented!() + } + + fn write_to_tty_stdin(&self, _id: u32, _buf: &[u8]) -> Result<usize> { + unimplemented!() + } + + fn tcdrain(&self, _id: u32) -> Result<()> { + unimplemented!() + } + + fn kill(&self, _pid: Pid) -> Result<()> { + unimplemented!() + } + + fn force_kill(&self, _pid: Pid) -> Result<()> { + unimplemented!() + } + + fn box_clone(&self) -> Box<dyn ServerOsApi> { + Box::new((*self).clone()) + } + + fn send_to_client(&self, _client_id: ClientId, _msg: ServerToClientMsg) -> Result<()> { + unimplemented!() + } + + fn new_client( + &mut self, + _client_id: ClientId, + _stream: LocalSocketStream, + ) -> Result<IpcReceiverWithContext<ClientToServerMsg>> { + unimplemented!() + } + + fn remove_client(&mut self, _client_id: ClientId) -> Result<()> { + unimplemented!() + } + + fn load_palette(&self) -> Palette { + unimplemented!() + } + + fn get_cwd(&self, _pid: Pid) -> Option<PathBuf> { + unimplemented!() + } + + fn write_to_file(&mut self, _buf: String, _name: Option<String>) -> Result<()> { + unimplemented!() + } + + fn re_run_command_in_terminal( + &self, + _terminal_id: u32, + _run_command: RunCommand, + _quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, + ) -> Result<(RawFd, RawFd)> { + unimplemented!() + } + + fn clear_terminal_id(&self, _terminal_id: u32) -> Result<()> { + unimplemented!() + } + + fn send_sigint(&self, _pid: Pid) -> Result<()> { + unimplemented!() + } +} + +/// Parse KDL layout string and extract tiled and floating layouts +fn parse_kdl_layout(kdl_str: &str) -> (TiledPaneLayout, Vec<FloatingPaneLayout>) { + let layout = Layout::from_kdl(kdl_str, Some("test_layout".into()), None, None) + .expect("Failed to parse KDL layout"); + layout.new_tab() +} + +/// Creates all the fixtures needed for LayoutApplier tests +#[allow(clippy::type_complexity)] +fn create_layout_applier_fixtures( + size: Size, +) -> ( + Rc<RefCell<Viewport>>, + ThreadSenders, + Rc<RefCell<SixelImageStore>>, + Rc<RefCell<LinkHandler>>, + Rc<RefCell<Palette>>, + Rc<RefCell<HashMap<usize, String>>>, + Rc<RefCell<Option<SizeInPixels>>>, + Rc<RefCell<HashMap<ClientId, bool>>>, + Style, + Rc<RefCell<Size>>, + TiledPanes, + FloatingPanes, + bool, + Option<PaneId>, + Box<dyn ServerOsApi>, + bool, + bool, + bool, + bool, +) { + let viewport = Rc::new(RefCell::new(Viewport { + x: 0, + y: 0, + rows: size.rows, + cols: size.cols, + })); + + let (mock_plugin_sender, _mock_plugin_receiver) = channels::unbounded(); + let mut senders = ThreadSenders::default().silently_fail_on_send(); + senders.replace_to_plugin(SenderWithContext::new(mock_plugin_sender)); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let link_handler = Rc::new(RefCell::new(LinkHandler::new())); + let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default())); + let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); + let character_cell_size = Rc::new(RefCell::new(None)); + + let client_id = 1; + let mut connected_clients_map = HashMap::new(); + connected_clients_map.insert(client_id, false); + let connected_clients = Rc::new(RefCell::new(connected_clients_map)); + + let style = Style::default(); + let display_area = Rc::new(RefCell::new(size)); + + let os_api = Box::new(FakeInputOutput {}); + + // Create TiledPanes + let connected_clients_set = Rc::new(RefCell::new(HashSet::from([client_id]))); + let mode_info = Rc::new(RefCell::new(HashMap::new())); + let stacked_resize = Rc::new(RefCell::new(false)); + let session_is_mirrored = true; + let draw_pane_frames = true; + let default_mode_info = ModeInfo::default(); + + let tiled_panes = TiledPanes::new( + display_area.clone(), + viewport.clone(), + connected_clients_set.clone(), + connected_clients.clone(), + mode_info.clone(), + character_cell_size.clone(), + stacked_resize, + session_is_mirrored, + draw_pane_frames, + default_mode_info.clone(), + style.clone(), + os_api.box_clone(), + senders.clone(), + ); + + // Create FloatingPanes + let floating_panes = FloatingPanes::new( + display_area.clone(), + viewport.clone(), + connected_clients_set, + connected_clients.clone(), + mode_info, + character_cell_size.clone(), + session_is_mirrored, + default_mode_info, + style.clone(), + os_api.box_clone(), + senders.clone(), + ); + + let focus_pane_id = None; + let debug = false; + let arrow_fonts = true; + let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; + + ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + tiled_panes, + floating_panes, + draw_pane_frames, + focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) +} + +/// Creates fixtures with receivers for verifying messages sent to pty and plugin threads +#[allow(clippy::type_complexity)] +fn create_layout_applier_fixtures_with_receivers( + size: Size, +) -> ( + Rc<RefCell<Viewport>>, + ThreadSenders, + Rc<RefCell<SixelImageStore>>, + Rc<RefCell<LinkHandler>>, + Rc<RefCell<Palette>>, + Rc<RefCell<HashMap<usize, String>>>, + Rc<RefCell<Option<SizeInPixels>>>, + Rc<RefCell<HashMap<ClientId, bool>>>, + Style, + Rc<RefCell<Size>>, + TiledPanes, + FloatingPanes, + bool, + Option<PaneId>, + Box<dyn ServerOsApi>, + bool, + bool, + bool, + bool, + Receiver<(PtyInstruction, zellij_utils::errors::ErrorContext)>, + Receiver<(PluginInstruction, zellij_utils::errors::ErrorContext)>, +) { + let viewport = Rc::new(RefCell::new(Viewport { + x: 0, + y: 0, + rows: size.rows, + cols: size.cols, + })); + + let (mock_pty_sender, mock_pty_receiver): ChannelWithContext<PtyInstruction> = + channels::unbounded(); + let (mock_plugin_sender, mock_plugin_receiver): ChannelWithContext<PluginInstruction> = + channels::unbounded(); + + let mut senders = ThreadSenders::default().silently_fail_on_send(); + senders.replace_to_pty(SenderWithContext::new(mock_pty_sender)); + senders.replace_to_plugin(SenderWithContext::new(mock_plugin_sender)); + + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let link_handler = Rc::new(RefCell::new(LinkHandler::new())); + let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default())); + let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); + let character_cell_size = Rc::new(RefCell::new(None)); + + let client_id = 1; + let mut connected_clients_map = HashMap::new(); + connected_clients_map.insert(client_id, false); + let connected_clients = Rc::new(RefCell::new(connected_clients_map)); + + let style = Style::default(); + let display_area = Rc::new(RefCell::new(size)); + + let os_api = Box::new(FakeInputOutput {}); + + // Create TiledPanes + let connected_clients_set = Rc::new(RefCell::new(HashSet::from([client_id]))); + let mode_info = Rc::new(RefCell::new(HashMap::new())); + let stacked_resize = Rc::new(RefCell::new(false)); + let session_is_mirrored = true; + let draw_pane_frames = true; + let default_mode_info = ModeInfo::default(); + + let tiled_panes = TiledPanes::new( + display_area.clone(), + viewport.clone(), + connected_clients_set.clone(), + connected_clients.clone(), + mode_info.clone(), + character_cell_size.clone(), + stacked_resize, + session_is_mirrored, + draw_pane_frames, + default_mode_info.clone(), + style.clone(), + os_api.box_clone(), + senders.clone(), + ); + + // Create FloatingPanes + let floating_panes = FloatingPanes::new( + display_area.clone(), + viewport.clone(), + connected_clients_set, + connected_clients.clone(), + mode_info, + character_cell_size.clone(), + session_is_mirrored, + default_mode_info, + style.clone(), + os_api.box_clone(), + senders.clone(), + ); + + let focus_pane_id = None; + let debug = false; + let arrow_fonts = true; + let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; + + ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + tiled_panes, + floating_panes, + draw_pane_frames, + focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + mock_pty_receiver, + mock_plugin_receiver, + ) +} + +/// Takes a snapshot of the current pane state for assertion +fn take_pane_state_snapshot( + tiled_panes: &TiledPanes, + floating_panes: &FloatingPanes, + focus_pane_id: &Option<PaneId>, + viewport: &Rc<RefCell<Viewport>>, + display_area: &Rc<RefCell<Size>>, +) -> String { + let mut output = String::new(); + + // Viewport info + let viewport_state = viewport.borrow(); + writeln!( + &mut output, + "VIEWPORT: x={}, y={}, cols={}, rows={}", + viewport_state.x, viewport_state.y, viewport_state.cols, viewport_state.rows + ) + .unwrap(); + + let display_state = display_area.borrow(); + writeln!( + &mut output, + "DISPLAY: cols={}, rows={}", + display_state.cols, display_state.rows + ) + .unwrap(); + + // Focus state + writeln!(&mut output, "FOCUS: {:?}", focus_pane_id).unwrap(); + + writeln!(&mut output).unwrap(); + + // Tiled panes + writeln!(&mut output, "TILED PANES ({})", tiled_panes.panes.len()).unwrap(); + let mut tiled_list: Vec<_> = tiled_panes.get_panes().collect(); + tiled_list.sort_by_key(|(id, _)| **id); + + for (pane_id, pane) in tiled_list { + let geom = pane.position_and_size(); + let run = pane.invoked_with(); + let selectable = pane.selectable(); + + writeln!(&mut output, " {:?}:", pane_id).unwrap(); + writeln!( + &mut output, + " geom: x={}, y={}, cols={}, rows={}", + geom.x, + geom.y, + geom.cols.as_usize(), + geom.rows.as_usize() + ) + .unwrap(); + + if let Some(logical_pos) = geom.logical_position { + writeln!(&mut output, " logical_position: {}", logical_pos).unwrap(); + } + + if let Some(stack_id) = geom.stacked { + writeln!(&mut output, " stacked: {}", stack_id).unwrap(); + } + + writeln!(&mut output, " run: {}", format_run_instruction(run)).unwrap(); + + writeln!(&mut output, " selectable: {}", selectable).unwrap(); + writeln!(&mut output, " title: {}", pane.current_title()).unwrap(); + writeln!(&mut output, " borderless: {}", pane.borderless()).unwrap(); + + writeln!(&mut output).unwrap(); + } + + // Floating panes + if floating_panes.pane_ids().count() > 0 { + writeln!( + &mut output, + "FLOATING PANES ({})", + floating_panes.pane_ids().count() + ) + .unwrap(); + let mut floating_list: Vec<_> = floating_panes.get_panes().collect(); + floating_list.sort_by_key(|(id, _)| **id); + + for (pane_id, pane) in floating_list { + let geom = pane.position_and_size(); + let run = pane.invoked_with(); + + writeln!(&mut output, " {:?}:", pane_id).unwrap(); + writeln!( + &mut output, + " geom: x={}, y={}, cols={}, rows={}", + geom.x, + geom.y, + geom.cols.as_usize(), + geom.rows.as_usize() + ) + .unwrap(); + + if let Some(logical_pos) = geom.logical_position { + writeln!(&mut output, " logical_position: {}", logical_pos).unwrap(); + } + + writeln!(&mut output, " run: {}", format_run_instruction(run)).unwrap(); + writeln!(&mut output, " pinned: {}", geom.is_pinned).unwrap(); + writeln!(&mut output, " selectable: {}", pane.selectable()).unwrap(); + writeln!(&mut output, " title: {}", pane.current_title()).unwrap(); + writeln!(&mut output, " borderless: {}", pane.borderless()).unwrap(); + writeln!(&mut output).unwrap(); + } + } + + output +} + +/// Format a Run instruction as a human-readable string +fn format_run_instruction(run: &Option<Run>) -> String { + match run { + None => "None".to_string(), + Some(Run::Command(cmd)) => { + let mut s = format!("Command({})", cmd.command.display()); + if !cmd.args.is_empty() { + s.push_str(&format!(" args={:?}", cmd.args)); + } + if let Some(cwd) = &cmd.cwd { + s.push_str(&format!(" cwd={:?}", cwd)); + } + s + }, + Some(Run::Plugin(plugin)) => { + format!("Plugin({})", plugin.location_string()) + }, + Some(Run::Cwd(path)) => format!("Cwd({:?})", path), + Some(Run::EditFile(path, line, cwd)) => { + format!("EditFile({:?}, line={:?}, cwd={:?})", path, line, cwd) + }, + } +} + +/// Collect all close pane messages from the pty receiver +fn collect_close_pane_messages( + pty_receiver: &Receiver<(PtyInstruction, zellij_utils::errors::ErrorContext)>, +) -> Vec<PaneId> { + let mut closed_panes = Vec::new(); + while let Ok((instruction, _)) = pty_receiver.try_recv() { + if let PtyInstruction::ClosePane(pane_id, _) = instruction { + closed_panes.push(pane_id); + } + } + closed_panes +} + +/// Collect all unload plugin messages from the plugin receiver +fn collect_unload_plugin_messages( + plugin_receiver: &Receiver<(PluginInstruction, zellij_utils::errors::ErrorContext)>, +) -> Vec<u32> { + let mut unloaded_plugins = Vec::new(); + while let Ok((instruction, _)) = plugin_receiver.try_recv() { + if let PluginInstruction::Unload(plugin_id) = instruction { + unloaded_plugins.push(plugin_id); + } + } + unloaded_plugins +} + +#[test] +fn test_apply_empty_layout() { + let kdl_layout = r#" + layout { + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, // blocking_terminal + ); + + let result = applier.apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], // new_floating_terminal_ids + HashMap::new(), // new_plugin_ids + 1, // client_id + ); + + assert!(result.is_ok()); + + let snapshot = take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + ); + + assert_snapshot!(snapshot); +} + +#[test] +fn test_apply_simple_two_pane_layout() { + let kdl_layout = r#" + layout { + pane + pane + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None), (2, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + let snapshot = take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + ); + + assert_snapshot!(snapshot); +} + +#[test] +fn test_apply_three_pane_layout() { + let kdl_layout = r#" + layout { + pane + pane + pane + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_horizontal_split_with_sizes() { + let kdl_layout = r#" + layout { + pane split_direction="Horizontal" { + pane size="30%" + pane size="70%" + } + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None), (2, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_vertical_split_with_sizes() { + let kdl_layout = r#" + layout { + pane split_direction="Vertical" { + pane size="60%" + pane size="40%" + } + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None), (2, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_nested_layout() { + let kdl_layout = r#" + layout { + pane split_direction="Vertical" { + pane size="60%" + pane size="40%" split_direction="Horizontal" { + pane + pane + } + } + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_layout_with_focus() { + let kdl_layout = r#" + layout { + pane + pane focus=true + pane + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Snapshot should show FOCUS: Some(Terminal(2)) + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_layout_with_commands() { + let kdl_layout = r#" + layout { + pane command="htop" + pane command="tail" { + args "-f" "/var/log/syslog" + } + pane + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_layout_with_named_panes() { + let kdl_layout = r#" + layout { + pane name="editor" + pane name="terminal" + pane name="logs" + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_layout_with_borderless_panes() { + let kdl_layout = r#" + layout { + pane borderless=true + pane + pane borderless=true + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Snapshot should show viewport adjusted for borderless panes + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_layout_with_floating_panes() { + let kdl_layout = r#" + layout { + pane + pane + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + } + } + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None), (2, None)]; + let floating_terminal_ids = vec![(3, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + let should_show_floating = applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + assert_eq!(should_show_floating, true); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_layout_with_floating_pane_with_command() { + let kdl_layout = r#" + layout { + pane + floating_panes { + pane { + x 10 + y 10 + width 50 + height 25 + command "htop" + } + } + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None)]; + let floating_terminal_ids = vec![(2, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_layout_with_mixed_tiled_and_floating_panes() { + let kdl_layout = r#" + layout { + pane split_direction="Vertical" { + pane size="60%" command="vim" + pane size="40%" split_direction="Horizontal" { + pane name="terminal" focus=true + pane command="tail" { + args "-f" "/var/log/syslog" + } + } + } + floating_panes { + pane { + x 5 + y 5 + width 40 + height 15 + command "htop" + name "monitor" + } + pane { + x "50%" + y 10 + width 45 + height 20 + name "notes" + } + } + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + let floating_terminal_ids = vec![(4, None), (5, None)]; + + let size = Size { + cols: 150, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + let should_show_floating = applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + assert_eq!(should_show_floating, true); + + // Snapshot should show: + // - 3 tiled panes with correct geometries, commands, and names + // - 2 floating panes with correct positions and properties + // - Focus on terminal pane (Terminal(2)) + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_reapply_layout_exact_match() { + // First apply initial layout + let initial_kdl = r#" + layout { + pane command="htop" + pane command="vim" + pane + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Now reapply with commands in different positions + let new_kdl = r#" + layout { + pane + pane command="htop" + pane command="vim" + } + "#; + + let (new_layout, _) = parse_kdl_layout(new_kdl); + + applier + .apply_tiled_panes_layout_to_existing_panes(&new_layout) + .unwrap(); + + let snapshot = take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + ); + + // Snapshot will show panes matched by command and repositioned + assert_snapshot!(snapshot); +} + +#[test] +fn test_reapply_layout_logical_position_match() { + // Apply initial layout - 3 panes in horizontal split + let initial_kdl = r#" + layout { + pane + pane + pane + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Reapply DIFFERENT layout - still 3 panes but with different split + // This tests logical position matching (position 0, 1, 2) without exact command match + let new_kdl = r#" + layout { + pane split_direction="Vertical" { + pane size="50%" + pane size="50%" + } + pane + } + "#; + + let (new_layout, _) = parse_kdl_layout(new_kdl); + + applier + .apply_tiled_panes_layout_to_existing_panes(&new_layout) + .unwrap(); + + // Panes should be repositioned according to new layout structure + // while being matched by their logical positions (0, 1, 2) + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_reapply_layout_with_more_positions() { + // Apply initial layout with 2 panes + let initial_kdl = r#" + layout { + pane + pane + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Reapply with 4 positions (but we only have 2 panes) + let new_kdl = r#" + layout { + pane + pane + pane + pane + } + "#; + + let (new_layout, _) = parse_kdl_layout(new_kdl); + + applier + .apply_tiled_panes_layout_to_existing_panes(&new_layout) + .unwrap(); + + // Should show 2 panes filling first 2 positions, remaining positions empty + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_reapply_floating_pane_layout() { + // Apply initial layout + let initial_kdl = r#" + layout { + pane + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + } + } + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None)]; + let floating_terminal_ids = vec![(2, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + // Reapply with different position + let new_kdl = r#" + layout { + pane + floating_panes { + pane { + x 20 + y 20 + width 50 + height 25 + } + } + } + "#; + + let (_, new_floating_layout) = parse_kdl_layout(new_kdl); + + applier + .apply_floating_panes_layout_to_existing_panes(&new_floating_layout) + .unwrap(); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_complex_nested_layout() { + let kdl_layout = r#" + layout { + pane split_direction="Vertical" { + pane size="25%" + pane size="50%" split_direction="Horizontal" { + pane size="60%" + pane size="40%" split_direction="Vertical" { + pane + pane + } + } + pane size="25%" + } + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None), (2, None), (3, None), (4, None), (5, None)]; + + let size = Size { + cols: 200, + rows: 60, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_layout_with_stacked_panes() { + let kdl_layout = r#" + layout { + pane split_direction="Vertical" { + pane size="70%" stacked=true { + pane name="editor-1" + pane name="editor-2" + pane name="editor-3" + } + pane size="30%" + } + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None), (2, None), (3, None), (4, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Snapshot should show: + // - 4 panes total (3 in stack + 1 regular) + // - Stack panes should have stacked field set with same stack_id + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_layout_with_multiple_stacks() { + let kdl_layout = r#" + layout { + pane split_direction="Vertical" { + pane size="50%" stacked=true { + pane name="left-1" + pane name="left-2" + } + pane size="50%" stacked=true { + pane name="right-1" + pane name="right-2" + pane name="right-3" + } + } + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None), (2, None), (3, None), (4, None), (5, None)]; + + let size = Size { + cols: 150, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Snapshot should show: + // - 5 panes in 2 different stacks (different stack_ids) + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_layout_with_plugin_panes() { + let kdl_layout = r#" + layout { + pane + pane { + plugin location="zellij:tab-bar" + } + pane { + plugin location="zellij:status-bar" + } + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None)]; + + // Create plugin IDs - need to match the RunPluginOrAlias from the layout + + let mut new_plugin_ids = HashMap::new(); + + // Create plugin aliases that match the layout + let tab_bar_plugin = RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(); + let status_bar_plugin = + RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(); + + new_plugin_ids.insert(tab_bar_plugin, vec![100]); + new_plugin_ids.insert(status_bar_plugin, vec![101]); + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + new_plugin_ids, + 1, + ) + .unwrap(); + + // Snapshot should show: + // - 1 terminal pane + // - 2 plugin panes with correct plugin locations + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_layout_with_mixed_plugin_and_terminal_panes() { + let kdl_layout = r#" + layout { + pane split_direction="Vertical" { + pane size="20%" { + plugin location="file:///path/to/filebrowser.wasm" + } + pane size="60%" split_direction="Horizontal" { + pane command="vim" + pane command="cargo" { + args "watch" "-x" "test" + } + } + pane size="20%" { + plugin location="zellij:compact-bar" + } + } + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None), (2, None)]; + + let mut new_plugin_ids = HashMap::new(); + + let filebrowser_plugin = + RunPluginOrAlias::from_url("file:///path/to/filebrowser.wasm", &None, None, None).unwrap(); + let compact_bar_plugin = + RunPluginOrAlias::from_url("zellij:compact-bar", &None, None, None).unwrap(); + + new_plugin_ids.insert(filebrowser_plugin, vec![102]); + new_plugin_ids.insert(compact_bar_plugin, vec![103]); + + let size = Size { + cols: 200, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + new_plugin_ids, + 1, + ) + .unwrap(); + + // Snapshot should show: + // - 2 terminal panes with commands + // - 2 plugin panes with different locations + // - Correct size distribution (20%, 60%, 20%) + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_apply_layout_with_missing_plugin_ids() { + let kdl_layout = r#" + layout { + pane + pane { + plugin location="zellij:tab-bar" + } + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + let terminal_ids = vec![(1, None)]; + // Don't provide plugin IDs - empty HashMap + let new_plugin_ids = HashMap::new(); + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + let result = applier.apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + new_plugin_ids, + 1, + ); + + // This should return an error - missing plugin ID + assert!(result.is_err()); +} + +#[test] +fn test_apply_layout_with_excess_terminal_ids() { + let kdl_layout = r#" + layout { + pane + pane + } + "#; + + let (tiled_layout, floating_layout) = parse_kdl_layout(kdl_layout); + // Provide more terminal IDs than needed + let terminal_ids = vec![(1, None), (2, None), (3, None), (4, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + let result = applier.apply_layout( + tiled_layout, + floating_layout, + terminal_ids, + vec![], + HashMap::new(), + 1, + ); + + assert!(result.is_ok()); + + // Snapshot should show only 2 panes created + // Excess IDs should be closed by the applier + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_layout_basic_with_both_tiled_and_floating() { + // Setup: Apply initial layout with 2 tiled panes + 1 floating pane + let initial_kdl = r#" + layout { + pane command="htop" + pane command="vim" + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + command "tail" + args "-f" "/var/log/syslog" + } + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None)]; + let floating_terminal_ids = vec![(3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + // Now override with different layout (2 tiled + 1 floating) + let override_kdl = r#" + layout { + pane command="top" + pane command="htop" + floating_panes { + pane { + x 20 + y 20 + width 50 + height 25 + command "watch" + args "df" "-h" + } + } + } + "#; + + let (override_tiled, override_floating) = parse_kdl_layout(override_kdl); + let new_terminal_ids = vec![(4, None)]; + let new_floating_terminal_ids = vec![(5, None)]; + + let retain_existing_terminal_panes = false; + let retain_existing_plugin_panes = false; + let should_show_floating = applier + .override_layout( + override_tiled, + override_floating, + new_terminal_ids, + new_floating_terminal_ids, + HashMap::new(), + retain_existing_terminal_panes, + retain_existing_plugin_panes, + 1, + ) + .unwrap(); + + // Should show floating panes + assert_eq!(should_show_floating, true); + + // Verify close messages were sent for vim (Terminal(2)) and tail (Terminal(3)) + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!(closed_panes.len(), 2); + assert!(closed_panes.contains(&PaneId::Terminal(2))); // vim + assert!(closed_panes.contains(&PaneId::Terminal(3))); // tail + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_layout_hide_floating_panes_true() { + // Setup: Initial layout with floating panes + let initial_kdl = r#" + layout { + pane + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + } + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None)]; + let floating_terminal_ids = vec![(2, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + // Override with layout that has hide_floating_panes true + let override_kdl = r#" + layout { + hide_floating_panes true + pane + pane + floating_panes { + pane { + x 15 + y 15 + width 45 + height 22 + } + } + } + "#; + + let (override_tiled, override_floating) = parse_kdl_layout(override_kdl); + let new_terminal_ids = vec![(3, None)]; + let new_floating_terminal_ids = vec![(4, None)]; + + let retain_existing_terminal_panes = false; + let retain_existing_plugin_panes = false; + let should_show_floating = applier + .override_layout( + override_tiled, + override_floating, + new_terminal_ids, + new_floating_terminal_ids, + HashMap::new(), + retain_existing_terminal_panes, + retain_existing_plugin_panes, + 1, + ) + .unwrap(); + + // Should NOT show floating panes because of hide_floating_panes + assert_eq!(should_show_floating, false); + + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!(closed_panes.len(), 0); + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_layout_show_floating_panes() { + // Setup: Initial layout + let initial_kdl = r#" + layout { + pane + pane + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Override with layout containing floating panes + let override_kdl = r#" + layout { + pane + floating_panes { + pane { + x 20 + y 10 + width 50 + height 30 + } + } + } + "#; + + let (override_tiled, override_floating) = parse_kdl_layout(override_kdl); + let new_floating_terminal_ids = vec![(3, None)]; + + let retain_existing_terminal_panes = false; + let retain_existing_plugin_panes = false; + let should_show_floating = applier + .override_layout( + override_tiled, + override_floating, + vec![], + new_floating_terminal_ids, + HashMap::new(), + retain_existing_terminal_panes, + retain_existing_plugin_panes, + 1, + ) + .unwrap(); + + // Should show floating panes + assert_eq!(should_show_floating, true); + + // Verify close message was sent for one tiled pane (Terminal(2)) + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!(closed_panes.len(), 1); + assert!(closed_panes.contains(&PaneId::Terminal(2))); + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +// ============================================================================ +// Suite 2: override_tiled_panes_layout_for_existing_panes Tests +// ============================================================================ + +#[test] +fn test_override_tiled_exact_match_preservation_commands() { + // Setup: Apply initial layout with 3 panes running different commands + let initial_kdl = r#" + layout { + pane command="htop" + pane command="vim" + pane command="tail" { + args "-f" "/var/log/syslog" + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: New layout with only htop and vim in different positions + let override_kdl = r#" + layout { + pane command="vim" + pane command="htop" + } + "#; + + let (override_tiled, _) = parse_kdl_layout(override_kdl); + + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + vec![], + &mut HashMap::new(), + false, + false, + 1, + ) + .unwrap(); + + // htop and vim panes should be preserved (same PaneIds: Terminal(1) and Terminal(2)) + // tail pane should be closed + // Panes should be repositioned to new layout positions + + // Verify close message was sent for tail pane (Terminal(3)) + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!(closed_panes.len(), 1); + assert!(closed_panes.contains(&PaneId::Terminal(3))); // tail + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_tiled_exact_match_preservation_plugins() { + // Setup: Initial layout with 2 terminal panes + 1 plugin pane + let initial_kdl = r#" + layout { + pane + pane + pane { + plugin location="zellij:tab-bar" + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None)]; + + let mut initial_plugin_ids = HashMap::new(); + let tab_bar_plugin = RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(); + initial_plugin_ids.insert(tab_bar_plugin.clone(), vec![100]); + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + initial_plugin_ids, + 1, + ) + .unwrap(); + + // Override: New layout with only the plugin pane + let override_kdl = r#" + layout { + pane { + plugin location="zellij:tab-bar" + } + } + "#; + + let (override_tiled, _) = parse_kdl_layout(override_kdl); + + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + vec![], + &mut HashMap::new(), + false, + false, + 1, + ) + .unwrap(); + + // Plugin pane should be preserved + // Terminal panes should be closed + // Total pane count is 1 + + // Verify close messages were sent for both terminal panes (Terminal(1) and Terminal(2)) + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!(closed_panes.len(), 2); + assert!(closed_panes.contains(&PaneId::Terminal(1))); + assert!(closed_panes.contains(&PaneId::Terminal(2))); + + // No plugins should be unloaded (plugin is preserved) + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_tiled_all_panes_closed_no_matches() { + // Setup: 3 panes running htop, vim, tail + let initial_kdl = r#" + layout { + pane command="htop" + pane command="vim" + pane command="tail" { + args "-f" "/var/log/syslog" + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: New layout with 3 completely different commands + let override_kdl = r#" + layout { + pane command="cargo" { + args "watch" + } + pane command="npm" { + args "start" + } + pane command="python" { + args "-m" "http.server" + } + } + "#; + + let (override_tiled, _) = parse_kdl_layout(override_kdl); + let new_terminal_ids = vec![(4, None), (5, None), (6, None)]; + + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + new_terminal_ids, + &mut HashMap::new(), + false, + false, + 1, + ) + .unwrap(); + + // All original pane IDs gone (1, 2, 3 should not be present) + // 3 new panes with new IDs (4, 5, 6) + // Total pane count is 3 + + // Verify close messages were sent for all original panes (Terminal(1), Terminal(2), Terminal(3)) + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!(closed_panes.len(), 3); + assert!(closed_panes.contains(&PaneId::Terminal(1))); // htop + assert!(closed_panes.contains(&PaneId::Terminal(2))); // vim + assert!(closed_panes.contains(&PaneId::Terminal(3))); // tail + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_tiled_mixed_some_matches_some_new() { + // Setup: 2 panes - one running htop, one generic shell (no command) + let initial_kdl = r#" + layout { + pane command="htop" + pane + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Layout with htop, vim, and generic shell + let override_kdl = r#" + layout { + pane command="htop" + pane command="vim" + pane + } + "#; + + let (override_tiled, _) = parse_kdl_layout(override_kdl); + let new_terminal_ids = vec![(3, None), (4, None)]; + + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + new_terminal_ids, + &mut HashMap::new(), + false, + false, + 1, + ) + .unwrap(); + + // htop preserved with same ID (Terminal(1)) + // Original shell pane closed (generic shells are NOT exact matches) + // 2 new panes created (vim and new shell) + // Total pane count is 3 + + // Verify close message was not sent for original shell pane (Terminal(2)) + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!(closed_panes.len(), 0); + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_tiled_new_panes_for_unmatched_positions() { + // Setup: 1 pane running htop + let initial_kdl = r#" + layout { + pane command="htop" + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None)]; + + let size = Size { + cols: 150, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Layout with 4 positions + let override_kdl = r#" + layout { + pane command="htop" + pane command="vim" + pane + pane + } + "#; + + let (override_tiled, _) = parse_kdl_layout(override_kdl); + let new_terminal_ids = vec![(2, None), (3, None), (4, None)]; + + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + new_terminal_ids, + &mut HashMap::new(), + false, + false, + 1, + ) + .unwrap(); + + // 1 original htop pane preserved (Terminal(1)) + // 3 new panes created (Terminal(2), Terminal(3), Terminal(4)) + // Total 4 panes + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_tiled_focus_on_new_pane() { + // Setup: 2 panes, first one focused + let initial_kdl = r#" + layout { + pane focus=true + pane + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Layout with 3 panes where second pane has focus=true + let override_kdl = r#" + layout { + pane + pane focus=true + pane + } + "#; + + let (override_tiled, _) = parse_kdl_layout(override_kdl); + let new_terminal_ids = vec![(3, None)]; + + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + new_terminal_ids, + &mut HashMap::new(), + false, + false, + 1, + ) + .unwrap(); + + // focus_pane_id should point to the newly created middle pane (Terminal(3)) + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_tiled_focus_when_focused_pane_closed() { + // Setup: 3 panes, middle one focused running vim + let initial_kdl = r#" + layout { + pane command="htop" + pane command="vim" focus=true + pane command="tail" { + args "-f" "/var/log/syslog" + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Layout with 2 panes running htop and cargo (not vim) + let override_kdl = r#" + layout { + pane command="htop" + pane command="cargo" { + args "check" + } + } + "#; + + let (override_tiled, _) = parse_kdl_layout(override_kdl); + let new_terminal_ids = vec![(4, None)]; + + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + new_terminal_ids, + &mut HashMap::new(), + false, + false, + 1, + ) + .unwrap(); + + // Focused pane (vim) no longer exists + // Focus should be moved to one of the remaining panes + + // Verify close messages were sent for vim (Terminal(2)) and tail (Terminal(3)) + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!(closed_panes.len(), 2); + assert!(closed_panes.contains(&PaneId::Terminal(2))); // vim (focused) + assert!(closed_panes.contains(&PaneId::Terminal(3))); // tail + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_tiled_empty_layout_closes_all() { + // Setup: 3 panes running various commands + let initial_kdl = r#" + layout { + pane command="htop" + pane command="vim" + pane command="tail" { + args "-f" "/var/log/syslog" + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Empty layout + let override_kdl = r#" + layout { + } + "#; + + let (override_tiled, _) = parse_kdl_layout(override_kdl); + + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + vec![], + &mut HashMap::new(), + false, + false, + 1, + ) + .unwrap(); + + // No panes in snapshot + // Pane count is 0 + + // Verify close messages were sent for all panes (Terminal(1), Terminal(2), Terminal(3)) + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!(closed_panes.len(), 3); + assert!(closed_panes.contains(&PaneId::Terminal(1))); // htop + assert!(closed_panes.contains(&PaneId::Terminal(2))); // vim + assert!(closed_panes.contains(&PaneId::Terminal(3))); // tail + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +// ============================================================================ +// Suite 3: override_floating_panes_layout_for_existing_panes Tests +// ============================================================================ + +#[test] +fn test_override_floating_exact_match_preservation() { + // Setup: 2 floating panes running htop and vim + let initial_kdl = r#" + layout { + pane + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + command "htop" + } + pane { + x 20 + y 20 + width 50 + height 25 + command "vim" + } + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None)]; + let floating_terminal_ids = vec![(2, None), (3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Layout with htop at different x/y position + let override_kdl = r#" + layout { + pane + floating_panes { + pane { + x 50 + y 30 + width 45 + height 22 + command "htop" + } + } + } + "#; + + let (_, override_floating) = parse_kdl_layout(override_kdl); + + applier + .override_floating_panes_layout_for_existing_panes( + &override_floating, + vec![], + &mut HashMap::new(), + false, + false, + ) + .unwrap(); + + // htop pane preserved (Terminal(2)), repositioned + // vim pane closed (Terminal(3)) + // Total floating pane count is 1 + + // Verify close message was sent for vim pane (Terminal(3)) + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!(closed_panes.len(), 1); + assert!(closed_panes.contains(&PaneId::Terminal(3))); // vim + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_floating_all_closed_no_matches() { + // Setup: 2 floating panes with specific commands + let initial_kdl = r#" + layout { + pane + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + command "htop" + } + pane { + x 20 + y 20 + width 50 + height 25 + command "vim" + } + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None)]; + let floating_terminal_ids = vec![(2, None), (3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Layout with different commands + let override_kdl = r#" + layout { + pane + floating_panes { + pane { + x 15 + y 15 + width 45 + height 22 + command "top" + } + pane { + x 25 + y 25 + width 55 + height 27 + command "emacs" + } + } + } + "#; + + let (_, override_floating) = parse_kdl_layout(override_kdl); + let new_floating_terminal_ids = vec![(4, None), (5, None)]; + + applier + .override_floating_panes_layout_for_existing_panes( + &override_floating, + new_floating_terminal_ids, + &mut HashMap::new(), + false, + false, + ) + .unwrap(); + + // Both original panes closed (IDs 2, 3 gone) + // New panes created with new IDs (4, 5) + // Pane count matches new layout (2 floating panes) + + // Verify close messages were sent for both floating panes (Terminal(2), Terminal(3)) + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!(closed_panes.len(), 2); + assert!(closed_panes.contains(&PaneId::Terminal(2))); // htop + assert!(closed_panes.contains(&PaneId::Terminal(3))); // vim + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_floating_new_panes_created() { + // Setup: 1 floating pane running htop + let initial_kdl = r#" + layout { + pane + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + command "htop" + } + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None)]; + let floating_terminal_ids = vec![(2, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Layout with 3 floating panes: htop, vim, and generic shell + let override_kdl = r#" + layout { + pane + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + command "htop" + } + pane { + x 20 + y 20 + width 50 + height 25 + command "vim" + } + pane { + x 30 + y 30 + width 45 + height 22 + } + } + } + "#; + + let (_, override_floating) = parse_kdl_layout(override_kdl); + let new_floating_terminal_ids = vec![(3, None), (4, None)]; + + applier + .override_floating_panes_layout_for_existing_panes( + &override_floating, + new_floating_terminal_ids, + &mut HashMap::new(), + false, + false, + ) + .unwrap(); + + // Original htop preserved (Terminal(2)) + // 2 new floating panes created (Terminal(3), Terminal(4)) + // Total 3 floating panes + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_floating_focus_handling() { + // Setup: 2 floating panes, one focused + let initial_kdl = r#" + layout { + pane + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + focus true + } + pane { + x 20 + y 20 + width 50 + height 25 + } + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None)]; + let floating_terminal_ids = vec![(2, None), (3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Layout with 1 new pane that has focus=true + let override_kdl = r#" + layout { + pane + floating_panes { + pane { + x 30 + y 30 + width 60 + height 30 + focus true + } + } + } + "#; + + let (_, override_floating) = parse_kdl_layout(override_kdl); + let new_floating_terminal_ids = vec![(4, None)]; + + applier + .override_floating_panes_layout_for_existing_panes( + &override_floating, + new_floating_terminal_ids, + &mut HashMap::new(), + false, + false, + ) + .unwrap(); + + // Focus should be set on newly created pane (Terminal(4)) + + // Verify close messages were sent for Terminal(3) + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!(closed_panes.len(), 1); + assert!(closed_panes.contains(&PaneId::Terminal(3))); + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_floating_position_and_size_update() { + // Setup: 1 floating pane running htop at specific position + let initial_kdl = r#" + layout { + pane + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + command "htop" + } + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None)]; + let floating_terminal_ids = vec![(2, None)]; + + let size = Size { + cols: 120, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Layout with htop at different position and size + let override_kdl = r#" + layout { + pane + floating_panes { + pane { + x 50 + y 30 + width 60 + height 30 + command "htop" + } + } + } + "#; + + let (_, override_floating) = parse_kdl_layout(override_kdl); + + applier + .override_floating_panes_layout_for_existing_panes( + &override_floating, + vec![], + &mut HashMap::new(), + false, + false, + ) + .unwrap(); + + // Same pane ID preserved (Terminal(2)) + // Geometry updated: x=50, y=30, cols=60, rows=30 + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_floating_return_value_has_panes() { + // Setup: Empty floating panes + let initial_kdl = r#" + layout { + pane + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + ) = create_layout_applier_fixtures(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Layout with 1 floating pane + let override_kdl = r#" + layout { + pane + floating_panes { + pane { + x 20 + y 10 + width 50 + height 30 + } + } + } + "#; + + let (_, override_floating) = parse_kdl_layout(override_kdl); + let new_floating_terminal_ids = vec![(2, None)]; + + let has_floating_panes = applier + .override_floating_panes_layout_for_existing_panes( + &override_floating, + new_floating_terminal_ids, + &mut HashMap::new(), + false, + false, + ) + .unwrap(); + + // Function should return true because layout has floating panes + assert_eq!(has_floating_panes, true); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_floating_return_value_no_panes() { + // Setup: 1 floating pane + let initial_kdl = r#" + layout { + pane + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + } + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None)]; + let floating_terminal_ids = vec![(2, None)]; + + let size = Size { + cols: 100, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Empty floating layout (no floating_panes block) + let override_kdl = r#" + layout { + pane + } + "#; + + let (_, override_floating) = parse_kdl_layout(override_kdl); + + let has_floating_panes = applier + .override_floating_panes_layout_for_existing_panes( + &override_floating, + vec![], + &mut HashMap::new(), + false, + false, + ) + .unwrap(); + + // Function should return false because layout has no floating panes + assert_eq!(has_floating_panes, false); + + // Verify close message was sent for floating pane (Terminal(2)) + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!(closed_panes.len(), 1); + assert!(closed_panes.contains(&PaneId::Terminal(2))); + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +// ============================================================================ +// Suite 4: Integration Tests +// ============================================================================ + +#[test] +fn test_override_full_tiled_and_floating_together() { + // Setup: Initial layout with 3 tiled panes + 2 floating panes + let initial_kdl = r#" + layout { + pane command="htop" + pane command="vim" + pane + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + command "cargo" + args "watch" + } + pane { + x 20 + y 20 + width 50 + height 25 + command "tail" + args "-f" "/var/log/syslog" + } + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + let floating_terminal_ids = vec![(4, None), (5, None)]; + + let size = Size { + cols: 150, + rows: 50, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Layout with 2 tiled (htop, npm start) + 1 floating (cargo watch) + let override_kdl = r#" + layout { + pane command="htop" + pane command="npm" { + args "start" + } + floating_panes { + pane { + x 30 + y 30 + width 60 + height 30 + command "cargo" + args "watch" + } + } + } + "#; + + let (override_tiled, override_floating) = parse_kdl_layout(override_kdl); + let new_terminal_ids = vec![(6, None)]; + + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + new_terminal_ids, + &mut HashMap::new(), + false, + false, + 1, + ) + .unwrap(); + + applier + .override_floating_panes_layout_for_existing_panes( + &override_floating, + vec![], + &mut HashMap::new(), + false, + false, + ) + .unwrap(); + + // Tiled: htop preserved (Terminal(1)), vim and shell closed, npm start created (Terminal(6)) + // Floating: cargo watch preserved (Terminal(4)), tail closed + // Total: 2 tiled + 1 floating + + // Verify close messages were sent for vim (Terminal(2)), shell (Terminal(3)), and tail (Terminal(5)) + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!(closed_panes.len(), 3); + assert!(closed_panes.contains(&PaneId::Terminal(2))); // vim + assert!(closed_panes.contains(&PaneId::Terminal(3))); // shell + assert!(closed_panes.contains(&PaneId::Terminal(5))); // tail + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_viewport_adjustment_with_borderless() { + // Setup: Initial layout with borderless panes + let initial_kdl = r#" + layout { + pane borderless=true + pane + pane borderless=true + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Layout with different borderless configuration + let override_kdl = r#" + layout { + pane + pane borderless=true + } + "#; + + let (override_tiled, _) = parse_kdl_layout(override_kdl); + + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + vec![], + &mut HashMap::new(), + false, + false, + 1, + ) + .unwrap(); + + // Viewport dimensions should be correctly adjusted for borderless panes + + // Verify close message was sent for at least the extra pane (Terminal(3)) + // Generic panes without commands don't match exactly, so all 3 may be closed + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert!(closed_panes.len() >= 1); + assert!(closed_panes.contains(&PaneId::Terminal(3))); + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_tiled_retain_terminal_panes_partial_match() { + // Test that when retain_existing_terminal_panes is true, terminal panes that don't match + // the new layout are retained instead of being closed. + // Setup: Apply initial layout with 3 panes running different commands + let initial_kdl = r#" + layout { + pane command="htop" + pane command="vim" + pane command="tail" { + args "-f" "/var/log/syslog" + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: New layout with only vim and htop (tail is not in the new layout) + let override_kdl = r#" + layout { + pane command="vim" + pane command="htop" + } + "#; + + let (override_tiled, _) = parse_kdl_layout(override_kdl); + + // Override with retain_existing_terminal_panes = true + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + vec![], + &mut HashMap::new(), + true, // retain_existing_terminal_panes + false, + 1, + ) + .unwrap(); + + // With retain_existing_terminal_panes = true: + // - NO terminal panes should be closed (not even tail) + // - All 3 original terminals (Terminal(1), Terminal(2), Terminal(3)) should still exist + // - vim and htop panes should match the new layout positions + // - tail pane should be retained and added after the matched panes + + // Verify NO close messages were sent + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!( + closed_panes.len(), + 0, + "No terminal panes should be closed when retain_existing_terminal_panes is true" + ); + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + // All 3 original terminals should still exist + assert_eq!( + tiled_panes.visible_panes_count(), + 3, + "All 3 terminal panes should be retained" + ); + + // we're not asserting a snapshot here because adding panes uses unstable sorting and so the + // test would be flaky +} + +#[test] +fn test_override_tiled_retain_terminal_panes_no_matches() { + // Test that when retain_existing_terminal_panes is true and NO panes match, + // all original terminals are retained AND new terminals are created. + // Setup: Apply initial layout with 3 panes + let initial_kdl = r#" + layout { + pane command="htop" + pane command="vim" + pane command="tail" { + args "-f" "/var/log/syslog" + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: New layout with completely different commands (no matches) + let override_kdl = r#" + layout { + pane command="cargo" + pane command="npm" + pane command="python" + } + "#; + + let (override_tiled, _) = parse_kdl_layout(override_kdl); + let new_terminal_ids = vec![(4, None), (5, None), (6, None)]; + + // Override with retain_existing_terminal_panes = true + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + new_terminal_ids, + &mut HashMap::new(), + true, // retain_existing_terminal_panes + false, + 1, + ) + .unwrap(); + + // With retain_existing_terminal_panes = true and no matches: + // - NO terminal panes should be closed + // - All 3 original terminals (Terminal(1), Terminal(2), Terminal(3)) should still exist + // - 3 NEW terminals (Terminal(4), Terminal(5), Terminal(6)) should be created + // - Total: 6 terminal panes + + // Verify NO close messages were sent + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!( + closed_panes.len(), + 0, + "No terminal panes should be closed when retain_existing_terminal_panes is true" + ); + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + // Should have 6 total panes (3 original + 3 new) + assert_eq!( + tiled_panes.visible_panes_count(), + 6, + "Should have 6 terminal panes (3 original + 3 new)" + ); + + // we're not asserting a snapshot here because adding panes uses unstable sorting and so the + // test would be flaky +} + +#[test] +fn test_override_floating_retain_terminal_panes_partial_match() { + // Test that when retain_existing_terminal_panes is true, floating terminal panes + // that don't match the new layout are retained instead of being closed. + // Setup: 1 tiled pane + 2 floating panes running htop and vim + let initial_kdl = r#" + layout { + pane + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + command "htop" + } + pane { + x 20 + y 20 + width 50 + height 25 + command "vim" + } + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None)]; + let floating_terminal_ids = vec![(2, None), (3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Layout with only htop (vim is not in the new layout) + let override_kdl = r#" + layout { + pane + floating_panes { + pane { + x 50 + y 30 + width 45 + height 22 + command "htop" + } + } + } + "#; + + let (_, override_floating) = parse_kdl_layout(override_kdl); + + // Override with retain_existing_terminal_panes = true + applier + .override_floating_panes_layout_for_existing_panes( + &override_floating, + vec![], + &mut HashMap::new(), + true, // retain_existing_terminal_panes + false, + ) + .unwrap(); + + // With retain_existing_terminal_panes = true: + // - NO terminal panes should be closed (not even vim) + // - Both original floating terminals (Terminal(2), Terminal(3)) should still exist + // - htop pane should match the new layout position + // - vim pane should be retained as a floating pane + + // Verify NO close messages were sent + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!( + closed_panes.len(), + 0, + "No terminal panes should be closed when retain_existing_terminal_panes is true" + ); + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + // Both floating panes should still exist + assert_eq!( + floating_panes.visible_panes_count(), + 2, + "Both floating terminal panes should be retained" + ); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_floating_retain_terminal_panes_no_matches() { + // Test that when retain_existing_terminal_panes is true and NO floating panes match, + // all original floating terminals are retained AND new floating terminals are created. + // Setup: 1 tiled pane + 2 floating panes running htop and vim + let initial_kdl = r#" + layout { + pane + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + command "htop" + } + pane { + x 20 + y 20 + width 50 + height 25 + command "vim" + } + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None)]; + let floating_terminal_ids = vec![(2, None), (3, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: Layout with 2 different floating panes (top and emacs) + let override_kdl = r#" + layout { + pane + floating_panes { + pane { + x 30 + y 30 + width 40 + height 20 + command "top" + } + pane { + x 40 + y 40 + width 50 + height 25 + command "emacs" + } + } + } + "#; + + let (_, override_floating) = parse_kdl_layout(override_kdl); + let new_floating_terminal_ids = vec![(4, None), (5, None)]; + + // Override with retain_existing_terminal_panes = true + applier + .override_floating_panes_layout_for_existing_panes( + &override_floating, + new_floating_terminal_ids, + &mut HashMap::new(), + true, // retain_existing_terminal_panes + false, + ) + .unwrap(); + + // With retain_existing_terminal_panes = true and no matches: + // - NO terminal panes should be closed + // - Both original floating terminals (Terminal(2), Terminal(3)) should still exist + // - 2 NEW floating terminals (Terminal(4), Terminal(5)) should be created + // - Total: 4 floating panes + + // Verify NO close messages were sent + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!( + closed_panes.len(), + 0, + "No terminal panes should be closed when retain_existing_terminal_panes is true" + ); + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + // Should have 4 floating panes (2 original + 2 new) + assert_eq!( + floating_panes.visible_panes_count(), + 4, + "Should have 4 floating panes (2 original + 2 new)" + ); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_mixed_retain_terminal_panes_both_tiled_and_floating() { + // Test that when retain_existing_terminal_panes is true, both tiled and floating + // terminal panes that don't match the new layout are retained. + // Setup: Apply initial layout with 3 tiled panes + 1 floating pane + let initial_kdl = r#" + layout { + pane command="htop" + pane command="vim" + pane command="tail" { + args "-f" "/var/log/syslog" + } + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + command "watch" + args "df" "-h" + } + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None), (3, None)]; + let floating_terminal_ids = vec![(4, None)]; + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + floating_terminal_ids, + HashMap::new(), + 1, + ) + .unwrap(); + + // Override: 2 tiled (top, htop) + 1 floating (different watch command) + let override_kdl = r#" + layout { + pane command="top" + pane command="htop" + floating_panes { + pane { + x 20 + y 20 + width 50 + height 25 + command "watch" + args "free" "-h" + } + } + } + "#; + + let (override_tiled, override_floating) = parse_kdl_layout(override_kdl); + let new_terminal_ids = vec![(5, None)]; + let new_floating_terminal_ids = vec![(6, None)]; + + let retain_existing_terminal_panes = true; + let retain_existing_plugin_panes = false; + applier + .override_layout( + override_tiled, + override_floating, + new_terminal_ids, + new_floating_terminal_ids, + HashMap::new(), + retain_existing_terminal_panes, + retain_existing_plugin_panes, + 1, + ) + .unwrap(); + + // With retain_existing_terminal_panes = true: + // - NO terminal panes should be closed (neither tiled nor floating) + // - Original tiled terminals: Terminal(1), Terminal(2), Terminal(3) retained + // (htop matches, so it's reused; vim and tail are retained) + // - Original floating terminal: Terminal(4) retained + // - New tiled terminal: Terminal(5) created (for top, htop matches) + // - New floating terminal: Terminal(6) created (different watch command) + + // Verify NO close messages were sent + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!( + closed_panes.len(), + 0, + "No terminal panes should be closed when retain_existing_terminal_panes is true" + ); + + // No plugins should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert!(unloaded_plugins.is_empty()); + + // Check tiled pane count (3 original + 1 new = 4) + assert_eq!( + tiled_panes.visible_panes_count(), + 4, + "Should have 4 tiled panes (3 original + 1 new)" + ); + + // Check floating pane count (1 original + 1 new = 2) + assert_eq!( + floating_panes.visible_panes_count(), + 2, + "Should have 2 floating panes (1 original + 1 new)" + ); + + // we're not asserting a snapshot here because adding panes uses unstable sorting and so the + // test would be flaky +} + +#[test] +fn test_override_retain_terminal_but_close_plugin_panes() { + // Test that when retain_existing_terminal_panes is true, the flag ONLY affects + // terminal panes and plugin panes are still closed as normal. + // Setup: Initial layout with 2 terminal panes + 1 plugin pane + let initial_kdl = r#" + layout { + pane command="htop" + pane command="vim" + pane { + plugin location="zellij:tab-bar" + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + let terminal_ids = vec![(1, None), (2, None)]; + + let mut initial_plugin_ids = HashMap::new(); + let tab_bar_plugin = RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(); + initial_plugin_ids.insert(tab_bar_plugin.clone(), vec![100]); + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + initial_plugin_ids, + 1, + ) + .unwrap(); + + // Override: New layout with only htop (vim and tab-bar plugin not in new layout) + let override_kdl = r#" + layout { + pane command="htop" + } + "#; + + let (override_tiled, _) = parse_kdl_layout(override_kdl); + + // Override with retain_existing_terminal_panes = true + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + vec![], + &mut HashMap::new(), + true, // retain_existing_terminal_panes + false, + 1, + ) + .unwrap(); + + // With retain_existing_terminal_panes = true: + // - Terminal panes NOT closed: Terminal(1) matched (htop), Terminal(2) retained (vim) + // - Plugin pane IS closed: Plugin(100) unloaded (tab-bar) + // - Both terminals should exist, but plugin should be gone + + // No terminal panes should be closed + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!( + closed_panes.len(), + 0, + "No terminal panes should be closed when retain_existing_terminal_panes is true" + ); + + // Plugin pane should be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert_eq!( + unloaded_plugins.len(), + 1, + "Plugin pane should be unloaded even with retain_existing_terminal_panes" + ); + assert!( + unloaded_plugins.contains(&100), + "Tab-bar plugin (100) should be unloaded" + ); + + // Both terminals should exist + assert_eq!( + tiled_panes.visible_panes_count(), + 2, + "Both terminal panes should be retained" + ); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} + +#[test] +fn test_override_tiled_retain_plugin_panes_partial_match() { + // Verify that when retain_existing_plugin_panes = true, plugin panes that don't match + // the new layout are retained instead of being closed + let initial_kdl = r#" + layout { + pane + pane { + plugin location="zellij:tab-bar" + } + pane { + plugin location="zellij:status-bar" + } + pane { + plugin location="file:///path/to/custom.wasm" + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + + let terminal_ids = vec![(1, None)]; + + let mut initial_plugin_ids = HashMap::new(); + let tab_bar = RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(); + let status_bar = RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(); + let custom = + RunPluginOrAlias::from_url("file:///path/to/custom.wasm", &None, None, None).unwrap(); + initial_plugin_ids.insert(tab_bar, vec![100]); + initial_plugin_ids.insert(status_bar, vec![101]); + initial_plugin_ids.insert(custom, vec![102]); + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + initial_plugin_ids, + 1, + ) + .unwrap(); + + let override_kdl = r#" + layout { + pane + pane { + plugin location="zellij:tab-bar" + } + pane { + plugin location="zellij:status-bar" + } + } + "#; + + let (override_tiled, _) = parse_kdl_layout(override_kdl); + + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + vec![], + &mut HashMap::new(), + false, + true, + 1, + ) + .unwrap(); + + // Verify NO plugin panes were unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert_eq!( + unloaded_plugins.len(), + 0, + "No plugin panes should be unloaded when retain_existing_plugin_panes is true" + ); + + // No terminals should be closed + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert!(closed_panes.is_empty()); + + // All 4 panes should exist (1 terminal + 3 plugins) + assert_eq!( + tiled_panes.visible_panes_count(), + 4, + "All plugin panes should be retained" + ); +} + +#[test] +fn test_override_tiled_retain_plugin_panes_no_matches() { + // When NO plugins match the new layout, all original plugins are retained AND new plugins are created + let initial_kdl = r#" + layout { + pane + pane { + plugin location="zellij:tab-bar" + } + pane { + plugin location="zellij:status-bar" + } + pane { + plugin location="zellij:compact-bar" + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + + let terminal_ids = vec![(1, None)]; + + let mut initial_plugin_ids = HashMap::new(); + let tab_bar = RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(); + let status_bar = RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(); + let compact_bar = RunPluginOrAlias::from_url("zellij:compact-bar", &None, None, None).unwrap(); + initial_plugin_ids.insert(tab_bar, vec![100]); + initial_plugin_ids.insert(status_bar, vec![101]); + initial_plugin_ids.insert(compact_bar, vec![102]); + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + initial_plugin_ids, + 1, + ) + .unwrap(); + + let override_kdl = r#" + layout { + pane + pane { + plugin location="file:///path/to/plugin1.wasm" + } + pane { + plugin location="file:///path/to/plugin2.wasm" + } + pane { + plugin location="file:///path/to/plugin3.wasm" + } + } + "#; + + let (override_tiled, _) = parse_kdl_layout(override_kdl); + + let mut override_plugin_ids = HashMap::new(); + let plugin1 = + RunPluginOrAlias::from_url("file:///path/to/plugin1.wasm", &None, None, None).unwrap(); + let plugin2 = + RunPluginOrAlias::from_url("file:///path/to/plugin2.wasm", &None, None, None).unwrap(); + let plugin3 = + RunPluginOrAlias::from_url("file:///path/to/plugin3.wasm", &None, None, None).unwrap(); + override_plugin_ids.insert(plugin1, vec![103]); + override_plugin_ids.insert(plugin2, vec![104]); + override_plugin_ids.insert(plugin3, vec![105]); + + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + vec![], + &mut override_plugin_ids, + false, + true, + 1, + ) + .unwrap(); + + // Verify NO plugin panes were unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert_eq!( + unloaded_plugins.len(), + 0, + "No plugin panes should be unloaded when retain_existing_plugin_panes is true" + ); + + // Total: 1 terminal + 6 plugins (3 original + 3 new) + assert_eq!( + tiled_panes.visible_panes_count(), + 7, + "All original plugins retained and new plugins created" + ); +} + +#[test] +fn test_override_floating_retain_plugin_panes_partial_match() { + // Floating plugin panes that don't match the new layout are retained + let initial_kdl = r#" + layout { + pane + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + plugin location="zellij:tab-bar" + } + pane { + x 20 + y 20 + width 50 + height 25 + plugin location="zellij:status-bar" + } + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + + let terminal_ids = vec![(1, None)]; + + let mut initial_plugin_ids = HashMap::new(); + let tab_bar = RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(); + let status_bar = RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(); + initial_plugin_ids.insert(tab_bar, vec![100]); + initial_plugin_ids.insert(status_bar, vec![101]); + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + initial_plugin_ids, + 1, + ) + .unwrap(); + + let override_kdl = r#" + layout { + pane + floating_panes { + pane { + x 15 + y 15 + width 45 + height 22 + plugin location="zellij:tab-bar" + } + } + } + "#; + + let (_, override_floating) = parse_kdl_layout(override_kdl); + + applier + .override_floating_panes_layout_for_existing_panes( + &override_floating, + vec![], + &mut HashMap::new(), + false, + true, + ) + .unwrap(); + + // Verify NO plugin panes were unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert_eq!( + unloaded_plugins.len(), + 0, + "No floating plugin panes should be unloaded when retain_existing_plugin_panes is true" + ); + + // No terminals should be closed + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert!(closed_panes.is_empty()); + + // Both floating plugins should still exist + assert_eq!( + floating_panes.visible_panes_count(), + 2, + "Both floating plugin panes should be retained" + ); + + // Verify tiled pane exists + assert_eq!(tiled_panes.visible_panes_count(), 1); +} + +#[test] +fn test_override_floating_retain_plugin_panes_no_matches() { + // All original floating plugins are retained AND new floating plugins are created when there are no matches + let initial_kdl = r#" + layout { + pane + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + plugin location="zellij:tab-bar" + } + pane { + x 20 + y 20 + width 50 + height 25 + plugin location="zellij:status-bar" + } + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + + let terminal_ids = vec![(1, None)]; + + let mut initial_plugin_ids = HashMap::new(); + let tab_bar = RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(); + let status_bar = RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(); + initial_plugin_ids.insert(tab_bar, vec![100]); + initial_plugin_ids.insert(status_bar, vec![101]); + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + initial_plugin_ids, + 1, + ) + .unwrap(); + + let override_kdl = r#" + layout { + pane + floating_panes { + pane { + x 15 + y 15 + width 45 + height 22 + plugin location="file:///path/to/plugin1.wasm" + } + pane { + x 25 + y 25 + width 55 + height 27 + plugin location="file:///path/to/plugin2.wasm" + } + } + } + "#; + + let (_, override_floating) = parse_kdl_layout(override_kdl); + + let mut override_plugin_ids = HashMap::new(); + let plugin1 = + RunPluginOrAlias::from_url("file:///path/to/plugin1.wasm", &None, None, None).unwrap(); + let plugin2 = + RunPluginOrAlias::from_url("file:///path/to/plugin2.wasm", &None, None, None).unwrap(); + override_plugin_ids.insert(plugin1, vec![102]); + override_plugin_ids.insert(plugin2, vec![103]); + + applier + .override_floating_panes_layout_for_existing_panes( + &override_floating, + vec![], + &mut override_plugin_ids, + false, + true, + ) + .unwrap(); + + // Verify NO plugin panes were unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert_eq!( + unloaded_plugins.len(), + 0, + "No floating plugin panes should be unloaded when retain_existing_plugin_panes is true" + ); + + // Total: 4 floating plugins (2 original + 2 new) + assert_eq!( + floating_panes.visible_panes_count(), + 4, + "All original floating plugins retained and new plugins created" + ); + + // 1 tiled terminal pane + assert_eq!(tiled_panes.visible_panes_count(), 1); +} + +#[test] +fn test_override_mixed_retain_plugin_panes_both_tiled_and_floating() { + // Both tiled and floating plugin panes are retained when the flag is true + let initial_kdl = r#" + layout { + pane command="htop" + pane command="vim" + pane { + plugin location="zellij:tab-bar" + } + floating_panes { + pane { + x 10 + y 10 + width 40 + height 20 + plugin location="zellij:status-bar" + } + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + + let terminal_ids = vec![(1, None), (2, None)]; + + let mut initial_plugin_ids = HashMap::new(); + let tab_bar = RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(); + let status_bar = RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(); + initial_plugin_ids.insert(tab_bar, vec![100]); + initial_plugin_ids.insert(status_bar, vec![101]); + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + initial_plugin_ids, + 1, + ) + .unwrap(); + + let override_kdl = r#" + layout { + pane command="htop" + pane { + plugin location="zellij:tab-bar" + } + pane { + plugin location="zellij:compact-bar" + } + floating_panes { + pane { + x 20 + y 20 + width 50 + height 25 + plugin location="file:///path/to/custom.wasm" + } + } + } + "#; + + let (override_tiled, override_floating) = parse_kdl_layout(override_kdl); + + let mut override_plugin_ids = HashMap::new(); + let compact_bar = RunPluginOrAlias::from_url("zellij:compact-bar", &None, None, None).unwrap(); + let custom = + RunPluginOrAlias::from_url("file:///path/to/custom.wasm", &None, None, None).unwrap(); + override_plugin_ids.insert(compact_bar, vec![102]); + override_plugin_ids.insert(custom, vec![103]); + + let retain_existing_terminal_panes = false; + let retain_existing_plugin_panes = true; + applier + .override_layout( + override_tiled, + override_floating, + vec![], + vec![], + override_plugin_ids, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + 1, + ) + .unwrap(); + + // Verify NO plugin panes were unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert_eq!( + unloaded_plugins.len(), + 0, + "No plugin panes should be unloaded when retain_existing_plugin_panes is true" + ); + + // vim terminal should be closed (doesn't match layout) + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!(closed_panes.len(), 1, "vim terminal should be closed"); + assert!( + closed_panes.contains(&PaneId::Terminal(2)), + "Terminal(2) (vim) should be closed" + ); + + // Verify plugins exist (exact counts may vary based on where retained panes land) + assert!( + tiled_panes.visible_panes_count() >= 2, + "At least htop and matched plugins" + ); + assert!( + floating_panes.visible_panes_count() >= 1, + "At least one floating plugin" + ); +} + +#[test] +fn test_override_retain_plugin_but_close_terminal_panes() { + // Verify that retain_existing_plugin_panes = true ONLY affects plugin panes; terminals are still closed normally + let initial_kdl = r#" + layout { + pane command="htop" + pane command="vim" + pane { + plugin location="zellij:tab-bar" + } + } + "#; + + let (initial_tiled, initial_floating) = parse_kdl_layout(initial_kdl); + + let terminal_ids = vec![(1, None), (2, None)]; + + let mut initial_plugin_ids = HashMap::new(); + let tab_bar = RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(); + initial_plugin_ids.insert(tab_bar, vec![100]); + + let size = Size { + cols: 120, + rows: 40, + }; + let ( + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + connected_clients, + style, + display_area, + mut tiled_panes, + mut floating_panes, + draw_pane_frames, + mut focus_pane_id, + os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + pty_receiver, + plugin_receiver, + ) = create_layout_applier_fixtures_with_receivers(size); + + let mut applier = LayoutApplier::new( + &viewport, + &senders, + &sixel_image_store, + &link_handler, + &terminal_emulator_colors, + &terminal_emulator_color_codes, + &character_cell_size, + &connected_clients, + &style, + &display_area, + &mut tiled_panes, + &mut floating_panes, + draw_pane_frames, + &mut focus_pane_id, + &os_api, + debug, + arrow_fonts, + styled_underlines, + explicitly_disable_kitty_keyboard_protocol, + None, + ); + + applier + .apply_layout( + initial_tiled, + initial_floating, + terminal_ids, + vec![], + initial_plugin_ids, + 1, + ) + .unwrap(); + + let override_kdl = r#" + layout { + pane command="htop" + } + "#; + + let (override_tiled, _) = parse_kdl_layout(override_kdl); + + applier + .override_tiled_panes_layout_for_existing_panes( + &override_tiled, + vec![], + &mut HashMap::new(), + false, + true, + 1, + ) + .unwrap(); + + // vim terminal should be closed + let closed_panes = collect_close_pane_messages(&pty_receiver); + assert_eq!( + closed_panes.len(), + 1, + "Terminal pane should be closed even with retain_existing_plugin_panes" + ); + assert!( + closed_panes.contains(&PaneId::Terminal(2)), + "vim (Terminal(2)) should be closed" + ); + + // Plugin should NOT be unloaded + let unloaded_plugins = collect_unload_plugin_messages(&plugin_receiver); + assert_eq!( + unloaded_plugins.len(), + 0, + "No plugin panes should be unloaded" + ); + + // Final panes: htop + tab-bar = 2 + assert_eq!( + tiled_panes.visible_panes_count(), + 2, + "htop and tab-bar should remain" + ); + + assert_snapshot!(take_pane_state_snapshot( + &tiled_panes, + &floating_panes, + &focus_pane_id, + &viewport, + &display_area, + )); +} diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_complex_nested_layout.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_complex_nested_layout.snap new file mode 100644 index 00000000..0f75e61d --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_complex_nested_layout.snap @@ -0,0 +1,48 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=200, rows=60 +DISPLAY: cols=200, rows=60 +FOCUS: None + +TILED PANES (5) + Terminal(1): + geom: x=0, y=0, cols=50, rows=60 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(2): + geom: x=50, y=0, cols=100, rows=36 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false + + Terminal(3): + geom: x=150, y=0, cols=50, rows=60 + logical_position: 2 + run: None + selectable: true + title: Pane #3 + borderless: false + + Terminal(4): + geom: x=50, y=36, cols=50, rows=24 + logical_position: 3 + run: None + selectable: true + title: Pane #4 + borderless: false + + Terminal(5): + geom: x=100, y=36, cols=50, rows=24 + logical_position: 4 + run: None + selectable: true + title: Pane #5 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_empty_layout.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_empty_layout.snap new file mode 100644 index 00000000..0104e919 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_empty_layout.snap @@ -0,0 +1,9 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: snapshot +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (0) diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_horizontal_split_with_sizes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_horizontal_split_with_sizes.snap new file mode 100644 index 00000000..f3c2acd6 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_horizontal_split_with_sizes.snap @@ -0,0 +1,24 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (2) + Terminal(1): + geom: x=0, y=0, cols=100, rows=15 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(2): + geom: x=0, y=15, cols=100, rows=35 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_borderless_panes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_borderless_panes.snap new file mode 100644 index 00000000..a5b7ff6e --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_borderless_panes.snap @@ -0,0 +1,32 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (3) + Terminal(1): + geom: x=0, y=0, cols=100, rows=17 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: true + + Terminal(2): + geom: x=0, y=17, cols=100, rows=17 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false + + Terminal(3): + geom: x=0, y=34, cols=100, rows=16 + logical_position: 2 + run: None + selectable: true + title: Pane #3 + borderless: true diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_commands.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_commands.snap new file mode 100644 index 00000000..6542b039 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_commands.snap @@ -0,0 +1,32 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (3) + Terminal(1): + geom: x=0, y=0, cols=100, rows=17 + logical_position: 0 + run: Command(htop) + selectable: true + title: htop + borderless: false + + Terminal(2): + geom: x=0, y=17, cols=100, rows=17 + logical_position: 1 + run: Command(tail) args=["-f", "/var/log/syslog"] + selectable: true + title: tail -f /var/log/syslog + borderless: false + + Terminal(3): + geom: x=0, y=34, cols=100, rows=16 + logical_position: 2 + run: None + selectable: true + title: Pane #3 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_excess_terminal_ids.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_excess_terminal_ids.snap new file mode 100644 index 00000000..c1decb8d --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_excess_terminal_ids.snap @@ -0,0 +1,24 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (2) + Terminal(1): + geom: x=0, y=0, cols=100, rows=25 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(2): + geom: x=0, y=25, cols=100, rows=25 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_floating_pane_with_command.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_floating_pane_with_command.snap new file mode 100644 index 00000000..de2feb2b --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_floating_pane_with_command.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (1) + Terminal(1): + geom: x=0, y=0, cols=100, rows=50 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + +FLOATING PANES (1) + Terminal(2): + geom: x=10, y=10, cols=50, rows=25 + logical_position: 0 + run: Command(htop) + pinned: false + selectable: true + title: htop + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_floating_panes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_floating_panes.snap new file mode 100644 index 00000000..d443e93c --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_floating_panes.snap @@ -0,0 +1,34 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (2) + Terminal(1): + geom: x=0, y=0, cols=100, rows=25 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(2): + geom: x=0, y=25, cols=100, rows=25 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false + +FLOATING PANES (1) + Terminal(3): + geom: x=10, y=10, cols=40, rows=20 + logical_position: 0 + run: None + pinned: false + selectable: true + title: Pane #3 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_focus.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_focus.snap new file mode 100644 index 00000000..ae9d8d2d --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_focus.snap @@ -0,0 +1,32 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: Some(Terminal(2)) + +TILED PANES (3) + Terminal(1): + geom: x=0, y=0, cols=100, rows=17 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(2): + geom: x=0, y=17, cols=100, rows=17 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false + + Terminal(3): + geom: x=0, y=34, cols=100, rows=16 + logical_position: 2 + run: None + selectable: true + title: Pane #3 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_missing_terminal_ids.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_missing_terminal_ids.snap new file mode 100644 index 00000000..63a159a6 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_missing_terminal_ids.snap @@ -0,0 +1,24 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (2) + Terminal(1): + geom: x=0, y=0, cols=100, rows=17 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(2): + geom: x=0, y=17, cols=100, rows=33 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_mixed_plugin_and_terminal_panes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_mixed_plugin_and_terminal_panes.snap new file mode 100644 index 00000000..c96bea2f --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_mixed_plugin_and_terminal_panes.snap @@ -0,0 +1,40 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=200, rows=50 +DISPLAY: cols=200, rows=50 +FOCUS: None + +TILED PANES (4) + Terminal(1): + geom: x=40, y=0, cols=120, rows=25 + logical_position: 1 + run: Command(vim) + selectable: true + title: vim + borderless: false + + Terminal(2): + geom: x=40, y=25, cols=120, rows=25 + logical_position: 3 + run: Command(cargo) args=["watch", "-x", "test"] + selectable: true + title: cargo watch -x test + borderless: false + + Plugin(102): + geom: x=0, y=0, cols=40, rows=50 + logical_position: 0 + run: Plugin(file:/path/to/filebrowser.wasm) + selectable: true + title: file:/path/to/filebrowser.wasm + borderless: false + + Plugin(103): + geom: x=160, y=0, cols=40, rows=50 + logical_position: 2 + run: Plugin(zellij:compact-bar) + selectable: true + title: zellij:compact-bar + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_mixed_tiled_and_floating_panes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_mixed_tiled_and_floating_panes.snap new file mode 100644 index 00000000..5dd5a441 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_mixed_tiled_and_floating_panes.snap @@ -0,0 +1,51 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=150, rows=50 +DISPLAY: cols=150, rows=50 +FOCUS: Some(Terminal(2)) + +TILED PANES (3) + Terminal(1): + geom: x=0, y=0, cols=90, rows=50 + logical_position: 0 + run: Command(vim) + selectable: true + title: vim + borderless: false + + Terminal(2): + geom: x=90, y=0, cols=60, rows=25 + logical_position: 1 + run: None + selectable: true + title: terminal + borderless: false + + Terminal(3): + geom: x=90, y=25, cols=60, rows=25 + logical_position: 2 + run: Command(tail) args=["-f", "/var/log/syslog"] + selectable: true + title: tail -f /var/log/syslog + borderless: false + +FLOATING PANES (2) + Terminal(4): + geom: x=5, y=5, cols=40, rows=15 + logical_position: 0 + run: Command(htop) + pinned: false + selectable: true + title: monitor + borderless: false + + Terminal(5): + geom: x=75, y=10, cols=45, rows=20 + logical_position: 1 + run: None + pinned: false + selectable: true + title: notes + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_multiple_stacks.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_multiple_stacks.snap new file mode 100644 index 00000000..57cfafbb --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_multiple_stacks.snap @@ -0,0 +1,53 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=150, rows=40 +DISPLAY: cols=150, rows=40 +FOCUS: None + +TILED PANES (5) + Terminal(1): + geom: x=0, y=0, cols=75, rows=39 + logical_position: 0 + stacked: 0 + run: None + selectable: true + title: left-1 + borderless: false + + Terminal(2): + geom: x=75, y=0, cols=75, rows=1 + logical_position: 1 + stacked: 1 + run: None + selectable: true + title: right-1 + borderless: false + + Terminal(3): + geom: x=0, y=39, cols=75, rows=1 + logical_position: 2 + stacked: 0 + run: None + selectable: true + title: left-2 + borderless: false + + Terminal(4): + geom: x=75, y=1, cols=75, rows=1 + logical_position: 3 + stacked: 1 + run: None + selectable: true + title: right-2 + borderless: false + + Terminal(5): + geom: x=75, y=2, cols=75, rows=38 + logical_position: 4 + stacked: 1 + run: None + selectable: true + title: right-3 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_named_panes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_named_panes.snap new file mode 100644 index 00000000..9bce2ed3 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_named_panes.snap @@ -0,0 +1,32 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (3) + Terminal(1): + geom: x=0, y=0, cols=100, rows=17 + logical_position: 0 + run: None + selectable: true + title: editor + borderless: false + + Terminal(2): + geom: x=0, y=17, cols=100, rows=17 + logical_position: 1 + run: None + selectable: true + title: terminal + borderless: false + + Terminal(3): + geom: x=0, y=34, cols=100, rows=16 + logical_position: 2 + run: None + selectable: true + title: logs + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_plugin_panes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_plugin_panes.snap new file mode 100644 index 00000000..31ab79e8 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_plugin_panes.snap @@ -0,0 +1,32 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (3) + Terminal(1): + geom: x=0, y=0, cols=120, rows=14 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Plugin(100): + geom: x=0, y=14, cols=120, rows=13 + logical_position: 1 + run: Plugin(zellij:tab-bar) + selectable: true + title: zellij:tab-bar + borderless: false + + Plugin(101): + geom: x=0, y=27, cols=120, rows=13 + logical_position: 2 + run: Plugin(zellij:status-bar) + selectable: true + title: zellij:status-bar + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_stacked_panes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_stacked_panes.snap new file mode 100644 index 00000000..78cb82a6 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_layout_with_stacked_panes.snap @@ -0,0 +1,43 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (4) + Terminal(1): + geom: x=0, y=0, cols=84, rows=38 + logical_position: 0 + stacked: 0 + run: None + selectable: true + title: editor-1 + borderless: false + + Terminal(2): + geom: x=84, y=0, cols=36, rows=40 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false + + Terminal(3): + geom: x=0, y=38, cols=84, rows=1 + logical_position: 2 + stacked: 0 + run: None + selectable: true + title: editor-2 + borderless: false + + Terminal(4): + geom: x=0, y=39, cols=84, rows=1 + logical_position: 3 + stacked: 0 + run: None + selectable: true + title: editor-3 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_nested_layout.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_nested_layout.snap new file mode 100644 index 00000000..45d8601e --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_nested_layout.snap @@ -0,0 +1,32 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (3) + Terminal(1): + geom: x=0, y=0, cols=72, rows=40 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(2): + geom: x=72, y=0, cols=48, rows=20 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false + + Terminal(3): + geom: x=72, y=20, cols=48, rows=20 + logical_position: 2 + run: None + selectable: true + title: Pane #3 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_simple_two_pane_layout.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_simple_two_pane_layout.snap new file mode 100644 index 00000000..7704828a --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_simple_two_pane_layout.snap @@ -0,0 +1,24 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: snapshot +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (2) + Terminal(1): + geom: x=0, y=0, cols=100, rows=25 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(2): + geom: x=0, y=25, cols=100, rows=25 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_three_pane_layout.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_three_pane_layout.snap new file mode 100644 index 00000000..96068aad --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_three_pane_layout.snap @@ -0,0 +1,32 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (3) + Terminal(1): + geom: x=0, y=0, cols=100, rows=17 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(2): + geom: x=0, y=17, cols=100, rows=17 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false + + Terminal(3): + geom: x=0, y=34, cols=100, rows=16 + logical_position: 2 + run: None + selectable: true + title: Pane #3 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_vertical_split_with_sizes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_vertical_split_with_sizes.snap new file mode 100644 index 00000000..226bc86a --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__apply_vertical_split_with_sizes.snap @@ -0,0 +1,24 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (2) + Terminal(1): + geom: x=0, y=0, cols=60, rows=50 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(2): + geom: x=60, y=0, cols=40, rows=50 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_all_closed_no_matches.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_all_closed_no_matches.snap new file mode 100644 index 00000000..6bc1b62e --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_all_closed_no_matches.snap @@ -0,0 +1,35 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (1) + Terminal(1): + geom: x=0, y=0, cols=120, rows=40 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + +FLOATING PANES (2) + Terminal(4): + geom: x=15, y=15, cols=45, rows=22 + logical_position: 0 + run: Command(top) + pinned: false + selectable: true + title: top + borderless: false + + Terminal(5): + geom: x=25, y=13, cols=55, rows=27 + logical_position: 1 + run: Command(emacs) + pinned: false + selectable: true + title: emacs + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_exact_match_preservation.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_exact_match_preservation.snap new file mode 100644 index 00000000..c4c6d2f0 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_exact_match_preservation.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (1) + Terminal(1): + geom: x=0, y=0, cols=120, rows=40 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + +FLOATING PANES (1) + Terminal(2): + geom: x=50, y=18, cols=45, rows=22 + logical_position: 0 + run: Command(htop) + pinned: false + selectable: true + title: htop + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_focus_handling.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_focus_handling.snap new file mode 100644 index 00000000..e14c7ba2 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_focus_handling.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (1) + Terminal(1): + geom: x=0, y=0, cols=120, rows=40 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + +FLOATING PANES (1) + Terminal(2): + geom: x=30, y=10, cols=60, rows=30 + logical_position: 0 + run: None + pinned: false + selectable: true + title: Pane #2 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_new_panes_created.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_new_panes_created.snap new file mode 100644 index 00000000..948a272d --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_new_panes_created.snap @@ -0,0 +1,44 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (1) + Terminal(1): + geom: x=0, y=0, cols=120, rows=40 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + +FLOATING PANES (3) + Terminal(2): + geom: x=10, y=10, cols=40, rows=20 + logical_position: 0 + run: Command(htop) + pinned: false + selectable: true + title: htop + borderless: false + + Terminal(3): + geom: x=20, y=15, cols=50, rows=25 + logical_position: 1 + run: Command(vim) + pinned: false + selectable: true + title: vim + borderless: false + + Terminal(4): + geom: x=30, y=18, cols=45, rows=22 + logical_position: 2 + run: None + pinned: false + selectable: true + title: Pane #4 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_position_and_size_update.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_position_and_size_update.snap new file mode 100644 index 00000000..a8a2abd9 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_position_and_size_update.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=50 +DISPLAY: cols=120, rows=50 +FOCUS: None + +TILED PANES (1) + Terminal(1): + geom: x=0, y=0, cols=120, rows=50 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + +FLOATING PANES (1) + Terminal(2): + geom: x=50, y=20, cols=60, rows=30 + logical_position: 0 + run: Command(htop) + pinned: false + selectable: true + title: htop + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_retain_terminal_panes_no_matches.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_retain_terminal_panes_no_matches.snap new file mode 100644 index 00000000..dde1b2f3 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_retain_terminal_panes_no_matches.snap @@ -0,0 +1,53 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (1) + Terminal(1): + geom: x=0, y=0, cols=120, rows=40 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + +FLOATING PANES (4) + Terminal(2): + geom: x=30, y=10, cols=60, rows=20 + logical_position: 2 + run: Command(htop) + pinned: false + selectable: true + title: htop + borderless: false + + Terminal(3): + geom: x=32, y=12, cols=60, rows=20 + logical_position: 2 + run: Command(vim) + pinned: false + selectable: true + title: vim + borderless: false + + Terminal(4): + geom: x=30, y=20, cols=40, rows=20 + logical_position: 0 + run: Command(top) + pinned: false + selectable: true + title: top + borderless: false + + Terminal(5): + geom: x=40, y=15, cols=50, rows=25 + logical_position: 1 + run: Command(emacs) + pinned: false + selectable: true + title: emacs + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_retain_terminal_panes_partial_match.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_retain_terminal_panes_partial_match.snap new file mode 100644 index 00000000..b85b40a1 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_retain_terminal_panes_partial_match.snap @@ -0,0 +1,35 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (1) + Terminal(1): + geom: x=0, y=0, cols=120, rows=40 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + +FLOATING PANES (2) + Terminal(2): + geom: x=50, y=18, cols=45, rows=22 + logical_position: 0 + run: Command(htop) + pinned: false + selectable: true + title: htop + borderless: false + + Terminal(3): + geom: x=30, y=10, cols=60, rows=20 + logical_position: 1 + run: Command(vim) + pinned: false + selectable: true + title: vim + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_return_value_has_panes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_return_value_has_panes.snap new file mode 100644 index 00000000..c8b0490a --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_return_value_has_panes.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (1) + Terminal(1): + geom: x=0, y=0, cols=100, rows=50 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + +FLOATING PANES (1) + Terminal(2): + geom: x=20, y=10, cols=50, rows=30 + logical_position: 0 + run: None + pinned: false + selectable: true + title: Pane #2 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_return_value_no_panes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_return_value_no_panes.snap new file mode 100644 index 00000000..16b74511 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_floating_return_value_no_panes.snap @@ -0,0 +1,16 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (1) + Terminal(1): + geom: x=0, y=0, cols=100, rows=50 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_full_tiled_and_floating_together.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_full_tiled_and_floating_together.snap new file mode 100644 index 00000000..5ed2a41c --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_full_tiled_and_floating_together.snap @@ -0,0 +1,34 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=150, rows=50 +DISPLAY: cols=150, rows=50 +FOCUS: None + +TILED PANES (2) + Terminal(1): + geom: x=0, y=0, cols=150, rows=25 + logical_position: 0 + run: Command(htop) + selectable: true + title: htop + borderless: false + + Terminal(6): + geom: x=0, y=25, cols=150, rows=25 + logical_position: 1 + run: Command(npm) args=["start"] + selectable: true + title: npm start + borderless: false + +FLOATING PANES (1) + Terminal(4): + geom: x=30, y=20, cols=60, rows=30 + logical_position: 0 + run: Command(cargo) args=["watch"] + pinned: false + selectable: true + title: cargo watch + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_layout_basic_with_both_tiled_and_floating.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_layout_basic_with_both_tiled_and_floating.snap new file mode 100644 index 00000000..9c782f2b --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_layout_basic_with_both_tiled_and_floating.snap @@ -0,0 +1,34 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (2) + Terminal(1): + geom: x=0, y=20, cols=120, rows=20 + logical_position: 1 + run: Command(htop) + selectable: true + title: htop + borderless: false + + Terminal(4): + geom: x=0, y=0, cols=120, rows=20 + logical_position: 0 + run: Command(top) + selectable: true + title: top + borderless: false + +FLOATING PANES (1) + Terminal(5): + geom: x=20, y=15, cols=50, rows=25 + logical_position: 0 + run: Command(watch) args=["df", "-h"] + pinned: false + selectable: true + title: watch df -h + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_layout_hide_floating_panes_true.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_layout_hide_floating_panes_true.snap new file mode 100644 index 00000000..776d59e8 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_layout_hide_floating_panes_true.snap @@ -0,0 +1,34 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (2) + Terminal(1): + geom: x=0, y=0, cols=100, rows=25 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(3): + geom: x=0, y=25, cols=100, rows=25 + logical_position: 1 + run: None + selectable: true + title: Pane #3 + borderless: false + +FLOATING PANES (1) + Terminal(2): + geom: x=15, y=15, cols=45, rows=22 + logical_position: 0 + run: None + pinned: false + selectable: true + title: Pane #2 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_layout_show_floating_panes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_layout_show_floating_panes.snap new file mode 100644 index 00000000..0bd05777 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_layout_show_floating_panes.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (1) + Terminal(1): + geom: x=0, y=0, cols=100, rows=50 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + +FLOATING PANES (1) + Terminal(3): + geom: x=20, y=10, cols=50, rows=30 + logical_position: 0 + run: None + pinned: false + selectable: true + title: Pane #2 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_mixed_retain_terminal_panes_both_tiled_and_floating.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_mixed_retain_terminal_panes_both_tiled_and_floating.snap new file mode 100644 index 00000000..a4937d41 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_mixed_retain_terminal_panes_both_tiled_and_floating.snap @@ -0,0 +1,59 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (4) + Terminal(1): + geom: x=0, y=20, cols=60, rows=20 + logical_position: 1 + run: Command(htop) + selectable: true + title: htop + borderless: false + + Terminal(2): + geom: x=60, y=20, cols=60, rows=20 + logical_position: 1 + run: Command(vim) + selectable: true + title: vim + borderless: false + + Terminal(3): + geom: x=60, y=0, cols=60, rows=20 + logical_position: 2 + run: Command(tail) args=["-f", "/var/log/syslog"] + selectable: true + title: tail -f /var/log/syslog + borderless: false + + Terminal(5): + geom: x=0, y=0, cols=60, rows=20 + logical_position: 0 + run: Command(top) + selectable: true + title: top + borderless: false + +FLOATING PANES (2) + Terminal(4): + geom: x=30, y=10, cols=60, rows=20 + logical_position: 1 + run: Command(watch) args=["df", "-h"] + pinned: false + selectable: true + title: watch df -h + borderless: false + + Terminal(6): + geom: x=20, y=15, cols=50, rows=25 + logical_position: 0 + run: Command(watch) args=["free", "-h"] + pinned: false + selectable: true + title: watch free -h + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_retain_plugin_but_close_terminal_panes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_retain_plugin_but_close_terminal_panes.snap new file mode 100644 index 00000000..ab483e3c --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_retain_plugin_but_close_terminal_panes.snap @@ -0,0 +1,24 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (2) + Terminal(1): + geom: x=0, y=0, cols=120, rows=20 + logical_position: 0 + run: Command(htop) + selectable: true + title: htop + borderless: false + + Plugin(100): + geom: x=0, y=20, cols=120, rows=20 + logical_position: 0 + run: Plugin(zellij:tab-bar) + selectable: true + title: zellij:tab-bar + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_retain_terminal_but_close_plugin_panes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_retain_terminal_but_close_plugin_panes.snap new file mode 100644 index 00000000..819fef39 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_retain_terminal_but_close_plugin_panes.snap @@ -0,0 +1,24 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (2) + Terminal(1): + geom: x=0, y=0, cols=120, rows=20 + logical_position: 0 + run: Command(htop) + selectable: true + title: htop + borderless: false + + Terminal(2): + geom: x=0, y=20, cols=120, rows=20 + logical_position: 0 + run: Command(vim) + selectable: true + title: vim + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_all_panes_closed_no_matches.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_all_panes_closed_no_matches.snap new file mode 100644 index 00000000..c328232e --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_all_panes_closed_no_matches.snap @@ -0,0 +1,32 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (3) + Terminal(4): + geom: x=0, y=0, cols=120, rows=13 + logical_position: 0 + run: Command(cargo) args=["watch"] + selectable: true + title: cargo watch + borderless: false + + Terminal(5): + geom: x=0, y=13, cols=120, rows=13 + logical_position: 1 + run: Command(npm) args=["start"] + selectable: true + title: npm start + borderless: false + + Terminal(6): + geom: x=0, y=26, cols=120, rows=14 + logical_position: 2 + run: Command(python) args=["-m", "http.server"] + selectable: true + title: python -m http.server + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_empty_layout_closes_all.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_empty_layout_closes_all.snap new file mode 100644 index 00000000..ea6257d6 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_empty_layout_closes_all.snap @@ -0,0 +1,9 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (0) diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_exact_match_preservation_commands.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_exact_match_preservation_commands.snap new file mode 100644 index 00000000..3f063bd1 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_exact_match_preservation_commands.snap @@ -0,0 +1,24 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (2) + Terminal(1): + geom: x=0, y=20, cols=120, rows=20 + logical_position: 1 + run: Command(htop) + selectable: true + title: htop + borderless: false + + Terminal(2): + geom: x=0, y=0, cols=120, rows=20 + logical_position: 0 + run: Command(vim) + selectable: true + title: vim + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_exact_match_preservation_plugins.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_exact_match_preservation_plugins.snap new file mode 100644 index 00000000..688718e2 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_exact_match_preservation_plugins.snap @@ -0,0 +1,16 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (1) + Plugin(100): + geom: x=0, y=0, cols=120, rows=40 + logical_position: 0 + run: Plugin(zellij:tab-bar) + selectable: true + title: zellij:tab-bar + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_focus_on_new_pane.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_focus_on_new_pane.snap new file mode 100644 index 00000000..5061a53f --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_focus_on_new_pane.snap @@ -0,0 +1,32 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: Some(Terminal(1)) + +TILED PANES (3) + Terminal(1): + geom: x=0, y=0, cols=100, rows=16 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(2): + geom: x=0, y=16, cols=100, rows=16 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false + + Terminal(3): + geom: x=0, y=32, cols=100, rows=18 + logical_position: 2 + run: None + selectable: true + title: Pane #3 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_focus_when_focused_pane_closed.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_focus_when_focused_pane_closed.snap new file mode 100644 index 00000000..212107ae --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_focus_when_focused_pane_closed.snap @@ -0,0 +1,24 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: Some(Terminal(2)) + +TILED PANES (2) + Terminal(1): + geom: x=0, y=0, cols=120, rows=20 + logical_position: 0 + run: Command(htop) + selectable: true + title: htop + borderless: false + + Terminal(4): + geom: x=0, y=20, cols=120, rows=20 + logical_position: 1 + run: Command(cargo) args=["check"] + selectable: true + title: cargo check + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_mixed_some_matches_some_new.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_mixed_some_matches_some_new.snap new file mode 100644 index 00000000..b6d0bf45 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_mixed_some_matches_some_new.snap @@ -0,0 +1,32 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (3) + Terminal(1): + geom: x=0, y=0, cols=120, rows=13 + logical_position: 0 + run: Command(htop) + selectable: true + title: htop + borderless: false + + Terminal(2): + geom: x=0, y=26, cols=120, rows=14 + logical_position: 2 + run: None + selectable: true + title: Pane #2 + borderless: false + + Terminal(3): + geom: x=0, y=13, cols=120, rows=13 + logical_position: 1 + run: Command(vim) + selectable: true + title: vim + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_new_panes_for_unmatched_positions.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_new_panes_for_unmatched_positions.snap new file mode 100644 index 00000000..bd516b92 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_new_panes_for_unmatched_positions.snap @@ -0,0 +1,40 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=150, rows=50 +DISPLAY: cols=150, rows=50 +FOCUS: None + +TILED PANES (4) + Terminal(1): + geom: x=0, y=0, cols=150, rows=12 + logical_position: 0 + run: Command(htop) + selectable: true + title: htop + borderless: false + + Terminal(2): + geom: x=0, y=12, cols=150, rows=12 + logical_position: 1 + run: Command(vim) + selectable: true + title: vim + borderless: false + + Terminal(3): + geom: x=0, y=24, cols=150, rows=12 + logical_position: 2 + run: None + selectable: true + title: Pane #3 + borderless: false + + Terminal(4): + geom: x=0, y=36, cols=150, rows=14 + logical_position: 3 + run: None + selectable: true + title: Pane #4 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_retain_terminal_panes_no_matches.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_retain_terminal_panes_no_matches.snap new file mode 100644 index 00000000..2a7ac2fc --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_retain_terminal_panes_no_matches.snap @@ -0,0 +1,56 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (6) + Terminal(1): + geom: x=60, y=26, cols=60, rows=14 + logical_position: 2 + run: Command(htop) + selectable: true + title: htop + borderless: false + + Terminal(2): + geom: x=60, y=0, cols=60, rows=13 + logical_position: 3 + run: Command(vim) + selectable: true + title: vim + borderless: false + + Terminal(3): + geom: x=60, y=13, cols=60, rows=13 + logical_position: 4 + run: Command(tail) args=["-f", "/var/log/syslog"] + selectable: true + title: tail -f /var/log/syslog + borderless: false + + Terminal(4): + geom: x=0, y=0, cols=60, rows=13 + logical_position: 0 + run: Command(cargo) + selectable: true + title: cargo + borderless: false + + Terminal(5): + geom: x=0, y=13, cols=60, rows=13 + logical_position: 1 + run: Command(npm) + selectable: true + title: npm + borderless: false + + Terminal(6): + geom: x=0, y=26, cols=60, rows=14 + logical_position: 2 + run: Command(python) + selectable: true + title: python + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_retain_terminal_panes_partial_match.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_retain_terminal_panes_partial_match.snap new file mode 100644 index 00000000..12e7bdb9 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_tiled_retain_terminal_panes_partial_match.snap @@ -0,0 +1,32 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (3) + Terminal(1): + geom: x=0, y=20, cols=60, rows=20 + logical_position: 1 + run: Command(htop) + selectable: true + title: htop + borderless: false + + Terminal(2): + geom: x=0, y=0, cols=120, rows=20 + logical_position: 0 + run: Command(vim) + selectable: true + title: vim + borderless: false + + Terminal(3): + geom: x=60, y=20, cols=60, rows=20 + logical_position: 1 + run: Command(tail) args=["-f", "/var/log/syslog"] + selectable: true + title: tail -f /var/log/syslog + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_viewport_adjustment_with_borderless.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_viewport_adjustment_with_borderless.snap new file mode 100644 index 00000000..2bef48e7 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__override_viewport_adjustment_with_borderless.snap @@ -0,0 +1,24 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (2) + Terminal(1): + geom: x=0, y=0, cols=120, rows=20 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(2): + geom: x=0, y=20, cols=120, rows=20 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: true diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__reapply_floating_pane_layout.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__reapply_floating_pane_layout.snap new file mode 100644 index 00000000..c5f8b6fc --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__reapply_floating_pane_layout.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (1) + Terminal(1): + geom: x=0, y=0, cols=100, rows=50 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + +FLOATING PANES (1) + Terminal(2): + geom: x=20, y=20, cols=50, rows=25 + logical_position: 0 + run: None + pinned: false + selectable: true + title: Pane #2 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__reapply_layout_exact_match.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__reapply_layout_exact_match.snap new file mode 100644 index 00000000..06a7a9ea --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__reapply_layout_exact_match.snap @@ -0,0 +1,32 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: snapshot +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (3) + Terminal(1): + geom: x=0, y=13, cols=120, rows=13 + logical_position: 1 + run: Command(htop) + selectable: true + title: htop + borderless: false + + Terminal(2): + geom: x=0, y=26, cols=120, rows=14 + logical_position: 2 + run: Command(vim) + selectable: true + title: vim + borderless: false + + Terminal(3): + geom: x=0, y=0, cols=120, rows=13 + logical_position: 0 + run: None + selectable: true + title: Pane #3 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__reapply_layout_logical_position_match.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__reapply_layout_logical_position_match.snap new file mode 100644 index 00000000..60cf272b --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__reapply_layout_logical_position_match.snap @@ -0,0 +1,32 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=120, rows=40 +DISPLAY: cols=120, rows=40 +FOCUS: None + +TILED PANES (3) + Terminal(1): + geom: x=0, y=0, cols=60, rows=20 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(2): + geom: x=0, y=20, cols=120, rows=20 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false + + Terminal(3): + geom: x=60, y=0, cols=60, rows=20 + logical_position: 2 + run: None + selectable: true + title: Pane #3 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__reapply_layout_with_fewer_positions.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__reapply_layout_with_fewer_positions.snap new file mode 100644 index 00000000..ed8952b6 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__reapply_layout_with_fewer_positions.snap @@ -0,0 +1,38 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (4) + Terminal(1): + geom: x=0, y=0, cols=50, rows=25 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(2): + geom: x=0, y=25, cols=50, rows=25 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false + + Terminal(3): + geom: x=50, y=25, cols=50, rows=25 + run: None + selectable: true + title: Pane #3 + borderless: false + + Terminal(4): + geom: x=50, y=0, cols=50, rows=25 + run: None + selectable: true + title: Pane #4 + borderless: false diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__reapply_layout_with_more_positions.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__reapply_layout_with_more_positions.snap new file mode 100644 index 00000000..c1decb8d --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__layout_applier_tests__reapply_layout_with_more_positions.snap @@ -0,0 +1,24 @@ +--- +source: zellij-server/src/tab/./unit/layout_applier_tests.rs +expression: "take_pane_state_snapshot(&tiled_panes, &floating_panes, &focus_pane_id,\n&viewport, &display_area,)" +--- +VIEWPORT: x=0, y=0, cols=100, rows=50 +DISPLAY: cols=100, rows=50 +FOCUS: None + +TILED PANES (2) + Terminal(1): + geom: x=0, y=0, cols=100, rows=25 + logical_position: 0 + run: None + selectable: true + title: Pane #1 + borderless: false + + Terminal(2): + geom: x=0, y=25, cols=100, rows=25 + logical_position: 1 + run: None + selectable: true + title: Pane #2 + borderless: false diff --git a/zellij-server/src/thread_bus.rs b/zellij-server/src/thread_bus.rs index 3247ecf4..ce997539 100644 --- a/zellij-server/src/thread_bus.rs +++ b/zellij-server/src/thread_bus.rs @@ -143,6 +143,11 @@ impl ThreadSenders { // this is mostly used for the tests, see struct self.to_pty_writer.replace(new_pty_writer); } + #[allow(unused)] + pub fn replace_to_pty(&mut self, new_pty: SenderWithContext<PtyInstruction>) { + // this is mostly used for the tests, see struct + self.to_pty.replace(new_pty); + } #[allow(unused)] pub fn replace_to_plugin(&mut self, new_to_plugin: SenderWithContext<PluginInstruction>) { diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap index d61ae4d7..ff0a8382 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap @@ -17,7 +17,7 @@ NewTab( split_size: None, run: Some( Cwd( - ".", + "./.", ), ), borderless: false, @@ -37,7 +37,7 @@ NewTab( split_size: None, run: Some( Cwd( - ".", + "./.", ), ), borderless: false, @@ -57,7 +57,7 @@ NewTab( split_size: None, run: Some( Cwd( - ".", + "./.", ), ), borderless: false, @@ -72,7 +72,11 @@ NewTab( }, ], split_size: None, - run: None, + run: Some( + Cwd( + ".", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/assets/plugins/about.wasm b/zellij-utils/assets/plugins/about.wasm Binary files differindex ae99b4a1..812659bc 100755 --- a/zellij-utils/assets/plugins/about.wasm +++ b/zellij-utils/assets/plugins/about.wasm diff --git a/zellij-utils/assets/plugins/compact-bar.wasm b/zellij-utils/assets/plugins/compact-bar.wasm Binary files differindex 0d6ab290..75f94c4b 100755 --- a/zellij-utils/assets/plugins/compact-bar.wasm +++ b/zellij-utils/assets/plugins/compact-bar.wasm diff --git a/zellij-utils/assets/plugins/configuration.wasm b/zellij-utils/assets/plugins/configuration.wasm Binary files differindex b448d343..ffa97232 100755 --- a/zellij-utils/assets/plugins/configuration.wasm +++ b/zellij-utils/assets/plugins/configuration.wasm diff --git a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm Binary files differindex 407f41f1..cd74ed1d 100755 --- a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm +++ b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm diff --git a/zellij-utils/assets/plugins/multiple-select.wasm b/zellij-utils/assets/plugins/multiple-select.wasm Binary files differindex 77f0310b..4412b8df 100755 --- a/zellij-utils/assets/plugins/multiple-select.wasm +++ b/zellij-utils/assets/plugins/multiple-select.wasm diff --git a/zellij-utils/assets/plugins/plugin-manager.wasm b/zellij-utils/assets/plugins/plugin-manager.wasm Binary files differindex 5c9fb461..cb8e2239 100755 --- a/zellij-utils/assets/plugins/plugin-manager.wasm +++ b/zellij-utils/assets/plugins/plugin-manager.wasm diff --git a/zellij-utils/assets/plugins/sequence.wasm b/zellij-utils/assets/plugins/sequence.wasm Binary files differindex 0a982532..d6286cb0 100755 --- a/zellij-utils/assets/plugins/sequence.wasm +++ b/zellij-utils/assets/plugins/sequence.wasm diff --git a/zellij-utils/assets/plugins/session-manager.wasm b/zellij-utils/assets/plugins/session-manager.wasm Binary files differindex 0a8460d0..ff97eb14 100755 --- a/zellij-utils/assets/plugins/session-manager.wasm +++ b/zellij-utils/assets/plugins/session-manager.wasm diff --git a/zellij-utils/assets/plugins/share.wasm b/zellij-utils/assets/plugins/share.wasm Binary files differindex 360e55a2..ed580f3f 100755 --- a/zellij-utils/assets/plugins/share.wasm +++ b/zellij-utils/assets/plugins/share.wasm diff --git a/zellij-utils/assets/plugins/status-bar.wasm b/zellij-utils/assets/plugins/status-bar.wasm Binary files differindex ac1b076a..98e8e647 100755 --- a/zellij-utils/assets/plugins/status-bar.wasm +++ b/zellij-utils/assets/plugins/status-bar.wasm diff --git a/zellij-utils/assets/plugins/strider.wasm b/zellij-utils/assets/plugins/strider.wasm Binary files differindex ec6de885..c33e5e2f 100755 --- a/zellij-utils/assets/plugins/strider.wasm +++ b/zellij-utils/assets/plugins/strider.wasm diff --git a/zellij-utils/assets/plugins/tab-bar.wasm b/zellij-utils/assets/plugins/tab-bar.wasm Binary files differindex 4a31b561..609fa718 100755 --- a/zellij-utils/assets/plugins/tab-bar.wasm +++ b/zellij-utils/assets/plugins/tab-bar.wasm diff --git a/zellij-utils/assets/prost/api.action.rs b/zellij-utils/assets/prost/api.action.rs index 46435021..ccf96860 100644 --- a/zellij-utils/assets/prost/api.action.rs +++ b/zellij-utils/assets/prost/api.action.rs @@ -257,10 +257,28 @@ pub struct NewTabPayload { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct OverrideLayoutPayload { + #[prost(message, optional, tag="1")] + pub tiled_layout: ::core::option::Option<TiledPaneLayout>, + #[prost(message, repeated, tag="2")] + pub floating_layouts: ::prost::alloc::vec::Vec<FloatingPaneLayout>, + #[prost(message, repeated, tag="3")] + pub swap_tiled_layouts: ::prost::alloc::vec::Vec<SwapTiledLayout>, + #[prost(message, repeated, tag="4")] + pub swap_floating_layouts: ::prost::alloc::vec::Vec<SwapFloatingLayout>, + #[prost(string, optional, tag="5")] + pub tab_name: ::core::option::Option<::prost::alloc::string::String>, + #[prost(bool, tag="10")] + pub retain_existing_terminal_panes: bool, + #[prost(bool, tag="11")] + pub retain_existing_plugin_panes: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Action { #[prost(enumeration="ActionName", tag="1")] pub name: i32, - #[prost(oneof="action::OptionalPayload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52")] + #[prost(oneof="action::OptionalPayload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53")] pub optional_payload: ::core::option::Option<action::OptionalPayload>, } /// Nested message and enum types in `Action`. @@ -364,6 +382,8 @@ pub mod action { NewTabPayload(super::NewTabPayload), #[prost(message, tag="52")] NewInPlacePanePayload(super::NewInPlacePanePayload), + #[prost(message, tag="53")] + OverrideLayoutPayload(super::OverrideLayoutPayload), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -994,6 +1014,7 @@ pub enum ActionName { SwitchSession = 90, NewBlockingPane = 91, NewInPlacePane = 92, + OverrideLayout = 93, } impl ActionName { /// String value of the enum field names used in the ProtoBuf definition. @@ -1092,6 +1113,7 @@ impl ActionName { ActionName::SwitchSession => "SwitchSession", ActionName::NewBlockingPane => "NewBlockingPane", ActionName::NewInPlacePane => "NewInPlacePane", + ActionName::OverrideLayout => "OverrideLayout", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1187,6 +1209,7 @@ impl ActionName { "SwitchSession" => Some(Self::SwitchSession), "NewBlockingPane" => Some(Self::NewBlockingPane), "NewInPlacePane" => Some(Self::NewInPlacePane), + "OverrideLayout" => Some(Self::OverrideLayout), _ => None, } } diff --git a/zellij-utils/assets/prost_ipc/client_server_contract.rs b/zellij-utils/assets/prost_ipc/client_server_contract.rs index 32031dba..b0a9be5b 100644 --- a/zellij-utils/assets/prost_ipc/client_server_contract.rs +++ b/zellij-utils/assets/prost_ipc/client_server_contract.rs @@ -119,7 +119,7 @@ pub struct RgbColor { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Action { - #[prost(oneof="action::ActionType", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93")] + #[prost(oneof="action::ActionType", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94")] pub action_type: ::core::option::Option<action::ActionType>, } /// Nested message and enum types in `Action`. @@ -313,6 +313,8 @@ pub mod action { SwitchSession(super::SwitchSessionAction), #[prost(message, tag="93")] NewBlockingPane(super::NewBlockingPaneAction), + #[prost(message, tag="94")] + OverrideLayout(super::OverrideLayoutAction), } } // Action message definitions (all 92 variants) @@ -464,6 +466,24 @@ pub struct NextSwapLayoutAction { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct OverrideLayoutAction { + #[prost(message, optional, tag="1")] + pub tiled_layout: ::core::option::Option<TiledPaneLayout>, + #[prost(message, repeated, tag="2")] + pub floating_layouts: ::prost::alloc::vec::Vec<FloatingPaneLayout>, + #[prost(message, repeated, tag="3")] + pub swap_tiled_layouts: ::prost::alloc::vec::Vec<SwapTiledLayout>, + #[prost(message, repeated, tag="4")] + pub swap_floating_layouts: ::prost::alloc::vec::Vec<SwapFloatingLayout>, + #[prost(string, optional, tag="5")] + pub tab_name: ::core::option::Option<::prost::alloc::string::String>, + #[prost(bool, tag="6")] + pub retain_existing_terminal_panes: bool, + #[prost(bool, tag="7")] + pub retain_existing_plugin_panes: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct QueryTabNamesAction { } #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index 7e4e7188..1d196df9 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -933,6 +933,24 @@ pub enum CliAction { }, PreviousSwapLayout, NextSwapLayout, + /// Override the layout of the active tab + OverrideLayout { + /// Path to the layout file + #[clap(value_parser)] + layout: PathBuf, + + /// Default folder to look for layouts + #[clap(long, value_parser)] + layout_dir: Option<PathBuf>, + + /// Retain existing terminal panes that do not fit in the layout (default: false) + #[clap(long, value_parser, takes_value(false), default_value("false"))] + retain_existing_terminal_panes: bool, + + /// Retain existing plugin panes that do not fit with the layout default: false) + #[clap(long, value_parser, takes_value(false), default_value("false"))] + retain_existing_plugin_panes: bool, + }, /// Query all tab names QueryTabNames, StartOrReloadPlugin { diff --git a/zellij-utils/src/client_server_contract/common_types.proto b/zellij-utils/src/client_server_contract/common_types.proto index e4561989..bbaeadad 100644 --- a/zellij-utils/src/client_server_contract/common_types.proto +++ b/zellij-utils/src/client_server_contract/common_types.proto @@ -229,6 +229,7 @@ message Action { ToggleGroupMarkingAction toggle_group_marking = 91; SwitchSessionAction switch_session = 92; NewBlockingPaneAction new_blocking_pane = 93; + OverrideLayoutAction override_layout = 94; } } @@ -271,6 +272,15 @@ message DenyAction {} message ToggleMouseModeAction {} message PreviousSwapLayoutAction {} message NextSwapLayoutAction {} +message OverrideLayoutAction { + optional TiledPaneLayout tiled_layout = 1; + repeated FloatingPaneLayout floating_layouts = 2; + repeated SwapTiledLayout swap_tiled_layouts = 3; + repeated SwapFloatingLayout swap_floating_layouts = 4; + optional string tab_name = 5; + bool retain_existing_terminal_panes = 6; + bool retain_existing_plugin_panes = 7; +} message QueryTabNamesAction {} message BreakPaneAction {} message BreakPaneRightAction {} diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index 74c40534..78330d76 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -324,6 +324,8 @@ pub enum ScreenContext { ClearPaneFrameColorOverride, PreviousSwapLayout, NextSwapLayout, + OverrideLayout, + OverrideLayoutComplete, QueryTabNames, NewTiledPluginPane, StartOrReloadPluginPane, @@ -405,6 +407,7 @@ pub enum PtyContext { UpdateActivePane, GoToTab, NewTab, + OverrideLayout, ClosePane, CloseTab, ReRunCommandInPane, @@ -440,6 +443,7 @@ pub enum PluginContext { AddClient, RemoveClient, NewTab, + OverrideLayout, ApplyCachedEvents, ApplyCachedWorkerMessages, PostMessageToPluginWorker, @@ -635,6 +639,9 @@ open an issue on GitHub: #[error("The plugin does not exist")] PluginDoesNotExist, + + #[error("Ran out of room for spans")] + RanOutOfRoomForSpans, } #[cfg(not(target_family = "wasm"))] diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index 92d697a9..42ec2037 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -349,6 +349,16 @@ pub enum Action { ToggleMouseMode, PreviousSwapLayout, NextSwapLayout, + /// Override the layout of the active tab + OverrideLayout { + tiled_layout: Option<TiledPaneLayout>, + floating_layouts: Vec<FloatingPaneLayout>, + swap_tiled_layouts: Option<Vec<SwapTiledLayout>>, + swap_floating_layouts: Option<Vec<SwapFloatingLayout>>, + tab_name: Option<String>, + retain_existing_terminal_panes: bool, + retain_existing_plugin_panes: bool, + }, /// Query all tab names QueryTabNames, /// Open a new tiled (embedded, non-floating) plugin pane @@ -473,6 +483,7 @@ impl Action { (Action::NewTab { .. }, Action::NewTab { .. }) => true, (Action::LaunchOrFocusPlugin { .. }, Action::LaunchOrFocusPlugin { .. }) => true, (Action::LaunchPlugin { .. }, Action::LaunchPlugin { .. }) => true, + (Action::OverrideLayout { .. }, Action::OverrideLayout { .. }) => true, _ => self == other_action, } } @@ -973,6 +984,90 @@ impl Action { }, CliAction::PreviousSwapLayout => Ok(vec![Action::PreviousSwapLayout]), CliAction::NextSwapLayout => Ok(vec![Action::NextSwapLayout]), + CliAction::OverrideLayout { + layout, + layout_dir, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + } => { + // Determine layout_dir: CLI arg > config > default + let layout_dir = layout_dir + .or_else(|| config.and_then(|c| c.options.layout_dir)) + .or_else(|| get_layout_dir(find_default_config_dir())); + + // Load layout from URL or file path + let (path_to_raw_layout, raw_layout, swap_layouts) = if let Some(layout_url) = + layout.to_str().and_then(|l| { + if l.starts_with("http://") || l.starts_with("https://") { + Some(l) + } else { + None + } + }) { + ( + layout_url.to_owned(), + Layout::stringified_from_url(layout_url) + .map_err(|e| format!("Failed to load layout from URL: {}", e))?, + None, + ) + } else { + Layout::stringified_from_path_or_default(Some(&layout), layout_dir) + .map_err(|e| format!("Failed to load layout: {}", e))? + }; + + // Parse KDL layout + let layout = Layout::from_str( + &raw_layout, + path_to_raw_layout, + swap_layouts.as_ref().map(|(f, p)| (f.as_str(), p.as_str())), + None, // cwd + ) + .map_err(|e| { + let stringified_error = match e { + ConfigError::KdlError(kdl_error) => { + let error = kdl_error.add_src( + layout.as_path().as_os_str().to_string_lossy().to_string(), + String::from(raw_layout), + ); + let report: Report = error.into(); + format!("{:?}", report) + }, + ConfigError::KdlDeserializationError(kdl_error) => { + let error_message = kdl_error.to_string(); + format!("Failed to deserialize KDL layout: {}", error_message) + }, + e => format!("{}", e), + }; + stringified_error + })?; + + // Extract layout from first tab or use default + let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone()); + let swap_floating_layouts = Some(layout.swap_floating_layouts.clone()); + + if layout.tabs.len() > 1 { + return Err(format!("This layout has {} tabs, overriding only supports layouts with 0 or 1 tabs.", layout.tabs.len())); + } + + let (name, tiled_layout, floating_panes_layout) = + if let Some(first_tab) = layout.tabs.get(0).take() { + first_tab.clone() + } else { + let name = None; + let (tiled_panes, floating_panes) = layout.new_tab(); + (name, tiled_panes, floating_panes) + }; + + Ok(vec![Action::OverrideLayout { + tiled_layout: Some(tiled_layout), + floating_layouts: floating_panes_layout, + swap_tiled_layouts, + swap_floating_layouts, + tab_name: name, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + }]) + }, CliAction::QueryTabNames => Ok(vec![Action::QueryTabNames]), CliAction::StartOrReloadPlugin { url, configuration } => { let current_dir = get_current_dir(); diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs index d13dd2c0..292909e5 100644 --- a/zellij-utils/src/input/unit/layout_test.rs +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -2242,3 +2242,122 @@ fn env_var_missing() { let layout = Layout::from_kdl(kdl_layout, Some("layout_file_name".into()), None, None); assert!(layout.is_err(), "invalid env var lookup should fail"); } + +#[test] +fn layout_node_with_tab_name() { + let kdl_layout = r#" + layout name="my-layout" { + pane + } + "#; + + let layout = Layout::from_kdl(kdl_layout, Some("layout_file_name".into()), None, None).unwrap(); + + assert_eq!(layout.tabs.len(), 1); + assert_eq!(layout.tabs[0].0, Some("my-layout".to_string())); +} + +#[test] +fn layout_node_with_split_direction() { + let kdl_layout = r#" + layout split_direction="vertical" { + pane + pane + } + "#; + + let layout = Layout::from_kdl(kdl_layout, Some("layout_file_name".into()), None, None).unwrap(); + + assert_eq!( + layout.tabs[0].1.children_split_direction, + SplitDirection::Vertical + ); +} + +#[test] +fn layout_node_with_hide_floating_panes() { + let kdl_layout = r#" + layout hide_floating_panes=true { + pane + } + "#; + + let layout = Layout::from_kdl(kdl_layout, Some("layout_file_name".into()), None, None).unwrap(); + + assert_eq!(layout.tabs[0].1.hide_floating_panes, true); +} + +#[test] +fn layout_node_with_cwd() { + let kdl_layout = r#" + layout cwd="/tmp" { + pane + } + "#; + + let layout = Layout::from_kdl(kdl_layout, Some("layout_file_name".into()), None, None).unwrap(); + + // Verify cwd was applied - check the first pane's run property + assert!(layout.tabs.len() > 0); + // The cwd should be propagated to children + if let Some(Run::Cwd(path)) = &layout.tabs[0].1.children[0].run { + assert_eq!(path.to_str(), Some("/tmp")); + } else { + panic!("Expected cwd to be set on pane"); + } +} + +#[test] +fn layout_node_with_multiple_tab_properties() { + let kdl_layout = r#" + layout name="test" split_direction="vertical" hide_floating_panes=true { + pane + pane + } + "#; + + let layout = Layout::from_kdl(kdl_layout, Some("layout_file_name".into()), None, None).unwrap(); + + assert_eq!(layout.tabs[0].0, Some("test".to_string())); + assert_eq!( + layout.tabs[0].1.children_split_direction, + SplitDirection::Vertical + ); + assert_eq!(layout.tabs[0].1.hide_floating_panes, true); +} + +#[test] +fn layout_node_with_tab_properties_and_explicit_tabs_errors() { + let kdl_layout = r#" + layout name="test" { + tab { + pane + } + } + "#; + + let layout = Layout::from_kdl(kdl_layout, Some("layout_file_name".into()), None, None); + + assert!(layout.is_err()); + let error_message = format!("{:?}", layout.unwrap_err()); + assert!(error_message.contains( + "Tab properties on the layout node can only be used when there are no explicit tab nodes" + )); +} + +#[test] +fn layout_node_with_name_and_only_floating_panes() { + let kdl_layout = r#" + layout name="floating-only" { + floating_panes { + pane + } + } + "#; + + let layout = Layout::from_kdl(kdl_layout, Some("layout_file_name".into()), None, None).unwrap(); + + // Verify the tab name was applied + assert_eq!(layout.tabs.len(), 1); + assert_eq!(layout.tabs[0].0, Some("floating-only".to_string())); +} diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__env_var_expansion.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__env_var_expansion.snap index 165050f5..2c5b1bdc 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__env_var_expansion.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__env_var_expansion.snap @@ -3,7 +3,245 @@ source: zellij-utils/src/input/./unit/layout_test.rs expression: "format!(\"{layout:#?}\")" --- Layout { - tabs: [], + tabs: [ + ( + None, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Cwd( + "/abs/path/relative", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Cwd( + "/another/abs", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Cwd( + "/another/abs", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Cwd( + "/abs/path/relative", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "ls", + args: [], + cwd: Some( + "/somewhere", + ), + hold_on_close: true, + hold_on_start: false, + originating_plugin: None, + use_terminal_title: false, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + EditFile( + "/somewhere/file.rs", + None, + Some( + "/somewhere", + ), + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + EditFile( + "/home/aram/backup/file.rs", + None, + Some( + "/home/aram/backup", + ), + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "/home/aram/backup/executable", + args: [], + cwd: Some( + "/abs/path", + ), + hold_on_close: true, + hold_on_start: false, + originating_plugin: None, + use_terminal_title: false, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + EditFile( + "/home/aram/backup/foo.txt", + None, + Some( + "/abs/path", + ), + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: Some( + Cwd( + "/abs/path", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ], focused_tab_index: None, template: Some( ( @@ -225,7 +463,11 @@ Layout { }, ], split_size: None, - run: None, + run: Some( + Cwd( + "/abs/path", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap index a1ef07f5..eca5399e 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap @@ -3,7 +3,83 @@ source: zellij-utils/src/input/./unit/layout_test.rs expression: "format!(\"{:#?}\", layout)" --- Layout { - tabs: [], + tabs: [ + ( + None, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "tail", + args: [], + cwd: Some( + "/tmp", + ), + hold_on_close: true, + hold_on_start: false, + originating_plugin: None, + use_terminal_title: false, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ], focused_tab_index: None, template: Some( ( @@ -63,7 +139,11 @@ Layout { }, ], split_size: None, - run: None, + run: Some( + Cwd( + "/tmp", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap index a1ef07f5..0d5a8d8c 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap @@ -63,7 +63,11 @@ Layout { }, ], split_size: None, - run: None, + run: Some( + Cwd( + "/tmp", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap index a1ef07f5..fd0fc3a7 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap @@ -3,7 +3,83 @@ source: zellij-utils/src/input/./unit/layout_test.rs expression: "format!(\"{:#?}\", layout)" --- Layout { - tabs: [], + tabs: [ + ( + None, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "tail", + args: [], + cwd: Some( + "/tmp", + ), + hold_on_close: true, + hold_on_start: false, + originating_plugin: None, + use_terminal_title: false, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: Some( + Cwd( + "/home", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ], focused_tab_index: None, template: Some( ( @@ -63,7 +139,11 @@ Layout { }, ], split_size: None, - run: None, + run: Some( + Cwd( + "/home", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap index 6e84d38d..62bce0d4 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap @@ -3,7 +3,83 @@ source: zellij-utils/src/input/./unit/layout_test.rs expression: "format!(\"{:#?}\", layout)" --- Layout { - tabs: [], + tabs: [ + ( + None, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Cwd( + "/tmp/foo", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "tail", + args: [], + cwd: Some( + "/home/foo", + ), + hold_on_close: true, + hold_on_start: false, + originating_plugin: None, + use_terminal_title: false, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ], focused_tab_index: None, template: Some( ( @@ -63,7 +139,11 @@ Layout { }, ], split_size: None, - run: None, + run: Some( + Cwd( + "/tmp", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap index 791e69a3..1698b575 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap @@ -3,7 +3,63 @@ source: zellij-utils/src/input/./unit/layout_test.rs expression: "format!(\"{:#?}\", layout)" --- Layout { - tabs: [], + tabs: [ + ( + None, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "tail", + args: [], + cwd: Some( + "/tmp/bar", + ), + hold_on_close: true, + hold_on_start: false, + originating_plugin: None, + use_terminal_title: false, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ], focused_tab_index: None, template: Some( ( @@ -43,7 +99,11 @@ Layout { }, ], split_size: None, - run: None, + run: Some( + Cwd( + "/tmp", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap index 13e6a55d..ea74b798 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap @@ -3,7 +3,63 @@ source: zellij-utils/src/input/./unit/layout_test.rs expression: "format!(\"{:#?}\", layout)" --- Layout { - tabs: [], + tabs: [ + ( + None, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "pwd", + args: [], + cwd: Some( + "/tmp/foo", + ), + hold_on_close: true, + hold_on_start: false, + originating_plugin: None, + use_terminal_title: false, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ], focused_tab_index: None, template: Some( ( @@ -43,7 +99,11 @@ Layout { }, ], split_size: None, - run: None, + run: Some( + Cwd( + "/tmp", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap index 5d34b55a..4ab7252f 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap @@ -3,7 +3,63 @@ source: zellij-utils/src/input/./unit/layout_test.rs expression: "format!(\"{:#?}\", layout)" --- Layout { - tabs: [], + tabs: [ + ( + None, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "pwd", + args: [], + cwd: Some( + "/tmp/bar", + ), + hold_on_close: true, + hold_on_start: false, + originating_plugin: None, + use_terminal_title: false, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ], focused_tab_index: None, template: Some( ( @@ -43,7 +99,11 @@ Layout { }, ], split_size: None, - run: None, + run: Some( + Cwd( + "/tmp", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap index 5d34b55a..4ab7252f 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap @@ -3,7 +3,63 @@ source: zellij-utils/src/input/./unit/layout_test.rs expression: "format!(\"{:#?}\", layout)" --- Layout { - tabs: [], + tabs: [ + ( + None, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "pwd", + args: [], + cwd: Some( + "/tmp/bar", + ), + hold_on_close: true, + hold_on_start: false, + originating_plugin: None, + use_terminal_title: false, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ], focused_tab_index: None, template: Some( ( @@ -43,7 +99,11 @@ Layout { }, ], split_size: None, - run: None, + run: Some( + Cwd( + "/tmp", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap index 791e69a3..1698b575 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap @@ -3,7 +3,63 @@ source: zellij-utils/src/input/./unit/layout_test.rs expression: "format!(\"{:#?}\", layout)" --- Layout { - tabs: [], + tabs: [ + ( + None, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "tail", + args: [], + cwd: Some( + "/tmp/bar", + ), + hold_on_close: true, + hold_on_start: false, + originating_plugin: None, + use_terminal_title: false, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ], focused_tab_index: None, template: Some( ( @@ -43,7 +99,11 @@ Layout { }, ], split_size: None, - run: None, + run: Some( + Cwd( + "/tmp", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap index 62f8e038..9ff94a33 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap @@ -1,10 +1,55 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1594 expression: "format!(\"{:#?}\", layout)" --- Layout { - tabs: [], + tabs: [ + ( + None, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Cwd( + "/tmp/bar", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ], focused_tab_index: None, template: Some( ( @@ -34,7 +79,11 @@ Layout { }, ], split_size: None, - run: None, + run: Some( + Cwd( + "/tmp", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap index 791e69a3..1698b575 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap @@ -3,7 +3,63 @@ source: zellij-utils/src/input/./unit/layout_test.rs expression: "format!(\"{:#?}\", layout)" --- Layout { - tabs: [], + tabs: [ + ( + None, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "tail", + args: [], + cwd: Some( + "/tmp/bar", + ), + hold_on_close: true, + hold_on_start: false, + originating_plugin: None, + use_terminal_title: false, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ], focused_tab_index: None, template: Some( ( @@ -43,7 +99,11 @@ Layout { }, ], split_size: None, - run: None, + run: Some( + Cwd( + "/tmp", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap index 3eba1d77..a33edb62 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap @@ -3,7 +3,63 @@ source: zellij-utils/src/input/./unit/layout_test.rs expression: "format!(\"{:#?}\", layout)" --- Layout { - tabs: [], + tabs: [ + ( + None, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "tail", + args: [], + cwd: Some( + "/tmp/foo", + ), + hold_on_close: true, + hold_on_start: false, + originating_plugin: None, + use_terminal_title: false, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ], focused_tab_index: None, template: Some( ( @@ -43,7 +99,11 @@ Layout { }, ], split_size: None, - run: None, + run: Some( + Cwd( + "/tmp", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap index adaa2de1..b12ec15b 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap @@ -1,10 +1,59 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1644 expression: "format!(\"{:#?}\", layout)" --- Layout { - tabs: [], + tabs: [ + ( + None, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + EditFile( + "/tmp/foo/bar", + None, + Some( + "/tmp/foo", + ), + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ], focused_tab_index: None, template: Some( ( @@ -38,7 +87,11 @@ Layout { }, ], split_size: None, - run: None, + run: Some( + Cwd( + "/tmp", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap index eef3d4ba..c716ad07 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap @@ -1,10 +1,59 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1660 expression: "format!(\"{:#?}\", layout)" --- Layout { - tabs: [], + tabs: [ + ( + None, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + EditFile( + "/tmp/foo/bar", + None, + Some( + "/tmp/foo/", + ), + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ], focused_tab_index: None, template: Some( ( @@ -38,7 +87,11 @@ Layout { }, ], split_size: None, - run: None, + run: Some( + Cwd( + "/tmp", + ), + ), borderless: false, focus: None, external_children_index: None, diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs index f79af3a9..455fd4b3 100644 --- a/zellij-utils/src/ipc.rs +++ b/zellij-utils/src/ipc.rs @@ -302,10 +302,7 @@ where None }, }, - Err(e) => { - warn!("Error in protobuf IpcReceiver.recv_client_msg(): {:?}", e); - None - }, + Err(_e) => None, } } @@ -318,10 +315,7 @@ where None }, }, - Err(e) => { - warn!("Error in protobuf IpcReceiver.recv_server_msg(): {:?}", e); - None - }, + Err(_e) => None, } } @@ -392,10 +386,7 @@ pub fn recv_protobuf_client_to_server( None }, }, - Err(e) => { - warn!("Error reading protobuf message: {:?}", e); - None - }, + Err(_e) => None, } } @@ -410,9 +401,6 @@ pub fn recv_protobuf_server_to_client( None }, }, - Err(e) => { - warn!("Error reading protobuf message: {:?}", e); - None - }, + Err(_e) => None, } } diff --git a/zellij-utils/src/ipc/protobuf_conversion.rs b/zellij-utils/src/ipc/protobuf_conversion.rs index cf7b9802..e803f51e 100644 --- a/zellij-utils/src/ipc/protobuf_conversion.rs +++ b/zellij-utils/src/ipc/protobuf_conversion.rs @@ -723,19 +723,19 @@ impl From<crate::input::actions::Action> MoveTabAction, NewBlockingPaneAction, NewFloatingPaneAction, NewFloatingPluginPaneAction, NewInPlacePaneAction, NewInPlacePluginPaneAction, NewPaneAction, NewStackedPaneAction, NewTabAction, NewTiledPaneAction, - NewTiledPluginPaneAction, NextSwapLayoutAction, NoOpAction, PageScrollDownAction, - PageScrollUpAction, PaneIdWithPlugin, PaneNameInputAction, PreviousSwapLayoutAction, - QueryTabNamesAction, QuitAction, RenamePluginPaneAction, RenameSessionAction, - RenameTabAction, RenameTerminalPaneAction, ResizeAction, RunAction, ScrollDownAction, - ScrollDownAtAction, ScrollToBottomAction, ScrollToTopAction, ScrollUpAction, - ScrollUpAtAction, SearchAction, SearchInputAction, SearchToggleOptionAction, - SkipConfirmAction, StackPanesAction, StartOrReloadPluginAction, SwitchFocusAction, - SwitchModeForAllClientsAction, SwitchSessionAction, SwitchToModeAction, - TabNameInputAction, ToggleActiveSyncTabAction, ToggleFloatingPanesAction, - ToggleFocusFullscreenAction, ToggleGroupMarkingAction, ToggleMouseModeAction, - TogglePaneEmbedOrFloatingAction, TogglePaneFramesAction, TogglePaneInGroupAction, - TogglePanePinnedAction, ToggleTabAction, UndoRenamePaneAction, UndoRenameTabAction, - WriteAction, WriteCharsAction, + NewTiledPluginPaneAction, NextSwapLayoutAction, NoOpAction, OverrideLayoutAction, + PageScrollDownAction, PageScrollUpAction, PaneIdWithPlugin, PaneNameInputAction, + PreviousSwapLayoutAction, QueryTabNamesAction, QuitAction, RenamePluginPaneAction, + RenameSessionAction, RenameTabAction, RenameTerminalPaneAction, ResizeAction, + RunAction, ScrollDownAction, ScrollDownAtAction, ScrollToBottomAction, + ScrollToTopAction, ScrollUpAction, ScrollUpAtAction, SearchAction, SearchInputAction, + SearchToggleOptionAction, SkipConfirmAction, StackPanesAction, + StartOrReloadPluginAction, SwitchFocusAction, SwitchModeForAllClientsAction, + SwitchSessionAction, SwitchToModeAction, TabNameInputAction, ToggleActiveSyncTabAction, + ToggleFloatingPanesAction, ToggleFocusFullscreenAction, ToggleGroupMarkingAction, + ToggleMouseModeAction, TogglePaneEmbedOrFloatingAction, TogglePaneFramesAction, + TogglePaneInGroupAction, TogglePanePinnedAction, ToggleTabAction, UndoRenamePaneAction, + UndoRenameTabAction, WriteAction, WriteCharsAction, }; use std::collections::HashMap; @@ -1097,6 +1097,27 @@ impl From<crate::input::actions::Action> crate::input::actions::Action::NextSwapLayout => { ActionType::NextSwapLayout(NextSwapLayoutAction {}) }, + crate::input::actions::Action::OverrideLayout { + tiled_layout, + floating_layouts, + swap_tiled_layouts, + swap_floating_layouts, + tab_name, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + } => ActionType::OverrideLayout(OverrideLayoutAction { + tiled_layout: tiled_layout.map(|l| l.into()), + floating_layouts: floating_layouts.into_iter().map(|l| l.into()).collect(), + swap_tiled_layouts: swap_tiled_layouts + .map(|layouts| layouts.into_iter().map(|l| l.into()).collect()) + .unwrap_or_default(), + swap_floating_layouts: swap_floating_layouts + .map(|layouts| layouts.into_iter().map(|l| l.into()).collect()) + .unwrap_or_default(), + tab_name, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + }), crate::input::actions::Action::QueryTabNames => { ActionType::QueryTabNames(QueryTabNamesAction {}) }, @@ -1672,6 +1693,49 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action> Ok(crate::input::actions::Action::PreviousSwapLayout) }, ActionType::NextSwapLayout(_) => Ok(crate::input::actions::Action::NextSwapLayout), + ActionType::OverrideLayout(override_layout_action) => { + Ok(crate::input::actions::Action::OverrideLayout { + tiled_layout: override_layout_action + .tiled_layout + .map(|l| l.try_into()) + .transpose()?, + floating_layouts: override_layout_action + .floating_layouts + .into_iter() + .map(|l| l.try_into()) + .collect::<Result<Vec<_>>>()?, + swap_tiled_layouts: if override_layout_action.swap_tiled_layouts.is_empty() { + None + } else { + Some( + override_layout_action + .swap_tiled_layouts + .into_iter() + .map(|l| l.try_into()) + .collect::<Result<Vec<_>>>()?, + ) + }, + swap_floating_layouts: if override_layout_action + .swap_floating_layouts + .is_empty() + { + None + } else { + Some( + override_layout_action + .swap_floating_layouts + .into_iter() + .map(|l| l.try_into()) + .collect::<Result<Vec<_>>>()?, + ) + }, + tab_name: override_layout_action.tab_name, + retain_existing_terminal_panes: override_layout_action + .retain_existing_terminal_panes, + retain_existing_plugin_panes: override_layout_action + .retain_existing_plugin_panes, + }) + }, ActionType::QueryTabNames(_) => Ok(crate::input::actions::Action::QueryTabNames), ActionType::NewTiledPluginPane(new_tiled_plugin_action) => { Ok(crate::input::actions::Action::NewTiledPluginPane { diff --git a/zellij-utils/src/ipc/tests/roundtrip_tests.rs b/zellij-utils/src/ipc/tests/roundtrip_tests.rs index 61981f38..216c6e14 100644 --- a/zellij-utils/src/ipc/tests/roundtrip_tests.rs +++ b/zellij-utils/src/ipc/tests/roundtrip_tests.rs @@ -2053,6 +2053,20 @@ fn test_client_messages() { is_cli_client: true, }); test_client_roundtrip!(ClientToServerMsg::Action { + action: Action::OverrideLayout { + tiled_layout: None, + floating_layouts: vec![], + swap_tiled_layouts: None, + swap_floating_layouts: None, + tab_name: None, + retain_existing_terminal_panes: false, + retain_existing_plugin_panes: false, + }, + terminal_id: Some(1), + client_id: Some(100), + is_cli_client: true, + }); + test_client_roundtrip!(ClientToServerMsg::Action { action: Action::NoOp, terminal_id: Some(1), client_id: Some(100), diff --git a/zellij-utils/src/kdl/kdl_layout_parser.rs b/zellij-utils/src/kdl/kdl_layout_parser.rs index c38c0146..38cda189 100644 --- a/zellij-utils/src/kdl/kdl_layout_parser.rs +++ b/zellij-utils/src/kdl/kdl_layout_parser.rs @@ -2131,19 +2131,39 @@ impl<'a> KdlLayoutParser<'a> { floating_panes: Vec<FloatingPaneLayout>, swap_tiled_layouts: Vec<SwapTiledLayout>, swap_floating_layouts: Vec<SwapFloatingLayout>, + tab_name: Option<String>, + split_direction: SplitDirection, + hide_floating_panes: bool, + tab_cwd: Option<PathBuf>, ) -> Result<Layout, ConfigError> { - let main_tab_layout = TiledPaneLayout { + let mut main_tab_layout = TiledPaneLayout { children: panes, + children_split_direction: split_direction, + hide_floating_panes, ..Default::default() }; + let mut floating_panes = floating_panes; + if let Some(cwd_prefix) = self.cwd_prefix(tab_cwd.as_ref())? { + main_tab_layout.add_cwd_to_layout(&cwd_prefix); + for floating_pane in floating_panes.iter_mut() { + floating_pane.add_cwd_to_layout(&cwd_prefix); + } + } let default_template = self.default_template()?; - let tabs = if default_template.is_none() && self.new_tab_template.is_none() { - // in this case, the layout will be created as the default template and we don't need - // to explicitly place it in the first tab - vec![] - } else { - vec![(None, main_tab_layout.clone(), floating_panes.clone())] - }; + // Check if any tab properties are specified that would require creating an explicit tab + let has_tab_properties = tab_name.is_some() + || split_direction != SplitDirection::default() + || hide_floating_panes != false + || tab_cwd.is_some(); + let tabs = + if default_template.is_none() && self.new_tab_template.is_none() && !has_tab_properties + { + // in this case, the layout will be created as the default template and we don't need + // to explicitly place it in the first tab + vec![] + } else { + vec![(tab_name, main_tab_layout.clone(), floating_panes.clone())] + }; let template = default_template .map(|tiled_panes_template| (tiled_panes_template, floating_panes.clone())) .or_else(|| self.new_tab_template.clone()) @@ -2162,16 +2182,51 @@ impl<'a> KdlLayoutParser<'a> { child_floating_panes: Vec<FloatingPaneLayout>, swap_tiled_layouts: Vec<SwapTiledLayout>, swap_floating_layouts: Vec<SwapFloatingLayout>, + tab_name: Option<String>, + split_direction: SplitDirection, + hide_floating_panes: bool, + tab_cwd: Option<PathBuf>, ) -> Result<Layout, ConfigError> { + let mut child_floating_panes = child_floating_panes; let template = if let Some(new_tab_template) = &self.new_tab_template { Some(new_tab_template.clone()) } else { - let default_tab_tiled_panes_template = self + let mut default_tab_tiled_panes_template = self .default_template()? .unwrap_or_else(|| TiledPaneLayout::default()); - Some((default_tab_tiled_panes_template, child_floating_panes)) + + default_tab_tiled_panes_template.children_split_direction = split_direction; + default_tab_tiled_panes_template.hide_floating_panes = hide_floating_panes; + + if let Some(cwd_prefix) = self.cwd_prefix(tab_cwd.as_ref())? { + default_tab_tiled_panes_template.add_cwd_to_layout(&cwd_prefix); + for floating_pane in child_floating_panes.iter_mut() { + floating_pane.add_cwd_to_layout(&cwd_prefix); + } + } + + Some(( + default_tab_tiled_panes_template, + child_floating_panes.clone(), + )) + }; + // Check if any tab properties are specified that would require creating an explicit tab + let has_tab_properties = tab_name.is_some() + || split_direction != SplitDirection::default() + || hide_floating_panes != false + || tab_cwd.is_some(); + let tabs = if has_tab_properties { + // If we have tab properties, we need to create a tab with those properties + if let Some((ref tiled_layout, ref floating_panes)) = template { + vec![(tab_name, tiled_layout.clone(), floating_panes.clone())] + } else { + vec![] + } + } else { + vec![] }; Ok(Layout { + tabs, template, swap_tiled_layouts, swap_floating_layouts, @@ -2423,6 +2478,23 @@ impl<'a> KdlLayoutParser<'a> { } } if !child_tabs.is_empty() { + // Check if layout_node has tab properties when there are explicit tabs + let layout_has_tab_name = + kdl_get_string_property_or_child_value!(layout_node, "name").is_some(); + let layout_has_split_direction = + kdl_get_string_property_or_child_value_with_error!(layout_node, "split_direction") + .is_some(); + let layout_has_hide_floating = + kdl_get_bool_property_or_child_value!(layout_node, "hide_floating_panes").is_some(); + + if layout_has_tab_name || layout_has_split_direction || layout_has_hide_floating { + return Err(ConfigError::new_layout_kdl_error( + "Tab properties on the layout node can only be used when there are no explicit tab nodes".into(), + layout_node.span().offset(), + layout_node.span().len(), + )); + } + let has_more_than_one_focused_tab = child_tabs .iter() .filter(|(is_focused, _, _, _)| *is_focused) @@ -2454,17 +2526,43 @@ impl<'a> KdlLayoutParser<'a> { swap_floating_layouts, ) } else if !child_panes.is_empty() { + // Extract tab properties from layout_node + let tab_name = + kdl_get_string_property_or_child_value!(layout_node, "name").map(|s| s.to_string()); + let split_direction = self.parse_split_direction(layout_node)?; + let hide_floating_panes = + kdl_get_bool_property_or_child_value!(layout_node, "hide_floating_panes") + .unwrap_or(false); + let tab_cwd = self.parse_path(layout_node, "cwd")?; + self.layout_with_one_tab( child_panes, child_floating_panes, swap_tiled_layouts, swap_floating_layouts, + tab_name, + split_direction, + hide_floating_panes, + tab_cwd, ) } else { + // Extract tab properties for layout_with_one_pane case + let tab_name = + kdl_get_string_property_or_child_value!(layout_node, "name").map(|s| s.to_string()); + let split_direction = self.parse_split_direction(layout_node)?; + let hide_floating_panes = + kdl_get_bool_property_or_child_value!(layout_node, "hide_floating_panes") + .unwrap_or(false); + let tab_cwd = self.parse_path(layout_node, "cwd")?; + self.layout_with_one_pane( child_floating_panes, swap_tiled_layouts, swap_floating_layouts, + tab_name, + split_direction, + hide_floating_panes, + tab_cwd, ) } } diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index 5ca02d86..7603af3f 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -1723,6 +1723,110 @@ impl TryFrom<(&KdlNode, &Options)> for Action { }) } }, + "OverrideLayout" => { + let command_metadata = action_children.iter().next(); + if command_metadata.is_none() { + return Ok(Action::OverrideLayout { + tiled_layout: None, + floating_layouts: vec![], + swap_tiled_layouts: None, + swap_floating_layouts: None, + tab_name: None, + retain_existing_terminal_panes: false, + retain_existing_plugin_panes: false, + }); + } + + let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); + + let layout = command_metadata + .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "layout")) + .map(|layout_string| PathBuf::from(layout_string)) + .or_else(|| config_options.default_layout.clone()); + let cwd = command_metadata + .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "cwd")) + .map(|cwd_string| PathBuf::from(cwd_string)) + .map(|cwd| current_dir.join(cwd)); + let name = command_metadata + .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "name")) + .map(|name_string| name_string.to_string()); + let retain_existing_terminal_panes = command_metadata + .and_then(|c_m| { + kdl_child_bool_value_for_entry(c_m, "retain_existing_terminal_panes") + }) + .unwrap_or(false); + let retain_existing_plugin_panes = command_metadata + .and_then(|c_m| { + kdl_child_bool_value_for_entry(c_m, "retain_existing_plugin_panes") + }) + .unwrap_or(false); + + let layout_dir = config_options + .layout_dir + .clone() + .or_else(|| get_layout_dir(find_default_config_dir())); + let (path_to_raw_layout, raw_layout, swap_layouts) = + Layout::stringified_from_path_or_default(layout.as_ref(), layout_dir).map_err( + |e| { + ConfigError::new_kdl_error( + format!("Failed to load layout: {}", e), + kdl_action.span().offset(), + kdl_action.span().len(), + ) + }, + )?; + + let layout = Layout::from_str( + &raw_layout, + path_to_raw_layout, + swap_layouts.as_ref().map(|(f, p)| (f.as_str(), p.as_str())), + cwd.clone(), + ) + .map_err(|e| { + ConfigError::new_kdl_error( + format!("Failed to load layout: {}", e), + kdl_action.span().offset(), + kdl_action.span().len(), + ) + })?; + + let swap_tiled_layouts = Some(layout.swap_tiled_layouts.clone()); + let swap_floating_layouts = Some(layout.swap_floating_layouts.clone()); + + let mut tabs = layout.tabs(); + if tabs.len() > 1 { + return Err(ConfigError::new_kdl_error( + "Tab layout cannot itself have tabs".to_string(), + kdl_action.span().offset(), + kdl_action.span().len(), + )); + } else if !tabs.is_empty() { + let (tab_name, layout, floating_panes_layout) = tabs.drain(..).next().unwrap(); + let name = tab_name.or(name); + + Ok(Action::OverrideLayout { + tiled_layout: Some(layout), + floating_layouts: floating_panes_layout, + swap_tiled_layouts, + swap_floating_layouts, + tab_name: name, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + }) + } else { + let (layout, floating_panes_layout) = layout.new_tab(); + + Ok(Action::OverrideLayout { + tiled_layout: Some(layout), + floating_layouts: floating_panes_layout, + swap_tiled_layouts, + swap_floating_layouts, + tab_name: name, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + }) + } + }, "GoToTab" => parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action), "TabNameInput" => { parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action) diff --git a/zellij-utils/src/plugin_api/action.proto b/zellij-utils/src/plugin_api/action.proto index c29884af..de8ef44f 100644 --- a/zellij-utils/src/plugin_api/action.proto +++ b/zellij-utils/src/plugin_api/action.proto @@ -162,6 +162,16 @@ message NewTabPayload { optional UnblockCondition first_pane_unblock_condition = 9; } +message OverrideLayoutPayload { + optional TiledPaneLayout tiled_layout = 1; + repeated FloatingPaneLayout floating_layouts = 2; + repeated SwapTiledLayout swap_tiled_layouts = 3; + repeated SwapFloatingLayout swap_floating_layouts = 4; + optional string tab_name = 5; + bool retain_existing_terminal_panes = 10; + bool retain_existing_plugin_panes = 11; +} + message Action { ActionName name = 1; oneof optional_payload { @@ -213,6 +223,7 @@ message Action { NewBlockingPanePayload new_blocking_pane_payload = 50; NewTabPayload new_tab_payload = 51; NewInPlacePanePayload new_in_place_pane_payload = 52; + OverrideLayoutPayload override_layout_payload = 53; } } @@ -429,6 +440,7 @@ enum ActionName { SwitchSession = 90; NewBlockingPane = 91; NewInPlacePane = 92; + OverrideLayout = 93; } message Position { diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs index f8de7b76..860b090b 100644 --- a/zellij-utils/src/plugin_api/action.rs +++ b/zellij-utils/src/plugin_api/action.rs @@ -37,6 +37,7 @@ pub use super::generated_api::api::{ NewPluginPanePayload, NewTabPayload, NewTiledPanePayload, + OverrideLayoutPayload, PaneId as ProtobufPaneId, PaneIdAndShouldFloat, PaneRun as ProtobufPaneRun, @@ -720,6 +721,55 @@ impl TryFrom<ProtobufAction> for Action { Some(_) => Err("NextSwapLayout should not have a payload"), None => Ok(Action::NextSwapLayout), }, + Some(ProtobufActionName::OverrideLayout) => match protobuf_action.optional_payload { + Some(OptionalPayload::OverrideLayoutPayload(payload)) => { + let tiled_layout = payload.tiled_layout.map(|l| l.try_into()).transpose()?; + + let floating_layouts = payload + .floating_layouts + .into_iter() + .map(|l| l.try_into()) + .collect::<Result<Vec<_>, _>>()?; + + let swap_tiled_layouts = if payload.swap_tiled_layouts.is_empty() { + None + } else { + Some( + payload + .swap_tiled_layouts + .into_iter() + .map(|l| l.try_into()) + .collect::<Result<Vec<_>, _>>()?, + ) + }; + + let swap_floating_layouts = if payload.swap_floating_layouts.is_empty() { + None + } else { + Some( + payload + .swap_floating_layouts + .into_iter() + .map(|l| l.try_into()) + .collect::<Result<Vec<_>, _>>()?, + ) + }; + + let tab_name = payload.tab_name.filter(|s| !s.is_empty()); + + Ok(Action::OverrideLayout { + tiled_layout, + floating_layouts, + swap_tiled_layouts, + swap_floating_layouts, + tab_name, + retain_existing_terminal_panes: payload.retain_existing_terminal_panes, + retain_existing_plugin_panes: payload.retain_existing_plugin_panes, + }) + }, + Some(_) => Err("Mismatched payload for OverrideLayout"), + None => Err("Missing payload for OverrideLayout"), + }, Some(ProtobufActionName::QueryTabNames) => match protobuf_action.optional_payload { Some(_) => Err("QueryTabNames should not have a payload"), None => Ok(Action::QueryTabNames), @@ -1505,6 +1555,57 @@ impl TryFrom<Action> for ProtobufAction { name: ProtobufActionName::NextSwapLayout as i32, optional_payload: None, }), + Action::OverrideLayout { + tiled_layout, + floating_layouts, + swap_tiled_layouts, + swap_floating_layouts, + tab_name, + retain_existing_terminal_panes, + retain_existing_plugin_panes, + } => { + let protobuf_tiled_layout = tiled_layout.map(|l| l.try_into()).transpose()?; + + let protobuf_floating_layouts = floating_layouts + .into_iter() + .map(|l| l.try_into()) + .collect::<Result<Vec<_>, _>>()?; + + let protobuf_swap_tiled_layouts = swap_tiled_layouts + .map(|layouts| { + layouts + .into_iter() + .map(|l| l.try_into()) + .collect::<Result<Vec<_>, _>>() + }) + .transpose()? + .unwrap_or_default(); + + let protobuf_swap_floating_layouts = swap_floating_layouts + .map(|layouts| { + layouts + .into_iter() + .map(|l| l.try_into()) + .collect::<Result<Vec<_>, _>>() + }) + .transpose()? + .unwrap_or_default(); + + Ok(ProtobufAction { + name: ProtobufActionName::OverrideLayout as i32, + optional_payload: Some(OptionalPayload::OverrideLayoutPayload( + OverrideLayoutPayload { + tiled_layout: protobuf_tiled_layout, + floating_layouts: protobuf_floating_layouts, + swap_tiled_layouts: protobuf_swap_tiled_layouts, + swap_floating_layouts: protobuf_swap_floating_layouts, + tab_name: tab_name.clone(), + retain_existing_terminal_panes, + retain_existing_plugin_panes, + }, + )), + }) + }, Action::QueryTabNames => Ok(ProtobufAction { name: ProtobufActionName::QueryTabNames as i32, optional_payload: None, |
