summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2022-10-28 13:03:37 +0200
committerGitHub <noreply@github.com>2022-10-28 13:03:37 +0200
commitc97b972383d50ae6db750d4d7f2441232e41ba4c (patch)
tree448c6f3d626e7c405ec1e54c2be2ccf5a6bd2be7
parenteed9541a74879e1ec683beda13ccfda7e63bfa88 (diff)
feat(command-panes): optionally allow panes to be closed on exit (#1869)
* feat(cli): allow option to close command pane on exit * feat(layouts): allow option to close command panes on exit * style(fmt): rustfmt
-rw-r--r--src/main.rs2
-rw-r--r--zellij-server/src/unit/screen_tests.rs3
-rw-r--r--zellij-utils/src/cli.rs15
-rw-r--r--zellij-utils/src/input/actions.rs4
-rw-r--r--zellij-utils/src/input/layout.rs20
-rw-r--r--zellij-utils/src/input/unit/layout_test.rs75
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap60
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap60
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap41
-rw-r--r--zellij-utils/src/kdl/kdl_layout_parser.rs84
10 files changed, 342 insertions, 22 deletions
diff --git a/src/main.rs b/src/main.rs
index 3df61c42e..3ed592c53 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -25,6 +25,7 @@ fn main() {
cwd,
floating,
name,
+ close_on_exit,
})) = opts.command
{
let command_cli_action = CliAction::NewPane {
@@ -33,6 +34,7 @@ fn main() {
cwd,
floating,
name,
+ close_on_exit,
};
commands::send_action_to_session(command_cli_action, opts.session);
std::process::exit(0);
diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs
index 287ffab4d..299bab6a3 100644
--- a/zellij-server/src/unit/screen_tests.rs
+++ b/zellij-server/src/unit/screen_tests.rs
@@ -1821,6 +1821,7 @@ pub fn send_cli_new_pane_action_with_default_parameters() {
cwd: None,
floating: false,
name: None,
+ close_on_exit: false,
};
send_cli_action_to_server(
&session_metadata,
@@ -1859,6 +1860,7 @@ pub fn send_cli_new_pane_action_with_split_direction() {
cwd: None,
floating: false,
name: None,
+ close_on_exit: false,
};
send_cli_action_to_server(
&session_metadata,
@@ -1897,6 +1899,7 @@ pub fn send_cli_new_pane_action_with_command_and_cwd() {
cwd: Some("/some/folder".into()),
floating: false,
name: None,
+ close_on_exit: false,
};
send_cli_action_to_server(
&session_metadata,
diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs
index 0525c0380..6f0ac59a7 100644
--- a/zellij-utils/src/cli.rs
+++ b/zellij-utils/src/cli.rs
@@ -146,6 +146,10 @@ pub enum Sessions {
/// Name of the new pane
#[clap(short, long, value_parser)]
name: Option<String>,
+
+ /// Close the pane immediately when its command exits
+ #[clap(short, long, value_parser, default_value("false"), takes_value(false))]
+ close_on_exit: bool,
},
/// Edit file with default $EDITOR / $VISUAL
#[clap(visible_alias = "e")]
@@ -246,6 +250,17 @@ pub enum CliAction {
/// Name of the new pane
#[clap(short, long, value_parser)]
name: Option<String>,
+
+ /// Close the pane immediately when its command exits
+ #[clap(
+ short,
+ long,
+ value_parser,
+ default_value("false"),
+ takes_value(false),
+ requires("command")
+ )]
+ close_on_exit: bool,
},
/// Open the specified file in a new zellij pane with your default EDITOR
Edit {
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index a9ab37053..51ead67b3 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -258,17 +258,19 @@ impl Action {
cwd,
floating,
name,
+ close_on_exit,
} => {
if !command.is_empty() {
let mut command = command.clone();
let (command, args) = (PathBuf::from(command.remove(0)), command);
let cwd = cwd.or_else(|| std::env::current_dir().ok());
+ let hold_on_close = !close_on_exit;
let run_command_action = RunCommandAction {
command,
args,
cwd,
direction,
- hold_on_close: true,
+ hold_on_close,
};
if floating {
Ok(vec![Action::NewFloatingPane(
diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs
index 8d0dc104d..fa50237a9 100644
--- a/zellij-utils/src/input/layout.rs
+++ b/zellij-utils/src/input/layout.rs
@@ -130,6 +130,26 @@ impl Run {
_ => {}, // plugins aren't yet supported
}
}
+ pub fn add_args(&mut self, args: Option<Vec<String>>) {
+ // overrides the args of a Run::Command if they are Some
+ // and not empty
+ if let Some(args) = args {
+ if let Run::Command(run_command) = self {
+ if !args.is_empty() {
+ run_command.args = args.clone();
+ }
+ }
+ }
+ }
+ pub fn add_close_on_exit(&mut self, close_on_exit: Option<bool>) {
+ // overrides the args of a Run::Command if they are Some
+ // and not empty
+ if let Some(close_on_exit) = close_on_exit {
+ if let Run::Command(run_command) = self {
+ run_command.hold_on_close = !close_on_exit;
+ }
+ }
+ }
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs
index 1addb9382..9614eef46 100644
--- a/zellij-utils/src/input/unit/layout_test.rs
+++ b/zellij-utils/src/input/unit/layout_test.rs
@@ -269,6 +269,19 @@ fn layout_with_command_panes_and_cwd_and_args() {
}
#[test]
+fn layout_with_command_panes_and_close_on_exit() {
+ let kdl_layout = r#"
+ layout {
+ pane command="htop" {
+ close_on_exit true
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap();
+ assert_snapshot!(format!("{:#?}", layout));
+}
+
+#[test]
fn layout_with_plugin_panes() {
let kdl_layout = r#"
layout {
@@ -1034,6 +1047,24 @@ fn args_override_args_in_template() {
}
#[test]
+fn close_on_exit_overrides_close_on_exit_in_template() {
+ let kdl_layout = r#"
+ layout {
+ pane_template name="tail" {
+ command "tail"
+ close_on_exit false
+ }
+ tail
+ tail {
+ close_on_exit true
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap();
+ assert_snapshot!(format!("{:#?}", layout));
+}
+
+#[test]
fn args_added_to_args_in_template() {
let kdl_layout = r#"
layout {
@@ -1051,6 +1082,23 @@ fn args_added_to_args_in_template() {
}
#[test]
+fn close_on_exit_added_to_close_on_exit_in_template() {
+ let kdl_layout = r#"
+ layout {
+ pane_template name="tail" {
+ command "tail"
+ }
+ tail
+ tail {
+ close_on_exit true
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap();
+ assert_snapshot!(format!("{:#?}", layout));
+}
+
+#[test]
fn cwd_override_cwd_in_template() {
let kdl_layout = r#"
layout {
@@ -1126,6 +1174,19 @@ fn error_on_bare_args_without_command() {
}
#[test]
+fn error_on_bare_close_on_exit_without_command() {
+ let kdl_layout = r#"
+ layout {
+ pane {
+ close_on_exit true
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None);
+ assert!(layout.is_err(), "error provided");
+}
+
+#[test]
fn error_on_bare_args_in_template_without_command() {
let kdl_layout = r#"
layout {
@@ -1140,6 +1201,20 @@ fn error_on_bare_args_in_template_without_command() {
}
#[test]
+fn error_on_bare_close_on_exit_in_template_without_command() {
+ let kdl_layout = r#"
+ layout {
+ pane_template name="my_template"
+ my_template {
+ close_on_exit true
+ }
+ }
+ "#;
+ 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 {
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
new file mode 100644
index 000000000..da83cc62c
--- /dev/null
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap
@@ -0,0 +1,60 @@
+---
+source: zellij-utils/src/input/./unit/layout_test.rs
+assertion_line: 1098
+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: None,
+ hold_on_close: true,
+ },
+ ),
+ ),
+ 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: None,
+ hold_on_close: false,
+ },
+ ),
+ ),
+ 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__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
new file mode 100644
index 000000000..3cb80cf74
--- /dev/null
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap
@@ -0,0 +1,60 @@
+---
+source: zellij-utils/src/input/./unit/layout_test.rs
+assertion_line: 1064
+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: None,
+ hold_on_close: true,
+ },
+ ),
+ ),
+ 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: None,
+ hold_on_close: false,
+ },
+ ),
+ ),
+ 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__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
new file mode 100644
index 000000000..69e3d7f1f
--- /dev/null
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap
@@ -0,0 +1,41 @@
+---
+source: zellij-utils/src/input/./unit/layout_test.rs
+assertion_line: 281
+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: "htop",
+ args: [],
+ cwd: None,
+ hold_on_close: false,
+ },
+ ),
+ ),
+ 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 ba3e1f204..b92c6a1b2 100644
--- a/zellij-utils/src/kdl/kdl_layout_parser.rs
+++ b/zellij-utils/src/kdl/kdl_layout_parser.rs
@@ -53,6 +53,7 @@ impl<'a> KdlLayoutParser<'a> {
|| word == "children"
|| word == "tab"
|| word == "args"
+ || word == "close_on_exit"
|| word == "borderless"
|| word == "focus"
|| word == "name"
@@ -70,6 +71,7 @@ impl<'a> KdlLayoutParser<'a> {
|| property_name == "edit"
|| property_name == "cwd"
|| property_name == "args"
+ || property_name == "close_on_exit"
|| property_name == "split_direction"
|| property_name == "pane"
|| property_name == "children"
@@ -237,22 +239,28 @@ impl<'a> KdlLayoutParser<'a> {
.map(|c| PathBuf::from(c));
let cwd = self.parse_cwd(pane_node)?;
let args = self.parse_args(pane_node)?;
- match (command, edit, cwd, args, is_template) {
- (None, None, Some(cwd), _, _) => Ok(Some(Run::Cwd(cwd))),
- (None, _, _, Some(_args), false) => Err(ConfigError::new_layout_kdl_error(
- "args can only be set if a command was specified".into(),
- pane_node.span().offset(),
- pane_node.span().len(),
- )),
- (Some(command), None, cwd, args, _) => Ok(Some(Run::Command(RunCommand {
+ let close_on_exit =
+ kdl_get_bool_property_or_child_value_with_error!(pane_node, "close_on_exit");
+ if !is_template {
+ self.assert_no_bare_attributes_in_pane_node(
+ &command,
+ &args,
+ &close_on_exit,
+ pane_node,
+ )?;
+ }
+ let hold_on_close = close_on_exit.map(|c| !c).unwrap_or(true);
+ match (command, edit, cwd) {
+ (None, None, Some(cwd)) => Ok(Some(Run::Cwd(cwd))),
+ (Some(command), None, cwd) => Ok(Some(Run::Command(RunCommand {
command,
args: args.unwrap_or_else(|| vec![]),
cwd,
- hold_on_close: true,
+ hold_on_close,
}))),
- (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_layout_kdl_error(
+ (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_layout_kdl_error(
"cannot have both a command and an edit instruction for the same pane".into(),
pane_node.span().offset(),
pane_node.span().len(),
@@ -370,12 +378,15 @@ impl<'a> KdlLayoutParser<'a> {
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 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_args_in_pane_node_with_template(
+ self.assert_no_bare_attributes_in_pane_node_with_template(
&run,
&pane_template.run,
&args,
+ &close_on_exit,
kdl_node,
)?;
self.insert_children_to_pane_template(
@@ -384,13 +395,12 @@ impl<'a> KdlLayoutParser<'a> {
pane_template_kdl_node,
)?;
pane_template.run = Run::merge(&pane_template.run, &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(pane_template_run_command) = pane_template.run.as_mut() {
+ // we need to do this because panes consuming a pane_templates
+ // 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);
+ };
if let Some(borderless) = borderless {
pane_template.borderless = borderless;
}
@@ -584,11 +594,12 @@ impl<'a> KdlLayoutParser<'a> {
}
false
}
- fn assert_no_bare_args_in_pane_node_with_template(
+ fn assert_no_bare_attributes_in_pane_node_with_template(
&self,
pane_run: &Option<Run>,
pane_template_run: &Option<Run>,
args: &Option<Vec<String>>,
+ close_on_exit: &Option<bool>,
pane_node: &KdlNode,
) -> Result<(), ConfigError> {
if let (None, None, true) = (pane_run, pane_template_run, args.is_some()) {
@@ -597,6 +608,37 @@ impl<'a> KdlLayoutParser<'a> {
pane_node
));
}
+ if let (None, None, true) = (pane_run, pane_template_run, close_on_exit.is_some()) {
+ return Err(kdl_parsing_error!(
+ format!("close_on_exit can only be specified if a command was specified either in the pane_template or in the pane"),
+ pane_node
+ ));
+ }
+ Ok(())
+ }
+ fn assert_no_bare_attributes_in_pane_node(
+ &self,
+ command: &Option<PathBuf>,
+ args: &Option<Vec<String>>,
+ close_on_exit: &Option<bool>,
+ pane_node: &KdlNode,
+ ) -> Result<(), ConfigError> {
+ if command.is_none() {
+ if close_on_exit.is_some() {
+ return Err(ConfigError::new_layout_kdl_error(
+ "close_on_exit can only be set if a command was specified".into(),
+ pane_node.span().offset(),
+ pane_node.span().len(),
+ ));
+ }
+ if args.is_some() {
+ return Err(ConfigError::new_layout_kdl_error(
+ "args can only be set if a command was specified".into(),
+ pane_node.span().offset(),
+ pane_node.span().len(),
+ ));
+ }
+ }
Ok(())
}
fn assert_one_children_block(