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
This commit is contained in:
Aram Drevekenin 2022-10-13 13:55:16 +02:00 committed by GitHub
parent 8d56def4fc
commit bece86b95c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 677 additions and 176 deletions

View file

@ -492,8 +492,8 @@ impl Tab {
free_space.cols.set_inner(viewport_cols); free_space.cols.set_inner(viewport_cols);
free_space.rows.set_inner(viewport_rows); 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 positions_and_size = positions_in_layout.iter();
let mut new_ids = new_ids.iter(); let mut new_ids = new_ids.iter();
@ -510,7 +510,9 @@ impl Tab {
let (pid_tx, pid_rx) = channel(); let (pid_tx, pid_rx) = channel();
let pane_title = run.location.to_string(); let pane_title = run.location.to_string();
self.senders self.senders
.send_to_plugin(PluginInstruction::Load(pid_tx, run, tab_index, client_id)) .send_to_plugin(PluginInstruction::Load(
pid_tx, run, tab_index, client_id,
))
.with_context(err_context)?; .with_context(err_context)?;
let pid = pid_rx.recv().with_context(err_context)?; let pid = pid_rx.recv().with_context(err_context)?;
let mut new_plugin = PluginPane::new( let mut new_plugin = PluginPane::new(
@ -550,8 +552,10 @@ impl Tab {
initial_title, initial_title,
); );
new_pane.set_borderless(layout.borderless); new_pane.set_borderless(layout.borderless);
self.tiled_panes self.tiled_panes.add_pane_with_existing_geom(
.add_pane_with_existing_geom(PaneId::Terminal(*pid), Box::new(new_pane)); PaneId::Terminal(*pid),
Box::new(new_pane),
);
set_focus_pane_id(layout, PaneId::Terminal(*pid)); set_focus_pane_id(layout, PaneId::Terminal(*pid));
} }
} }
@ -595,6 +599,17 @@ impl Tab {
} }
} }
Ok(()) 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(())
},
}
} }
pub fn update_input_modes(&mut self) -> Result<()> { pub fn update_input_modes(&mut self) -> Result<()> {
// this updates all plugins with the client's input mode // this updates all plugins with the client's input mode

View file

@ -156,8 +156,17 @@ impl PaneLayout {
} }
count count
} }
pub fn position_panes_in_space(&self, space: &PaneGeom) -> Vec<(PaneLayout, PaneGeom)> { pub fn position_panes_in_space(
split_space(space, self, 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>> { pub fn extract_run_instructions(&self) -> Vec<Option<Run>> {
let mut run_instructions = vec![]; let mut run_instructions = vec![];

View file

@ -700,7 +700,7 @@ fn children_not_as_first_child_of_pane_template() {
} }
pane pane
} }
horizontal-with-vertical-top name="my tab" { horizontal-with-vertical-top name="my pane" {
pane 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(); let layout_error = Layout::from_kdl(kdl_layout, "layout_file_name".into()).unwrap_err();
assert_snapshot!(format!("{:?}", layout_error)); 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");
}

View file

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

View file

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

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 848 assertion_line: 711
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -13,7 +13,9 @@ Layout {
children: [ children: [
PaneLayout { PaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
name: None, name: Some(
"my pane",
),
children: [ children: [
PaneLayout { PaneLayout {
children_split_direction: Vertical, children_split_direction: Vertical,

View file

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

View file

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

View file

@ -12,7 +12,7 @@ use std::str::FromStr;
use crate::{ use crate::{
kdl_child_with_name, kdl_children_nodes, kdl_get_bool_property_or_child_value, 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_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_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_name, kdl_parsing_error, kdl_property_names, kdl_property_or_child_value_node,
kdl_string_arguments, kdl_string_arguments,
@ -92,8 +92,22 @@ impl<'a> KdlLayoutParser<'a> {
} }
fn parse_split_size(&self, kdl_node: &KdlNode) -> Result<Option<SplitSize>, ConfigError> { 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") { 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") { } 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))) Ok(Some(SplitSize::Fixed(size as usize)))
} else if let Some(node) = kdl_property_or_child_value_node!(kdl_node, "size") { } else if let Some(node) = kdl_property_or_child_value_node!(kdl_node, "size") {
Err(kdl_parsing_error!( Err(kdl_parsing_error!(
@ -143,37 +157,44 @@ impl<'a> KdlLayoutParser<'a> {
location, location,
}))) })))
} }
fn parse_pane_command(&self, pane_node: &KdlNode) -> Result<Option<Run>, ConfigError> { fn parse_args(&self, pane_node: &KdlNode) -> Result<Option<Vec<String>>, ConfigError> {
let command = kdl_get_string_property_or_child_value_with_error!(pane_node, "command") match kdl_get_child!(pane_node, "args") {
.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") {
Some(kdl_args) => { Some(kdl_args) => {
if kdl_args.entries().is_empty() { 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)); 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) kdl_string_arguments!(kdl_args)
.iter() .iter()
.map(|s| String::from(*s)) .map(|s| String::from(*s))
.collect(), .collect(),
) ))
}, },
None => None, None => Ok(None),
}; }
match (command, cwd, args) { }
(None, Some(_cwd), _) => Err(ConfigError::new_kdl_error( 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(), "cwd can only be set if a command was specified".into(),
pane_node.span().offset(), pane_node.span().offset(),
pane_node.span().len(), 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(), "args can only be set if a command was specified".into(),
pane_node.span().offset(), pane_node.span().offset(),
pane_node.span().len(), pane_node.span().len(),
)), )),
(Some(command), cwd, args) => Ok(Some(Run::Command(RunCommand { (Some(command), cwd, args, _) => Ok(Some(Run::Command(RunCommand {
command, command,
args: args.unwrap_or_else(|| vec![]), args: args.unwrap_or_else(|| vec![]),
cwd, cwd,
@ -186,7 +207,24 @@ impl<'a> KdlLayoutParser<'a> {
&self, &self,
kdl_node: &KdlNode, kdl_node: &KdlNode,
) -> Result<Option<Run>, ConfigError> { ) -> 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 let Some(plugin_block) = kdl_get_child!(kdl_node, "plugin") {
if run.is_some() { if run.is_some() {
return Err(ConfigError::new_kdl_error( return Err(ConfigError::new_kdl_error(
@ -235,16 +273,38 @@ impl<'a> KdlLayoutParser<'a> {
let focus = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus"); 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") let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name")
.map(|name| name.to_string()); .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 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)?; let children_split_direction = self.parse_split_direction(kdl_node)?;
match kdl_children_nodes!(kdl_node) {
Some(children) => { let (external_children_index, pane_parts) = match kdl_children_nodes!(kdl_node) {
let child_panes = self.parse_child_pane_nodes_for_tab(children)?; Some(children) => self.parse_child_pane_nodes_for_pane(&children)?,
None => (None, vec![]),
};
if pane_parts.len() > 0 {
let child_panes_layout = PaneLayout { let child_panes_layout = PaneLayout {
children_split_direction, children_split_direction,
children: child_panes, children: pane_parts,
external_children_index,
..Default::default() ..Default::default()
}; };
self.assert_one_children_block(&pane_layout, pane_layout_kdl_node)?; self.assert_one_children_block(&pane_layout, pane_layout_kdl_node)?;
@ -253,8 +313,7 @@ impl<'a> KdlLayoutParser<'a> {
child_panes_layout, child_panes_layout,
pane_layout_kdl_node, pane_layout_kdl_node,
)?; )?;
}, }
None => {
if let Some(borderless) = borderless { if let Some(borderless) = borderless {
pane_layout.borderless = borderless; pane_layout.borderless = borderless;
} }
@ -270,21 +329,34 @@ impl<'a> KdlLayoutParser<'a> {
if let Some(run) = run { if let Some(run) = run {
pane_layout.run = Some(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 { if let Some(index_of_children) = pane_layout.external_children_index {
pane_layout pane_layout
.children .children
.insert(index_of_children, PaneLayout::default()); .insert(index_of_children, PaneLayout::default());
} }
},
}
pane_layout.external_children_index = None; pane_layout.external_children_index = None;
Ok(pane_layout) Ok(pane_layout)
} }
fn parse_split_direction(&self, kdl_node: &KdlNode) -> Result<SplitDirection, ConfigError> { fn parse_split_direction(&self, kdl_node: &KdlNode) -> Result<SplitDirection, ConfigError> {
Ok(match kdl_get_string_entry!(kdl_node, "split_direction") { match kdl_get_string_property_or_child_value_with_error!(kdl_node, "split_direction") {
Some(direction) => SplitDirection::from_str(direction)?, Some(direction) => match SplitDirection::from_str(direction) {
None => SplitDirection::default(), 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> { fn parse_pane_template_node(&mut self, kdl_node: &KdlNode) -> Result<(), ConfigError> {
self.assert_valid_pane_properties(kdl_node)?; self.assert_valid_pane_properties(kdl_node)?;
@ -333,10 +405,7 @@ impl<'a> KdlLayoutParser<'a> {
let tab_name = let tab_name =
kdl_get_string_property_or_child_value!(kdl_node, "name").map(|s| s.to_string()); 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 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") { let children_split_direction = self.parse_split_direction(kdl_node)?;
Some(direction) => SplitDirection::from_str(direction)?,
None => SplitDirection::default(),
};
let children = match kdl_children_nodes!(kdl_node) { let children = match kdl_children_nodes!(kdl_node) {
Some(children) => self.parse_child_pane_nodes_for_tab(children)?, Some(children) => self.parse_child_pane_nodes_for_tab(children)?,
None => vec![], None => vec![],
@ -397,6 +466,15 @@ impl<'a> KdlLayoutParser<'a> {
if kdl_name!(child) == "pane" { if kdl_name!(child) == "pane" {
nodes.push(self.parse_pane_node(child)?); nodes.push(self.parse_pane_node(child)?);
} else if kdl_name!(child) == "children" { } 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); external_children_index = Some(i);
} else if let Some((pane_template, pane_template_kdl_node)) = } else if let Some((pane_template, pane_template_kdl_node)) =
self.pane_templates.get(kdl_name!(child)).cloned() 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(); 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_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); let has_nested_nodes_or_children_block = self.has_child_panes_tabs_or_templates(kdl_node);
if has_nested_nodes_or_children_block if has_nested_nodes_or_children_block
&& (has_borderless_prop || has_focus_prop || has_run_prop) && (has_borderless_prop || has_focus_prop || has_run_prop)
{ {
@ -549,10 +626,7 @@ impl<'a> KdlLayoutParser<'a> {
let tab_name = let tab_name =
kdl_get_string_property_or_child_value!(kdl_node, "name").map(|s| s.to_string()); 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 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") { let children_split_direction = self.parse_split_direction(kdl_node)?;
Some(direction) => SplitDirection::from_str(direction)?,
None => SplitDirection::default(),
};
match kdl_children_nodes!(kdl_node) { match kdl_children_nodes!(kdl_node) {
Some(children) => { Some(children) => {
let child_panes = self.parse_child_pane_nodes_for_tab(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> { fn parse_tab_template_node(&self, kdl_node: &KdlNode) -> Result<PaneLayout, ConfigError> {
self.assert_valid_tab_properties(kdl_node)?; self.assert_valid_tab_properties(kdl_node)?;
let children_split_direction = let children_split_direction = self.parse_split_direction(kdl_node)?;
match kdl_get_string_property_or_child_value_with_error!(kdl_node, "split_direction") {
Some(direction) => SplitDirection::from_str(direction)?,
None => SplitDirection::default(),
};
let mut tab_children = vec![]; let mut tab_children = vec![];
let mut external_children_index = None; let mut external_children_index = None;
if let Some(children) = kdl_children_nodes!(kdl_node) { if let Some(children) = kdl_children_nodes!(kdl_node) {
@ -630,6 +700,16 @@ impl<'a> KdlLayoutParser<'a> {
if kdl_name!(child) == "pane" { if kdl_name!(child) == "pane" {
tab_children.push(self.parse_pane_node(child)?); tab_children.push(self.parse_pane_node(child)?);
} else if kdl_name!(child) == "children" { } 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); external_children_index = Some(i);
} else if let Some((pane_template, pane_template_kdl_node)) = } else if let Some((pane_template, pane_template_kdl_node)) =
self.pane_templates.get(kdl_name!(child)).cloned() self.pane_templates.get(kdl_name!(child)).cloned()

View file

@ -130,6 +130,9 @@ impl PaneGeom {
&& self.y <= row && self.y <= row
&& row < self.y + self.rows.as_usize() && 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 { impl Offset {