diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 4422105d..9b2c23fb 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -179,7 +179,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { vec![ (s("Move focus"), s("Move"), focus_keys), - (s("New"), s("New"), action_key(&km, &[A::NewTab(None, None), TO_NORMAL])), + (s("New"), s("New"), action_key(&km, &[A::NewTab(None, vec![], None), TO_NORMAL])), (s("Close"), s("Close"), action_key(&km, &[A::CloseTab, TO_NORMAL])), (s("Rename"), s("Rename"), action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])), @@ -253,7 +253,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { (s("Split down"), s("Down"), action_key(&km, &[A::NewPane(Some(Dir::Down), None), TO_NORMAL])), (s("Split right"), s("Right"), action_key(&km, &[A::NewPane(Some(Dir::Right), None), TO_NORMAL])), (s("Fullscreen"), s("Fullscreen"), action_key(&km, &[A::ToggleFocusFullscreen, TO_NORMAL])), - (s("New tab"), s("New"), action_key(&km, &[A::NewTab(None, None), TO_NORMAL])), + (s("New tab"), s("New"), action_key(&km, &[A::NewTab(None, vec![], None), TO_NORMAL])), (s("Rename tab"), s("Rename"), action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])), (s("Previous Tab"), s("Previous"), action_key(&km, &[A::GoToPreviousTab, TO_NORMAL])), diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 40c2edc2..bae871f4 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -332,7 +332,7 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { }) }); - let spawn_tabs = |tab_layout, tab_name| { + let spawn_tabs = |tab_layout, floating_panes_layout, tab_name| { session_data .read() .unwrap() @@ -342,6 +342,7 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { .send_to_screen(ScreenInstruction::NewTab( default_shell.clone(), tab_layout, + floating_panes_layout, tab_name, client_id, )) @@ -349,8 +350,12 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { }; if layout.has_tabs() { - for (tab_name, tab_layout) in layout.tabs() { - spawn_tabs(Some(tab_layout.clone()), tab_name); + for (tab_name, tab_layout, floating_panes_layout) in layout.tabs() { + spawn_tabs( + Some(tab_layout.clone()), + floating_panes_layout.clone(), + tab_name, + ); } if let Some(focused_tab_index) = layout.focused_tab_index() { @@ -367,7 +372,7 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { .unwrap(); } } else { - spawn_tabs(None, None); + spawn_tabs(None, layout.floating_panes_template.clone(), None); } session_data .read() diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs index fe98ee63..47b3e2f2 100644 --- a/zellij-server/src/panes/floating_panes/mod.rs +++ b/zellij-server/src/panes/floating_panes/mod.rs @@ -25,7 +25,8 @@ use zellij_utils::{ data::{ModeInfo, Style}, errors::prelude::*, input::command::RunCommand, - pane_size::{Offset, PaneGeom, Size, Viewport}, + input::layout::FloatingPanesLayout, + pane_size::{Dimension, Offset, PaneGeom, Size, Viewport}, }; const RESIZE_INCREMENT_WIDTH: usize = 5; @@ -224,9 +225,55 @@ impl FloatingPanes { ); floating_pane_grid.find_room_for_new_pane() } + pub fn position_floating_pane_layout( + &mut self, + floating_pane_layout: &FloatingPanesLayout, + ) -> PaneGeom { + let display_area = *self.display_area.borrow(); + let viewport = *self.viewport.borrow(); + let floating_pane_grid = FloatingPaneGrid::new( + &mut self.panes, + &mut self.desired_pane_positions, + display_area, + viewport, + ); + let mut position = floating_pane_grid.find_room_for_new_pane().unwrap(); // TODO: no unwrap + if let Some(x) = &floating_pane_layout.x { + position.x = x.to_position(display_area.cols); + } + if let Some(y) = &floating_pane_layout.y { + position.y = y.to_position(display_area.rows); + } + if let Some(width) = &floating_pane_layout.width { + position.cols = Dimension::fixed(width.to_position(display_area.cols)); + } + if let Some(height) = &floating_pane_layout.height { + position.rows = Dimension::fixed(height.to_position(display_area.rows)); + } + if position.cols.as_usize() > display_area.cols { + position.cols = Dimension::fixed(display_area.cols); + } + if position.rows.as_usize() > display_area.rows { + position.rows = Dimension::fixed(display_area.rows); + } + if position.x + position.cols.as_usize() > display_area.cols { + position.x = position + .x + .saturating_sub((position.x + position.cols.as_usize()) - display_area.cols); + } + if position.y + position.rows.as_usize() > display_area.rows { + position.y = position + .y + .saturating_sub((position.y + position.rows.as_usize()) - display_area.rows); + } + position + } pub fn first_floating_pane_id(&self) -> Option { self.panes.keys().next().copied() } + pub fn last_floating_pane_id(&self) -> Option { + self.panes.keys().last().copied() + } pub fn first_active_floating_pane_id(&self) -> Option { self.active_panes.values().next().copied() } diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs index 726627f4..cad53e3f 100644 --- a/zellij-server/src/panes/tiled_panes/mod.rs +++ b/zellij-server/src/panes/tiled_panes/mod.rs @@ -539,10 +539,13 @@ impl TiledPanes { viewport.cols = (viewport.cols as isize + column_difference) as usize; display_area.cols = cols; }, - Err(e) => { - Err::<(), _>(anyError::msg(e)) - .context("failed to resize tab horizontally") - .non_fatal(); + Err(e) => match e.downcast_ref::() { + Some(ZellijError::PaneSizeUnchanged) => {}, // ignore unchanged layout + _ => { + Err::<(), _>(anyError::msg(e)) + .context("failed to resize tab horizontally") + .non_fatal(); + }, }, }; match pane_grid.layout(SplitDirection::Vertical, rows) { @@ -551,10 +554,13 @@ impl TiledPanes { viewport.rows = (viewport.rows as isize + row_difference) as usize; display_area.rows = rows; }, - Err(e) => { - Err::<(), _>(anyError::msg(e)) - .context("failed to resize tab vertically") - .non_fatal(); + Err(e) => match e.downcast_ref::() { + Some(ZellijError::PaneSizeUnchanged) => {}, // ignore unchanged layout + _ => { + Err::<(), _>(anyError::msg(e)) + .context("failed to resize tab vertically") + .non_fatal(); + }, }, }; } diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs index 100833ae..ba46a545 100644 --- a/zellij-server/src/plugins/mod.rs +++ b/zellij-server/src/plugins/mod.rs @@ -12,7 +12,7 @@ use zellij_utils::{ errors::{prelude::*, ContextType, PluginContext}, input::{ command::TerminalAction, - layout::{Layout, PaneLayout, Run, RunPlugin, RunPluginLocation}, + layout::{FloatingPanesLayout, Layout, PaneLayout, Run, RunPlugin, RunPluginLocation}, plugins::PluginsConfig, }, pane_size::Size, @@ -29,6 +29,7 @@ pub enum PluginInstruction { NewTab( Option, Option, + Vec, Option, // tab name usize, // tab_index ClientId, @@ -69,7 +70,6 @@ pub(crate) fn plugin_thread_main( let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel"); err_ctx.add_call(ContextType::Plugin((&event).into())); match event { - // TODO: remove pid_tx from here PluginInstruction::Load(run, tab_index, client_id, size) => { wasm_bridge.load_plugin(&run, tab_index, size, client_id)?; }, @@ -91,16 +91,22 @@ pub(crate) fn plugin_thread_main( PluginInstruction::NewTab( terminal_action, tab_layout, + floating_panes_layout, tab_name, tab_index, client_id, ) => { let mut plugin_ids: HashMap> = HashMap::new(); - let extracted_run_instructions = tab_layout + let mut extracted_run_instructions = tab_layout .clone() - .unwrap_or_else(|| layout.new_tab()) + .unwrap_or_else(|| layout.new_tab().0) .extract_run_instructions(); - let size = Size::default(); // TODO: is this bad? + let size = Size::default(); + let mut extracted_floating_plugins: Vec> = floating_panes_layout + .iter() + .map(|f| f.run.clone()) + .collect(); + extracted_run_instructions.append(&mut extracted_floating_plugins); for run_instruction in extracted_run_instructions { if let Some(Run::Plugin(run)) = run_instruction { let plugin_id = @@ -111,6 +117,7 @@ pub(crate) fn plugin_thread_main( drop(bus.senders.send_to_pty(PtyInstruction::NewTab( terminal_action, tab_layout, + floating_panes_layout, tab_name, tab_index, plugin_ids, diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index 9f4cd3e3..04e3391f 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -15,7 +15,7 @@ use zellij_utils::{ errors::{ContextType, PtyContext}, input::{ command::{RunCommand, TerminalAction}, - layout::{Layout, PaneLayout, Run, RunPluginLocation}, + layout::{FloatingPanesLayout, Layout, PaneLayout, Run, RunPluginLocation}, }, }; @@ -50,6 +50,7 @@ pub enum PtyInstruction { NewTab( Option, Option, + Vec, Option, usize, // tab_index HashMap>, // plugin_ids @@ -335,6 +336,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { PtyInstruction::NewTab( terminal_action, tab_layout, + floating_panes_layout, tab_name, tab_index, plugin_ids, @@ -342,8 +344,14 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { ) => { let err_context = || format!("failed to open new tab for client {}", client_id); + let floating_panes_layout = if floating_panes_layout.is_empty() { + layout.new_tab().1 + } else { + floating_panes_layout + }; pty.spawn_terminals_for_layout( - tab_layout.unwrap_or_else(|| layout.new_tab()), + tab_layout.unwrap_or_else(|| layout.new_tab().0), + floating_panes_layout, terminal_action.clone(), plugin_ids, tab_index, @@ -562,6 +570,7 @@ impl Pty { pub fn spawn_terminals_for_layout( &mut self, layout: PaneLayout, + floating_panes_layout: Vec, default_shell: Option, plugin_ids: HashMap>, tab_index: usize, @@ -572,176 +581,28 @@ impl Pty { let mut default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal(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().map(|f| f.run.clone()); let mut new_pane_pids: Vec<(u32, bool, Option, Result)> = vec![]; // (terminal_id, // starts_held, // run_command, // file_descriptor) + let mut new_floating_panes_pids: Vec<(u32, bool, Option, Result)> = + vec![]; // same + // as + // new_pane_pids for run_instruction in extracted_run_instructions { - let quit_cb = Box::new({ - let senders = self.bus.senders.clone(); - move |pane_id, _exit_status, _command| { - let _ = senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None)); - } - }); - match run_instruction { - Some(Run::Command(command)) => { - let starts_held = command.hold_on_start; - let hold_on_close = command.hold_on_close; - let quit_cb = Box::new({ - let senders = self.bus.senders.clone(); - move |pane_id, exit_status, command| { - if hold_on_close { - let _ = senders.send_to_screen(ScreenInstruction::HoldPane( - pane_id, - exit_status, - command, - None, - )); - } else { - let _ = senders - .send_to_screen(ScreenInstruction::ClosePane(pane_id, None)); - } - } - }); - let cmd = TerminalAction::RunCommand(command.clone()); - if starts_held { - // we don't actually open a terminal in this case, just wait for the user to run it - match self - .bus - .os_input - .as_mut() - .context("no OS I/O interface found") - .with_context(err_context)? - .reserve_terminal_id() - { - Ok(terminal_id) => { - new_pane_pids.push(( - terminal_id, - starts_held, - Some(command.clone()), - Ok(terminal_id as i32), // this is not actually correct but gets - // stripped later - )); - }, - Err(e) => Err::<(), _>(e).with_context(err_context).non_fatal(), - } - } else { - match self - .bus - .os_input - .as_mut() - .context("no OS I/O interface found") - .with_context(err_context)? - .spawn_terminal(cmd, quit_cb, self.default_editor.clone()) - .with_context(err_context) - { - Ok((terminal_id, pid_primary, child_fd)) => { - self.id_to_child_pid.insert(terminal_id, child_fd); - new_pane_pids.push(( - terminal_id, - starts_held, - Some(command.clone()), - Ok(pid_primary), - )); - }, - Err(err) => match err.downcast_ref::() { - Some(ZellijError::CommandNotFound { terminal_id, .. }) => { - new_pane_pids.push(( - *terminal_id, - starts_held, - Some(command.clone()), - Err(err), - )); - }, - _ => { - Err::<(), _>(err).non_fatal(); - }, - }, - } - } - }, - Some(Run::Cwd(cwd)) => { - let starts_held = false; // we do not hold Cwd panes - let shell = self.get_default_terminal(Some(cwd)); - match self - .bus - .os_input - .as_mut() - .context("no OS I/O interface found") - .with_context(err_context)? - .spawn_terminal(shell, quit_cb, self.default_editor.clone()) - .with_context(err_context) - { - Ok((terminal_id, pid_primary, child_fd)) => { - self.id_to_child_pid.insert(terminal_id, child_fd); - new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary))); - }, - Err(err) => match err.downcast_ref::() { - Some(ZellijError::CommandNotFound { terminal_id, .. }) => { - new_pane_pids.push((*terminal_id, starts_held, None, Err(err))); - }, - _ => { - Err::<(), _>(err).non_fatal(); - }, - }, - } - }, - Some(Run::EditFile(path_to_file, line_number)) => { - let starts_held = false; // we do not hold edit panes (for now?) - match self - .bus - .os_input - .as_mut() - .context("no OS I/O interface found") - .with_context(err_context)? - .spawn_terminal( - TerminalAction::OpenFile(path_to_file, line_number), - quit_cb, - self.default_editor.clone(), - ) - .with_context(err_context) - { - Ok((terminal_id, pid_primary, child_fd)) => { - self.id_to_child_pid.insert(terminal_id, child_fd); - new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary))); - }, - Err(err) => match err.downcast_ref::() { - Some(ZellijError::CommandNotFound { terminal_id, .. }) => { - new_pane_pids.push((*terminal_id, starts_held, None, Err(err))); - }, - _ => { - Err::<(), _>(err).non_fatal(); - }, - }, - } - }, - None => { - let starts_held = false; - match self - .bus - .os_input - .as_mut() - .context("no OS I/O interface found") - .with_context(err_context)? - .spawn_terminal(default_shell.clone(), quit_cb, self.default_editor.clone()) - .with_context(err_context) - { - Ok((terminal_id, pid_primary, child_fd)) => { - self.id_to_child_pid.insert(terminal_id, child_fd); - new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary))); - }, - Err(err) => match err.downcast_ref::() { - Some(ZellijError::CommandNotFound { terminal_id, .. }) => { - new_pane_pids.push((*terminal_id, starts_held, None, Err(err))); - }, - _ => { - Err::<(), _>(err).non_fatal(); - }, - }, - } - }, - // Investigate moving plugin loading to here. - Some(Run::Plugin(_)) => {}, + if let Some(new_pane_data) = + self.apply_run_instruction(run_instruction, default_shell.clone())? + { + new_pane_pids.push(new_pane_data); + } + } + for run_instruction in extracted_floating_run_instructions { + if let Some(new_pane_data) = + self.apply_run_instruction(run_instruction, default_shell.clone())? + { + new_floating_panes_pids.push(new_pane_data); } } // Option should only be Some if the pane starts held @@ -755,17 +616,32 @@ impl Pty { } }) .collect(); + let new_tab_floating_pane_ids: Vec<(u32, Option)> = 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::ApplyLayout( layout, + floating_panes_layout, new_tab_pane_ids, + new_tab_floating_pane_ids, plugin_ids, tab_index, client_id, )) .with_context(err_context)?; - for (terminal_id, starts_held, run_command, pid_primary) in new_pane_pids { + 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; @@ -820,6 +696,172 @@ impl Pty { } Ok(()) } + fn apply_run_instruction( + &mut self, + run_instruction: Option, + default_shell: TerminalAction, + ) -> Result, Result)>> { + // terminal_id, + // starts_held, + // command + // successfully opened + let err_context = || format!("failed to apply run instruction"); + let quit_cb = Box::new({ + let senders = self.bus.senders.clone(); + move |pane_id, _exit_status, _command| { + let _ = senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None)); + } + }); + match run_instruction { + Some(Run::Command(command)) => { + let starts_held = command.hold_on_start; + let hold_on_close = command.hold_on_close; + let quit_cb = Box::new({ + let senders = self.bus.senders.clone(); + move |pane_id, exit_status, command| { + if hold_on_close { + let _ = senders.send_to_screen(ScreenInstruction::HoldPane( + pane_id, + exit_status, + command, + None, + )); + } else { + let _ = + senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None)); + } + } + }); + let cmd = TerminalAction::RunCommand(command.clone()); + if starts_held { + // we don't actually open a terminal in this case, just wait for the user to run it + match self + .bus + .os_input + .as_mut() + .context("no OS I/O interface found") + .with_context(err_context)? + .reserve_terminal_id() + { + Ok(terminal_id) => { + Ok(Some(( + terminal_id, + starts_held, + Some(command.clone()), + Ok(terminal_id as i32), // this is not actually correct but gets + // stripped later + ))) + }, + Err(e) => Err(e), + } + } else { + match self + .bus + .os_input + .as_mut() + .context("no OS I/O interface found") + .with_context(err_context)? + .spawn_terminal(cmd, quit_cb, self.default_editor.clone()) + .with_context(err_context) + { + Ok((terminal_id, pid_primary, child_fd)) => { + self.id_to_child_pid.insert(terminal_id, child_fd); + Ok(Some(( + terminal_id, + starts_held, + Some(command.clone()), + Ok(pid_primary), + ))) + }, + Err(err) => { + match err.downcast_ref::() { + Some(ZellijError::CommandNotFound { terminal_id, .. }) => Ok(Some( + (*terminal_id, starts_held, Some(command.clone()), Err(err)), + )), + _ => Err(err), + } + }, + } + } + }, + Some(Run::Cwd(cwd)) => { + let starts_held = false; // we do not hold Cwd panes + let shell = self.get_default_terminal(Some(cwd)); + match self + .bus + .os_input + .as_mut() + .context("no OS I/O interface found") + .with_context(err_context)? + .spawn_terminal(shell, quit_cb, self.default_editor.clone()) + .with_context(err_context) + { + Ok((terminal_id, pid_primary, child_fd)) => { + self.id_to_child_pid.insert(terminal_id, child_fd); + Ok(Some((terminal_id, starts_held, None, Ok(pid_primary)))) + }, + Err(err) => match err.downcast_ref::() { + Some(ZellijError::CommandNotFound { terminal_id, .. }) => { + Ok(Some((*terminal_id, starts_held, None, Err(err)))) + }, + _ => Err(err), + }, + } + }, + Some(Run::EditFile(path_to_file, line_number)) => { + let starts_held = false; // we do not hold edit panes (for now?) + match self + .bus + .os_input + .as_mut() + .context("no OS I/O interface found") + .with_context(err_context)? + .spawn_terminal( + TerminalAction::OpenFile(path_to_file, line_number), + quit_cb, + self.default_editor.clone(), + ) + .with_context(err_context) + { + Ok((terminal_id, pid_primary, child_fd)) => { + self.id_to_child_pid.insert(terminal_id, child_fd); + Ok(Some((terminal_id, starts_held, None, Ok(pid_primary)))) + }, + Err(err) => match err.downcast_ref::() { + Some(ZellijError::CommandNotFound { terminal_id, .. }) => { + Ok(Some((*terminal_id, starts_held, None, Err(err)))) + }, + _ => Err(err), + }, + } + }, + None => { + let starts_held = false; + match self + .bus + .os_input + .as_mut() + .context("no OS I/O interface found") + .with_context(err_context)? + .spawn_terminal(default_shell.clone(), quit_cb, self.default_editor.clone()) + .with_context(err_context) + { + Ok((terminal_id, pid_primary, child_fd)) => { + self.id_to_child_pid.insert(terminal_id, child_fd); + Ok(Some((terminal_id, starts_held, None, Ok(pid_primary)))) + }, + Err(err) => match err.downcast_ref::() { + Some(ZellijError::CommandNotFound { terminal_id, .. }) => { + Ok(Some((*terminal_id, starts_held, None, Err(err)))) + }, + _ => Err(err), + }, + } + }, + // Investigate moving plugin loading to here. + Some(Run::Plugin(_)) => Ok(None), + } + } pub fn close_pane(&mut self, id: PaneId) -> Result<()> { let err_context = || format!("failed to close for pane {id:?}"); match id { diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 584bff3f..ea5f6693 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -430,12 +430,16 @@ pub(crate) fn route_action( .send_to_screen(ScreenInstruction::CloseFocusedPane(client_id)) .with_context(err_context)?; }, - Action::NewTab(tab_layout, tab_name) => { + Action::NewTab(tab_layout, floating_panes_layout, tab_name) => { let shell = session.default_shell.clone(); session .senders .send_to_screen(ScreenInstruction::NewTab( - shell, tab_layout, tab_name, client_id, + shell, + tab_layout, + floating_panes_layout, + tab_name, + client_id, )) .with_context(err_context)?; }, diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index a862cb40..2c5e44b5 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -12,7 +12,7 @@ use zellij_utils::input::options::Clipboard; use zellij_utils::pane_size::{Size, SizeInPixels}; use zellij_utils::{ input::command::TerminalAction, - input::layout::{PaneLayout, RunPluginLocation}, + input::layout::{FloatingPanesLayout, PaneLayout, RunPluginLocation}, position::Position, }; @@ -177,12 +177,15 @@ pub enum ScreenInstruction { NewTab( Option, Option, + Vec, Option, ClientId, ), ApplyLayout( PaneLayout, - Vec<(u32, HoldForCommand)>, + Vec, + Vec<(u32, HoldForCommand)>, // new pane pids + Vec<(u32, HoldForCommand)>, // new floating pane pids HashMap>, usize, // tab_index ClientId, @@ -920,7 +923,9 @@ impl Screen { pub fn apply_layout( &mut self, layout: PaneLayout, + floating_panes_layout: Vec, new_terminal_ids: Vec<(u32, HoldForCommand)>, + new_floating_terminal_ids: Vec<(u32, HoldForCommand)>, new_plugin_ids: HashMap>, tab_index: usize, client_id: ClientId, @@ -968,9 +973,10 @@ impl Screen { let tab = self.tabs.get_mut(&tab_index).unwrap(); // TODO: no unwrap tab.apply_layout( layout, + floating_panes_layout, new_terminal_ids, + new_floating_terminal_ids, new_plugin_ids, - tab_index, client_id, ) .with_context(err_context)?; @@ -1878,7 +1884,13 @@ pub(crate) fn screen_thread_main( screen.unblock_input()?; screen.render()?; }, - ScreenInstruction::NewTab(default_shell, layout, tab_name, client_id) => { + ScreenInstruction::NewTab( + default_shell, + layout, + floating_panes_layout, + tab_name, + client_id, + ) => { let tab_index = screen.get_new_tab_index(); screen.new_tab(tab_index, client_id)?; screen @@ -1887,6 +1899,7 @@ pub(crate) fn screen_thread_main( .send_to_plugin(PluginInstruction::NewTab( default_shell, layout, + floating_panes_layout, tab_name, tab_index, client_id, @@ -1894,12 +1907,22 @@ pub(crate) fn screen_thread_main( }, ScreenInstruction::ApplyLayout( layout, + floating_panes_layout, new_pane_pids, + new_floating_pane_pids, new_plugin_ids, tab_index, client_id, ) => { - screen.apply_layout(layout, new_pane_pids, new_plugin_ids, tab_index, client_id)?; + screen.apply_layout( + layout, + floating_panes_layout, + new_pane_pids, + new_floating_pane_pids, + new_plugin_ids, + tab_index, + client_id, + )?; screen.unblock_input()?; screen.render()?; }, diff --git a/zellij-server/src/tab/layout_applier.rs b/zellij-server/src/tab/layout_applier.rs new file mode 100644 index 00000000..a0b641df --- /dev/null +++ b/zellij-server/src/tab/layout_applier.rs @@ -0,0 +1,376 @@ +use zellij_utils::errors::prelude::*; + +use crate::resize_pty; +use crate::tab::{get_next_terminal_position, HoldForCommand, Pane}; + +use crate::{ + os_input_output::ServerOsApi, + panes::sixel::SixelImageStore, + panes::{FloatingPanes, TiledPanes}, + panes::{LinkHandler, PaneId, PluginPane, TerminalPane}, + plugins::PluginInstruction, + pty::PtyInstruction, + thread_bus::ThreadSenders, + ClientId, +}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; +use zellij_utils::{ + data::{Palette, Style}, + input::layout::{FloatingPanesLayout, PaneLayout, Run, RunPluginLocation}, + pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport}, +}; + +pub struct LayoutApplier<'a> { + viewport: Rc>, // includes all non-UI panes + senders: ThreadSenders, + sixel_image_store: Rc>, + link_handler: Rc>, + terminal_emulator_colors: Rc>, + terminal_emulator_color_codes: Rc>>, + character_cell_size: Rc>>, + style: Style, + display_area: Rc>, // includes all panes (including eg. the status bar and tab bar in the default layout) + tiled_panes: &'a mut TiledPanes, + floating_panes: &'a mut FloatingPanes, + draw_pane_frames: bool, + focus_pane_id: &'a mut Option, + os_api: Box, +} + +impl<'a> LayoutApplier<'a> { + pub fn new( + viewport: &Rc>, + senders: &ThreadSenders, + sixel_image_store: &Rc>, + link_handler: &Rc>, + terminal_emulator_colors: &Rc>, + terminal_emulator_color_codes: &Rc>>, + character_cell_size: &Rc>>, + style: &Style, + display_area: &Rc>, // includes all panes (including eg. the status bar and tab bar in the default layout) + tiled_panes: &'a mut TiledPanes, + floating_panes: &'a mut FloatingPanes, + draw_pane_frames: bool, + focus_pane_id: &'a mut Option, + os_api: &Box, + ) -> Self { + let viewport = viewport.clone(); + let senders = senders.clone(); + let sixel_image_store = sixel_image_store.clone(); + let link_handler = link_handler.clone(); + let terminal_emulator_colors = terminal_emulator_colors.clone(); + let terminal_emulator_color_codes = terminal_emulator_color_codes.clone(); + let character_cell_size = character_cell_size.clone(); + let style = style.clone(); + let display_area = display_area.clone(); + let os_api = os_api.clone(); + LayoutApplier { + viewport, + senders, + sixel_image_store, + link_handler, + terminal_emulator_colors, + terminal_emulator_color_codes, + character_cell_size, + style, + display_area, + tiled_panes, + floating_panes, + draw_pane_frames, + focus_pane_id, + os_api, + } + } + pub fn apply_layout( + &mut self, + layout: PaneLayout, + floating_panes_layout: Vec, + new_terminal_ids: Vec<(u32, HoldForCommand)>, + new_floating_terminal_ids: Vec<(u32, HoldForCommand)>, + mut new_plugin_ids: HashMap>, + client_id: ClientId, + ) -> Result { + // true => layout has floating panes + let layout_name = layout.name.clone(); + self.apply_tiled_panes_layout(layout, new_terminal_ids, &mut new_plugin_ids, client_id)?; + let layout_has_floating_panes = self.apply_floating_panes_layout( + floating_panes_layout, + new_floating_terminal_ids, + &mut new_plugin_ids, + layout_name, + )?; + return Ok(layout_has_floating_panes); + } + fn apply_tiled_panes_layout( + &mut self, + layout: PaneLayout, + new_terminal_ids: Vec<(u32, HoldForCommand)>, + new_plugin_ids: &mut HashMap>, + client_id: ClientId, + ) -> Result<()> { + let err_context = || format!("failed to apply tiled panes layout"); + let (viewport_cols, viewport_rows) = { + let viewport = self.viewport.borrow(); + (viewport.cols, viewport.rows) + }; + let mut free_space = PaneGeom::default(); + free_space.cols.set_inner(viewport_cols); + free_space.rows.set_inner(viewport_rows); + match layout.position_panes_in_space(&free_space) { + Ok(positions_in_layout) => { + let positions_and_size = positions_in_layout.iter(); + let mut new_terminal_ids = new_terminal_ids.iter(); + + let mut focus_pane_id: Option = None; + let mut set_focus_pane_id = |layout: &PaneLayout, pane_id: PaneId| { + if layout.focus.unwrap_or(false) && focus_pane_id.is_none() { + focus_pane_id = Some(pane_id); + } + }; + + for (layout, position_and_size) in positions_and_size { + // A plugin pane + if let Some(Run::Plugin(run)) = layout.run.clone() { + let pane_title = run.location.to_string(); + let pid = new_plugin_ids + .get_mut(&run.location) + .and_then(|ids| ids.pop()) + .with_context(err_context)?; + let mut new_plugin = PluginPane::new( + pid, + *position_and_size, + self.senders + .to_plugin + .as_ref() + .with_context(err_context)? + .clone(), + pane_title, + layout.name.clone().unwrap_or_default(), + self.sixel_image_store.clone(), + self.terminal_emulator_colors.clone(), + self.terminal_emulator_color_codes.clone(), + self.link_handler.clone(), + self.character_cell_size.clone(), + self.style, + ); + new_plugin.set_borderless(layout.borderless); + self.tiled_panes + .add_pane_with_existing_geom(PaneId::Plugin(pid), Box::new(new_plugin)); + set_focus_pane_id(layout, PaneId::Plugin(pid)); + } else { + // there are still panes left to fill, use the pids we received in this method + if let Some((pid, hold_for_command)) = new_terminal_ids.next() { + let next_terminal_position = + get_next_terminal_position(&self.tiled_panes, &self.floating_panes); + let initial_title = match &layout.run { + Some(Run::Command(run_command)) => Some(run_command.to_string()), + _ => None, + }; + let mut new_pane = TerminalPane::new( + *pid, + *position_and_size, + self.style, + next_terminal_position, + layout.name.clone().unwrap_or_default(), + self.link_handler.clone(), + self.character_cell_size.clone(), + self.sixel_image_store.clone(), + self.terminal_emulator_colors.clone(), + self.terminal_emulator_color_codes.clone(), + initial_title, + ); + new_pane.set_borderless(layout.borderless); + if let Some(held_command) = hold_for_command { + new_pane.hold(None, true, held_command.clone()); + } + self.tiled_panes.add_pane_with_existing_geom( + PaneId::Terminal(*pid), + Box::new(new_pane), + ); + set_focus_pane_id(layout, PaneId::Terminal(*pid)); + } + } + } + for (unused_pid, _) in new_terminal_ids { + self.senders + .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid))) + .with_context(err_context)?; + } + self.adjust_viewport(); + self.set_focused_tiled_pane(focus_pane_id, client_id); + }, + Err(e) => { + for (unused_pid, _) in new_terminal_ids { + self.senders + .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(unused_pid))) + .with_context(err_context)?; + } + Err::<(), _>(anyError::msg(e)) + .with_context(err_context) + .non_fatal(); // TODO: propagate this to the user + }, + }; + Ok(()) + } + fn apply_floating_panes_layout( + &mut self, + floating_panes_layout: Vec, + new_floating_terminal_ids: Vec<(u32, HoldForCommand)>, + new_plugin_ids: &mut HashMap>, + layout_name: Option, + ) -> Result { + // true => has floating panes + let err_context = || format!("Failed to apply_floating_panes_layout"); + let mut layout_has_floating_panes = false; + let floating_panes_layout = floating_panes_layout.iter(); + let mut focused_floating_pane = None; + let mut new_floating_terminal_ids = new_floating_terminal_ids.iter(); + for floating_pane_layout in floating_panes_layout { + layout_has_floating_panes = true; + if let Some(Run::Plugin(run)) = floating_pane_layout.run.clone() { + let position_and_size = self + .floating_panes + .position_floating_pane_layout(&floating_pane_layout); + let pane_title = run.location.to_string(); + let pid = new_plugin_ids + .get_mut(&run.location) + .and_then(|ids| ids.pop()) + .with_context(err_context)?; + let mut new_pane = PluginPane::new( + pid, + position_and_size, + self.senders + .to_plugin + .as_ref() + .with_context(err_context)? + .clone(), + pane_title, + layout_name.clone().unwrap_or_default(), + self.sixel_image_store.clone(), + self.terminal_emulator_colors.clone(), + self.terminal_emulator_color_codes.clone(), + self.link_handler.clone(), + self.character_cell_size.clone(), + self.style, + ); + new_pane.set_borderless(false); + new_pane.set_content_offset(Offset::frame(1)); + resize_pty!(new_pane, self.os_api, self.senders)?; + self.floating_panes + .add_pane(PaneId::Plugin(pid), Box::new(new_pane)); + if floating_pane_layout.focus.unwrap_or(false) { + focused_floating_pane = Some(PaneId::Plugin(pid)); + } + } else if let Some((pid, hold_for_command)) = new_floating_terminal_ids.next() { + let position_and_size = self + .floating_panes + .position_floating_pane_layout(&floating_pane_layout); + let next_terminal_position = + get_next_terminal_position(&self.tiled_panes, &self.floating_panes); + let initial_title = match &floating_pane_layout.run { + Some(Run::Command(run_command)) => Some(run_command.to_string()), + _ => None, + }; + let mut new_pane = TerminalPane::new( + *pid, + position_and_size, + self.style, + next_terminal_position, + floating_pane_layout.name.clone().unwrap_or_default(), + self.link_handler.clone(), + self.character_cell_size.clone(), + self.sixel_image_store.clone(), + self.terminal_emulator_colors.clone(), + self.terminal_emulator_color_codes.clone(), + initial_title, + ); + new_pane.set_borderless(false); + new_pane.set_content_offset(Offset::frame(1)); + if let Some(held_command) = hold_for_command { + new_pane.hold(None, true, held_command.clone()); + } + resize_pty!(new_pane, self.os_api, self.senders)?; + self.floating_panes + .add_pane(PaneId::Terminal(*pid), Box::new(new_pane)); + if floating_pane_layout.focus.unwrap_or(false) { + focused_floating_pane = Some(PaneId::Terminal(*pid)); + } + } + } + if let Some(focused_floating_pane) = focused_floating_pane { + self.floating_panes + .focus_pane_for_all_clients(focused_floating_pane); + } + if layout_has_floating_panes { + Ok(true) + } else { + Ok(false) + } + } + fn resize_whole_tab(&mut self, new_screen_size: Size) { + self.floating_panes.resize(new_screen_size); + self.floating_panes + .resize_pty_all_panes(&mut self.os_api) + .unwrap(); // we need to do this explicitly because floating_panes.resize does not do this + self.tiled_panes.resize(new_screen_size); + } + fn offset_viewport(&mut self, position_and_size: &Viewport) { + let mut viewport = self.viewport.borrow_mut(); + if position_and_size.x == viewport.x + && position_and_size.x + position_and_size.cols == viewport.x + viewport.cols + { + if position_and_size.y == viewport.y { + viewport.y += position_and_size.rows; + viewport.rows -= position_and_size.rows; + } else if position_and_size.y + position_and_size.rows == viewport.y + viewport.rows { + viewport.rows -= position_and_size.rows; + } + } + if position_and_size.y == viewport.y + && position_and_size.y + position_and_size.rows == viewport.y + viewport.rows + { + if position_and_size.x == viewport.x { + viewport.x += position_and_size.cols; + viewport.cols -= position_and_size.cols; + } else if position_and_size.x + position_and_size.cols == viewport.x + viewport.cols { + viewport.cols -= position_and_size.cols; + } + } + } + fn adjust_viewport(&mut self) { + // here we offset the viewport after applying a tiled panes layout + // from borderless panes that are on the edges of the + // screen, this is so that when we don't have pane boundaries (eg. when they were + // disabled by the user) boundaries won't be drawn around these panes + // geometrically, we can only do this with panes that are on the edges of the + // screen - so it's mostly a best-effort thing + let display_area = { + let display_area = self.display_area.borrow(); + *display_area + }; + self.resize_whole_tab(display_area); + let boundary_geoms = self.tiled_panes.borderless_pane_geoms(); + for geom in boundary_geoms { + self.offset_viewport(&geom) + } + self.tiled_panes.set_pane_frames(self.draw_pane_frames); + } + fn set_focused_tiled_pane(&mut self, focus_pane_id: Option, client_id: ClientId) { + if let Some(pane_id) = focus_pane_id { + *self.focus_pane_id = Some(pane_id); + self.tiled_panes.focus_pane(pane_id, client_id); + } else { + let next_selectable_pane_id = self.tiled_panes.first_selectable_pane_id(); + match next_selectable_pane_id { + Some(active_pane_id) => { + self.tiled_panes.focus_pane(active_pane_id, client_id); + }, + None => { + self.tiled_panes.clear_active_panes(); + }, + } + } + } +} diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index c2b26ecf..0cf3a697 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -3,6 +3,7 @@ mod clipboard; mod copy_command; +mod layout_applier; use copy_command::CopyCommand; use std::env::temp_dir; @@ -17,6 +18,7 @@ use crate::background_jobs::BackgroundJob; use crate::pty_writer::PtyWriteInstruction; use crate::screen::CopyOptions; use crate::ui::pane_boundaries_frame::FrameParams; +use layout_applier::LayoutApplier; use self::clipboard::ClipboardProvider; use crate::{ @@ -24,7 +26,7 @@ use crate::{ output::{CharacterChunk, Output, SixelImageChunk}, panes::sixel::SixelImageStore, panes::{FloatingPanes, TiledPanes}, - panes::{LinkHandler, PaneId, PluginPane, TerminalPane}, + panes::{LinkHandler, PaneId, TerminalPane}, plugins::PluginInstruction, pty::{ClientOrTabIndex, PtyInstruction, VteBytes}, thread_bus::ThreadSenders, @@ -42,7 +44,7 @@ use zellij_utils::{ data::{Event, InputMode, ModeInfo, Palette, PaletteColor, Style}, input::{ command::TerminalAction, - layout::{PaneLayout, Run, RunPluginLocation}, + layout::{FloatingPanesLayout, PaneLayout, RunPluginLocation}, parse_keys, }, pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport}, @@ -398,6 +400,26 @@ pub enum AdjustedInput { ReRunCommandInThisPane(RunCommand), CloseThisPane, } +pub fn get_next_terminal_position( + tiled_panes: &TiledPanes, + floating_panes: &FloatingPanes, +) -> usize { + let tiled_panes_count = tiled_panes + .get_panes() + .filter(|(k, _)| match k { + PaneId::Plugin(_) => false, + PaneId::Terminal(_) => true, + }) + .count(); + let floating_panes_count = floating_panes + .get_panes() + .filter(|(k, _)| match k { + PaneId::Plugin(_) => false, + PaneId::Terminal(_) => true, + }) + .count(); + tiled_panes_count + floating_panes_count + 1 +} impl Tab { // FIXME: Still too many arguments for clippy to be happy... @@ -509,170 +531,45 @@ impl Tab { pub fn apply_layout( &mut self, layout: PaneLayout, + floating_panes_layout: Vec, new_terminal_ids: Vec<(u32, HoldForCommand)>, - mut new_plugin_ids: HashMap>, - tab_index: usize, + new_floating_terminal_ids: Vec<(u32, HoldForCommand)>, + new_plugin_ids: HashMap>, client_id: ClientId, ) -> Result<()> { - let err_context = || { - format!( - "failed to apply layout {layout:#?} in tab {tab_index} for client id {client_id}" - ) - }; - - if self.tiled_panes.has_panes() { - Err::<(), _>(anyhow!( - "Applying a layout to a tab with existing panes - this is not yet supported!" - )) - .with_context(err_context) - .non_fatal(); - } - let (viewport_cols, viewport_rows) = { - let viewport = self.viewport.borrow(); - (viewport.cols, viewport.rows) - }; - let mut free_space = PaneGeom::default(); - free_space.cols.set_inner(viewport_cols); - free_space.rows.set_inner(viewport_rows); - - match layout.position_panes_in_space(&free_space) { - Ok(positions_in_layout) => { - let positions_and_size = positions_in_layout.iter(); - let mut new_terminal_ids = new_terminal_ids.iter(); - - let mut focus_pane_id: Option = None; - let mut set_focus_pane_id = |layout: &PaneLayout, pane_id: PaneId| { - if layout.focus.unwrap_or(false) && focus_pane_id.is_none() { - focus_pane_id = Some(pane_id); - } - }; - - for (layout, position_and_size) in positions_and_size { - // A plugin pane - if let Some(Run::Plugin(run)) = layout.run.clone() { - // let (pid_tx, pid_rx) = channel(); - let pane_title = run.location.to_string(); - let pid = new_plugin_ids - .get_mut(&run.location) - .unwrap() - .pop() - .unwrap(); // TODO: - // err_context - // and - // stuff - let mut new_plugin = PluginPane::new( - pid, - *position_and_size, - self.senders - .to_plugin - .as_ref() - .with_context(err_context)? - .clone(), - pane_title, - layout.name.clone().unwrap_or_default(), - self.sixel_image_store.clone(), - self.terminal_emulator_colors.clone(), - self.terminal_emulator_color_codes.clone(), - self.link_handler.clone(), - self.character_cell_size.clone(), - self.style, - ); - new_plugin.set_borderless(layout.borderless); - self.tiled_panes - .add_pane_with_existing_geom(PaneId::Plugin(pid), Box::new(new_plugin)); - set_focus_pane_id(layout, PaneId::Plugin(pid)); - } else { - // there are still panes left to fill, use the pids we received in this method - if let Some((pid, hold_for_command)) = new_terminal_ids.next() { - let next_terminal_position = self.get_next_terminal_position(); - let initial_title = match &layout.run { - Some(Run::Command(run_command)) => Some(run_command.to_string()), - _ => None, - }; - let mut new_pane = TerminalPane::new( - *pid, - *position_and_size, - self.style, - next_terminal_position, - layout.name.clone().unwrap_or_default(), - self.link_handler.clone(), - self.character_cell_size.clone(), - self.sixel_image_store.clone(), - self.terminal_emulator_colors.clone(), - self.terminal_emulator_color_codes.clone(), - initial_title, - ); - new_pane.set_borderless(layout.borderless); - if let Some(held_command) = hold_for_command { - new_pane.hold(None, true, held_command.clone()); - } - self.tiled_panes.add_pane_with_existing_geom( - PaneId::Terminal(*pid), - Box::new(new_pane), - ); - set_focus_pane_id(layout, PaneId::Terminal(*pid)); - } - } - } - for (unused_pid, _) in new_terminal_ids { - // this is a bit of a hack and happens because we don't have any central location that - // can query the screen as to how many panes it needs to create a layout - // fixing this will require a bit of an architecture change - self.senders - .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid))) - .with_context(err_context)?; - } - - // here we offset the viewport from borderless panes that are on the edges of the - // screen, this is so that when we don't have pane boundaries (eg. when they were - // disabled by the user) boundaries won't be drawn around these panes - // geometrically, we can only do this with panes that are on the edges of the - // screen - so it's mostly a best-effort thing - let display_area = { - let display_area = self.display_area.borrow(); - *display_area - }; - self.resize_whole_tab(display_area); - let boundary_geoms = self.tiled_panes.borderless_pane_geoms(); - for geom in boundary_geoms { - self.offset_viewport(&geom) - } - self.tiled_panes.set_pane_frames(self.draw_pane_frames); - self.should_clear_display_before_rendering = true; - - if let Some(pane_id) = focus_pane_id { - self.focus_pane_id = Some(pane_id); - self.tiled_panes.focus_pane(pane_id, client_id); - } else { - // This is the end of the nasty viewport hack... - let next_selectable_pane_id = self.tiled_panes.first_selectable_pane_id(); - match next_selectable_pane_id { - Some(active_pane_id) => { - self.tiled_panes.focus_pane(active_pane_id, client_id); - }, - None => { - // this is very likely a configuration error (layout with no selectable panes) - self.tiled_panes.clear_active_panes(); - }, - } - } - self.is_pending = false; - self.apply_buffered_instructions()?; - Ok(()) - }, - Err(e) => { - for (unused_pid, _) in new_terminal_ids { - self.senders - .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(unused_pid))) - .with_context(err_context)?; - } - self.is_pending = false; - Err::<(), _>(anyError::msg(e)) - .with_context(err_context) - .non_fatal(); // TODO: propagate this to the user - Ok(()) - }, + let layout_has_floating_panes = 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.style, + &self.display_area, + &mut self.tiled_panes, + &mut self.floating_panes, + self.draw_pane_frames, + &mut self.focus_pane_id, + &self.os_api, + ) + .apply_layout( + layout, + floating_panes_layout, + new_terminal_ids, + new_floating_terminal_ids, + new_plugin_ids, + client_id, + )?; + if layout_has_floating_panes { + if !self.floating_panes.panes_are_visible() { + self.toggle_floating_panes(client_id, None)?; + } } + self.tiled_panes.set_pane_frames(self.draw_pane_frames); + self.is_pending = false; + self.apply_buffered_instructions()?; + Ok(()) } pub fn apply_buffered_instructions(&mut self) -> Result<()> { let buffered_instructions: Vec = @@ -852,7 +749,7 @@ impl Tab { self.set_force_render(); } else { self.show_floating_panes(); - match self.floating_panes.first_floating_pane_id() { + match self.floating_panes.last_floating_pane_id() { Some(first_floating_pane_id) => { if !self.floating_panes.active_panes_contain(&client_id) { self.floating_panes @@ -2708,30 +2605,6 @@ impl Tab { Ok(()) } - fn offset_viewport(&mut self, position_and_size: &Viewport) { - let mut viewport = self.viewport.borrow_mut(); - if position_and_size.x == viewport.x - && position_and_size.x + position_and_size.cols == viewport.x + viewport.cols - { - if position_and_size.y == viewport.y { - viewport.y += position_and_size.rows; - viewport.rows -= position_and_size.rows; - } else if position_and_size.y + position_and_size.rows == viewport.y + viewport.rows { - viewport.rows -= position_and_size.rows; - } - } - if position_and_size.y == viewport.y - && position_and_size.y + position_and_size.rows == viewport.y + viewport.rows - { - if position_and_size.x == viewport.x { - viewport.x += position_and_size.cols; - viewport.cols -= position_and_size.cols; - } else if position_and_size.x + position_and_size.cols == viewport.x + viewport.cols { - viewport.cols -= position_and_size.cols; - } - } - } - pub fn visible(&self, visible: bool) -> Result<()> { let pids_in_this_tab = self.tiled_panes.pane_ids().filter_map(|p| match p { PaneId::Plugin(pid) => Some(pid), diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__tab_with_layout_that_has_floating_panes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__tab_with_layout_that_has_floating_panes.snap new file mode 100644 index 00000000..6816fcb8 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__tab_with_layout_that_has_floating_panes.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +assertion_line: 2560 +expression: snapshot +--- +00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +01 (C): │ │ +02 (C): │ │ +03 (C): │ │ +04 (C): │ │ +05 (C): │ ┌ Pane #2 ─────────────────────────────────────────────────┐ │ +06 (C): │ │ │ │ +07 (C): │ │ ┌ Pane #3 ─────────────────────────────────────────────────┐ │ +08 (C): │ │ │ │ │ +09 (C): │ │ │ │ │ +10 (C): │ │ │ │ │ +11 (C): │ │ │ │ │ +12 (C): │ │ │ │ │ +13 (C): │ │ │ │ │ +14 (C): │ └─│ │ │ +15 (C): │ │ │ │ +16 (C): │ └──────────────────────────────────────────────────────────┘ │ +17 (C): │ │ +18 (C): │ │ +19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs index 81be51cc..21f1fe14 100644 --- a/zellij-server/src/tab/unit/tab_integration_tests.rs +++ b/zellij-server/src/tab/unit/tab_integration_tests.rs @@ -229,9 +229,10 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab { ); tab.apply_layout( PaneLayout::default(), + vec![], vec![(1, None)], + vec![], HashMap::new(), - index, client_id, ) .unwrap(); @@ -285,9 +286,10 @@ fn create_new_tab_with_os_api( ); tab.apply_layout( PaneLayout::default(), + vec![], vec![(1, None)], + vec![], HashMap::new(), - index, client_id, ) .unwrap(); @@ -316,7 +318,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str) let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let layout = Layout::from_str(layout, "layout_file_name".into(), None).unwrap(); - let tab_layout = layout.new_tab(); + let (tab_layout, floating_panes_layout) = layout.new_tab(); let mut tab = Tab::new( index, position, @@ -343,8 +345,20 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str) .enumerate() .map(|(i, _)| (i as u32, None)) .collect(); - tab.apply_layout(tab_layout, pane_ids, HashMap::new(), index, client_id) - .unwrap(); + let floating_pane_ids = floating_panes_layout + .iter() + .enumerate() + .map(|(i, _)| (i as u32, None)) + .collect(); + tab.apply_layout( + tab_layout, + floating_panes_layout, + pane_ids, + floating_pane_ids, + HashMap::new(), + client_id, + ) + .unwrap(); tab } @@ -396,9 +410,10 @@ fn create_new_tab_with_mock_pty_writer( ); tab.apply_layout( PaneLayout::default(), + vec![], vec![(1, None)], + vec![], HashMap::new(), - index, client_id, ) .unwrap(); @@ -455,9 +470,10 @@ fn create_new_tab_with_sixel_support( ); tab.apply_layout( PaneLayout::default(), + vec![], vec![(1, None)], + vec![], HashMap::new(), - index, client_id, ) .unwrap(); @@ -2519,6 +2535,34 @@ fn tab_with_basic_layout() { assert_snapshot!(snapshot); } +#[test] +fn tab_with_layout_that_has_floating_panes() { + let layout = r#" + layout { + pane + floating_panes { + pane + pane + } + } + "#; + let size = Size { + cols: 121, + rows: 20, + }; + let client_id = 1; + let mut tab = create_new_tab_with_layout(size, ModeInfo::default(), layout); + let mut output = Output::default(); + tab.render(&mut output, None).unwrap(); + let snapshot = take_snapshot( + output.serialize().unwrap().get(&client_id).unwrap(), + size.rows, + size.cols, + Palette::default(), + ); + assert_snapshot!(snapshot); +} + #[test] fn tab_with_nested_layout() { let layout = r#" diff --git a/zellij-server/src/tab/unit/tab_tests.rs b/zellij-server/src/tab/unit/tab_tests.rs index 86848045..0fd61245 100644 --- a/zellij-server/src/tab/unit/tab_tests.rs +++ b/zellij-server/src/tab/unit/tab_tests.rs @@ -178,9 +178,10 @@ fn create_new_tab(size: Size) -> Tab { ); tab.apply_layout( PaneLayout::default(), + vec![], vec![(1, None)], + vec![], HashMap::new(), - index, client_id, ) .unwrap(); @@ -231,8 +232,15 @@ fn create_new_tab_with_layout(size: Size, layout: PaneLayout) -> Tab { for i in 0..layout.extract_run_instructions().len() { new_terminal_ids.push((i as u32, None)); } - tab.apply_layout(layout, new_terminal_ids, HashMap::new(), index, client_id) - .unwrap(); + tab.apply_layout( + layout, + vec![], + new_terminal_ids, + vec![], + HashMap::new(), + client_id, + ) + .unwrap(); tab } @@ -280,9 +288,10 @@ fn create_new_tab_with_cell_size( ); tab.apply_layout( PaneLayout::default(), + vec![], vec![(1, None)], + vec![], HashMap::new(), - index, client_id, ) .unwrap(); diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index f6f993c7..dd212c32 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -14,7 +14,7 @@ use zellij_utils::data::Resize; use zellij_utils::errors::{prelude::*, ErrorContext}; use zellij_utils::input::actions::Action; use zellij_utils::input::command::{RunCommand, TerminalAction}; -use zellij_utils::input::layout::{PaneLayout, SplitDirection, SplitSize}; +use zellij_utils::input::layout::{PaneLayout, SplitDirection}; use zellij_utils::input::options::Options; use zellij_utils::ipc::IpcReceiverWithContext; use zellij_utils::pane_size::{Size, SizeInPixels}; @@ -297,12 +297,15 @@ impl MockScreen { let _ = self.to_screen.send(ScreenInstruction::NewTab( default_shell, Some(pane_layout.clone()), + vec![], // floating_panes_layout tab_name, self.main_client_id, )); let _ = self.to_screen.send(ScreenInstruction::ApplyLayout( pane_layout, + vec![], // floating panes layout pane_ids, + vec![], // floating pane ids plugin_ids, tab_index, self.main_client_id, @@ -323,12 +326,15 @@ impl MockScreen { let _ = self.to_screen.send(ScreenInstruction::NewTab( default_shell, Some(tab_layout.clone()), + vec![], // floating_panes_layout tab_name, self.main_client_id, )); let _ = self.to_screen.send(ScreenInstruction::ApplyLayout( tab_layout, + vec![], // floating_panes_layout pane_ids, + vec![], // floating panes ids plugin_ids, 0, self.main_client_id, @@ -472,7 +478,9 @@ fn new_tab(screen: &mut Screen, pid: u32, tab_index: usize) { screen .apply_layout( PaneLayout::default(), + vec![], // floating panes layout new_terminal_ids, + vec![], // new floating terminal ids new_plugin_ids, tab_index, client_id, diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap index 51737530..1ad6b84e 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 2272 +assertion_line: 2300 expression: "format!(\"{:#?}\", new_tab_action)" --- Some( @@ -39,6 +39,7 @@ Some( external_children_index: None, }, ), + [], None, 0, 1, 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 b2f34a98..7190eb8d 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 @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 2322 +assertion_line: 2350 expression: "format!(\"{:#?}\", new_tab_instruction)" --- NewTab( @@ -60,6 +60,7 @@ NewTab( external_children_index: None, }, ), + [], Some( "my-awesome-tab-name", ), diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_rename_tab.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_rename_tab.snap index 09a09015..fb7239db 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_rename_tab.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_rename_tab.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 2511 +assertion_line: 2534 expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())" --- [ @@ -61,6 +61,7 @@ expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())" external_children_index: None, }, ), + [], None, 0, 1, @@ -207,6 +208,7 @@ expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())" external_children_index: None, }, ), + [], None, 1, 1, diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_undo_rename_tab.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_undo_rename_tab.snap index 11bcb874..bda17001 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_undo_rename_tab.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_undo_rename_tab.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 2554 +assertion_line: 2577 expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())" --- [ @@ -61,6 +61,7 @@ expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())" external_children_index: None, }, ), + [], None, 0, 1, @@ -207,6 +208,7 @@ expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())" external_children_index: None, }, ), + [], None, 1, 1, diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index e1a45bf5..7bb13540 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -1,7 +1,7 @@ //! Definition of the actions that can be bound to keys. use super::command::RunCommandAction; -use super::layout::{Layout, PaneLayout}; +use super::layout::{FloatingPanesLayout, Layout, PaneLayout}; use crate::cli::CliAction; use crate::data::InputMode; use crate::data::{Direction, Resize}; @@ -162,7 +162,7 @@ pub enum Action { PaneNameInput(Vec), UndoRenamePane, /// Create a new tab, optionally with a specified tab layout. - NewTab(Option, Option), // the String is the tab name + NewTab(Option, Vec, Option), // the String is the tab name /// Do nothing. NoOp, /// Go to the next tab. @@ -368,15 +368,24 @@ impl Action { if tabs.len() > 1 { return Err(format!("Tab layout cannot itself have tabs")); } else if !tabs.is_empty() { - let (tab_name, layout) = tabs.drain(..).next().unwrap(); + let (tab_name, layout, floating_panes_layout) = + tabs.drain(..).next().unwrap(); let name = tab_name.or(name); - Ok(vec![Action::NewTab(Some(layout), name)]) + Ok(vec![Action::NewTab( + Some(layout), + floating_panes_layout, + name, + )]) } else { - let layout = layout.new_tab(); - Ok(vec![Action::NewTab(Some(layout), name)]) + let (layout, floating_panes_layout) = layout.new_tab(); + Ok(vec![Action::NewTab( + Some(layout), + floating_panes_layout, + name, + )]) } } else { - Ok(vec![Action::NewTab(None, name)]) + Ok(vec![Action::NewTab(None, vec![], name)]) } }, } diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index 6c4a222c..eaa2e395 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -213,9 +213,94 @@ impl fmt::Display for RunPluginLocation { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)] pub struct Layout { - pub tabs: Vec<(Option, PaneLayout)>, + pub tabs: Vec<(Option, PaneLayout, Vec)>, pub focused_tab_index: Option, pub template: Option, + pub floating_panes_template: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub enum PercentOrFixed { + Percent(usize), // 1 to 100 + Fixed(usize), // An absolute number of columns or rows +} + +impl PercentOrFixed { + pub fn to_position(&self, whole: usize) -> usize { + match self { + PercentOrFixed::Percent(percent) => { + (whole as f64 / 100.0 * *percent as f64).ceil() as usize + }, + PercentOrFixed::Fixed(fixed) => { + if *fixed > whole { + whole + } else { + *fixed + } + }, + } + } +} + +impl PercentOrFixed { + pub fn is_zero(&self) -> bool { + match self { + PercentOrFixed::Percent(percent) => *percent == 0, + PercentOrFixed::Fixed(fixed) => *fixed == 0, + } + } +} + +impl FromStr for PercentOrFixed { + type Err = Box; + fn from_str(s: &str) -> Result { + if s.chars().last() == Some('%') { + let char_count = s.chars().count(); + let percent_size = usize::from_str_radix(&s[..char_count.saturating_sub(1)], 10)?; + if percent_size <= 100 { + Ok(PercentOrFixed::Percent(percent_size)) + } else { + Err("Percent must be between 0 and 100".into()) + } + } else { + let fixed_size = usize::from_str_radix(s, 10)?; + Ok(PercentOrFixed::Fixed(fixed_size)) + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)] +pub struct FloatingPanesLayout { + // TODO: change name to singular + pub name: Option, + pub height: Option, + pub width: Option, + pub x: Option, + pub y: Option, + pub run: Option, + pub focus: Option, +} + +impl FloatingPanesLayout { + pub fn add_cwd_to_layout(&mut self, cwd: &PathBuf) { + match self.run.as_mut() { + Some(run) => run.add_cwd(cwd), + None => { + self.run = Some(Run::Cwd(cwd.clone())); + }, + } + } +} + +impl From<&PaneLayout> for FloatingPanesLayout { + fn from(pane_layout: &PaneLayout) -> Self { + FloatingPanesLayout { + name: pane_layout.name.clone(), + run: pane_layout.run.clone(), + focus: pane_layout.focus, + ..Default::default() + } + } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)] @@ -276,6 +361,8 @@ impl PaneLayout { Ok(layouts) } pub fn extract_run_instructions(&self) -> Vec> { + // the order of these run instructions is significant and needs to be the same + // as the order of the "flattened" layout panes received from eg. position_panes_in_space let mut run_instructions = vec![]; if self.children.is_empty() { run_instructions.push(self.run.clone()); @@ -452,11 +539,12 @@ impl Layout { Ok(String::from_utf8(setup::COMPACT_BAR_LAYOUT.to_vec())?) } - pub fn new_tab(&self) -> PaneLayout { - match &self.template { + pub fn new_tab(&self) -> (PaneLayout, Vec) { + let template = match &self.template { Some(template) => template.clone(), None => PaneLayout::default(), - } + }; + (template, self.floating_panes_template.clone()) } pub fn is_empty(&self) -> bool { @@ -467,7 +555,7 @@ impl Layout { !self.tabs.is_empty() } - pub fn tabs(&self) -> Vec<(Option, PaneLayout)> { + pub fn tabs(&self) -> Vec<(Option, PaneLayout, Vec)> { // String is the tab name self.tabs.clone() } diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs index f11c8686..d588fcda 100644 --- a/zellij-utils/src/input/unit/layout_test.rs +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -89,6 +89,175 @@ fn layout_with_nested_panes() { assert_eq!(layout, expected_layout); } +#[test] +fn layout_with_floating_panes() { + let kdl_layout = r#" + layout { + floating_panes { + pane + pane { + x 10 + y "10%" + width 10 + height "10%" + } + pane x=10 y="10%" + pane command="htop" + } + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + let expected_layout = Layout { + template: Some(PaneLayout::default()), + floating_panes_template: vec![ + FloatingPanesLayout::default(), + FloatingPanesLayout { + x: Some(PercentOrFixed::Fixed(10)), + y: Some(PercentOrFixed::Percent(10)), + width: Some(PercentOrFixed::Fixed(10)), + height: Some(PercentOrFixed::Percent(10)), + ..Default::default() + }, + FloatingPanesLayout { + x: Some(PercentOrFixed::Fixed(10)), + y: Some(PercentOrFixed::Percent(10)), + ..Default::default() + }, + FloatingPanesLayout { + run: Some(Run::Command(RunCommand { + command: PathBuf::from("htop"), + hold_on_close: true, + ..Default::default() + })), + ..Default::default() + }, + ], + ..Default::default() + }; + assert_eq!(layout, expected_layout); +} + +#[test] +fn layout_with_mixed_panes_and_floating_panes() { + let kdl_layout = r#" + layout { + pane + pane + floating_panes { + pane + } + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + let expected_layout = Layout { + template: Some(PaneLayout { + children: vec![PaneLayout::default(), PaneLayout::default()], + ..Default::default() + }), + floating_panes_template: vec![FloatingPanesLayout::default()], + ..Default::default() + }; + assert_eq!(layout, expected_layout); +} + +#[test] +fn layout_with_floating_panes_template() { + let kdl_layout = r#" + layout { + pane_template name="my_cool_template" { + x 10 + y "10%" + } + pane + floating_panes { + pane + my_cool_template + } + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + let expected_layout = Layout { + template: Some(PaneLayout { + children: vec![PaneLayout::default()], + ..Default::default() + }), + floating_panes_template: vec![ + FloatingPanesLayout::default(), + FloatingPanesLayout { + x: Some(PercentOrFixed::Fixed(10)), + y: Some(PercentOrFixed::Percent(10)), + ..Default::default() + }, + ], + ..Default::default() + }; + assert_eq!(layout, expected_layout); +} + +#[test] +fn layout_with_shared_tiled_and_floating_panes_template() { + let kdl_layout = r#" + layout { + pane_template name="htop" { + command "htop" + } + htop + floating_panes { + pane + htop + } + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + let expected_layout = Layout { + template: Some(PaneLayout { + children: vec![PaneLayout { + run: Some(Run::Command(RunCommand { + command: PathBuf::from("htop"), + hold_on_close: true, + ..Default::default() + })), + ..Default::default() + }], + ..Default::default() + }), + floating_panes_template: vec![ + FloatingPanesLayout::default(), + FloatingPanesLayout { + run: Some(Run::Command(RunCommand { + command: PathBuf::from("htop"), + hold_on_close: true, + ..Default::default() + })), + ..Default::default() + }, + ], + ..Default::default() + }; + assert_eq!(layout, expected_layout); +} + +#[test] +fn layout_with_tabs_and_floating_panes() { + let kdl_layout = r#" + layout { + tab { + floating_panes { + pane + } + } + tab { + floating_panes { + pane + pane + } + } + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + assert_snapshot!(format!("{:#?}", layout)); +} + #[test] fn layout_with_tabs() { let kdl_layout = r#" @@ -98,7 +267,7 @@ fn layout_with_tabs() { "#; let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { - tabs: vec![(None, PaneLayout::default())], + tabs: vec![(None, PaneLayout::default(), vec![])], template: Some(PaneLayout::default()), ..Default::default() }; @@ -134,6 +303,7 @@ fn layout_with_nested_differing_tabs() { ], ..Default::default() }, + vec![], // floating panes ), ( None, @@ -142,6 +312,7 @@ fn layout_with_nested_differing_tabs() { children: vec![PaneLayout::default(), PaneLayout::default()], ..Default::default() }, + vec![], // floating panes ), ], template: Some(PaneLayout::default()), @@ -412,6 +583,7 @@ fn layout_with_tab_names() { children: vec![], ..Default::default() }, + vec![], // floating panes ), ( Some("my cool tab name 2".into()), @@ -419,6 +591,7 @@ fn layout_with_tab_names() { children: vec![], ..Default::default() }, + vec![], // floating panes ), ], template: Some(PaneLayout::default()), @@ -439,9 +612,9 @@ fn layout_with_focused_tab() { let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { tabs: vec![ - (None, PaneLayout::default()), - (None, PaneLayout::default()), - (None, PaneLayout::default()), + (None, PaneLayout::default(), vec![]), + (None, PaneLayout::default(), vec![]), + (None, PaneLayout::default(), vec![]), ], template: Some(PaneLayout::default()), focused_tab_index: Some(1), @@ -488,6 +661,7 @@ fn layout_with_tab_templates() { ], ..Default::default() }, + vec![], // floating panes ), ( Some("my second tab".into()), @@ -504,6 +678,7 @@ fn layout_with_tab_templates() { ], ..Default::default() }, + vec![], // floating panes ), ( None, @@ -516,6 +691,7 @@ fn layout_with_tab_templates() { ], ..Default::default() }, + vec![], // floating panes ), ], template: Some(PaneLayout::default()), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap index 8b6d9050..0fc51aad 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1081 +assertion_line: 1101 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -62,4 +62,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap index 4b026d75..64d47573 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1046 +assertion_line: 1066 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -65,4 +65,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap index f7f904f6..36eeb717 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 711 +assertion_line: 744 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -138,4 +138,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap index 003a3d41..ac027457 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 801 +assertion_line: 696 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -82,6 +82,7 @@ Layout { focus: None, external_children_index: None, }, + [], ), ( None, @@ -129,6 +130,7 @@ Layout { focus: None, external_children_index: None, }, + [], ), ], focused_tab_index: None, @@ -144,4 +146,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap index d23bb40c..0386fd19 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1098 +assertion_line: 1118 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -59,4 +59,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap index abce2964..487bfb82 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1064 +assertion_line: 1084 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -59,4 +59,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap index a964c655..0c4cb680 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 899 +assertion_line: 796 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -105,6 +105,7 @@ Layout { focus: None, external_children_index: None, }, + [], ), ( None, @@ -183,6 +184,7 @@ Layout { focus: None, external_children_index: None, }, + [], ), ], focused_tab_index: None, @@ -198,4 +200,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap index 48bb1183..beb1f67f 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1133 +assertion_line: 1153 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -61,4 +61,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap index 7d57e6d0..36cc0c24 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1116 +assertion_line: 1136 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -63,4 +63,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap index 58a78678..71b46db4 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1516 +assertion_line: 1536 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -58,6 +58,7 @@ Layout { focus: None, external_children_index: None, }, + [], ), ], focused_tab_index: None, @@ -73,4 +74,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap index c6b3ed5f..5c39bbd1 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1537 +assertion_line: 1557 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -102,6 +102,7 @@ Layout { focus: None, external_children_index: None, }, + [], ), ], focused_tab_index: None, @@ -117,4 +118,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap index cd1a51e1..4c6bfd2d 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1556 +assertion_line: 1576 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -87,6 +87,7 @@ Layout { focus: None, external_children_index: None, }, + [], ), ], focused_tab_index: None, @@ -102,4 +103,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } 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 ba246e06..5256565c 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 @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1403 +assertion_line: 1423 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -55,4 +55,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } 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 3b546891..b0eaf353 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 @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1435 +assertion_line: 1455 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -55,4 +55,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } 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 50b92892..5aa11997 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 @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1455 +assertion_line: 1475 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -55,4 +55,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } 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 f5d6b95a..8c5d71c1 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 @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1416 +assertion_line: 1436 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -55,4 +55,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap index 09d209c1..b54634fb 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1471 +assertion_line: 1491 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -58,6 +58,7 @@ Layout { focus: None, external_children_index: None, }, + [], ), ], focused_tab_index: None, @@ -73,4 +74,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap index 32d1341c..c16d495e 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 281 +assertion_line: 283 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -39,4 +39,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap index 136c95fb..6723bc21 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 294 +assertion_line: 296 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -39,4 +39,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap index f61145b4..8065fc4d 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 549 +assertion_line: 555 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -71,6 +71,7 @@ Layout { focus: None, external_children_index: None, }, + [], ), ( Some( @@ -138,6 +139,7 @@ Layout { focus: None, external_children_index: None, }, + [], ), ( None, @@ -182,6 +184,7 @@ Layout { focus: None, external_children_index: None, }, + [], ), ], focused_tab_index: None, @@ -228,4 +231,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap index 77b7478c..687d7618 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 756 +assertion_line: 650 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -122,4 +122,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap index fd12e42b..e5a655f7 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 729 +assertion_line: 624 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -91,4 +91,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap index 561c35f1..cbbddf74 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 626 +assertion_line: 582 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -236,4 +236,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap index fbe11152..464c1647 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 583 +assertion_line: 603 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -80,6 +80,7 @@ Layout { focus: None, external_children_index: None, }, + [], ), ], focused_tab_index: None, @@ -95,4 +96,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tabs_and_floating_panes.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tabs_and_floating_panes.snap new file mode 100644 index 00000000..5cdf517a --- /dev/null +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tabs_and_floating_panes.snap @@ -0,0 +1,102 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 263 +expression: "format!(\"{:#?}\", layout)" +--- +Layout { + tabs: [ + ( + None, + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + [ + FloatingPanesLayout { + name: None, + height: None, + width: None, + x: None, + y: None, + run: None, + focus: None, + }, + ], + ), + ( + None, + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + [ + FloatingPanesLayout { + name: None, + height: None, + width: None, + x: None, + y: None, + run: None, + focus: None, + }, + FloatingPanesLayout { + name: None, + height: None, + width: None, + x: None, + y: None, + run: None, + focus: None, + }, + ], + ), + ], + focused_tab_index: None, + template: Some( + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + ), + floating_panes_template: [], +} 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 94a52aac..4f4552ee 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 @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1287 +assertion_line: 1307 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -41,4 +41,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } 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 6f2255cd..fc5523fa 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 @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1233 +assertion_line: 1253 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -41,4 +41,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } 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 d659eba3..3c00d46f 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 @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1250 +assertion_line: 1270 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -41,4 +41,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } 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 feb8a88b..b88f2a9e 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 @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1268 +assertion_line: 1288 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -41,4 +41,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } 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 6cad38df..3d561463 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 @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1305 +assertion_line: 1325 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -41,4 +41,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } 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 0f488990..89332ea3 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,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1235 +assertion_line: 1343 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -33,4 +33,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } 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 42fb84d2..3797afc5 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 @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1357 +assertion_line: 1377 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -41,4 +41,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } 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 a09f2ab2..072d0bc5 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 @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1339 +assertion_line: 1359 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -41,4 +41,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } 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 32bc6880..a6aac1f1 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,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1298 +assertion_line: 1393 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -34,4 +34,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } 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 f5f50be1..e77e7b95 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,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1314 +assertion_line: 1409 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -34,4 +34,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap index 4508cb07..004238aa 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1486 +assertion_line: 1506 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -58,6 +58,7 @@ Layout { focus: None, external_children_index: None, }, + [], ), ], focused_tab_index: None, @@ -73,4 +74,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap index ac1d4240..573e0465 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1501 +assertion_line: 1521 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -58,6 +58,7 @@ Layout { focus: None, external_children_index: None, }, + [], ), ], focused_tab_index: None, @@ -73,4 +74,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/kdl/kdl_layout_parser.rs b/zellij-utils/src/kdl/kdl_layout_parser.rs index c5a84a7b..a24e6948 100644 --- a/zellij-utils/src/kdl/kdl_layout_parser.rs +++ b/zellij-utils/src/kdl/kdl_layout_parser.rs @@ -1,7 +1,10 @@ use crate::input::{ command::RunCommand, config::ConfigError, - layout::{Layout, PaneLayout, Run, RunPlugin, RunPluginLocation, SplitDirection, SplitSize}, + layout::{ + FloatingPanesLayout, Layout, PaneLayout, PercentOrFixed, Run, RunPlugin, RunPluginLocation, + SplitDirection, SplitSize, + }, }; use kdl::*; @@ -23,12 +26,19 @@ use std::path::PathBuf; use std::vec::Vec; use url::Url; +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PaneOrFloatingPane { + Pane(PaneLayout), + FloatingPane(FloatingPanesLayout), + Either(PaneLayout), +} + pub struct KdlLayoutParser<'a> { global_cwd: Option, raw_layout: &'a str, - tab_templates: HashMap, - pane_templates: HashMap, - default_tab_template: Option<(PaneLayout, KdlNode)>, + tab_templates: HashMap, KdlNode)>, + pane_templates: HashMap, + default_tab_template: Option<(PaneLayout, Vec, KdlNode)>, } impl<'a> KdlLayoutParser<'a> { @@ -78,11 +88,28 @@ impl<'a> KdlLayoutParser<'a> { || property_name == "pane" || property_name == "children" } + fn is_a_valid_floating_pane_property(&self, property_name: &str) -> bool { + property_name == "borderless" + || property_name == "focus" + || property_name == "name" + || property_name == "plugin" + || property_name == "command" + || property_name == "edit" + || property_name == "cwd" + || property_name == "args" + || property_name == "close_on_exit" + || property_name == "start_suspended" + || property_name == "x" + || property_name == "y" + || property_name == "width" + || property_name == "height" + } fn is_a_valid_tab_property(&self, property_name: &str) -> bool { property_name == "focus" || property_name == "name" || property_name == "split_direction" || property_name == "cwd" + || property_name == "floating_panes" } fn assert_legal_node_name(&self, name: &str, kdl_node: &KdlNode) -> Result<(), ConfigError> { if name.contains(char::is_whitespace) { @@ -168,6 +195,60 @@ impl<'a> KdlLayoutParser<'a> { Ok(None) } } + fn parse_percent_or_fixed( + &self, + kdl_node: &KdlNode, + value_name: &str, + can_be_zero: bool, + ) -> Result, ConfigError> { + if let Some(size) = kdl_get_string_property_or_child_value!(kdl_node, value_name) { + match PercentOrFixed::from_str(size) { + Ok(size) => { + if !can_be_zero && size.is_zero() { + Err(kdl_parsing_error!( + format!("{} should be greater than 0", value_name), + kdl_node + )) + } else { + Ok(Some(size)) + } + }, + Err(_e) => Err(kdl_parsing_error!( + format!( + "{} should be a fixed number (eg. 1) or a quoted percent (eg. \"50%\")", + value_name + ), + kdl_node + )), + } + } else if let Some(size) = kdl_get_int_property_or_child_value!(kdl_node, value_name) { + if size == 0 && !can_be_zero { + return Err(kdl_parsing_error!( + format!("{} should be greater than 0", value_name), + kdl_node + )); + } + Ok(Some(PercentOrFixed::Fixed(size as usize))) + } else if let Some(node) = kdl_property_or_child_value_node!(kdl_node, "size") { + Err(kdl_parsing_error!( + format!( + "{} should be a fixed number (eg. 1) or a quoted percent (eg. \"50%\")", + value_name + ), + node + )) + } else if let Some(node) = kdl_child_with_name!(kdl_node, "size") { + Err(kdl_parsing_error!( + format!( + "{} cannot be bare, it should have a value (eg. 'size 1', or 'size \"50%\"')", + value_name + ), + node + )) + } else { + Ok(None) + } + } fn parse_plugin_block(&self, plugin_block: &KdlNode) -> Result, ConfigError> { let _allow_exec_host_cmd = kdl_get_bool_property_or_child_value_with_error!(plugin_block, "_allow_exec_host_cmd") @@ -347,6 +428,31 @@ impl<'a> KdlLayoutParser<'a> { ..Default::default() }) } + fn parse_floating_pane_node( + &self, + kdl_node: &KdlNode, + ) -> Result { + self.assert_valid_floating_pane_properties(kdl_node)?; + let height = self.parse_percent_or_fixed(kdl_node, "height", false)?; + let width = self.parse_percent_or_fixed(kdl_node, "width", false)?; + let x = self.parse_percent_or_fixed(kdl_node, "x", true)?; + let y = self.parse_percent_or_fixed(kdl_node, "y", true)?; + let run = self.parse_command_plugin_or_edit_block(kdl_node)?; + let focus = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus"); + let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name") + .map(|name| name.to_string()); + self.assert_no_mixed_children_and_properties(kdl_node)?; + Ok(FloatingPanesLayout { + name, + height, + width, + x, + y, + run, + focus, + ..Default::default() + }) + } fn insert_children_to_pane_template( &self, kdl_node: &KdlNode, @@ -377,60 +483,201 @@ impl<'a> KdlLayoutParser<'a> { fn parse_pane_node_with_template( &self, kdl_node: &KdlNode, - mut pane_template: PaneLayout, + pane_template: PaneOrFloatingPane, pane_template_kdl_node: &KdlNode, ) -> Result { - let borderless = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless"); - let focus = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus"); - let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name") - .map(|name| name.to_string()); - let args = self.parse_args(kdl_node)?; - let close_on_exit = - kdl_get_bool_property_or_child_value_with_error!(kdl_node, "close_on_exit"); - let start_suspended = - kdl_get_bool_property_or_child_value_with_error!(kdl_node, "start_suspended"); - let split_size = self.parse_split_size(kdl_node)?; - let run = self.parse_command_plugin_or_edit_block_for_template(kdl_node)?; - self.assert_no_bare_attributes_in_pane_node_with_template( - &run, - &pane_template.run, - &args, - &close_on_exit, - &start_suspended, - kdl_node, - )?; - self.insert_children_to_pane_template( - kdl_node, - &mut pane_template, - pane_template_kdl_node, - )?; - pane_template.run = Run::merge(&pane_template.run, &run); - if let Some(pane_template_run_command) = pane_template.run.as_mut() { - // we need to do this because panes consuming a pane_template - // can have bare args without a command - pane_template_run_command.add_args(args); - pane_template_run_command.add_close_on_exit(close_on_exit); - pane_template_run_command.add_start_suspended(start_suspended); - }; - if let Some(borderless) = borderless { - pane_template.borderless = borderless; + match pane_template { + PaneOrFloatingPane::Pane(mut pane_template) + | PaneOrFloatingPane::Either(mut pane_template) => { + let borderless = + kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless"); + let focus = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus"); + let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name") + .map(|name| name.to_string()); + let args = self.parse_args(kdl_node)?; + let close_on_exit = + kdl_get_bool_property_or_child_value_with_error!(kdl_node, "close_on_exit"); + let start_suspended = + kdl_get_bool_property_or_child_value_with_error!(kdl_node, "start_suspended"); + let split_size = self.parse_split_size(kdl_node)?; + let run = self.parse_command_plugin_or_edit_block_for_template(kdl_node)?; + self.assert_no_bare_attributes_in_pane_node_with_template( + &run, + &pane_template.run, + &args, + &close_on_exit, + &start_suspended, + kdl_node, + )?; + self.insert_children_to_pane_template( + kdl_node, + &mut pane_template, + pane_template_kdl_node, + )?; + pane_template.run = Run::merge(&pane_template.run, &run); + if let Some(pane_template_run_command) = pane_template.run.as_mut() { + // we need to do this because panes consuming a pane_template + // can have bare args without a command + pane_template_run_command.add_args(args); + pane_template_run_command.add_close_on_exit(close_on_exit); + pane_template_run_command.add_start_suspended(start_suspended); + }; + if let Some(borderless) = borderless { + pane_template.borderless = borderless; + } + if let Some(focus) = focus { + pane_template.focus = Some(focus); + } + if let Some(name) = name { + pane_template.name = Some(name); + } + if let Some(split_size) = split_size { + pane_template.split_size = Some(split_size); + } + if let Some(index_of_children) = pane_template.external_children_index { + pane_template + .children + .insert(index_of_children, PaneLayout::default()); + } + pane_template.external_children_index = None; + Ok(pane_template) + }, + PaneOrFloatingPane::FloatingPane(_) => { + let pane_template_name = kdl_get_string_property_or_child_value_with_error!( + pane_template_kdl_node, + "name" + ) + .map(|name| name.to_string()); + Err(ConfigError::new_layout_kdl_error( + format!("pane_template {}, is a floating pane template (derived from its properties) and cannot be applied to a tiled pane", pane_template_name.unwrap_or("".into())), + kdl_node.span().offset(), + kdl_node.span().len(), + )) + }, } - if let Some(focus) = focus { - pane_template.focus = Some(focus); + } + fn parse_floating_pane_node_with_template( + &self, + kdl_node: &KdlNode, + pane_template: PaneOrFloatingPane, + pane_template_kdl_node: &KdlNode, + ) -> Result { + match pane_template { + PaneOrFloatingPane::Pane(_) => { + let pane_template_name = kdl_get_string_property_or_child_value_with_error!( + pane_template_kdl_node, + "name" + ) + .map(|name| name.to_string()); + Err(ConfigError::new_layout_kdl_error( + format!("pane_template {}, is a non-floating pane template (derived from its properties) and cannot be applied to a floating pane", pane_template_name.unwrap_or("".into())), + kdl_node.span().offset(), + kdl_node.span().len(), + )) + }, + PaneOrFloatingPane::FloatingPane(mut pane_template) => { + let focus = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus"); + let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name") + .map(|name| name.to_string()); + let args = self.parse_args(kdl_node)?; + let close_on_exit = + kdl_get_bool_property_or_child_value_with_error!(kdl_node, "close_on_exit"); + let start_suspended = + kdl_get_bool_property_or_child_value_with_error!(kdl_node, "start_suspended"); + let run = self.parse_command_plugin_or_edit_block_for_template(kdl_node)?; + self.assert_no_bare_attributes_in_pane_node_with_template( + &run, + &pane_template.run, + &args, + &close_on_exit, + &start_suspended, + kdl_node, + )?; + pane_template.run = Run::merge(&pane_template.run, &run); + if let Some(pane_template_run_command) = pane_template.run.as_mut() { + // we need to do this because panes consuming a pane_template + // can have bare args without a command + pane_template_run_command.add_args(args); + pane_template_run_command.add_close_on_exit(close_on_exit); + pane_template_run_command.add_start_suspended(start_suspended); + }; + if let Some(focus) = focus { + pane_template.focus = Some(focus); + } + if let Some(name) = name { + pane_template.name = Some(name); + } + let height = self.parse_percent_or_fixed(kdl_node, "height", false)?; + let width = self.parse_percent_or_fixed(kdl_node, "width", false)?; + let x = self.parse_percent_or_fixed(kdl_node, "x", true)?; + let y = self.parse_percent_or_fixed(kdl_node, "y", true)?; + // let mut floating_pane = FloatingPanesLayout::from(&pane_template); + if let Some(height) = height { + pane_template.height = Some(height); + } + if let Some(width) = width { + pane_template.width = Some(width); + } + if let Some(y) = y { + pane_template.y = Some(y); + } + if let Some(x) = x { + pane_template.x = Some(x); + } + Ok(pane_template) + }, + PaneOrFloatingPane::Either(mut pane_template) => { + let focus = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus"); + let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name") + .map(|name| name.to_string()); + let args = self.parse_args(kdl_node)?; + let close_on_exit = + kdl_get_bool_property_or_child_value_with_error!(kdl_node, "close_on_exit"); + let start_suspended = + kdl_get_bool_property_or_child_value_with_error!(kdl_node, "start_suspended"); + let run = self.parse_command_plugin_or_edit_block_for_template(kdl_node)?; + self.assert_no_bare_attributes_in_pane_node_with_template( + &run, + &pane_template.run, + &args, + &close_on_exit, + &start_suspended, + kdl_node, + )?; + pane_template.run = Run::merge(&pane_template.run, &run); + if let Some(pane_template_run_command) = pane_template.run.as_mut() { + // we need to do this because panes consuming a pane_template + // can have bare args without a command + pane_template_run_command.add_args(args); + pane_template_run_command.add_close_on_exit(close_on_exit); + pane_template_run_command.add_start_suspended(start_suspended); + }; + if let Some(focus) = focus { + pane_template.focus = Some(focus); + } + if let Some(name) = name { + pane_template.name = Some(name); + } + let height = self.parse_percent_or_fixed(kdl_node, "height", false)?; + let width = self.parse_percent_or_fixed(kdl_node, "width", false)?; + let x = self.parse_percent_or_fixed(kdl_node, "x", true)?; + let y = self.parse_percent_or_fixed(kdl_node, "y", true)?; + let mut floating_pane = FloatingPanesLayout::from(&pane_template); + if let Some(height) = height { + floating_pane.height = Some(height); + } + if let Some(width) = width { + floating_pane.width = Some(width); + } + if let Some(y) = y { + floating_pane.y = Some(y); + } + if let Some(x) = x { + floating_pane.x = Some(x); + } + Ok(floating_pane) + }, } - if let Some(name) = name { - pane_template.name = Some(name); - } - if let Some(split_size) = split_size { - pane_template.split_size = Some(split_size); - } - if let Some(index_of_children) = pane_template.external_children_index { - pane_template - .children - .insert(index_of_children, PaneLayout::default()); - } - pane_template.external_children_index = None; - Ok(pane_template) } fn parse_split_direction(&self, kdl_node: &KdlNode) -> Result { match kdl_get_string_property_or_child_value_with_error!(kdl_node, "split_direction") { @@ -447,8 +694,104 @@ impl<'a> KdlLayoutParser<'a> { None => Ok(SplitDirection::default()), } } + fn has_only_neutral_pane_template_properties( + &self, + kdl_node: &KdlNode, + ) -> Result { + // pane properties + let borderless = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless"); + let split_size = self.parse_split_size(kdl_node)?; + let split_direction = + kdl_get_string_property_or_child_value_with_error!(kdl_node, "split_direction"); + let has_children_nodes = self.has_child_nodes(kdl_node); + + // floating pane properties + let height = self.parse_percent_or_fixed(kdl_node, "height", false)?; + let width = self.parse_percent_or_fixed(kdl_node, "width", false)?; + let x = self.parse_percent_or_fixed(kdl_node, "x", true)?; + let y = self.parse_percent_or_fixed(kdl_node, "y", true)?; + + let has_pane_properties = borderless.is_some() + || split_size.is_some() + || split_direction.is_some() + || has_children_nodes; + let has_floating_pane_properties = + height.is_some() || width.is_some() || x.is_some() || y.is_some(); + if has_pane_properties || has_floating_pane_properties { + Ok(false) + } else { + Ok(true) + } + } + fn differentiate_pane_and_floating_pane_template( + &self, + kdl_node: &KdlNode, + ) -> Result { + // returns true if it's a floating_pane template, false if not + + // pane properties + let borderless = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless"); + let split_size = self.parse_split_size(kdl_node)?; + let split_direction = + kdl_get_string_property_or_child_value_with_error!(kdl_node, "split_direction"); + let has_children_nodes = self.has_child_nodes(kdl_node); + + // floating pane properties + let height = self.parse_percent_or_fixed(kdl_node, "height", false)?; + let width = self.parse_percent_or_fixed(kdl_node, "width", false)?; + let x = self.parse_percent_or_fixed(kdl_node, "x", true)?; + let y = self.parse_percent_or_fixed(kdl_node, "y", true)?; + + let has_pane_properties = borderless.is_some() + || split_size.is_some() + || split_direction.is_some() + || has_children_nodes; + let has_floating_pane_properties = + height.is_some() || width.is_some() || x.is_some() || y.is_some(); + + if has_pane_properties && has_floating_pane_properties { + let mut pane_properties = vec![]; + if borderless.is_some() { + pane_properties.push("borderless"); + } + if split_size.is_some() { + pane_properties.push("split_size"); + } + if split_direction.is_some() { + pane_properties.push("split_direction"); + } + if has_children_nodes { + pane_properties.push("child nodes"); + } + let mut floating_pane_properties = vec![]; + if height.is_some() { + floating_pane_properties.push("height"); + } + if width.is_some() { + floating_pane_properties.push("width"); + } + if x.is_some() { + floating_pane_properties.push("x"); + } + if y.is_some() { + floating_pane_properties.push("y"); + } + Err(ConfigError::new_layout_kdl_error( + format!( + "A pane_template cannot have both pane ({}) and floating pane ({}) properties", + pane_properties.join(", "), + floating_pane_properties.join(", ") + ), + kdl_node.span().offset(), + kdl_node.span().len(), + )) + } else if has_floating_pane_properties { + Ok(true) + } else { + Ok(false) + } + } fn parse_pane_template_node(&mut self, kdl_node: &KdlNode) -> Result<(), ConfigError> { - self.assert_valid_pane_properties(kdl_node)?; let template_name = kdl_get_string_property_or_child_value!(kdl_node, "name") .map(|s| s.to_string()) .ok_or(ConfigError::new_layout_kdl_error( @@ -458,39 +801,84 @@ impl<'a> KdlLayoutParser<'a> { ))?; self.assert_legal_node_name(&template_name, kdl_node)?; self.assert_legal_template_name(&template_name, kdl_node)?; - let borderless = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless"); let focus = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus"); - let split_size = self.parse_split_size(kdl_node)?; let run = self.parse_command_plugin_or_edit_block(kdl_node)?; - let children_split_direction = self.parse_split_direction(kdl_node)?; - let (external_children_index, pane_parts) = match kdl_children_nodes!(kdl_node) { - Some(children) => self.parse_child_pane_nodes_for_pane(&children)?, - None => (None, vec![]), - }; - self.assert_no_mixed_children_and_properties(kdl_node)?; - self.pane_templates.insert( - template_name, - ( - PaneLayout { - borderless: borderless.unwrap_or_default(), - focus, - split_size, - run, - children_split_direction, - external_children_index, - children: pane_parts, - ..Default::default() - }, - kdl_node.clone(), - ), - ); + + let is_floating = self.differentiate_pane_and_floating_pane_template(&kdl_node)?; + let can_be_either_floating_or_tiled = + self.has_only_neutral_pane_template_properties(&kdl_node)?; + if can_be_either_floating_or_tiled { + self.assert_valid_pane_or_floating_pane_properties(kdl_node)?; + self.pane_templates.insert( + template_name, + ( + PaneOrFloatingPane::Either(PaneLayout { + focus, + run, + ..Default::default() + }), + kdl_node.clone(), + ), + ); + } else if is_floating { + self.assert_valid_floating_pane_properties(kdl_node)?; + // floating pane properties + let height = self.parse_percent_or_fixed(kdl_node, "height", false)?; + let width = self.parse_percent_or_fixed(kdl_node, "width", false)?; + let x = self.parse_percent_or_fixed(kdl_node, "x", true)?; + let y = self.parse_percent_or_fixed(kdl_node, "y", true)?; + self.pane_templates.insert( + template_name, + ( + PaneOrFloatingPane::FloatingPane(FloatingPanesLayout { + focus, + run, + height, + width, + x, + y, + ..Default::default() + }), + kdl_node.clone(), + ), + ); + } else { + self.assert_valid_pane_properties(kdl_node)?; + // pane properties + let borderless = + kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless"); + let split_size = self.parse_split_size(kdl_node)?; + let children_split_direction = self.parse_split_direction(kdl_node)?; + let (external_children_index, pane_parts) = match kdl_children_nodes!(kdl_node) { + Some(children) => self.parse_child_pane_nodes_for_pane(&children)?, + None => (None, vec![]), + }; + self.assert_no_mixed_children_and_properties(kdl_node)?; + self.pane_templates.insert( + template_name, + ( + PaneOrFloatingPane::Pane(PaneLayout { + borderless: borderless.unwrap_or_default(), + focus, + split_size, + run, + children_split_direction, + external_children_index, + children: pane_parts, + ..Default::default() + }), + kdl_node.clone(), + ), + ); + } + Ok(()) } fn parse_tab_node( &mut self, kdl_node: &KdlNode, - ) -> Result<(bool, Option, PaneLayout), ConfigError> { - // (is_focused, Option, PaneLayout) + ) -> Result<(bool, Option, PaneLayout, Vec), ConfigError> { + // (is_focused, Option, PaneLayout, Vec) self.assert_valid_tab_properties(kdl_node)?; let tab_name = kdl_get_string_property_or_child_value!(kdl_node, "name").map(|s| s.to_string()); @@ -498,8 +886,11 @@ impl<'a> KdlLayoutParser<'a> { kdl_get_string_property_or_child_value!(kdl_node, "cwd").map(|c| PathBuf::from(c)); let is_focused = kdl_get_bool_property_or_child_value!(kdl_node, "focus").unwrap_or(false); let children_split_direction = self.parse_split_direction(kdl_node)?; + let mut child_floating_panes = vec![]; let children = match kdl_children_nodes!(kdl_node) { - Some(children) => self.parse_child_pane_nodes_for_tab(children)?, + Some(children) => { + self.parse_child_pane_nodes_for_tab(children, &mut child_floating_panes)? + }, None => vec![], }; let mut pane_layout = PaneLayout { @@ -510,11 +901,12 @@ impl<'a> KdlLayoutParser<'a> { if let Some(cwd_prefix) = &self.cwd_prefix(tab_cwd.as_ref())? { pane_layout.add_cwd_to_layout(&cwd_prefix); } - Ok((is_focused, tab_name, pane_layout)) + Ok((is_focused, tab_name, pane_layout, child_floating_panes)) } fn parse_child_pane_nodes_for_tab( &self, children: &[KdlNode], + child_floating_panes: &mut Vec, ) -> Result, ConfigError> { let mut nodes = vec![]; for child in children { @@ -528,6 +920,8 @@ impl<'a> KdlLayoutParser<'a> { pane_template, &pane_template_kdl_node, )?); + } else if kdl_name!(child) == "floating_panes" { + self.populate_floating_pane_children(child, child_floating_panes)?; } else if self.is_a_valid_tab_property(kdl_name!(child)) { return Err(ConfigError::new_layout_kdl_error( format!("Tab property '{}' must be placed on the tab title line and not in the child braces", kdl_name!(child)), @@ -586,6 +980,18 @@ impl<'a> KdlLayoutParser<'a> { } Ok((external_children_index, nodes)) } + fn has_child_nodes(&self, kdl_node: &KdlNode) -> bool { + if let Some(children) = kdl_children_nodes!(kdl_node) { + for child in children { + if kdl_name!(child) == "pane" || kdl_name!(child) == "children" { + return true; + } else if self.pane_templates.get(kdl_name!(child)).is_some() { + return true; + } + } + } + return false; + } fn has_child_panes_tabs_or_templates(&self, kdl_node: &KdlNode) -> bool { if let Some(children) = kdl_children_nodes!(kdl_node) { for child in children { @@ -705,6 +1111,68 @@ impl<'a> KdlLayoutParser<'a> { } Ok(()) } + fn assert_valid_floating_pane_properties( + &self, + pane_node: &KdlNode, + ) -> Result<(), ConfigError> { + for entry in pane_node.entries() { + match entry + .name() + .map(|e| e.value()) + .or_else(|| entry.value().as_string()) + { + Some(string_name) => { + if !self.is_a_valid_floating_pane_property(string_name) { + return Err(ConfigError::new_layout_kdl_error( + format!("Unknown floating pane property: {}", string_name), + entry.span().offset(), + entry.span().len(), + )); + } + }, + None => { + return Err(ConfigError::new_layout_kdl_error( + "Unknown floating pane property".into(), + entry.span().offset(), + entry.span().len(), + )); + }, + } + } + Ok(()) + } + fn assert_valid_pane_or_floating_pane_properties( + &self, + pane_node: &KdlNode, + ) -> Result<(), ConfigError> { + for entry in pane_node.entries() { + match entry + .name() + .map(|e| e.value()) + .or_else(|| entry.value().as_string()) + { + Some(string_name) => { + if !self.is_a_valid_floating_pane_property(string_name) + || !self.is_a_valid_pane_property(string_name) + { + return Err(ConfigError::new_layout_kdl_error( + format!("Unknown pane property: {}", string_name), + entry.span().offset(), + entry.span().len(), + )); + } + }, + None => { + return Err(ConfigError::new_layout_kdl_error( + "Unknown pane property".into(), + entry.span().offset(), + entry.span().len(), + )); + }, + } + } + Ok(()) + } fn assert_valid_tab_properties(&self, pane_node: &KdlNode) -> Result<(), ConfigError> { let all_property_names = kdl_property_names!(pane_node); for name in all_property_names { @@ -785,9 +1253,10 @@ impl<'a> KdlLayoutParser<'a> { &self, kdl_node: &KdlNode, mut tab_layout: PaneLayout, + mut tab_template_floating_panes: Vec, tab_layout_kdl_node: &KdlNode, - ) -> Result<(bool, Option, PaneLayout), ConfigError> { - // (is_focused, Option, PaneLayout) + ) -> Result<(bool, Option, PaneLayout, Vec), ConfigError> { + // (is_focused, Option, PaneLayout, Vec) let tab_name = kdl_get_string_property_or_child_value!(kdl_node, "name").map(|s| s.to_string()); let tab_cwd = @@ -796,7 +1265,8 @@ impl<'a> KdlLayoutParser<'a> { let children_split_direction = self.parse_split_direction(kdl_node)?; match kdl_children_nodes!(kdl_node) { Some(children) => { - let child_panes = self.parse_child_pane_nodes_for_tab(children)?; + let child_panes = self + .parse_child_pane_nodes_for_tab(children, &mut tab_template_floating_panes)?; let child_panes_layout = PaneLayout { children_split_direction, children: child_panes, @@ -821,7 +1291,12 @@ impl<'a> KdlLayoutParser<'a> { tab_layout.add_cwd_to_layout(&cwd_prefix); } tab_layout.external_children_index = None; - Ok((is_focused, tab_name, tab_layout)) + Ok(( + is_focused, + tab_name, + tab_layout, + tab_template_floating_panes, + )) } fn populate_one_tab_template(&mut self, kdl_node: &KdlNode) -> Result<(), ConfigError> { let template_name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name") @@ -850,22 +1325,29 @@ impl<'a> KdlLayoutParser<'a> { kdl_node.span().len(), )); } + let (tab_template, tab_template_floating_panes) = self.parse_tab_template_node(kdl_node)?; self.tab_templates.insert( template_name, - (self.parse_tab_template_node(kdl_node)?, kdl_node.clone()), + (tab_template, tab_template_floating_panes, kdl_node.clone()), ); Ok(()) } fn populate_default_tab_template(&mut self, kdl_node: &KdlNode) -> Result<(), ConfigError> { + let (tab_template, tab_template_floating_panes) = self.parse_tab_template_node(kdl_node)?; self.default_tab_template = - Some((self.parse_tab_template_node(kdl_node)?, kdl_node.clone())); + Some((tab_template, tab_template_floating_panes, kdl_node.clone())); Ok(()) } - fn parse_tab_template_node(&self, kdl_node: &KdlNode) -> Result { + fn parse_tab_template_node( + &self, + kdl_node: &KdlNode, + ) -> Result<(PaneLayout, Vec), ConfigError> { self.assert_valid_tab_properties(kdl_node)?; let children_split_direction = self.parse_split_direction(kdl_node)?; let mut tab_children = vec![]; + let mut tab_floating_children = vec![]; let mut external_children_index = None; + let mut children_index_offset = 0; if let Some(children) = kdl_children_nodes!(kdl_node) { for (i, child) in children.iter().enumerate() { if kdl_name!(child) == "pane" { @@ -881,7 +1363,7 @@ impl<'a> KdlLayoutParser<'a> { child.span().len(), )); } - external_children_index = Some(i); + external_children_index = Some(i.saturating_sub(children_index_offset)); } else if let Some((pane_template, pane_template_kdl_node)) = self.pane_templates.get(kdl_name!(child)).cloned() { @@ -890,6 +1372,9 @@ impl<'a> KdlLayoutParser<'a> { pane_template, &pane_template_kdl_node, )?); + } else if kdl_name!(child) == "floating_panes" { + children_index_offset += 1; + self.populate_floating_pane_children(child, &mut tab_floating_children)?; } else if self.is_a_valid_tab_property(kdl_name!(child)) { return Err(ConfigError::new_layout_kdl_error( format!("Tab property '{}' must be placed on the tab_template title line and not in the child braces", kdl_name!(child)), @@ -905,16 +1390,19 @@ impl<'a> KdlLayoutParser<'a> { } } } - Ok(PaneLayout { - children_split_direction, - children: tab_children, - external_children_index, - ..Default::default() - }) + Ok(( + PaneLayout { + children_split_direction, + children: tab_children, + external_children_index, + ..Default::default() + }, + tab_floating_children, + )) } fn default_template(&self) -> Result, ConfigError> { match &self.default_tab_template { - Some((template, _kdl_node)) => { + Some((template, _template_floating_panes, _kdl_node)) => { let mut template = template.clone(); if let Some(children_index) = template.external_children_index { template @@ -1058,7 +1546,7 @@ impl<'a> KdlLayoutParser<'a> { } fn layout_with_tabs( &self, - tabs: Vec<(Option, PaneLayout)>, + tabs: Vec<(Option, PaneLayout, Vec)>, focused_tab_index: Option, ) -> Result { let template = self @@ -1072,7 +1560,11 @@ impl<'a> KdlLayoutParser<'a> { ..Default::default() }) } - fn layout_with_one_tab(&self, panes: Vec) -> Result { + fn layout_with_one_tab( + &self, + panes: Vec, + floating_panes: Vec, + ) -> Result { let main_tab_layout = PaneLayout { children: panes, ..Default::default() @@ -1083,47 +1575,55 @@ impl<'a> KdlLayoutParser<'a> { // to explicitly place it in the first tab vec![] } else { - vec![(None, main_tab_layout.clone())] + vec![(None, main_tab_layout.clone(), floating_panes.clone())] }; let template = default_template.unwrap_or_else(|| main_tab_layout.clone()); // create a layout with one tab that has these child panes Ok(Layout { tabs, template: Some(template), + floating_panes_template: floating_panes, ..Default::default() }) } - fn layout_with_one_pane(&self) -> Result { + fn layout_with_one_pane( + &self, + child_floating_panes: Vec, + ) -> Result { let template = self .default_template()? .unwrap_or_else(|| PaneLayout::default()); Ok(Layout { template: Some(template), + floating_panes_template: child_floating_panes, ..Default::default() }) } fn populate_layout_child( &mut self, child: &KdlNode, - child_tabs: &mut Vec<(bool, Option, PaneLayout)>, + child_tabs: &mut Vec<(bool, Option, PaneLayout, Vec)>, child_panes: &mut Vec, + child_floating_panes: &mut Vec, ) -> Result<(), ConfigError> { let child_name = kdl_name!(child); + if (child_name == "pane" || child_name == "floating_panes") && !child_tabs.is_empty() { + return Err(ConfigError::new_layout_kdl_error( + "Cannot have both tabs and panes in the same node".into(), + child.span().offset(), + child.span().len(), + )); + } if child_name == "pane" { - if !child_tabs.is_empty() { - return Err(ConfigError::new_layout_kdl_error( - "Cannot have both tabs and panes in the same node".into(), - child.span().offset(), - child.span().len(), - )); - } let mut pane_node = self.parse_pane_node(child)?; if let Some(global_cwd) = &self.global_cwd { pane_node.add_cwd_to_layout(&global_cwd); } child_panes.push(pane_node); + } else if child_name == "floating_panes" { + self.populate_floating_pane_children(child, child_floating_panes)?; } else if child_name == "tab" { - if !child_panes.is_empty() { + if !child_panes.is_empty() || !child_floating_panes.is_empty() { return Err(ConfigError::new_layout_kdl_error( "Cannot have both tabs and panes in the same node".into(), child.span().offset(), @@ -1131,11 +1631,16 @@ impl<'a> KdlLayoutParser<'a> { )); } match &self.default_tab_template { - Some((default_tab_template, default_tab_template_kdl_node)) => { + Some(( + default_tab_template, + default_tab_template_floating_panes, + default_tab_template_kdl_node, + )) => { let default_tab_template = default_tab_template.clone(); child_tabs.push(self.parse_tab_node_with_template( child, default_tab_template, + default_tab_template_floating_panes.clone(), default_tab_template_kdl_node, )?); }, @@ -1143,7 +1648,7 @@ impl<'a> KdlLayoutParser<'a> { child_tabs.push(self.parse_tab_node(child)?); }, } - } else if let Some((tab_template, tab_template_kdl_node)) = + } else if let Some((tab_template, tab_template_floating_panes, tab_template_kdl_node)) = self.tab_templates.get(child_name).cloned() { if !child_panes.is_empty() { @@ -1156,6 +1661,7 @@ impl<'a> KdlLayoutParser<'a> { child_tabs.push(self.parse_tab_node_with_template( child, tab_template, + tab_template_floating_panes, &tab_template_kdl_node, )?); } else if let Some((pane_template, pane_template_kdl_node)) = @@ -1183,6 +1689,43 @@ impl<'a> KdlLayoutParser<'a> { } Ok(()) } + fn populate_floating_pane_children( + &self, + child: &KdlNode, + child_floating_panes: &mut Vec, + ) -> Result<(), ConfigError> { + if let Some(children) = kdl_children_nodes!(child) { + for child in children { + if kdl_name!(child) == "pane" { + let mut pane_node = self.parse_floating_pane_node(child)?; + if let Some(global_cwd) = &self.global_cwd { + pane_node.add_cwd_to_layout(&global_cwd); + } + child_floating_panes.push(pane_node); + } else if let Some((pane_template, pane_template_kdl_node)) = + self.pane_templates.get(kdl_name!(child)).cloned() + { + let pane_node = self.parse_floating_pane_node_with_template( + child, + pane_template, + &pane_template_kdl_node, + )?; + child_floating_panes.push(pane_node); + } else { + // TODO: invalid node name + return Err(ConfigError::new_layout_kdl_error( + format!( + "floating_panes can only contain pane nodes, found: {}", + kdl_name!(child) + ), + child.span().offset(), + child.span().len(), + )); + } + } + }; + Ok(()) + } pub fn parse(&mut self) -> Result { let kdl_layout: KdlDocument = self.raw_layout.parse()?; let layout_node = kdl_layout @@ -1209,18 +1752,24 @@ impl<'a> KdlLayoutParser<'a> { } let mut child_tabs = vec![]; let mut child_panes = vec![]; + let mut child_floating_panes = vec![]; if let Some(children) = kdl_children_nodes!(layout_node) { self.populate_global_cwd(layout_node)?; self.populate_pane_templates(children, &kdl_layout)?; self.populate_tab_templates(children)?; for child in children { - self.populate_layout_child(child, &mut child_tabs, &mut child_panes)?; + self.populate_layout_child( + child, + &mut child_tabs, + &mut child_panes, + &mut child_floating_panes, + )?; } } if !child_tabs.is_empty() { let has_more_than_one_focused_tab = child_tabs .iter() - .filter(|(is_focused, _, _)| *is_focused) + .filter(|(is_focused, _, _, _)| *is_focused) .count() > 1; if has_more_than_one_focused_tab { @@ -1230,16 +1779,23 @@ impl<'a> KdlLayoutParser<'a> { kdl_layout.span().len(), )); } - let focused_tab_index = child_tabs.iter().position(|(is_focused, _, _)| *is_focused); - let child_tabs: Vec<(Option, PaneLayout)> = child_tabs - .drain(..) - .map(|(_is_focused, tab_name, pane_layout)| (tab_name, pane_layout)) - .collect(); + let focused_tab_index = child_tabs + .iter() + .position(|(is_focused, _, _, _)| *is_focused); + let child_tabs: Vec<(Option, PaneLayout, Vec)> = + child_tabs + .drain(..) + .map( + |(_is_focused, tab_name, pane_layout, floating_panes_layout)| { + (tab_name, pane_layout, floating_panes_layout) + }, + ) + .collect(); self.layout_with_tabs(child_tabs, focused_tab_index) } else if !child_panes.is_empty() { - self.layout_with_one_tab(child_panes) + self.layout_with_one_tab(child_panes, child_floating_panes) } else { - self.layout_with_one_pane() + self.layout_with_one_pane(child_floating_panes) } } } diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index b28a8cc3..db001fb5 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -738,7 +738,7 @@ impl TryFrom<&KdlNode> for Action { "PaneNameInput" => { parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action) }, - "NewTab" => Ok(Action::NewTab(None, None)), + "NewTab" => Ok(Action::NewTab(None, vec![], None)), "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/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options-2.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options-2.snap index 263f3c60..8c6d5992 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options-2.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options-2.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 509 +assertion_line: 626 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -18,4 +18,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap index d4ea72ea..8d89277c 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 492 +assertion_line: 583 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -79,4 +79,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap index 77914594..8d946d7b 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap @@ -1,5 +1,6 @@ --- source: zellij-utils/src/setup.rs +assertion_line: 582 expression: "format!(\"{:#?}\", config)" --- Config { @@ -975,6 +976,7 @@ Config { ): [ NewTab( None, + [], None, ), SwitchToMode( @@ -3049,6 +3051,7 @@ Config { ): [ NewTab( None, + [], None, ), SwitchToMode( diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap index 9d15cc6a..97fb9f81 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap @@ -1,5 +1,6 @@ --- source: zellij-utils/src/setup.rs +assertion_line: 640 expression: "format!(\"{:#?}\", config)" --- Config { @@ -975,6 +976,7 @@ Config { ): [ NewTab( None, + [], None, ), SwitchToMode( @@ -3049,6 +3051,7 @@ Config { ): [ NewTab( None, + [], None, ), SwitchToMode( diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options-2.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options-2.snap index 3a4e696d..97b1c8b6 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options-2.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options-2.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 492 +assertion_line: 608 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -18,4 +18,5 @@ Layout { external_children_index: None, }, ), + floating_panes_template: [], } diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap index aadd52ed..f1060512 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap @@ -1,5 +1,6 @@ --- source: zellij-utils/src/setup.rs +assertion_line: 668 expression: "format!(\"{:#?}\", config)" --- Config { @@ -975,6 +976,7 @@ Config { ): [ NewTab( None, + [], None, ), SwitchToMode( @@ -3049,6 +3051,7 @@ Config { ): [ NewTab( None, + [], None, ), SwitchToMode( diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap index 46146fdf..13c499dd 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap @@ -1,5 +1,6 @@ --- source: zellij-utils/src/setup.rs +assertion_line: 682 expression: "format!(\"{:#?}\", config)" --- Config { @@ -975,6 +976,7 @@ Config { ): [ NewTab( None, + [], None, ), SwitchToMode( @@ -3049,6 +3051,7 @@ Config { ): [ NewTab( None, + [], None, ), SwitchToMode( diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap index 0f2e1e62..fc47de23 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap @@ -1,5 +1,6 @@ --- source: zellij-utils/src/setup.rs +assertion_line: 654 expression: "format!(\"{:#?}\", config)" --- Config { @@ -975,6 +976,7 @@ Config { ): [ NewTab( None, + [], None, ), SwitchToMode( @@ -3049,6 +3051,7 @@ Config { ): [ NewTab( None, + [], None, ), SwitchToMode(