diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index 9d720c71..7d83085f 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -140,8 +140,14 @@ fn handle_openpty( let cmd = cmd.clone(); let command = &mut Command::new(cmd.command); if let Some(current_dir) = cmd.cwd { - if current_dir.exists() { + if current_dir.exists() && current_dir.is_dir() { command.current_dir(current_dir); + } else { + // TODO: propagate this to the user + log::error!( + "Failed to set CWD for new pane. {} does not exist or is not a folder", + current_dir.display() + ); } } command diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index 9df221ce..84088b95 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -364,11 +364,11 @@ impl Pty { default_editor, } } - pub fn get_default_terminal(&self) -> TerminalAction { + pub fn get_default_terminal(&self, cwd: Option) -> TerminalAction { TerminalAction::RunCommand(RunCommand { args: vec![], command: PathBuf::from(env::var("SHELL").expect("Could not find the SHELL variable")), - cwd: None, // this should be filled by the calling function, eg. spawn_terminal + cwd, // note: this might also be filled by the calling function, eg. spawn_terminal hold_on_close: false, }) } @@ -400,12 +400,12 @@ impl Pty { let terminal_action = match client_or_tab_index { ClientOrTabIndex::ClientId(client_id) => { let mut terminal_action = - terminal_action.unwrap_or_else(|| self.get_default_terminal()); + terminal_action.unwrap_or_else(|| self.get_default_terminal(None)); self.fill_cwd(&mut terminal_action, client_id); terminal_action }, ClientOrTabIndex::TabIndex(_) => { - terminal_action.unwrap_or_else(|| self.get_default_terminal()) + terminal_action.unwrap_or_else(|| self.get_default_terminal(None)) }, }; let hold_on_close = match &terminal_action { @@ -454,7 +454,7 @@ impl Pty { default_shell: Option, client_id: ClientId, ) { - let mut default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal()); + 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 mut new_pane_pids: Vec<(u32, Option, Result)> = @@ -513,6 +513,29 @@ impl Pty { }, } }, + Some(Run::Cwd(cwd)) => { + let shell = self.get_default_terminal(Some(cwd)); + match self.bus.os_input.as_mut().unwrap().spawn_terminal( + shell, + quit_cb, + self.default_editor.clone(), + ) { + Ok((terminal_id, pid_primary, child_fd)) => { + self.id_to_child_pid.insert(terminal_id, child_fd); + new_pane_pids.push((terminal_id, None, Ok(pid_primary))); + }, + Err(SpawnTerminalError::CommandNotFound(terminal_id)) => { + new_pane_pids.push(( + terminal_id, + None, + Err(SpawnTerminalError::CommandNotFound(terminal_id)), + )); + }, + Err(e) => { + log::error!("Failed to spawn terminal: {}", e); + }, + } + }, None => { match self.bus.os_input.as_mut().unwrap().spawn_terminal( default_shell.clone(), diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs index f1efe339..42bfc366 100644 --- a/zellij-server/src/tab/unit/tab_integration_tests.rs +++ b/zellij-server/src/tab/unit/tab_integration_tests.rs @@ -251,7 +251,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str) let copy_options = CopyOptions::default(); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); - let layout = Layout::from_str(layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_str(layout, "layout_file_name".into(), None).unwrap(); let tab_layout = layout.new_tab(); let mut tab = Tab::new( index, diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 85e7b4a0..0ab13bf9 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -2207,6 +2207,7 @@ pub fn send_cli_new_tab_action_default_params() { let new_tab_action = CliAction::NewTab { name: None, layout: None, + cwd: None, }; send_cli_action_to_server( &session_metadata, @@ -2242,6 +2243,7 @@ pub fn send_cli_new_tab_action_with_name_and_layout() { "{}/src/unit/fixtures/layout-with-three-panes.kdl", env!("CARGO_MANIFEST_DIR") ))), + cwd: None, }; send_cli_action_to_server( &session_metadata, diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index bef1afb3..ae0c2141 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -253,5 +253,7 @@ pub enum CliAction { layout: Option, #[clap(short, long, value_parser)] name: Option, + #[clap(short, long, value_parser, requires("layout"))] + cwd: Option, }, } diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index de1350dd..d337a75a 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -344,12 +344,12 @@ impl Action { Action::TabNameInput(name.as_bytes().to_vec()), ]), CliAction::UndoRenameTab => Ok(vec![Action::UndoRenameTab]), - CliAction::NewTab { name, layout } => { + CliAction::NewTab { name, layout, cwd } => { if let Some(layout_path) = layout { let (path_to_raw_layout, raw_layout) = Layout::stringified_from_path_or_default(Some(&layout_path), None) .map_err(|e| format!("Failed to load layout: {}", e))?; - let layout = Layout::from_str(&raw_layout, path_to_raw_layout).map_err(|e| { + let layout = Layout::from_str(&raw_layout, path_to_raw_layout, cwd).map_err(|e| { let stringified_error = match e { ConfigError::KdlError(kdl_error) => { let error = kdl_error.add_src(layout_path.as_path().as_os_str().to_string_lossy().to_string(), String::from(raw_layout)); diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index f823ed40..8299c0bc 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -62,6 +62,41 @@ pub enum Run { Plugin(RunPlugin), #[serde(rename = "command")] Command(RunCommand), + Cwd(PathBuf), +} + +impl Run { + pub fn merge(base: &Option, other: &Option) -> Option { + // TODO: handle Plugin variants once there's a need + match (base, other) { + (Some(Run::Command(base_run_command)), Some(Run::Command(other_run_command))) => { + let mut merged = other_run_command.clone(); + if merged.cwd.is_none() && base_run_command.cwd.is_some() { + merged.cwd = base_run_command.cwd.clone(); + } + if merged.args.is_empty() && !base_run_command.args.is_empty() { + merged.args = base_run_command.args.clone(); + } + Some(Run::Command(merged)) + }, + (Some(Run::Command(base_run_command)), Some(Run::Cwd(other_cwd))) => { + let mut merged = base_run_command.clone(); + merged.cwd = Some(other_cwd.clone()); + Some(Run::Command(merged)) + }, + (Some(Run::Cwd(base_cwd)), Some(Run::Command(other_command))) => { + let mut merged = other_command.clone(); + if merged.cwd.is_none() { + merged.cwd = Some(base_cwd.clone()); + } + Some(Run::Command(merged)) + }, + (Some(_base), Some(other)) => Some(other.clone()), + (Some(base), _) => Some(base.clone()), + (None, Some(other)) => Some(other.clone()), + (None, None) => None, + } + } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] @@ -252,12 +287,16 @@ impl Layout { ) -> Result<(Layout, Config), ConfigError> { let (path_to_raw_layout, raw_layout) = Layout::stringified_from_path_or_default(layout_path, layout_dir)?; - let layout = Layout::from_kdl(&raw_layout, path_to_raw_layout)?; + let layout = Layout::from_kdl(&raw_layout, path_to_raw_layout, None)?; let config = Config::from_kdl(&raw_layout, Some(config))?; // this merges the two config, with Ok((layout, config)) } - pub fn from_str(raw: &str, path_to_raw_layout: String) -> Result { - Layout::from_kdl(raw, path_to_raw_layout) + pub fn from_str( + raw: &str, + path_to_raw_layout: String, + cwd: Option, + ) -> Result { + Layout::from_kdl(raw, path_to_raw_layout, cwd) } pub fn stringified_from_dir( layout: &PathBuf, diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs index ce077a42..82a221e2 100644 --- a/zellij-utils/src/input/unit/layout_test.rs +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -4,7 +4,7 @@ use insta::assert_snapshot; #[test] fn empty_layout() { let kdl_layout = "layout"; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { template: Some(PaneLayout::default()), ..Default::default() @@ -19,7 +19,7 @@ fn layout_with_one_pane() { pane } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { template: Some(PaneLayout { children: vec![PaneLayout::default()], @@ -39,7 +39,7 @@ fn layout_with_multiple_panes() { pane } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { template: Some(PaneLayout { children: vec![ @@ -68,7 +68,7 @@ fn layout_with_nested_panes() { } } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { template: Some(PaneLayout { children: vec![ @@ -96,7 +96,7 @@ fn layout_with_tabs() { tab } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { tabs: vec![(None, PaneLayout::default())], template: Some(PaneLayout::default()), @@ -120,7 +120,7 @@ fn layout_with_nested_differing_tabs() { } } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { tabs: vec![ ( @@ -160,7 +160,7 @@ fn layout_with_panes_in_different_mixed_split_sizes() { pane size=2; } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { template: Some(PaneLayout { children: vec![ @@ -195,7 +195,7 @@ fn layout_with_command_panes() { pane command="htop" } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { template: Some(PaneLayout { children: vec![PaneLayout { @@ -220,7 +220,7 @@ fn layout_with_command_panes_and_cwd() { pane command="htop" cwd="/path/to/my/cwd" } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { template: Some(PaneLayout { children: vec![PaneLayout { @@ -248,7 +248,7 @@ fn layout_with_command_panes_and_cwd_and_args() { } } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { template: Some(PaneLayout { children: vec![PaneLayout { @@ -280,7 +280,7 @@ fn layout_with_plugin_panes() { } } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { template: Some(PaneLayout { children: vec![ @@ -313,7 +313,7 @@ fn layout_with_borderless_panes() { pane borderless=true } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { template: Some(PaneLayout { children: vec![PaneLayout { @@ -334,7 +334,7 @@ fn layout_with_focused_panes() { pane focus=true } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { template: Some(PaneLayout { children: vec![PaneLayout { @@ -355,7 +355,7 @@ fn layout_with_pane_names() { pane name="my awesome pane" } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { template: Some(PaneLayout { children: vec![PaneLayout { @@ -377,7 +377,7 @@ fn layout_with_tab_names() { tab name="my cool tab name 2" } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { tabs: vec![ ( @@ -410,7 +410,7 @@ fn layout_with_focused_tab() { tab } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { tabs: vec![ (None, PaneLayout::default()), @@ -444,7 +444,7 @@ fn layout_with_tab_templates() { one-above-one-below } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); let expected_layout = Layout { tabs: vec![ ( @@ -518,7 +518,7 @@ fn layout_with_default_tab_template() { tab } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); assert_snapshot!(format!("{:#?}", layout)); } @@ -545,7 +545,7 @@ fn layout_with_pane_templates() { left-and-right } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); assert_snapshot!(format!("{:#?}", layout)); } @@ -566,7 +566,7 @@ fn layout_with_tab_and_pane_templates() { left-right-and-htop } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); assert_snapshot!(format!("{:#?}", layout)); } @@ -587,7 +587,7 @@ fn layout_with_nested_pane_templates() { left-and-right } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); assert_snapshot!(format!("{:#?}", layout)); } @@ -613,7 +613,7 @@ fn layout_with_nested_branched_pane_templates() { left-and-right } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); assert_snapshot!(format!("{:#?}", layout)); } @@ -637,7 +637,7 @@ fn circular_dependency_pane_templates_error() { one } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None); assert!(layout.is_err(), "circular dependency detected"); } @@ -659,7 +659,7 @@ fn children_not_as_first_child_of_tab_template() { horizontal-with-vertical-top } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); assert_snapshot!(format!("{:#?}", layout)); } @@ -682,7 +682,7 @@ fn error_on_more_than_one_children_block_in_tab_template() { horizontal-with-vertical-top } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None); assert!( layout.is_err(), "error provided for more than one children block" @@ -707,7 +707,7 @@ fn children_not_as_first_child_of_pane_template() { horizontal-with-vertical-top } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); assert_snapshot!(format!("{:#?}", layout)); } @@ -730,7 +730,7 @@ fn error_on_more_than_one_children_block_in_pane_template() { horizontal-with-vertical-top } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None); assert!( layout.is_err(), "error provided for more than one children block" @@ -759,7 +759,7 @@ fn combined_tab_and_pane_template_both_with_children() { horizontal-with-vertical-top } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); assert_snapshot!(format!("{:#?}", layout)); } @@ -775,7 +775,7 @@ fn cannot_define_tab_template_name_with_space() { pane } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None); assert!(layout.is_err(), "error provided for tab name with space"); } @@ -791,7 +791,7 @@ fn cannot_define_pane_template_name_with_space() { pane } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None); assert!(layout.is_err(), "error provided for tab name with space"); } @@ -805,7 +805,7 @@ fn cannot_define_panes_and_tabs_on_same_level() { } } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None); assert!( layout.is_err(), "error provided for tab and pane on the same level" @@ -839,7 +839,7 @@ fn cannot_define_tab_template_names_as_keywords() { ", keyword ); - let layout = Layout::from_kdl(&kdl_layout, "layout_file_name".into()); + let layout = Layout::from_kdl(&kdl_layout, "layout_file_name".into(), None); assert!( layout.is_err(), "{}", @@ -877,7 +877,7 @@ fn cannot_define_pane_template_names_as_keywords() { ", keyword ); - let layout = Layout::from_kdl(&kdl_layout, "layout_file_name".into()); + let layout = Layout::from_kdl(&kdl_layout, "layout_file_name".into(), None); assert!( layout.is_err(), "{}", @@ -897,7 +897,7 @@ fn error_on_multiple_layout_nodes_in_file() { layout " ); - let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into()).unwrap_err(); + let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into(), None).unwrap_err(); assert_snapshot!(format!("{:?}", layout_error)); } @@ -912,7 +912,7 @@ fn error_on_unknown_layout_node() { }} " ); - let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into()).unwrap_err(); + let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into(), None).unwrap_err(); assert_snapshot!(format!("{:?}", layout_error)); } @@ -925,7 +925,7 @@ fn error_on_unknown_layout_pane_property() { }} " ); - let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into()).unwrap_err(); + let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into(), None).unwrap_err(); assert_snapshot!(format!("{:?}", layout_error)); } @@ -938,7 +938,7 @@ fn error_on_unknown_layout_pane_template_property() { }} " ); - let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into()).unwrap_err(); + let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into(), None).unwrap_err(); assert_snapshot!(format!("{:?}", layout_error)); } @@ -951,7 +951,7 @@ fn error_on_unknown_layout_tab_property() { }} " ); - let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into()).unwrap_err(); + let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into(), None).unwrap_err(); assert_snapshot!(format!("{:?}", layout_error)); } @@ -964,7 +964,7 @@ fn error_on_unknown_layout_tab_template_property() { }} " ); - let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into()).unwrap_err(); + let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into(), None).unwrap_err(); assert_snapshot!(format!("{:?}", layout_error)); } @@ -981,7 +981,7 @@ fn error_on_pane_templates_without_a_name() { }} " ); - let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into()).unwrap_err(); + let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into(), None).unwrap_err(); assert_snapshot!(format!("{:?}", layout_error)); } @@ -998,7 +998,7 @@ fn error_on_tab_templates_without_a_name() { }} " ); - let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into()).unwrap_err(); + let layout_error = Layout::from_kdl(&kdl_layout, "layout_file_name".into(), None).unwrap_err(); assert_snapshot!(format!("{:?}", layout_error)); } @@ -1011,7 +1011,7 @@ fn error_on_more_than_one_focused_tab() { tab } "#; - let layout_error = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap_err(); + let layout_error = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap_err(); assert_snapshot!(format!("{:?}", layout_error)); } @@ -1029,7 +1029,7 @@ fn args_override_args_in_template() { } } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); assert_snapshot!(format!("{:#?}", layout)); } @@ -1046,7 +1046,7 @@ fn args_added_to_args_in_template() { } } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); assert_snapshot!(format!("{:#?}", layout)); } @@ -1064,7 +1064,7 @@ fn cwd_override_cwd_in_template() { } } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); assert_snapshot!(format!("{:#?}", layout)); } @@ -1081,7 +1081,7 @@ fn cwd_added_to_cwd_in_template() { } } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap(); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); assert_snapshot!(format!("{:#?}", layout)); } @@ -1094,7 +1094,21 @@ fn error_on_mixed_command_and_child_panes() { } } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None); + assert!(layout.is_err(), "error provided"); +} + +#[test] +fn error_on_mixed_cwd_and_child_panes() { + let kdl_layout = r#" + layout { + pane cwd="/tmp" { + pane + pane + } + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None); assert!(layout.is_err(), "error provided"); } @@ -1107,34 +1121,7 @@ fn error_on_bare_args_without_command() { } } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()); - assert!(layout.is_err(), "error provided"); -} - -#[test] -fn error_on_bare_cwd_without_command() { - let kdl_layout = r#" - layout { - pane { - cwd "/" - } - } - "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()); - assert!(layout.is_err(), "error provided"); -} - -#[test] -fn error_on_bare_cwd_in_template_without_command() { - let kdl_layout = r#" - layout { - pane_template name="my_template" - my_template { - cwd "/" - } - } - "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None); assert!(layout.is_err(), "error provided"); } @@ -1148,6 +1135,215 @@ fn error_on_bare_args_in_template_without_command() { } } "#; - let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()); + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None); assert!(layout.is_err(), "error provided"); } + +#[test] +fn pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd() { + let kdl_layout = r#" + layout { + cwd "/tmp" + pane_template name="tail" { + command "tail" + cwd "bar" + } + tail command="pwd" { + cwd "foo" + } + // pane should have /tmp/foo and not /tmp/bar as cwd + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + assert_snapshot!(format!("{:#?}", layout)); +} + +#[test] +fn pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd() { + let kdl_layout = r#" + layout { + cwd "/tmp" + pane_template name="tail" { + command "tail" + cwd "bar" + } + tail command="pwd" + // pane should have /tmp/bar as its cwd with the pwd command + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + assert_snapshot!(format!("{:#?}", layout)); +} + +#[test] +fn pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd() { + let kdl_layout = r#" + layout { + cwd "/tmp" + pane_template name="tail" { + command "tail" + } + tail command="pwd" { + cwd "bar" + } + // pane should have /tmp/bar as its cwd with the pwd command + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + assert_snapshot!(format!("{:#?}", layout)); +} + +#[test] +fn pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd() { + let kdl_layout = r#" + layout { + cwd "/tmp" + pane_template name="tail" { + command "tail" + cwd "foo" + } + tail { + cwd "bar" + } + // pane should have /tmp/bar as its cwd with the tail command + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + assert_snapshot!(format!("{:#?}", layout)); +} + +#[test] +fn pane_template_command_without_cwd_receives_its_consumers_bare_cwd() { + let kdl_layout = r#" + layout { + cwd "/tmp" + pane_template name="tail" { + command "tail" + } + tail { + cwd "bar" + } + // pane should have /tmp/bar as its cwd with the tail command + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + assert_snapshot!(format!("{:#?}", layout)); +} + +#[test] +fn pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd() { + let kdl_layout = r#" + layout { + cwd "/tmp" + pane_template name="tail" { + cwd "foo" + } + tail { + cwd "bar" + } + // pane should have /tmp/foo without a command + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + assert_snapshot!(format!("{:#?}", layout)); +} + +#[test] +fn pane_template_with_bare_propagated_to_its_consumer_command_without_cwd() { + let kdl_layout = r#" + layout { + cwd "/tmp" + pane_template name="tail" { + cwd "foo" + } + tail command="tail" + // pane should have /tmp/foo with the tail command + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + assert_snapshot!(format!("{:#?}", layout)); +} + +#[test] +fn pane_template_with_bare_propagated_to_its_consumer_command_with_cwd() { + let kdl_layout = r#" + layout { + cwd "/tmp" + pane_template name="tail" { + cwd "foo" + } + tail command="tail" { + cwd "bar" + } + // pane should have /tmp/bar with the tail command + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + assert_snapshot!(format!("{:#?}", layout)); +} + +#[test] +fn global_cwd_given_to_panes_without_cwd() { + let kdl_layout = r#" + layout { + cwd "/tmp" + pane + pane command="tail" + // both should have the /tmp cwd + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + assert_snapshot!(format!("{:#?}", layout)); +} + +#[test] +fn global_cwd_prepended_to_panes_with_cwd() { + let kdl_layout = r#" + layout { + cwd "/tmp" + pane cwd="foo" // should be /tmp/foo + pane command="tail" cwd="/home/foo" // should be /home/foo because its an absolute path + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + assert_snapshot!(format!("{:#?}", layout)); +} + +#[test] +fn global_cwd_passed_from_layout_constructor() { + // this is used by the new-tab cli action with --cwd + let kdl_layout = r#" + layout { + pane + pane command="tail" + // both should have the /tmp cwd + } + "#; + let layout = Layout::from_kdl( + kdl_layout, + "layout_file_name".into(), + Some(PathBuf::from("/tmp")), + ) + .unwrap(); + assert_snapshot!(format!("{:#?}", layout)); +} + +#[test] +fn global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file() { + // this is used by the new-tab cli action with --cwd + let kdl_layout = r#" + layout { + cwd "/home" + pane + pane command="tail" + // both should have the /tmp cwd + } + "#; + let layout = Layout::from_kdl( + kdl_layout, + "layout_file_name".into(), + Some(PathBuf::from("/tmp")), + ) + .unwrap(); + assert_snapshot!(format!("{:#?}", layout)); +} 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 new file mode 100644 index 00000000..548ae756 --- /dev/null +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap @@ -0,0 +1,57 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 1283 +expression: "format!(\"{:#?}\", layout)" +--- +Layout { + tabs: [], + focused_tab_index: None, + template: Some( + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "tail", + args: [], + cwd: Some( + "/tmp", + ), + hold_on_close: true, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + ), +} diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap new file mode 100644 index 00000000..d88a8ece --- /dev/null +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap @@ -0,0 +1,57 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 1309 +expression: "format!(\"{:#?}\", layout)" +--- +Layout { + tabs: [], + focused_tab_index: None, + template: Some( + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "tail", + args: [], + cwd: Some( + "/tmp", + ), + hold_on_close: true, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + ), +} diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap new file mode 100644 index 00000000..0710ae13 --- /dev/null +++ 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 @@ -0,0 +1,57 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 1324 +expression: "format!(\"{:#?}\", layout)" +--- +Layout { + tabs: [], + focused_tab_index: None, + template: Some( + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Cwd( + "/tmp", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "tail", + args: [], + cwd: Some( + "/tmp", + ), + hold_on_close: true, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + ), +} diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap new file mode 100644 index 00000000..956df1a5 --- /dev/null +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap @@ -0,0 +1,57 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 1295 +expression: "format!(\"{:#?}\", layout)" +--- +Layout { + tabs: [], + focused_tab_index: None, + template: Some( + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Cwd( + "/tmp/foo", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "tail", + args: [], + cwd: Some( + "/home/foo", + ), + hold_on_close: true, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + ), +} diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap new file mode 100644 index 00000000..03adaf85 --- /dev/null +++ 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 @@ -0,0 +1,43 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 1199 +expression: "format!(\"{:#?}\", layout)" +--- +Layout { + tabs: [], + focused_tab_index: None, + template: Some( + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "tail", + args: [], + cwd: Some( + "/tmp/bar", + ), + hold_on_close: true, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + ), +} diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap new file mode 100644 index 00000000..d01c6516 --- /dev/null +++ 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 @@ -0,0 +1,43 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 1144 +expression: "format!(\"{:#?}\", layout)" +--- +Layout { + tabs: [], + focused_tab_index: None, + template: Some( + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "pwd", + args: [], + cwd: Some( + "/tmp/foo", + ), + hold_on_close: true, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + ), +} diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap new file mode 100644 index 00000000..596c492b --- /dev/null +++ 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 @@ -0,0 +1,43 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 1161 +expression: "format!(\"{:#?}\", layout)" +--- +Layout { + tabs: [], + focused_tab_index: None, + template: Some( + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "pwd", + args: [], + cwd: Some( + "/tmp/bar", + ), + hold_on_close: true, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + ), +} diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap new file mode 100644 index 00000000..25bca8db --- /dev/null +++ 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 @@ -0,0 +1,43 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 1180 +expression: "format!(\"{:#?}\", layout)" +--- +Layout { + tabs: [], + focused_tab_index: None, + template: Some( + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "pwd", + args: [], + cwd: Some( + "/tmp/bar", + ), + hold_on_close: true, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + ), +} diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap new file mode 100644 index 00000000..05e377b9 --- /dev/null +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap @@ -0,0 +1,43 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 1216 +expression: "format!(\"{:#?}\", layout)" +--- +Layout { + tabs: [], + focused_tab_index: None, + template: Some( + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "tail", + args: [], + cwd: Some( + "/tmp/bar", + ), + hold_on_close: true, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + ), +} diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap new file mode 100644 index 00000000..0f488990 --- /dev/null +++ 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 @@ -0,0 +1,36 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 1235 +expression: "format!(\"{:#?}\", layout)" +--- +Layout { + tabs: [], + focused_tab_index: None, + template: Some( + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Cwd( + "/tmp/bar", + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + ), +} diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap new file mode 100644 index 00000000..8c3863c3 --- /dev/null +++ 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 @@ -0,0 +1,43 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 1269 +expression: "format!(\"{:#?}\", layout)" +--- +Layout { + tabs: [], + focused_tab_index: None, + template: Some( + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "tail", + args: [], + cwd: Some( + "/tmp/bar", + ), + hold_on_close: true, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + ), +} diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap new file mode 100644 index 00000000..ba91e574 --- /dev/null +++ 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 @@ -0,0 +1,43 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 1251 +expression: "format!(\"{:#?}\", layout)" +--- +Layout { + tabs: [], + focused_tab_index: None, + template: Some( + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + PaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: Some( + Command( + RunCommand { + command: "tail", + args: [], + cwd: Some( + "/tmp/foo", + ), + hold_on_close: true, + }, + ), + ), + borderless: false, + focus: None, + external_children_index: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + }, + ), +} diff --git a/zellij-utils/src/kdl/kdl_layout_parser.rs b/zellij-utils/src/kdl/kdl_layout_parser.rs index 6bf2d372..d7df5889 100644 --- a/zellij-utils/src/kdl/kdl_layout_parser.rs +++ b/zellij-utils/src/kdl/kdl_layout_parser.rs @@ -24,6 +24,7 @@ use std::vec::Vec; use url::Url; pub struct KdlLayoutParser<'a> { + global_cwd: Option, raw_layout: &'a str, tab_templates: HashMap, pane_templates: HashMap, @@ -31,12 +32,13 @@ pub struct KdlLayoutParser<'a> { } impl<'a> KdlLayoutParser<'a> { - pub fn new(raw_layout: &'a str) -> Self { + pub fn new(raw_layout: &'a str, global_cwd: Option) -> Self { KdlLayoutParser { raw_layout, tab_templates: HashMap::new(), pane_templates: HashMap::new(), default_tab_template: None, + global_cwd, } } fn is_a_reserved_word(&self, word: &str) -> bool { @@ -173,6 +175,16 @@ impl<'a> KdlLayoutParser<'a> { None => Ok(None), } } + fn parse_cwd(&self, kdl_node: &KdlNode) -> Result, ConfigError> { + Ok( + kdl_get_string_property_or_child_value_with_error!(kdl_node, "cwd") + .and_then(|c| match &self.global_cwd { + Some(global_cwd) => Some(global_cwd.join(c)), + None => Some(PathBuf::from(c)), + }) + .or_else(|| self.global_cwd.as_ref().map(|g| g.clone())), + ) + } fn parse_pane_command( &self, pane_node: &KdlNode, @@ -180,15 +192,16 @@ impl<'a> KdlLayoutParser<'a> { ) -> Result, ConfigError> { let command = kdl_get_string_property_or_child_value_with_error!(pane_node, "command") .map(|c| PathBuf::from(c)); - let cwd = kdl_get_string_property_or_child_value_with_error!(pane_node, "cwd") - .map(|c| PathBuf::from(c)); + let cwd = if is_template { + // we fill the global_cwd for templates later + kdl_get_string_property_or_child_value_with_error!(pane_node, "cwd") + .map(|c| PathBuf::from(c)) + } else { + self.parse_cwd(pane_node)? + }; let args = self.parse_args(pane_node)?; match (command, cwd, args, is_template) { - (None, Some(_cwd), _, false) => Err(ConfigError::new_kdl_error( - "cwd can only be set if a command was specified".into(), - pane_node.span().offset(), - pane_node.span().len(), - )), + (None, Some(cwd), _, _) => Ok(Some(Run::Cwd(cwd))), (None, _, Some(_args), false) => Err(ConfigError::new_kdl_error( "args can only be set if a command was specified".into(), pane_node.span().offset(), @@ -263,39 +276,13 @@ impl<'a> KdlLayoutParser<'a> { ..Default::default() }) } - fn parse_pane_node_with_template( + fn insert_children_to_pane_template( &self, kdl_node: &KdlNode, - mut pane_layout: PaneLayout, - pane_layout_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 cwd = kdl_get_string_property_or_child_value_with_error!(kdl_node, "cwd") - .map(|c| PathBuf::from(c)); - let split_size = self.parse_split_size(kdl_node)?; - let run = self.parse_command_or_plugin_block_for_template(kdl_node)?; - if let (None, None, true, true) | (None, None, false, true) | (None, None, true, false) = - (&run, &pane_layout.run, args.is_some(), cwd.is_some()) - { - let mut offending_nodes = vec![]; - if args.is_some() { - offending_nodes.push("args"); - } - if cwd.is_some() { - offending_nodes.push("cwd"); - } - return Err(kdl_parsing_error!( - format!("{} can only be specified if a command was specified either in the pane_template or in the pane", offending_nodes.join(" and ")), - kdl_node - )); - } - + pane_template: &mut PaneLayout, + pane_template_kdl_node: &KdlNode, + ) -> Result<(), ConfigError> { 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![]), @@ -307,41 +294,89 @@ impl<'a> KdlLayoutParser<'a> { external_children_index, ..Default::default() }; - self.assert_one_children_block(&pane_layout, pane_layout_kdl_node)?; + self.assert_one_children_block(&pane_template, pane_template_kdl_node)?; self.insert_layout_children_or_error( - &mut pane_layout, + pane_template, child_panes_layout, - pane_layout_kdl_node, + pane_template_kdl_node, )?; } + Ok(()) + } + fn parse_pane_node_with_template( + &self, + kdl_node: &KdlNode, + mut pane_template: PaneLayout, + 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 split_size = self.parse_split_size(kdl_node)?; + let run = self.parse_command_or_plugin_block_for_template(kdl_node)?; + self.assert_no_bare_args_in_pane_node_with_template( + &run, + &pane_template.run, + &args, + 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); + self.populate_global_cwd_for_pane_run(&mut pane_template.run)?; + if let (Some(Run::Command(pane_template_run_command)), Some(args)) = + (pane_template.run.as_mut(), args) + { + if !args.is_empty() { + pane_template_run_command.args = args.clone(); + } + } if let Some(borderless) = borderless { - pane_layout.borderless = borderless; + pane_template.borderless = borderless; } if let Some(focus) = focus { - pane_layout.focus = Some(focus); + pane_template.focus = Some(focus); } if let Some(name) = name { - pane_layout.name = Some(name); + pane_template.name = Some(name); } if let Some(split_size) = split_size { - pane_layout.split_size = Some(split_size); + pane_template.split_size = Some(split_size); } - if let Some(run) = run { - pane_layout.run = Some(run); - } - if let (Some(args), Some(Run::Command(run_command))) = (args, pane_layout.run.as_mut()) { - run_command.args = args.clone(); - } - if let (Some(cwd), Some(Run::Command(run_command))) = (cwd, pane_layout.run.as_mut()) { - run_command.cwd = Some(cwd.clone()); - } - if let Some(index_of_children) = pane_layout.external_children_index { - pane_layout + if let Some(index_of_children) = pane_template.external_children_index { + pane_template .children .insert(index_of_children, PaneLayout::default()); } - pane_layout.external_children_index = None; - Ok(pane_layout) + pane_template.external_children_index = None; + Ok(pane_template) + } + fn populate_global_cwd_for_pane_run( + &self, + pane_run: &mut Option, + ) -> Result<(), ConfigError> { + if let Some(global_cwd) = &self.global_cwd { + match pane_run.as_mut() { + Some(Run::Command(run_command)) => match run_command.cwd.as_mut() { + Some(run_command_cwd) => { + *run_command_cwd = global_cwd.join(&run_command_cwd); + }, + None => { + run_command.cwd = Some(global_cwd.clone()); + }, + }, + Some(Run::Cwd(pane_template_cwd)) => { + *pane_template_cwd = global_cwd.join(&pane_template_cwd); + }, + _ => {}, + } + } + Ok(()) } fn parse_split_direction(&self, kdl_node: &KdlNode) -> Result { match kdl_get_string_property_or_child_value_with_error!(kdl_node, "split_direction") { @@ -513,6 +548,21 @@ impl<'a> KdlLayoutParser<'a> { } false } + fn assert_no_bare_args_in_pane_node_with_template( + &self, + pane_run: &Option, + pane_template_run: &Option, + args: &Option>, + pane_node: &KdlNode, + ) -> Result<(), ConfigError> { + if let (None, None, true) = (pane_run, pane_template_run, args.is_some()) { + return Err(kdl_parsing_error!( + format!("args can only be specified if a command was specified either in the pane_template or in the pane"), + pane_node + )); + } + Ok(()) + } fn assert_one_children_block( &self, layout: &PaneLayout, @@ -572,10 +622,18 @@ impl<'a> KdlLayoutParser<'a> { kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless").is_some(); let has_focus_prop = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus").is_some(); - let has_run_prop = self.parse_command_or_plugin_block(kdl_node)?.is_some(); + let has_cwd_prop = + kdl_get_string_property_or_child_value_with_error!(kdl_node, "cwd").is_some(); + let has_non_cwd_run_prop = self + .parse_command_or_plugin_block(kdl_node)? + .map(|r| match r { + Run::Cwd(_) => false, + _ => true, + }) + .unwrap_or(false); let has_nested_nodes_or_children_block = self.has_child_panes_tabs_or_templates(kdl_node); if has_nested_nodes_or_children_block - && (has_borderless_prop || has_focus_prop || has_run_prop) + && (has_borderless_prop || has_focus_prop || has_non_cwd_run_prop || has_cwd_prop) { let mut offending_nodes = vec![]; if has_borderless_prop { @@ -584,9 +642,12 @@ impl<'a> KdlLayoutParser<'a> { if has_focus_prop { offending_nodes.push("focus"); } - if has_run_prop { + if has_non_cwd_run_prop { offending_nodes.push("command/plugin"); } + if has_cwd_prop { + offending_nodes.push("cwd"); + } Err(ConfigError::new_kdl_error( format!( "Cannot have both properties ({}) and nested children", @@ -821,6 +882,17 @@ impl<'a> KdlLayoutParser<'a> { } Ok(()) } + fn populate_global_cwd(&mut self, layout_node: &KdlNode) -> Result<(), ConfigError> { + // we only populate global cwd from the layout file if another wasn't explicitly passed to us + if self.global_cwd.is_none() { + if let Some(global_cwd) = + kdl_get_string_property_or_child_value_with_error!(layout_node, "cwd") + { + self.global_cwd = Some(PathBuf::from(global_cwd)); + } + } + Ok(()) + } fn populate_pane_templates( &mut self, layout_children: &[KdlNode], @@ -1019,6 +1091,7 @@ impl<'a> KdlLayoutParser<'a> { let mut child_tabs = vec![]; let mut child_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 { diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index b8d76c7b..e7b7fa50 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -1266,8 +1266,12 @@ impl RunPlugin { } } impl Layout { - pub fn from_kdl(raw_layout: &str, file_name: String) -> Result { - KdlLayoutParser::new(raw_layout).parse().map_err(|e| { + pub fn from_kdl( + raw_layout: &str, + file_name: String, + cwd: Option, + ) -> Result { + KdlLayoutParser::new(raw_layout, cwd).parse().map_err(|e| { match e { ConfigError::KdlError(kdl_error) => ConfigError::KdlError(kdl_error.add_src(file_name, String::from(raw_layout))), ConfigError::KdlDeserializationError(kdl_error) => {