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
This commit is contained in:
Aram Drevekenin 2022-10-14 15:08:32 +02:00 committed by GitHub
parent 5c43a59e00
commit d074bb1cda
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1061 additions and 151 deletions

View file

@ -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

View file

@ -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(),

View file

@ -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,

View file

@ -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,

View file

@ -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>,
},
}

View file

@ -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));

View file

@ -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,

View file

@ -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));
}

View file

@ -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,
},
),
}

View file

@ -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,
},
),
}

View file

@ -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,
},
),
}

View file

@ -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,
},
),
}

View file

@ -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,
},
),
}

View file

@ -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,
},
),
}

View file

@ -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,
},
),
}

View file

@ -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,
},
),
}

View file

@ -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,
},
),
}

View file

@ -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,
},
),
}

View file

@ -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,
},
),
}

View file

@ -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,
},
),
}

View file

@ -24,6 +24,7 @@ use std::vec::Vec;
use url::Url;
pub struct KdlLayoutParser<'a> {
global_cwd: Option<PathBuf>,
raw_layout: &'a str,
tab_templates: HashMap<String, (PaneLayout, KdlNode)>,
pane_templates: HashMap<String, (PaneLayout, KdlNode)>,
@ -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<PathBuf>) -> 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<Option<PathBuf>, 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<Option<Run>, 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<PaneLayout, ConfigError> {
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<PaneLayout, ConfigError> {
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<Run>,
) -> 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<SplitDirection, ConfigError> {
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<Run>,
pane_template_run: &Option<Run>,
args: &Option<Vec<String>>,
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 {

View file

@ -1266,8 +1266,12 @@ impl RunPlugin {
}
}
impl Layout {
pub fn from_kdl(raw_layout: &str, file_name: String) -> Result<Self, ConfigError> {
KdlLayoutParser::new(raw_layout).parse().map_err(|e| {
pub fn from_kdl(
raw_layout: &str,
file_name: String,
cwd: Option<PathBuf>,
) -> Result<Self, ConfigError> {
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) => {