aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2022-10-13 13:55:16 +0200
committerGitHub <noreply@github.com>2022-10-13 13:55:16 +0200
commitbece86b95c63d645368d7c82e56b5ce553f1ea1e (patch)
tree2c50fbf31f2837228f7a71dc02aec19a64253988
parentzellij/commands: Prevent recursive sessions (#1766) (diff)
downloadsl0ppij-bece86b95c63d645368d7c82e56b5ce553f1ea1e.tar.gz
sl0ppij-bece86b95c63d645368d7c82e56b5ce553f1ea1e.tar.xz
sl0ppij-bece86b95c63d645368d7c82e56b5ce553f1ea1e.zip
fix(layouts): various kdl layout issues and features (#1797)
* fix(layouts): error on non-bare children node * refactor(layout): consolidate split direction parsing * refactor(layout): remove unused import * fix(layouts): log error when there is no room for layout * fix(layout): error on size 0 * feat(layouts): allow pane templates to override template command attributes * style(fmt): rustfmt
-rw-r--r--zellij-server/src/tab/mod.rs209
-rw-r--r--zellij-utils/src/input/layout.rs13
-rw-r--r--zellij-utils/src/input/unit/layout_test.rs139
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap63
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap66
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap6
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap62
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap64
-rw-r--r--zellij-utils/src/kdl/kdl_layout_parser.rs226
-rw-r--r--zellij-utils/src/pane_size.rs3
10 files changed, 676 insertions, 175 deletions
diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs
index 40879d37..064141f7 100644
--- a/zellij-server/src/tab/mod.rs
+++ b/zellij-server/src/tab/mod.rs
@@ -492,109 +492,124 @@ impl Tab {
free_space.cols.set_inner(viewport_cols);
free_space.rows.set_inner(viewport_rows);
- let positions_in_layout = layout.position_panes_in_space(&free_space);
+ match layout.position_panes_in_space(&free_space) {
+ Ok(positions_in_layout) => {
+ let positions_and_size = positions_in_layout.iter();
+ let mut new_ids = new_ids.iter();
- let positions_and_size = positions_in_layout.iter();
- let mut new_ids = new_ids.iter();
-
- let mut focus_pane_id: Option<PaneId> = None;
- let mut set_focus_pane_id = |layout: &PaneLayout, pane_id: PaneId| {
- if layout.focus.unwrap_or(false) && focus_pane_id.is_none() {
- focus_pane_id = Some(pane_id);
- }
- };
+ let mut focus_pane_id: Option<PaneId> = None;
+ let mut set_focus_pane_id = |layout: &PaneLayout, pane_id: PaneId| {
+ if layout.focus.unwrap_or(false) && focus_pane_id.is_none() {
+ focus_pane_id = Some(pane_id);
+ }
+ };
- for (layout, position_and_size) in positions_and_size {
- // A plugin pane
- if let Some(Run::Plugin(run)) = layout.run.clone() {
- let (pid_tx, pid_rx) = channel();
- let pane_title = run.location.to_string();
- self.senders
- .send_to_plugin(PluginInstruction::Load(pid_tx, run, tab_index, client_id))
- .with_context(err_context)?;
- let pid = pid_rx.recv().with_context(err_context)?;
- let mut new_plugin = PluginPane::new(
- pid,
- *position_and_size,
+ for (layout, position_and_size) in positions_and_size {
+ // A plugin pane
+ if let Some(Run::Plugin(run)) = layout.run.clone() {
+ let (pid_tx, pid_rx) = channel();
+ let pane_title = run.location.to_string();
+ self.senders
+ .send_to_plugin(PluginInstruction::Load(
+ pid_tx, run, tab_index, client_id,
+ ))
+ .with_context(err_context)?;
+ let pid = pid_rx.recv().with_context(err_context)?;
+ let mut new_plugin = PluginPane::new(
+ pid,
+ *position_and_size,
+ self.senders
+ .to_plugin
+ .as_ref()
+ .with_context(err_context)?
+ .clone(),
+ pane_title,
+ layout.name.clone().unwrap_or_default(),
+ );
+ new_plugin.set_borderless(layout.borderless);
+ self.tiled_panes
+ .add_pane_with_existing_geom(PaneId::Plugin(pid), Box::new(new_plugin));
+ set_focus_pane_id(layout, PaneId::Plugin(pid));
+ } else {
+ // there are still panes left to fill, use the pids we received in this method
+ if let Some(pid) = new_ids.next() {
+ let next_terminal_position = self.get_next_terminal_position();
+ let initial_title = match &layout.run {
+ Some(Run::Command(run_command)) => Some(run_command.to_string()),
+ _ => None,
+ };
+ let mut new_pane = TerminalPane::new(
+ *pid,
+ *position_and_size,
+ self.style,
+ next_terminal_position,
+ layout.name.clone().unwrap_or_default(),
+ self.link_handler.clone(),
+ self.character_cell_size.clone(),
+ self.sixel_image_store.clone(),
+ self.terminal_emulator_colors.clone(),
+ self.terminal_emulator_color_codes.clone(),
+ initial_title,
+ );
+ new_pane.set_borderless(layout.borderless);
+ self.tiled_panes.add_pane_with_existing_geom(
+ PaneId::Terminal(*pid),
+ Box::new(new_pane),
+ );
+ set_focus_pane_id(layout, PaneId::Terminal(*pid));
+ }
+ }
+ }
+ for unused_pid in new_ids {
+ // this is a bit of a hack and happens because we don't have any central location that
+ // can query the screen as to how many panes it needs to create a layout
+ // fixing this will require a bit of an architecture change
self.senders
- .to_plugin
- .as_ref()
- .with_context(err_context)?
- .clone(),
- pane_title,
- layout.name.clone().unwrap_or_default(),
- );
- new_plugin.set_borderless(layout.borderless);
- self.tiled_panes
- .add_pane_with_existing_geom(PaneId::Plugin(pid), Box::new(new_plugin));
- set_focus_pane_id(layout, PaneId::Plugin(pid));
- } else {
- // there are still panes left to fill, use the pids we received in this method
- if let Some(pid) = new_ids.next() {
- let next_terminal_position = self.get_next_terminal_position();
- let initial_title = match &layout.run {
- Some(Run::Command(run_command)) => Some(run_command.to_string()),
- _ => None,
- };
- let mut new_pane = TerminalPane::new(
- *pid,
- *position_and_size,
- self.style,
- next_terminal_position,
- layout.name.clone().unwrap_or_default(),
- self.link_handler.clone(),
- self.character_cell_size.clone(),
- self.sixel_image_store.clone(),
- self.terminal_emulator_colors.clone(),
- self.terminal_emulator_color_codes.clone(),
- initial_title,
- );
- new_pane.set_borderless(layout.borderless);
- self.tiled_panes
- .add_pane_with_existing_geom(PaneId::Terminal(*pid), Box::new(new_pane));
- set_focus_pane_id(layout, PaneId::Terminal(*pid));
+ .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid)))
+ .with_context(err_context)?;
}
- }
- }
- for unused_pid in new_ids {
- // this is a bit of a hack and happens because we don't have any central location that
- // can query the screen as to how many panes it needs to create a layout
- // fixing this will require a bit of an architecture change
- self.senders
- .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid)))
- .with_context(err_context)?;
- }
- // FIXME: This is another hack to crop the viewport to fixed-size panes. Once you can have
- // non-fixed panes that are part of the viewport, get rid of this!
- let display_area = {
- let display_area = self.display_area.borrow();
- *display_area
- };
- self.resize_whole_tab(display_area);
- let boundary_geoms = self.tiled_panes.fixed_pane_geoms();
- for geom in boundary_geoms {
- self.offset_viewport(&geom)
- }
- self.tiled_panes.set_pane_frames(self.draw_pane_frames);
- self.should_clear_display_before_rendering = true;
+ // FIXME: This is another hack to crop the viewport to fixed-size panes. Once you can have
+ // non-fixed panes that are part of the viewport, get rid of this!
+ let display_area = {
+ let display_area = self.display_area.borrow();
+ *display_area
+ };
+ self.resize_whole_tab(display_area);
+ let boundary_geoms = self.tiled_panes.fixed_pane_geoms();
+ for geom in boundary_geoms {
+ self.offset_viewport(&geom)
+ }
+ self.tiled_panes.set_pane_frames(self.draw_pane_frames);
+ self.should_clear_display_before_rendering = true;
- if let Some(pane_id) = focus_pane_id {
- self.focus_pane_id = Some(pane_id);
- self.tiled_panes.focus_pane(pane_id, client_id);
- } else {
- // This is the end of the nasty viewport hack...
- let next_selectable_pane_id = self.tiled_panes.first_selectable_pane_id();
- match next_selectable_pane_id {
- Some(active_pane_id) => {
- self.tiled_panes.focus_pane(active_pane_id, client_id);
- },
- None => {
- // this is very likely a configuration error (layout with no selectable panes)
- self.tiled_panes.clear_active_panes();
- },
- }
+ if let Some(pane_id) = focus_pane_id {
+ self.focus_pane_id = Some(pane_id);
+ self.tiled_panes.focus_pane(pane_id, client_id);
+ } else {
+ // This is the end of the nasty viewport hack...
+ let next_selectable_pane_id = self.tiled_panes.first_selectable_pane_id();
+ match next_selectable_pane_id {
+ Some(active_pane_id) => {
+ self.tiled_panes.focus_pane(active_pane_id, client_id);
+ },
+ None => {
+ // this is very likely a configuration error (layout with no selectable panes)
+ self.tiled_panes.clear_active_panes();
+ },
+ }
+ }
+ Ok(())
+ },
+ Err(e) => {
+ for unused_pid in new_ids {
+ self.senders
+ .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(unused_pid)))
+ .with_context(err_context)?;
+ }
+ log::error!("{}", e); // TODO: propagate this to the user
+ Ok(())
+ },
}
- Ok(())
}
pub fn update_input_modes(&mut self) -> Result<()> {
// this updates all plugins with the client's input mode
diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs
index eea80859..f823ed40 100644
--- a/zellij-utils/src/input/layout.rs
+++ b/zellij-utils/src/input/layout.rs
@@ -156,8 +156,17 @@ impl PaneLayout {
}
count
}
- pub fn position_panes_in_space(&self, space: &PaneGeom) -> Vec<(PaneLayout, PaneGeom)> {
- split_space(space, self, space)
+ pub fn position_panes_in_space(
+ &self,
+ space: &PaneGeom,
+ ) -> Result<Vec<(PaneLayout, PaneGeom)>, &'static str> {
+ let layouts = split_space(space, self, space);
+ for (_pane_layout, pane_geom) in layouts.iter() {
+ if !pane_geom.is_at_least_minimum_size() {
+ return Err("No room on screen for this layout!");
+ }
+ }
+ Ok(layouts)
}
pub fn extract_run_instructions(&self) -> Vec<Option<Run>> {
let mut run_instructions = vec![];
diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs
index ffd35f20..ce077a42 100644
--- a/zellij-utils/src/input/unit/layout_test.rs
+++ b/zellij-utils/src/input/unit/layout_test.rs
@@ -700,7 +700,7 @@ fn children_not_as_first_child_of_pane_template() {
}
pane
}
- horizontal-with-vertical-top name="my tab" {
+ horizontal-with-vertical-top name="my pane" {
pane
pane
}
@@ -1014,3 +1014,140 @@ fn error_on_more_than_one_focused_tab() {
let layout_error = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap_err();
assert_snapshot!(format!("{:?}", layout_error));
}
+
+#[test]
+fn args_override_args_in_template() {
+ let kdl_layout = r#"
+ layout {
+ pane_template name="tail" {
+ command "tail"
+ args "-f" "/tmp/foo"
+ }
+ tail
+ tail {
+ args "-f" "/tmp/bar"
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap();
+ assert_snapshot!(format!("{:#?}", layout));
+}
+
+#[test]
+fn args_added_to_args_in_template() {
+ let kdl_layout = r#"
+ layout {
+ pane_template name="tail" {
+ command "tail"
+ }
+ tail
+ tail {
+ args "-f" "/tmp/bar"
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap();
+ assert_snapshot!(format!("{:#?}", layout));
+}
+
+#[test]
+fn cwd_override_cwd_in_template() {
+ let kdl_layout = r#"
+ layout {
+ pane_template name="tail" {
+ command "tail"
+ cwd "/tmp"
+ }
+ tail
+ tail {
+ cwd "/"
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap();
+ assert_snapshot!(format!("{:#?}", layout));
+}
+
+#[test]
+fn cwd_added_to_cwd_in_template() {
+ let kdl_layout = r#"
+ layout {
+ pane_template name="tail" {
+ command "tail"
+ }
+ tail
+ tail {
+ cwd "/home"
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap();
+ assert_snapshot!(format!("{:#?}", layout));
+}
+
+#[test]
+fn error_on_mixed_command_and_child_panes() {
+ let kdl_layout = r#"
+ layout {
+ pane command="tail" {
+ pane
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into());
+ assert!(layout.is_err(), "error provided");
+}
+
+#[test]
+fn error_on_bare_args_without_command() {
+ let kdl_layout = r#"
+ layout {
+ pane {
+ args "-f"
+ }
+ }
+ "#;
+ 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());
+ assert!(layout.is_err(), "error provided");
+}
+
+#[test]
+fn error_on_bare_args_in_template_without_command() {
+ let kdl_layout = r#"
+ layout {
+ pane_template name="my_template"
+ my_template {
+ args "--help"
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into());
+ assert!(layout.is_err(), "error provided");
+}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap
new file mode 100644
index 00000000..6ff62582
--- /dev/null
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap
@@ -0,0 +1,63 @@
+---
+source: zellij-utils/src/input/./unit/layout_test.rs
+assertion_line: 1050
+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: [
+ "-f",
+ "/tmp/bar",
+ ],
+ cwd: None,
+ 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,
+ },
+ ),
+}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap
new file mode 100644
index 00000000..4a449d35
--- /dev/null
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap
@@ -0,0 +1,66 @@
+---
+source: zellij-utils/src/input/./unit/layout_test.rs
+assertion_line: 1033
+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: [
+ "-f",
+ "/tmp/foo",
+ ],
+ 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: [
+ "-f",
+ "/tmp/bar",
+ ],
+ cwd: None,
+ 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,
+ },
+ ),
+}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap
index f82879d2..f7f904f6 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 848
+assertion_line: 711
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -13,7 +13,9 @@ Layout {
children: [
PaneLayout {
children_split_direction: Horizontal,
- name: None,
+ name: Some(
+ "my pane",
+ ),
children: [
PaneLayout {
children_split_direction: Vertical,
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap
new file mode 100644
index 00000000..3c26fbdd
--- /dev/null
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap
@@ -0,0 +1,62 @@
+---
+source: zellij-utils/src/input/./unit/layout_test.rs
+assertion_line: 1085
+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: Some(
+ "/home",
+ ),
+ 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,
+ },
+ ),
+}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap
new file mode 100644
index 00000000..fd1bc15a
--- /dev/null
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap
@@ -0,0 +1,64 @@
+---
+source: zellij-utils/src/input/./unit/layout_test.rs
+assertion_line: 1068
+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",
+ ),
+ 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: Some(
+ "/",
+ ),
+ 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,
+ },
+ ),
+}
diff --git a/zellij-utils/src/kdl/kdl_layout_parser.rs b/zellij-utils/src/kdl/kdl_layout_parser.rs
index 7261fecb..6bf2d372 100644
--- a/zellij-utils/src/kdl/kdl_layout_parser.rs
+++ b/zellij-utils/src/kdl/kdl_layout_parser.rs
@@ -12,7 +12,7 @@ use std::str::FromStr;
use crate::{
kdl_child_with_name, kdl_children_nodes, kdl_get_bool_property_or_child_value,
kdl_get_bool_property_or_child_value_with_error, kdl_get_child,
- kdl_get_int_property_or_child_value, kdl_get_property_or_child, kdl_get_string_entry,
+ kdl_get_int_property_or_child_value, kdl_get_property_or_child,
kdl_get_string_property_or_child_value, kdl_get_string_property_or_child_value_with_error,
kdl_name, kdl_parsing_error, kdl_property_names, kdl_property_or_child_value_node,
kdl_string_arguments,
@@ -92,8 +92,22 @@ impl<'a> KdlLayoutParser<'a> {
}
fn parse_split_size(&self, kdl_node: &KdlNode) -> Result<Option<SplitSize>, ConfigError> {
if let Some(size) = kdl_get_string_property_or_child_value!(kdl_node, "size") {
- Ok(Some(SplitSize::from_str(size)?))
+ match SplitSize::from_str(size) {
+ Ok(size) => Ok(Some(size)),
+ Err(_e) => Err(kdl_parsing_error!(
+ format!(
+ "size should be a fixed number (eg. 1) or a quoted percent (eg. \"50%\")"
+ ),
+ kdl_node
+ )),
+ }
} else if let Some(size) = kdl_get_int_property_or_child_value!(kdl_node, "size") {
+ if size == 0 {
+ return Err(kdl_parsing_error!(
+ format!("size should be greater than 0"),
+ kdl_node
+ ));
+ }
Ok(Some(SplitSize::Fixed(size as usize)))
} else if let Some(node) = kdl_property_or_child_value_node!(kdl_node, "size") {
Err(kdl_parsing_error!(
@@ -143,37 +157,44 @@ impl<'a> KdlLayoutParser<'a> {
location,
})))
}
- fn parse_pane_command(&self, pane_node: &KdlNode) -> 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 args = match kdl_get_child!(pane_node, "args") {
+ fn parse_args(&self, pane_node: &KdlNode) -> Result<Option<Vec<String>>, ConfigError> {
+ match kdl_get_child!(pane_node, "args") {
Some(kdl_args) => {
if kdl_args.entries().is_empty() {
return Err(kdl_parsing_error!(format!("args cannot be empty and should contain one or more command arguments (eg. args \"-h\" \"-v\")"), kdl_args));
}
- Some(
+ Ok(Some(
kdl_string_arguments!(kdl_args)
.iter()
.map(|s| String::from(*s))
.collect(),
- )
+ ))
},
- None => None,
- };
- match (command, cwd, args) {
- (None, Some(_cwd), _) => Err(ConfigError::new_kdl_error(
+ None => Ok(None),
+ }
+ }
+ fn parse_pane_command(
+ &self,
+ pane_node: &KdlNode,
+ is_template: bool,
+ ) -> 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 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(_args)) => Err(ConfigError::new_kdl_error(
+ (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), cwd, args, _) => Ok(Some(Run::Command(RunCommand {
command,
args: args.unwrap_or_else(|| vec![]),
cwd,
@@ -186,7 +207,24 @@ impl<'a> KdlLayoutParser<'a> {
&self,
kdl_node: &KdlNode,
) -> Result<Option<Run>, ConfigError> {
- let mut run = self.parse_pane_command(kdl_node)?;
+ let mut run = self.parse_pane_command(kdl_node, false)?;
+ 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(),
+ plugin_block.span().offset(),
+ plugin_block.span().len(),
+ ));
+ }
+ run = self.parse_plugin_block(plugin_block)?;
+ }
+ Ok(run)
+ }
+ fn parse_command_or_plugin_block_for_template(
+ &self,
+ kdl_node: &KdlNode,
+ ) -> Result<Option<Run>, ConfigError> {
+ let mut run = self.parse_pane_command(kdl_node, true)?;
if let Some(plugin_block) = kdl_get_child!(kdl_node, "plugin") {
if run.is_some() {
return Err(ConfigError::new_kdl_error(
@@ -235,56 +273,90 @@ impl<'a> KdlLayoutParser<'a> {
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(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
+ ));
+ }
let children_split_direction = self.parse_split_direction(kdl_node)?;
- match kdl_children_nodes!(kdl_node) {
- Some(children) => {
- let child_panes = self.parse_child_pane_nodes_for_tab(children)?;
- let child_panes_layout = PaneLayout {
- children_split_direction,
- children: child_panes,
- ..Default::default()
- };
- self.assert_one_children_block(&pane_layout, pane_layout_kdl_node)?;
- self.insert_layout_children_or_error(
- &mut pane_layout,
- child_panes_layout,
- pane_layout_kdl_node,
- )?;
- },
- None => {
- if let Some(borderless) = borderless {
- pane_layout.borderless = borderless;
- }
- if let Some(focus) = focus {
- pane_layout.focus = Some(focus);
- }
- if let Some(name) = name {
- pane_layout.name = Some(name);
- }
- if let Some(split_size) = split_size {
- pane_layout.split_size = Some(split_size);
- }
- if let Some(run) = run {
- pane_layout.run = Some(run);
- }
- if let Some(index_of_children) = pane_layout.external_children_index {
- pane_layout
- .children
- .insert(index_of_children, PaneLayout::default());
- }
- },
+
+ 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![]),
+ };
+ if pane_parts.len() > 0 {
+ let child_panes_layout = PaneLayout {
+ children_split_direction,
+ children: pane_parts,
+ external_children_index,
+ ..Default::default()
+ };
+ self.assert_one_children_block(&pane_layout, pane_layout_kdl_node)?;
+ self.insert_layout_children_or_error(
+ &mut pane_layout,
+ child_panes_layout,
+ pane_layout_kdl_node,
+ )?;
+ }
+ if let Some(borderless) = borderless {
+ pane_layout.borderless = borderless;
+ }
+ if let Some(focus) = focus {
+ pane_layout.focus = Some(focus);
+ }
+ if let Some(name) = name {
+ pane_layout.name = Some(name);
+ }
+ if let Some(split_size) = split_size {
+ pane_layout.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
+ .children
+ .insert(index_of_children, PaneLayout::default());
}
pane_layout.external_children_index = None;
Ok(pane_layout)
}
fn parse_split_direction(&self, kdl_node: &KdlNode) -> Result<SplitDirection, ConfigError> {
- Ok(match kdl_get_string_entry!(kdl_node, "split_direction") {
- Some(direction) => SplitDirection::from_str(direction)?,
- None => SplitDirection::default(),
- })
+ match kdl_get_string_property_or_child_value_with_error!(kdl_node, "split_direction") {
+ Some(direction) => match SplitDirection::from_str(direction) {
+ Ok(split_direction) => Ok(split_direction),
+ Err(_e) => Err(kdl_parsing_error!(
+ format!(
+ "split_direction should be either \"horizontal\" or \"vertical\" found: {}",
+ direction
+ ),
+ kdl_node
+ )),
+ },
+ None => Ok(SplitDirection::default()),
+ }
}
fn parse_pane_template_node(&mut self, kdl_node: &KdlNode) -> Result<(), ConfigError> {
self.assert_valid_pane_properties(kdl_node)?;
@@ -333,10 +405,7 @@ impl<'a> KdlLayoutParser<'a> {
let tab_name =
kdl_get_string_property_or_child_value!(kdl_node, "name").map(|s| s.to_string());
let is_focused = kdl_get_bool_property_or_child_value!(kdl_node, "focus").unwrap_or(false);
- let children_split_direction = match kdl_get_string_entry!(kdl_node, "split_direction") {
- Some(direction) => SplitDirection::from_str(direction)?,
- None => SplitDirection::default(),
- };
+ let children_split_direction = self.parse_split_direction(kdl_node)?;
let children = match kdl_children_nodes!(kdl_node) {
Some(children) => self.parse_child_pane_nodes_for_tab(children)?,
None => vec![],
@@ -397,6 +466,15 @@ impl<'a> KdlLayoutParser<'a> {
if kdl_name!(child) == "pane" {
nodes.push(self.parse_pane_node(child)?);
} else if kdl_name!(child) == "children" {
+ let node_has_child_nodes = child.children().map(|c| !c.is_empty()).unwrap_or(false);
+ let node_has_entries = !child.entries().is_empty();
+ if node_has_child_nodes || node_has_entries {
+ return Err(ConfigError::new_kdl_error(
+ format!("The `children` node must be bare. All properties should be places on the node consuming this template."),
+ child.span().offset(),
+ child.span().len(),
+ ));
+ }
external_children_index = Some(i);
} else if let Some((pane_template, pane_template_kdl_node)) =
self.pane_templates.get(kdl_name!(child)).cloned()
@@ -496,7 +574,6 @@ impl<'a> KdlLayoutParser<'a> {
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_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)
{
@@ -549,10 +626,7 @@ impl<'a> KdlLayoutParser<'a> {
let tab_name =
kdl_get_string_property_or_child_value!(kdl_node, "name").map(|s| s.to_string());
let is_focused = kdl_get_bool_property_or_child_value!(kdl_node, "focus").unwrap_or(false);
- let children_split_direction = match kdl_get_string_entry!(kdl_node, "split_direction") {
- Some(direction) => SplitDirection::from_str(direction)?,
- None => SplitDirection::default(),
- };
+ let children_split_direction = self.parse_split_direction(kdl_node)?;
match kdl_children_nodes!(kdl_node) {
Some(children) => {
let child_panes = self.parse_child_pane_nodes_for_tab(children)?;
@@ -618,11 +692,7 @@ impl<'a> KdlLayoutParser<'a> {
}
fn parse_tab_template_node(&self, kdl_node: &KdlNode) -> Result<PaneLayout, ConfigError> {
self.assert_valid_tab_properties(kdl_node)?;
- let children_split_direction =
- match kdl_get_string_property_or_child_value_with_error!(kdl_node, "split_direction") {
- Some(direction) => SplitDirection::from_str(direction)?,
- None => SplitDirection::default(),
- };
+ let children_split_direction = self.parse_split_direction(kdl_node)?;
let mut tab_children = vec![];
let mut external_children_index = None;
if let Some(children) = kdl_children_nodes!(kdl_node) {
@@ -630,6 +700,16 @@ impl<'a> KdlLayoutParser<'a> {
if kdl_name!(child) == "pane" {
tab_children.push(self.parse_pane_node(child)?);
} else if kdl_name!(child) == "children" {
+ let node_has_child_nodes =
+ child.children().map(|c| !c.is_empty()).unwrap_or(false);
+ let node_has_entries = !child.entries().is_empty();
+ if node_has_child_nodes || node_has_entries {
+ return Err(ConfigError::new_kdl_error(
+ format!("The `children` node must be bare. All properties should be places on the node consuming this template."),
+ child.span().offset(),
+ child.span().len(),
+ ));
+ }
external_children_index = Some(i);
} else if let Some((pane_template, pane_template_kdl_node)) =
self.pane_templates.get(kdl_name!(child)).cloned()
diff --git a/zellij-utils/src/pane_size.rs b/zellij-utils/src/pane_size.rs
index 007cf752..908a4dd8 100644
--- a/zellij-utils/src/pane_size.rs
+++ b/zellij-utils/src/pane_size.rs
@@ -130,6 +130,9 @@ impl PaneGeom {
&& self.y <= row
&& row < self.y + self.rows.as_usize()
}
+ pub fn is_at_least_minimum_size(&self) -> bool {
+ self.rows.as_usize() > 0 && self.cols.as_usize() > 0
+ }
}
impl Offset {