summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2022-10-14 15:08:32 +0200
committerGitHub <noreply@github.com>2022-10-14 15:08:32 +0200
commitd074bb1cda819bd660b76b3be54b4db10d0eeed4 (patch)
treefa85e65655909167cbd998b564e0a91353c3f5a3
parent5c43a59e00eb84bbba84b25cc2750c84359cd8a5 (diff)
feat(layouts): global cwd (#1798)
* feat(layouts): allow defining a global cwd * feat(layouts): allow passing global cwd from cli * style(fmt): rustfmt * fix(layouts): error on mixed cwd and pane children
-rw-r--r--zellij-server/src/os_input_output.rs8
-rw-r--r--zellij-server/src/pty.rs33
-rw-r--r--zellij-server/src/tab/unit/tab_integration_tests.rs2
-rw-r--r--zellij-server/src/unit/screen_tests.rs2
-rw-r--r--zellij-utils/src/cli.rs2
-rw-r--r--zellij-utils/src/input/actions.rs4
-rw-r--r--zellij-utils/src/input/layout.rs45
-rw-r--r--zellij-utils/src/input/unit/layout_test.rs322
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap57
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap57
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap57
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap57
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap43
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap43
-rw-r--r--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.snap43
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap43
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap43
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap36
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap43
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap43
-rw-r--r--zellij-utils/src/kdl/kdl_layout_parser.rs195
-rw-r--r--zellij-utils/src/kdl/mod.rs8
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_lay