diff options
22 files changed, 1048 insertions, 138 deletions
diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index 9d720c717..7d83085fc 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 9df221ce3..84088b951 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<PathBuf>) -> 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<TerminalAction>, 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<RunCommand>, Result<RawFd, SpawnTerminalError>)> = @@ -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 f1efe339d..42bfc3663 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 85e7b4a06..0ab13bf94 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 bef1afb33..ae0c2141c 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -253,5 +253,7 @@ pub enum CliAction { layout: Option<PathBuf>, #[clap(short, long, value_parser)] name: Option<String>, + #[clap(short, long, value_parser, requires("layout"))] + cwd: Option<PathBuf>, }, } diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index de1350ddd..d337a75a9 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 f823ed401..8299c0bc2 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<Run>, other: &Option<Run>) -> Option<Run> { + // 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, ConfigError> { - Layout::from_kdl(raw, path_to_raw_layout) + pub fn from_str( + raw: &str, + path_to_raw_layout: String, + cwd: Option<PathBuf>, + ) -> Result<Layout, ConfigError> { + 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 ce077a42e..82a221e2e 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)); } |