diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index 84088b9519..9151c1a152 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -536,6 +536,28 @@ impl Pty { }, } }, + Some(Run::EditFile(path_to_file, line_number)) => { + match self.bus.os_input.as_mut().unwrap().spawn_terminal( + TerminalAction::OpenFile(path_to_file, line_number), + 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-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index 8299c0bc2e..8e317a8e03 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -62,11 +62,15 @@ pub enum Run { Plugin(RunPlugin), #[serde(rename = "command")] Command(RunCommand), + EditFile(PathBuf, Option), // TODO: merge this with TerminalAction::OpenFile Cwd(PathBuf), } impl Run { pub fn merge(base: &Option, other: &Option) -> Option { + // This method is necessary to merge between pane_templates and their consumers + // TODO: reconsider the way we parse command/edit/plugin pane_templates from layouts to prevent this + // madness // TODO: handle Plugin variants once there's a need match (base, other) { (Some(Run::Command(base_run_command)), Some(Run::Command(other_run_command))) => { @@ -91,6 +95,16 @@ impl Run { } Some(Run::Command(merged)) }, + ( + Some(Run::Command(base_run_command)), + Some(Run::EditFile(file_to_edit, line_number)), + ) => match &base_run_command.cwd { + Some(cwd) => Some(Run::EditFile(cwd.join(&file_to_edit), *line_number)), + None => Some(Run::EditFile(file_to_edit.clone(), *line_number)), + }, + (Some(Run::Cwd(cwd)), Some(Run::EditFile(file_to_edit, line_number))) => { + Some(Run::EditFile(cwd.join(&file_to_edit), *line_number)) + }, (Some(_base), Some(other)) => Some(other.clone()), (Some(base), _) => Some(base.clone()), (None, Some(other)) => Some(other.clone()), diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs index 82a221e2ed..6155024b2d 100644 --- a/zellij-utils/src/input/unit/layout_test.rs +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -1282,6 +1282,38 @@ fn pane_template_with_bare_propagated_to_its_consumer_command_with_cwd() { assert_snapshot!(format!("{:#?}", layout)); } +#[test] +fn pane_template_with_bare_propagated_to_its_consumer_edit() { + let kdl_layout = r#" + layout { + cwd "/tmp" + pane_template name="tail" { + cwd "foo" + } + tail edit="bar" + // pane should have /tmp/foo/bar with the edit file variant + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap(); + assert_snapshot!(format!("{:#?}", layout)); +} + +#[test] +fn pane_template_with_command_propagated_to_its_consumer_edit() { + let kdl_layout = r#" + layout { + cwd "/tmp" + pane_template name="tail" command="not-vim" { + cwd "foo" + } + tail edit="bar" + // pane should have /tmp/foo/bar with the edit file variant + } + "#; + 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#" 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 new file mode 100644 index 0000000000..32bc6880c7 --- /dev/null +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap @@ -0,0 +1,37 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 1298 +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( + EditFile( + "/tmp/foo/bar", + None, + ), + ), + 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_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 new file mode 100644 index 0000000000..f5f50be114 --- /dev/null +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap @@ -0,0 +1,37 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 1314 +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( + EditFile( + "/tmp/foo/bar", + None, + ), + ), + 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 d7df588901..514a80e421 100644 --- a/zellij-utils/src/kdl/kdl_layout_parser.rs +++ b/zellij-utils/src/kdl/kdl_layout_parser.rs @@ -48,6 +48,7 @@ impl<'a> KdlLayoutParser<'a> { || word == "tab_template" || word == "default_tab_template" || word == "command" + || word == "edit" || word == "plugin" || word == "children" || word == "tab" @@ -66,6 +67,7 @@ impl<'a> KdlLayoutParser<'a> { || property_name == "size" || property_name == "plugin" || property_name == "command" + || property_name == "edit" || property_name == "cwd" || property_name == "args" || property_name == "split_direction" @@ -192,6 +194,8 @@ 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 edit = kdl_get_string_property_or_child_value_with_error!(pane_node, "edit") + .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") @@ -200,23 +204,30 @@ impl<'a> KdlLayoutParser<'a> { self.parse_cwd(pane_node)? }; let args = self.parse_args(pane_node)?; - match (command, cwd, args, is_template) { - (None, Some(cwd), _, _) => Ok(Some(Run::Cwd(cwd))), - (None, _, Some(_args), false) => Err(ConfigError::new_kdl_error( + match (command, edit, cwd, args, is_template) { + (None, 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(), pane_node.span().len(), )), - (Some(command), cwd, args, _) => Ok(Some(Run::Command(RunCommand { + (Some(command), None, cwd, args, _) => Ok(Some(Run::Command(RunCommand { command, args: args.unwrap_or_else(|| vec![]), cwd, hold_on_close: true, }))), + (None, Some(edit), Some(cwd), _, _) => Ok(Some(Run::EditFile(cwd.join(edit), None))), + (None, Some(edit), None, _, _) => Ok(Some(Run::EditFile(edit, None))), + (Some(_command), Some(_edit), _, _, _) => Err(ConfigError::new_kdl_error( + "cannot have both a command and an edit instruction for the same pane".into(), + pane_node.span().offset(), + pane_node.span().len(), + )), _ => Ok(None), } } - fn parse_command_or_plugin_block( + fn parse_command_plugin_or_edit_block( &self, kdl_node: &KdlNode, ) -> Result, ConfigError> { @@ -224,7 +235,7 @@ impl<'a> KdlLayoutParser<'a> { if let Some(plugin_block) = kdl_get_child!(kdl_node, "plugin") { if run.is_some() { return Err(ConfigError::new_kdl_error( - "Cannot have both a command and a plugin block for a single pane".into(), + "Cannot have both a command/edit and a plugin block for a single pane".into(), plugin_block.span().offset(), plugin_block.span().len(), )); @@ -233,7 +244,7 @@ impl<'a> KdlLayoutParser<'a> { } Ok(run) } - fn parse_command_or_plugin_block_for_template( + fn parse_command_plugin_or_edit_block_for_template( &self, kdl_node: &KdlNode, ) -> Result, ConfigError> { @@ -241,7 +252,7 @@ impl<'a> KdlLayoutParser<'a> { if let Some(plugin_block) = kdl_get_child!(kdl_node, "plugin") { if run.is_some() { return Err(ConfigError::new_kdl_error( - "Cannot have both a command and a plugin block for a single pane".into(), + "Cannot have both a command/edit and a plugin block for a single pane".into(), plugin_block.span().offset(), plugin_block.span().len(), )); @@ -257,7 +268,7 @@ impl<'a> KdlLayoutParser<'a> { let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name") .map(|name| name.to_string()); let split_size = self.parse_split_size(kdl_node)?; - let run = self.parse_command_or_plugin_block(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, children) = match kdl_children_nodes!(kdl_node) { Some(children) => self.parse_child_pane_nodes_for_pane(&children)?, @@ -315,7 +326,7 @@ impl<'a> KdlLayoutParser<'a> { .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)?; + let run = self.parse_command_plugin_or_edit_block_for_template(kdl_node)?; self.assert_no_bare_args_in_pane_node_with_template( &run, &pane_template.run, @@ -370,6 +381,9 @@ impl<'a> KdlLayoutParser<'a> { run_command.cwd = Some(global_cwd.clone()); }, }, + Some(Run::EditFile(path_to_file, _line_number)) => { + *path_to_file = global_cwd.join(&path_to_file); + }, Some(Run::Cwd(pane_template_cwd)) => { *pane_template_cwd = global_cwd.join(&pane_template_cwd); }, @@ -406,7 +420,7 @@ impl<'a> KdlLayoutParser<'a> { 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_or_plugin_block(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)?, @@ -625,7 +639,7 @@ impl<'a> KdlLayoutParser<'a> { 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)? + .parse_command_plugin_or_edit_block(kdl_node)? .map(|r| match r { Run::Cwd(_) => false, _ => true, @@ -643,7 +657,7 @@ impl<'a> KdlLayoutParser<'a> { offending_nodes.push("focus"); } if has_non_cwd_run_prop { - offending_nodes.push("command/plugin"); + offending_nodes.push("command/edit/plugin"); } if has_cwd_prop { offending_nodes.push("cwd");